Linux操作系统中BSD套接口开发的基础介绍

  这是关于如何用各种可以得到的接口为Linux开发网络程序的系列文章的第一篇。就像大多数Unix-based的操作系统一样,Linux支持将TCP/IP作为本地的网络传输协议。在这个系列中,我们假定你已经比较熟悉Linux上的C编程和Linux的一些系统知识诸如signals,forking等等。

  这篇文章是关于如何用BSD套接口创建网络程序的基础介绍 。在下一篇中,我们会解决涉及到建立(网络)deamon进程的问题。而且今后的文章我们还会涉及到使用远程过程调用(RPC),以及用CORBA/distributed objects进行开发。

  一、TCP/IP的基础介绍

  TCP/IP协议族允许两个运行在同一台电脑或者由网络连接在一起的两台电脑上的程序进行通讯。这个协议族是专门为了在不可靠的网络上进行通讯设计的。TCP/IP允许两个基本的操作模式——面向连接的可靠的传输(指TCP)和无连接的(connectionless)不可靠的传输(UDP)。

  TCP提供带有对上层协议透明的中继功能的,顺序的,可靠的,双向的(bi-directional),以连接为基础的字节传输流。TCP将你的信息分割成数据报(不大于64kb)并保证所有的数据报无误的按照顺序都到达目的地。由于以连接为基础,所以一个虚拟连接必须在一个网络实体(network entity)和另一个之间进行通信前建立。UDP相反则提供一个(非常快的)无连接的不可靠消息传输(消息的大小是一个确定的最大长度)。

  为了使程序间可以相互通信,不论他们是在同一个机器(通过loopback接口)还是不同主机,每一个程序都必须有独立的地址。

  TCP/IP地址由两部分组成——用来辨别机器的IP地址和用来辨别在那台机器上的特定程序的端口地址。

  地址可以是点分(dotted-quad)符号形式的(如,127.0.0.1)或者是主机名形式的(如,www.csdn.net)。系统可以使用/etc/hosts或DNS域名服务(如果可以获得的话)进行主机名到点分符号地址(也就是IP地址)的转换。

  端口从1号开始编号。1和IPP0RT_RESERVED(在/usr/include/netinet/in.h中定义,通常为1024)之间的段口号保留给系统使用(也就是说,你必须以root的身份建立一个网络服务来绑定这部分的端口)。

  最简单的网络程序大都用的客户-服务器模型。一个服务进程等待一个客户进程连接他。当连接建立时,服务器代表客户执行特定的任务,通常这这以后连接就中断了。

  二、使用BSD套接口界面

  最通行的TCP/IP编程方法就是使用BSD套接口界面编程。通过它,网络端点(network endpoints)(IP地址和端口地址)以套接口(sockets)的形式出现。

  这套套接口IPC(interprocess communication,进程间通讯)设施(从4.2BSD开始引入)的设计是为了能让网络程序的设计能够独立于不同的底层通信设施。

  1、建立一个服务器程序

  要使用BSD界面建立一个服务器程序,你必须通过以下步骤:

  (1)通过函数socket()建立一个套接口
  (2)通过函数bind()绑定一个地址(IP地址和端口地址)。这一步确定了服务器的位置,使客户端知道如何访问。
  (3)通过函数listem()监听(listen)端口的新的连接请求。
  (4)通过函数accept()接受新的连接。

  通常,维护代表了客户的请求可能需要花费相当长的一段时间。在处理一个请求时,接收和处理新的请求也应该是高效的。达到这种目的的最通常的做法是让服务器通过fork()函数拷贝一份自己的进程来接受新的连接。

  以下的例子显示了服务器是如何用C实现的:

/*
 * Simple "Hello, World!" server
 * Ivan Griffin (ivan.griffin@ul.ie)
 */

/* Hellwolf Misty translated */

#include  /* */
#include  /* exit() */
#include  /* memset(), memcpy() */
#include  /* uname() */
#include
#include  /* socket(), bind(),
               listen(), accept() */
#include
#include
#include
#include  /* fork(), write(), close() */

/*
 * constants
 */
const char MESSAGE[] = "Hello, World!\n";
const int BACK_LOG = 5;

/*

*程序需要一个命令行参数:需要绑定的端口号
 */
int main(int argc, char *argv[])
{
  int serverSocket = 0,
    on = 0,
    port = 0,
    status = 0,
    childPid = 0;
  struct hostent *hostPtr = NULL;
  char  hostname[80] = "";
  struct sockaddr_in serverName = { 0 };

  if (2 != argc)
  {
    fprintf(stderr, "Usage: %s \n",
  argv[0]);
    exit(1);
  }
  port = atoi(argv[1]);
/ *
  *socket()系统调用,带有三个参数:
  *    1、参数domain指明通信域,如PF_UNIX(unix域),PF_INET(IPv4),
  *      PF_INET6(IPv6)等
  *    2、type指明通信类型,最常用的如SOCK_STREAM(面向连接可靠方式, 
  *      比如TCP)、SOCK_DGRAM(非面向连接的非可靠方式,比如UDP)等。
  *    3、参数protocol指定需要使用的协议。虽然可以对同一个协议
  *      家族(protocol family)(或者说通信域(domain))指定不同的协议
  *      参数,但是通常只有一个。对于TCP参数可指定为IPPROTO_TCP,对于
  *      UDP可以用IPPROTO_UDP。你不必显式制定这个参数,使用0则根据前
  *      两个参数使用默认的协议。  
  */ 
  serverSocket = socket(PF_INET, SOCK_STREAM,
  IPPROTO_TCP);
  if (-1 == serverSocket)
  {
    perror("socket()");
    exit(1);
  }
 
  /*
   * 一旦套接口被建立,它的运作机制可以通过套接口选项(socket option)进行修改。   
   */

  /*
   * SO_REUSEADDR选项的设置将套接口设置成重新使用旧的地址(IP地址加端口号)而不等待
   * 注意:在Linux系统中,如果一个socket绑定了某个端口,该socket正常关闭或程序退出后,
   * 在一段时间内该端口依然保持被绑定的状态,其他程序(或者重新启动  的原程序)无法绑定该端口。
   *
   * 下面的调用中:SOL_SOCKET代表对SOCKET层进行操作
   */
  on = 1;

  status = setsockopt(serverSocket, SOL_SOCKET,
  SO_REUSEADDR,
    (const char *) &on, sizeof(on));

  if (-1 == status)
  {
    perror("setsockopt(...,SO_REUSEADDR,...)");
  }

  /* 当连接中断时,需要延迟关闭(linger)以保证所有数据都
   * 被传输,所以需要打开SO_LINGER这个选项
   * linger的结构在/usr/include/linux/socket.h中定义:
   *  struct linger
   *  {
   *   int l_onoff;  /* Linger active */
   *   int l_linger;  /* How long to linger */
   *  };

   *  如果l_onoff为0,则延迟关闭特性就被取消。如果非零,则允许套接口延迟关闭。
   *  l_linger字段则指明延迟关闭的时间
   */

  {
    struct linger linger = { 0 };

    linger.l_onoff = 1;
    linger.l_linger = 30;
    status = setsockopt(serverSocket,
  SOL_SOCKET, SO_LINGER,
  (const char *) &linger,
  sizeof(linger));

    if (-1 == status)
    {
      perror("setsockopt(...,SO_LINGER,...)");
    }
  }

  /*
   * find out who I am
   */

  status = gethostname(hostname,
  sizeof(hostname));
  if (-1 == status)
  {
    perror("gethostname()");
    exit(1);
  }

  hostPtr = gethostbyname(hostname);
  if (NULL == hostPtr)
  {
    perror("gethostbyname()");
    exit(1);
  }

  (void) memset(&serverName, 0,
  sizeof(serverName));
  (void) memcpy(&serverName.sin_addr,
  hostPtr->h_addr,
  hostPtr->h_length);
   /*
    *h_addr是h_addr_list[0]的同义词,
    *  h_addr_list是一组地址的数组
    *长度为4(byte)代表一个IP地址的长度
    */
/*
 * 为了使服务器绑定本机所有的IP地址,
 * 上面一行代码需要用下面的代码代替
 * serverName.sin_addr.s_addr=htonl(INADDR_ANY);
 */

  serverName.sin_family = AF_INET;
  /* htons:h(host byteorder,主机字节序)
   *     to n(network byteorder,网络字节序
   *     s(short类型)
   */
  serverName.sin_port = htons(port);
/* 在一个地址(本例中的serverSocket)被建立后
 * 它就应该被绑定到我们获得的套接口。
 */
  status = bind(serverSocket,
 (struct sockaddr *) &serverName,
    sizeof(serverName));
  if (-1 == status)
  {
    perror("bind()");
    exit(1);
  }
/* 现在套接口就可以被用来监听新的连接。
 * BACK_LOG指定了未决连接监听队列(listen queue for pending connections)
 * 的最大长度。当一个新的连接到达,而队列已满的话,客户就会得到连接拒绝错误。

* (这就是dos拒绝服务攻击的基础)。
 */
  status = listen(serverSocket, BACK_LOG);
  if (-1 == status)
  {
    perror("listen()");
    exit(1);
  }
/* 从这里开始,套接口就开始准备接受请求,并为他们服务。
 * 本例子是用for循环来达到这个目的。一旦连接被接受(accpepted),
 * 服务器可以通过指针获得客户的地址以便进行一些诸如记录客户登陆之类的
 * 任务。
  for (;;)
  {
    struct sockaddr_in clientName = { 0 };
    int slaveSocket, clientLength =
  sizeof(clientName);

    (void) memset(&clientName, 0,
  sizeof(clientName));

    slaveSocket = accept(serverSocket,
  (struct sockaddr *) &clientName,
  &clientLength);
    if (-1 == slaveSocket)
    {
      perror("accept()");
      exit(1);
    }

    childPid = fork();

    switch (childPid)
    {
    case -1: /* ERROR */
      perror("fork()");
      exit(1);

    case 0: /* child process */

      close(serverSocket);

      if (-1 == getpeername(slaveSocket,
  (struct sockaddr *) &clientName,
  &clientLength))
      {
        perror("getpeername()");
      }
      else
      {
      printf("Connection request from %s\n",
          inet_ntoa(clientName.sin_addr));
      }

      /*
       * Server application specific code
       * goes here, e.g. perform some
       * action, respond to client etc.
       */
      write(slaveSocket, MESSAGE,
  strlen(MESSAGE));
        /* 也可以使用带缓存的ANSI函数fprint,
        * 只要你记得必要时用fflush刷新缓存
        */
      close(slaveSocket);
      exit(0);

    default: /* parent process */
      close(slaveSocket);/* 这是一个非常好的习惯
                * 父进程关闭子进程的套接口描述符
                * 正如上面的子进程关闭父进程的套接口描述符。
                */               
    }
  }

  return 0;
}

(0)

相关推荐

  • Linux操作系统中BSD套接口开发的基础介绍

    这是关于如何用各种可以得到的接口为Linux开发网络程序的系列文章的第一篇.就像大多数Unix-based的操作系统一样,Linux支持将TCP/IP作为本地的网络传输协议.在这个系列中,我们假定你已经比较熟悉Linux上的C编程和Linux的一些系统知识诸如signals,forking等等. 这篇文章是关于如何用BSD套接口创建网络程序的基础介绍 .在下一篇中,我们会解决涉及到建立(网络)deamon进程的问题.而且今后的文章我们还会涉及到使用远程过程调用(RPC),以及用CORBA/dis

  • Linux中BSD套接口开发的基础介绍

    这是关于如何用各种可以得到的接口为Linux开发网络程序的系列文章的第一篇.就像大多数Unix-based的操作系统一样,Linux支持将TCP/IP作为本地的网络传输协议.在这个系列中,我们假定你已经比较熟悉Linux上的C编程和Linux的一些系统知识诸如signals,forking等等. 这篇文章是关于如何用BSD套接口创建网络程序的基础介绍 .在下一篇中,我们会解决涉及到建立(网络)deamon进程的问题.而且今后的文章我们还会涉及到使用远程过程调用(RPC),以及用CORBA/dis

  • 在Linux操作系统中修改环境变量的方法

    方法一:在/etc/profile文件中添加变量[对所有用户生效(永久的)] 用VI在文件/etc/profile文件中增加变量,该变量将会对Linux下所有用户有效,并且是"永久的". 要让刚才的修改马上生效,需要执行以下代码 复制代码 代码如下: # source /etc/profile 方法二:在用户目录下的.bash_profile文件中增加变量[对单一用户生效(永久的)] 用VI在用户目录下的.bash_profile文件中增加变量,改变量仅会对当前用户有效,并且是&quo

  • python中的mock接口开发示例详解

    什么是mock? mock在翻译过来有模拟的意思.它允许您用模拟对象替换您的系统的部分,并对它们已使用的方式进行断言. Mock通常是指,在测试一个对象时,我们构造一些假的对象来模拟与其交互.而这些Mock对象的行为是我们事先设定且符合预期.通过这些Mock对象来测试对象在正常逻辑,异常逻辑或压力情况下工作是否正常,Mock的行为固定,它确保当你访问该Mock的某个方法时总是能够获得一个没有任何逻辑的直接就返回的预期结果.Mock接口就是用一些合理的手段构造对象去模拟真实接口. import f

  • Linux操作系统中安装和试用IE、Office等软件

    听说出了一种新的Linux中文版本,LinXP,可以在Linux下运行IE6.0 及Office 2000,PhotoShop 7等Windows应用软件,太好了,其实用Windows软件大家主要还是因为许多常用的应用软件如IE,Office 等只能在Windows下运行,而Windows经常出现的蓝屏.死机.网络病毒让人叫哭不迭,如果能在Linux下运行这些常用的应用软件,又能享受Linux稳定的内核,强大的网络的安全性,无疑对广大的用户来说又多了一种新的选择.(可以躲过一浪接一浪的网络病毒攻

  • Linux虚拟机中node.js之开发环境搭建

    先说下nodejs这个哦,有人以为它是一种语言,其实不是,它是一个平台.下面来说下本次的话题:Vmware虚拟机node.js之开发环境搭建 一.安装linux系统 (已安装linux可跳此步骤) 虚拟机推荐选择:VirtualBox 或者 Vmware (专业版永久激活码:5A02H-AU243-TZJ49-GTC7K-3C61N) 我这里使用Vmware. 安装好Vmware后,点击新建虚拟机,选择稍后安装操作系统,然后进行配置.虚拟机设置如下: { 客户机操作系统:其他: 版本:其他64位

  • 在Linux系统中搭建Node.js开发环境的简单步骤讲解

    1. Linux安装node.js ubuntu: sudo apt-get install nodejs npm centos: yum install nodejs npm 更详细的安装参见:https://github.com/joyent/node/wiki/Installation npm为类似PHP中Pear的包管理器 2. 开始使用node.js 用文本编辑器新建hello.js写入以下内容 console.log('hello world'); 打开命令行输入 $ node he

  • linux shell中的比较符号与特殊符号介绍

    shell字符串比较.判断是否为数字 二元比较操作符,比较变量或者比较数字.注意数字与字符串的区别. 整数比较 -eq 等于,如:if [ "$a" -eq "$b" ] -ne 不等于,如:if [ "$a" -ne "$b" ] -gt 大于,如:if [ "$a" -gt "$b" ] -ge 大于等于,如:if [ "$a" -ge "$b"

  • 在Linux环境中使用USB存储器

    如何在Linux环境中使用USB接口的存储设备?这是各大电脑论坛上出现得比较多的一个问题,同此可见这也是摆在许多电脑玩家面前的一道难题.本文就为您提供一套完美的解决方案,通过下面的方法,您仅可以在Linux环境中方便地使用U盘.USB硬盘盒,而且还能将数码相机作为USB大容量存储器.这里就以Red Hat Linux 7.2为例,来一步一步地介绍如何在Linux环境中使用USB存储器. 1.在X-Windows环境中打开"控制中心",展开"信息→USB设备"控制台树

  • 如何解决windows系统和linux系统中端口被占用的问题

    一.在windows操作系统中,查询端口占用和清除端口占用的程序 提升权限后用:netstat -b 或用 1.查询端口占用的进程ID 点击"开始"-->"运行",输入"cmd"后点击确定按钮,进入DOS窗口,接下来分别运行以下命令: netstat -a -n  各个端口占用 netstat -ano   各个端口占用和进程PID          netstat -aon | findstr "80"      C:

随机推荐