• 套接字


    套接字是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行,Linux所提供的功能(如打印服 务,ftp等)通常都是通过套接字来进行通信的,套接字的创建和使用与管道是有区别的,因为套接字明确地将客户和服务器区分出来,套接字可以实现将多个客 户连接到一个服务器。

    套接字属性

    套接字的特性由3个属性确定,他们是,域,类型和协议

    域指定套接字通信中使用的网络介质,最常见的套接字域是AF_INET,它指的是Internet网络

    套接字类型

    一个套接字可能有多种不同的通信方式

    流套接字,流套接字提供一个有序,可靠,双向节流的链接,流套接字由类型SOCK_STREAM指定,它是在AF_INET域中通过TCP/IP链接实现的,这就是套接字类型(其实就是通信方式)

    与流套接字相反,由类型SOCK_DGRAM指定的数据报套接字不建立和维持一个连接,它对可以发送的数据长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失,复制或乱序

    最后一个是套接字协议,通常使用默认就可以了(也就是最后一个参数填0)

    创建套接字

    socket系统调用创建一个套接字并返回一个描述符,该描述符可以用来访问该套接字

    #include

    #include

    int socket(int domain,int type,int protocol);

    创建的套接字是一条通信线路的一个端点,domain参数指定协议族(使用的网络介质),type参数指定这个套接字的通信类型(通信方式),protocot参数指定使用的协议

    domain参数可以指定如下协议族

    AF_UNIX         UNIX域协议(文件系统套接字)

    AF_INET         ARPA因特网协议

    AF_ISSO         ISO标准协议

    AF_NS           Xerox网络协议

    AF_IPX          Novell IPX协议

    AF_APPLETALK    Appletalk DDS协议

    最常用的套接字域是AF_UNIX和AF_INET,前者用于通过UNIX和Linux文件系统实现本地套接字

    socket函数的第二个参数type指定用于新套接字的特性,它的取值包括SOCK_STREAM和SOCK_DGRAM

    SOCK_STREAM是一个有序,可靠,面向连接的双向字节流,一般用这个

    最后一个protocol参数,将参数设为0表示使用默认协议。

    套接字地址

    每个套接字(端点)都有其自己的地址格式,对于AF_UNIX套接字来说,它的地址由结构sockaddr_un来描述,该结构体定义在头文件sys/un.h中,如下:

    struct sockaddr_un {

    sa_family_t  sun_family; //套接字域

    char         sun_path[];//名字

    };

    而在AF_INET域中,套接字地址结构由sockaddr_in来指定,该结构体定义在头文件netinet/in.h中

    struct sockaddr_in {

    short int  sin_family;//套接字域

    unsigned short int   sin_port;//接口

    struct in_addr   sin_addr;

    }

    IP地址结构in_addr被定义如下:

    struct in_addr {

    unsigned long int  s_addr;

    };

    命名套接字

    要想让通过socket调用创建的套接字可以被其它进程使用,服务器程序就必须给该套接字命名,如下,AF_INET套接字就会关联到一个IP端口号

    #include

    int bind(int socket,const struct sockaddr *address,size_t address_len);

    bind系统调用把参数address中的地址分配给与文件描述符socket关联的未命名套接字

    创建套接字队列

    为了能够在套接字上接受进入链接,服务器程序必须创建一个队列来保存未处理的请求,它用listen系统调用来完成这一工作

    #include

    int listen(int socket,int backlog);

    Linux系统可能会对队列中未处理的连接的最大数目做出限制

    接受连接

    一旦服务器程序创建并命名套接字之后,他就可以通过accept系统调用来等待客户建立对该套接字的连接

    #include

    int accept(int socket,struct sockaddr *address,size_t *address_len);

    accept函数将创建一个新套接字来与该客户进行通信,并且返回新套接字描述符(这个描述符和客户端中描述符是一样等同)

    请求连接

    客户程序通过一个未命名套接字和服务器监听套接字之间建立的连接的方法来连接到服务器,如下:

    #include

    int connect(int socket,const struct sockaddr *address,size_t address_len);

    参数socket指定的套接字将连接到参数address指定的服务器套接字

    关闭套接字

    你可以通过调用close函数来终止服务器和客户上的套接字连接

    套接字通信

    套接字可以通过调用read(),write()系统调用来进行传输数据

    下面是套接字的一些例子

    一个简单的本地客户

    #include
    #include
    #include
    #include
    #include
    #include

    int main()
    {
      int sockfd;
      int len;
      struct sockaddr_un address;//套接字地址
      int result;
      char ch = 'A';

      sockfd = socket(AF_UNIX,SOCK_STREAM,0);//创建一个套接字(端点),并返回一个描述符

      address.sun_family = AF_UNIX;//指明网络介质
      strcpy(address.sun_path,"server_socket");// 名字
      len = sizeof(address);

      result = connect(sockfd,(struct sockaddr *)&address,len);//请求连接到address

      if(result == -1) {
        perror("oops: clientl");
        exit(1);
      }

      write(sockfd,&ch,1);//把数据写入套接字
      read(sockfd,&ch,1);//服务器处理后读出处理后数据
      printf("char form server = %c ",ch);
      close(sockfd);
      exit(0);
    }

    下面是一个本地服务器

    #include
    #include
    #include
    #include
    #include
    #include

    int main()
    {
      int server_sockfd,client_sockfd;//定义套接字描述符
      int server_len,client_len;
      struct sockaddr_un server_address;//套接字地址
      struct sockaddr_un client_address;//套接字地址

      unlink("server_socket");
      server_sockfd = socket(AF_UNIX,SOCK_STREAM,0);//创建一个套接字,并返回一个描述符

      server_address.sun_family = AF_UNIX;//指定网络介质
      strcpy(server_address.sun_path,"server_socket");//名字
      server_len = sizeof(server_address);
      bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接字

      listen(server_sockfd,5);//创建套接字队列
      while(1) {
        char ch;
        printf("server waiting ");

        client_len = sizeof(client_address);
        client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address,&client_len);//接受连接

        read(client_sockfd,&ch,1);//从套接字中读取数据
        ch++;// 处理数据
        write(client_sockfd,&ch,1);//把数据重新写回套接字
        close(client_sockfd);
      }
    }

    这两个程序运行如下:

    root@www:/opt/chengxu# ./server1 &
    [3] 4644
    root@www:/opt/chengxu# server waiting

    root@www:/opt/chengxu# ./client1 & ./client1 & ./client1 &
    [4] 4652
    [5] 4653
    [6] 4654
    root@www:/opt/chengxu# server waiting
    server waiting
    server waiting
    char form server = B
    char form server = B
    char form server = B

    [4]   Done                    ./client1
    [5]-  Done                    ./client1
    [6]+  Done                

    下面看一个网络套接字的例子,先看server程序:

    #include
    #include
    #include
    #include
    #include
    #include
    #include

    int main()
    {
      int server_sockfd,client_sockfd;//定义套接字描述符
      int server_len,client_len;
      struct sockaddr_in server_address;//套接字地址结构体
      struct sockaddr_in client_address;//套接字地址结构体

      unlink("server_socket");
      server_sockfd = socket(AF_INET,SOCK_STREAM,0);//创建一个套接字,并返回一个描述符

      server_address.sin_family = AF_INET;//指定网络介质
      //server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
      server_address.sin_addr.s_addr = htonl(INADDR_ANY);//客户连接到服务器的IP
      server_address.sin_port = htons(9734);//客户连接到端口
      server_len = sizeof(server_address);
      bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接字

      listen(server_sockfd,5);//创建套接字队列
      while(1) {
        char ch;
        printf("server waiting ");

        client_len = sizeof(client_address);
        client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address,&client_len);//接受连接

        read(client_sockfd,&ch,1);//从套接字中读取数据
        ch++;//处理数据
        write(client_sockfd,&ch,1);//把处理后数据写入套接字
        close(client_sockfd);//关闭套接字
      }
    }

    下面是客户端程序:

    #include
    #include
    #include
    #include
    #include
    #include
    #include

    int main()
    {
      int sockfd;//套接字描述符
      int len;
      struct sockaddr_in address;//套接字地址结构体
      int result;
      char ch = 'A';

      sockfd = socket(AF_INET,SOCK_STREAM,0);//创建一个套接字并返回一个描述符

      address.sin_family = AF_INET;// 指定网络介质
      address.sin_addr.s_addr = htonl(INADDR_ANY);//要连接到主机IP
      address.sin_port = htons(9734);//要连接到端口号
      len = sizeof(address);

      result = connect(sockfd,(struct sockaddr *)&address,len);//请求连接

      if(result == -1) {
        perror("oops: clientl");
        exit(1);
      }

      write(sockfd,&ch,1);//把数据写入套接字
      read(sockfd,&ch,1);//服务器处理完数据后从新读取出来
      printf("char form server = %c ",ch);
      close(sockfd);
      exit(0);
    }

    运行这两个程序输出如下:

    root@www:/opt/chengxu# ./server2 &
    [4] 4746
    root@www:/opt/chengxu# server waiting

    root@www:/opt/chengxu# ./client2 & ./client2 & ./client2 &
    [5] 4749
    [6] 4750
    [7] 4751
    root@www:/opt/chengxu# server waiting
    server waiting
    server waiting
    char form server = B
    char form server = B
    char form server = B

    [5]   Done                    ./client2
    [6]-  Done                    ./client2
    [7]+  Done                    ./client2
    root@www:/opt/chengxu#
    输出结果基本和上面的例子差不多,通过上面程序可以发现客户端写入到套接字中的数据可以从服务器中读出来,而且客户端会等待服务器把数据读出处理后再把数据读回来,这有一个顺序,并不会出现乱序,有点类型于管道通信而且是双向的

    主机字节序和网络字节序

    通过套接字接口传递的端口号和地址都是二进制的,不同计算机使用不同的字节序来表示整数,如32位的整数分为4个连续的字节,并以1-2-3-4存在内存中,这里的1表示最高位,也即大端模式,而有的处理器是以4-3-2-1存取的,两个不同的计算机得到的整数就会不一致

    为了使不同类型的计算机可以就通过网络传输多字节的值达成一致,客户和服务器程序必须在传出之前,将它们内部整数表示方式转换为网络字节序,它们通过下面函数转换(也就是把端口号等转换成统一网络字节序)

    #include

    unsigned long int htonl(unsigned long int hostlong);

    unsigned short int htons(unsigned short int hostshort);

    unsigned long int ntohl(unsigned long int netlong);

    unsigned short int ntohs(unsigned short int netshort);

    这些函数将16位和32位整数在主机字节序和标准的网络字节序之间进行转换,如上面例子中用到的

     address.sin_addr.s_addr = htonl(INADDR_ANY);//要连接到主机IP
    address.sin_port = htons(9734);//要连接到端口号

    这样就能保证网络字节序的正确,如果你使用的计算机上的主机字节序和网络字节序相同,将看不到任何差异

    网络信息

    到目前为止,我们客户和服务器程序一直是把地址和端口编译到它们自己的内部,对于一个更通用的服务器和客户程序来说,我们可以通过网络信息函数来决定应该使用的地址和端口(也就是说可以通过网络信息函数来获取IP和端口号等信息)

    类似的,如果给定一个计算机名字,你可以通过调用解析地址的主机数据库函数来确定它的IP地址等信息

    主机数据库函数在接口文件netdb.h中声明,如下:

    #include <netdb.h>

    struct hostent *gethostbyaddr(const void *addr,size_t len,int type);

    struct hostent *gethostbyname(const char *name);//获得计算机主机数据库信息

    这些函数返回的结构体中至少包括以下几个成员

    struct hostent {

    char *h_name; //name of the host

    char **h_aliases;//list of aliases

    int h_addrtypr;//address type

    int h_length;//length in bytes of the address

    char **h_addr_list;//list of address

    };

    如果没有与我们查询的主机或地址相关的数据项,这些信息函数将返回一个空指针

    类型地,与服务及相关联端口号有关的信息也可以通过一些服务信息函数来获取

    #include <netdb.h>

    struct  servent *getservbyname(const char *name,const char *proto);//检查是否有某个服务

    struct  servent *getservbyport(int port,const char *proto);

    proto参数指定用于连接该服务的协议,它的两个选项tcp和udp,前者用于SOCK_STREAM类型

    返回结构体中至少包含如下几个成员:

    struct servent {

    char *s_name;//name of the service

    char **s_aliases;//list of aliases

    int s_port;//The IP port number

    char *s_proto;//The service type,usually "tcp" or "udp"

    };

    如果想获取某台计算机主机数据库信息,可以调用gethostbyname函数并且将结果打印出来,注意,要把返回的地址表转换为正确的地址类型,并用函数inet_ntoa将它们从网络字节序装换为可打印的字符串,如下:

    #include <arpa/inet.h>

    char *inet_ntoa(struct in_addr in);

    这个函数的作用是将一个因特网主机地址转换为一个点分四元租格式的字符串

    下面这个程序getname.c用来获取一台主机有关的信息,如下:

    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <netdb.h>
    #include <stdio.h>
    #include <stdlib.h>

    int main(int argc,char *argv[])
    {
      char *host,**names,**addrs;//接收用到的一些指针
      struct hostent *hostinfo;//指向gethostbyname函数返回的结构体指针
      if(argc == 1) {
        char myname[256];
        gethostname(myname,255);
        host = myname;
      }
      else
         host = argv[1];//获取主机名

      hostinfo = gethostbyname(host);//获取主机数据库
      if(!hostinfo) {
        fprintf(stderr,"cannot get info for host: %s ",host);
        exit(1);
      }

      printf("results for host %s: ",host);
      printf("Name: %s ",hostinfo -> h_name);
      printf("Aliases");
      names = hostinfo -> h_aliases;
      while(*names) {
        printf(" %s",*names);
        names++;
      }
      printf(" ");

      if(hostinfo -> h_addrtype != AF_INET) {
        fprintf(stderr,"not an IP host! ");
        exit(1);
      }

    //显示主机的所有IP地址
      addrs = hostinfo -> h_addr_list;
      while(*addrs) {
        printf(" %s",inet_ntoa(*(struct in_addr *)*addrs));
        addrs++;
      }
      printf(" ");
      exit(0);
    }
    运行这个程序输出如下所示:

    root@www:/opt/chengxu# ./getname
    results for host www:
    Name: www.kugoo.com
    Aliases www
     127.0.1.1
    root@www:/opt/chengxu# ./getname localhost
    results for host localhost:
    Name: localhost
    Aliases ip6-localhost ip6-loopback
     127.0.0.1 127.0.0.1
    root@www:/opt/chengxu#

    下面一个例子是连接到标准服务 

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>

    int main(int argc,char *argv[])//argc表示参数个数,argv[]表示具体参数程序名为argv[0]
    {
      char *host;
      int sockfd;
      int len,result;
      struct sockaddr_in address;//套接字地址结构体
      struct hostent *hostinfo;//指向gethostbyname函数返回的结构体指针
      struct servent *servinfo;//指向getservbyname函数返回的结构体指针
      char buffer[128];

      if(argc == 1)
         host = "localhost";
      else
         host = argv[1];

      hostinfo = gethostbyname(host);//获取主机数据库信息
      if(!hostinfo) {
        fprintf(stderr,"no host: %s ",host);
        exit(1);
      }

    //检查主机上是否有daytime服务

      servinfo = getservbyname("daytime","tcp");
      if(!servinfo) {
        fprintf(stderr,"no daytime service ");
        exit(1);
      }

      sockfd = socket(AF_INET,SOCK_STREAM,0);//创建一个套接字,并返回一个描述符

      address.sin_family = AF_INET;//使用网络介质
      address.sin_port = servinfo -> s_port;//端口号
      address.sin_addr = *(struct in_addr *)*hostinfo -> h_addr_list;//获取主机IP地址
      len = sizeof(address);

      result = connect(sockfd,(struct sockaddr *)&address,len);//请求连接
      if(result == -1) {
        perror("oops:getdate");
        exit(1);
      }
      result = read(sockfd,buffer,sizeof(buffer));
      buffer[result] = '';
      printf("read %d bytes: %s",result,buffer);

      close(sockfd);
      exit(0);
    }

    运行这个程序输出如下:

    root@www:/opt/chengxu# ./getname1 localhost
    oops:getdate: Connection refused
    root@www:/opt/chengxu#
    之所以这样是因为我的虚拟机中没有启动daytime这个服务

    我用的是ubuntu虚拟机,下面来启动这个daytime服务看一下

    首先

    root@www:/opt/chengxu# vim /etc/inetd.conf
    进入到inetd的配置文件把这个服务前面的#号去掉,改成如下:

     18 #discard        dgram   udp wait    root    internal
     19 daytime     stream  tcp nowait  root    internal
     20 #time       stream  tcp nowait  root    internal
    然后保存退出

    接下来重启一下服务就可以了通过下面命令启动(或重启)xinetd服务(xinetd和openbsd-inetd差不多都是属于守护进程)

    root@www:/opt/chengxu# /etc/init.d/openbsd-inetd  restart
     * Restarting internet superserver inetd                                                  [ OK ]
    root@www:/opt/chengxu#

    可以通过下面命令看一下daytime这个服务是否处于监听状态了,如下:

    root@www:/opt/chengxu# netstat  -a | grep daytime
    tcp        0      0 *:daytime               *:*                     LISTEN    
    root@www:/opt/chengxu#
    下面再重新运行一下上面程序看看:

    root@www:/opt/chengxu# ./getname1 localhost
    read 26 bytes: Sun Sep 23 23:15:14 2012
    root@www:/opt/chengxu#

    因特网守护进程

     当有客户端连接到某个服务时,守护进程就运行相应的服务器,这使得针对各项网络服务器不需要移植运行着,我们通常是通过一个图形界面来配置xinetd,ubuntu用的好像是openbsd-inetd这 个守护进程,我们可以直接修改它的配置文件,ubuntu对应的是/etc/inetd.conf这个文件,就拿我们上面那个daytime这个服务为 例,在ubuntu中它默认是关闭的,这时我们要进入它的配置文件里,把它前面的#字符去掉就可以了,修改了的配置文件如下:

     17 #discard        stream  tcp nowait  root    internal

     18 #discard        dgram   udp wait    root    internal

     19 daytime     stream  tcp nowait  root    internal

     20 #time       stream  tcp nowait  root    internal

     21 

     22 #:STANDARD: These are standard services.

     23 

     24 #:BSD: Shell, login, exec and talk are BSD protocols.

     25 

     26 #:MAIL: Mail, news and uucp services.

     27 

     28 #:INFO: Info services

     29 

     30 #:BOOT: TFTP service is provided primarily for booting.  Most sites

     31 #       run this only on machines acting as "boot servers."

     32 bootps          dgram   udp     wait    root    /usr/sbin/bootpd        bootpd -i -t 120

     33 tftp        dgram   udp wait    nobody  /usr/sbin/tcpd  /usr/sbin/in.tftpd /srv/tftproot

     34 #bootps     dgram   udp wait    root    /usr/sbin/bootpd    bootpd -i -t 120

    之后重启一下守护进程就可以了,如下:
    root@www:/opt/chengxu# /etc/init.d/openbsd-inetd  restart
     * Restarting internet superserver inetd                                                  [ OK ] 
    root@www:/opt/chengxu# netstat  -a | grep daytime
    tcp        0      0 *:daytime               *:*                     LISTEN     
    root@www:/opt/chengxu# 
     
    多客户
    到目前为止,一直都是介绍如何用套接字来实现本地的和跨网络的客户/服务器系统,一旦连接建立,套接字连接的行为就类似于打开底层文件描述符,而且在很多方面类似双向管道,现在我们来考虑多个用户同时连接一个服务器的情况,下面是一个例子:
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <netinet/in.h>
    #include <signal.h>
    #include <unistd.h>
    #include <stdlib.h>
    int main()
    {
      int server_sockfd,client_sockfd;//定义套接字描述符
      int server_len,client_len;
      struct sockaddr_in server_address;//套接字地址结构体
      struct sockaddr_in client_address;//套接字地址结构体
      server_sockfd = socket(AF_INET,SOCK_STREAM,0);//创建一个套接字,并返回一个描述符
      server_address.sin_family = AF_INET;//网络介质
      server_address.sin_addr.s_addr = htonl(INADDR_ANY);//要连接到的服务器IP地址
      server_address.sin_port = htons(9734);//要连接到的端口号
      server_len = sizeof(server_address);
      bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接字
      listen(server_sockfd,5);//创建套接字队列
      signal(SIGCHLD,SIG_IGN);
      while(1) {
        char ch;
        printf("server waiting ");
        client_len = sizeof(client_address);
        client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address,&client_len);//接收连接
        if(fork() == 0) {//创建一个子进程用于传输数据
          read(client_sockfd,&ch,1);
          sleep(5);
          ch++;
          write(client_sockfd,&ch,1);
          close(client_sockfd);
          exit(0);
        }
        else {
          close(client_sockfd);//在父进程中关闭打开的套接字描述符
        }
      }
    }
    下面是这个程序的运行情况
    root@www:/opt/chengxu# ./server4 &
    root@www:/opt/chengxu# ./client2 & ./client2 &  ./client2 &
    [6] 6742
    [7] 6743
    [8] 6744
    server waiting
    root@www:/opt/chengxu# server waiting
    server waiting
    char form server = B
    char form server = B
    char form server = B
    [6]   Done                    ./client2
    [7]-  Done                    ./client2
    [8]+  Done                    ./client2
    root@www:/opt/chengxu#
    上面服务器程序用到了子进程来处理要处理数据,这样一来就可以多个客户连接到服务器了
     
    select系统调用
    select系统调用允许程序同时在多个底层文件描述符上等待用户输入(或完成输出),检测读,写,异常文件描述符集,select函数对数据结构fd_set进行操作(也就是说对文件描述符集进行操作),有一组定义好的宏可以用来控制这些文件描述符集,如下:
    #include <sys/types.h>
    #include <sys/time.h>
     
    void FD_ZERO(fd_set *fdset);//初始化(清零)文件描述符集
    void FD_CLR(int fd,fd_set *fdset);//清除文件描述符集
    void FD_SET(int fd,fd_set *fdset);//把文件描述符fd加到文件描述符集中
    int FD_ISSET(int fd,fd_set *fdset);//检测文件描述符变化情况
     
    select函数还可以用一个超时值来防止无限期阻塞,这个超时值由一个timeval结构给出,如下:
    struct timeval {
    time_t   tv_sec;
    long     tv_usec;
    }
     
    select系统调用的原型如下所示:
    #include <sys/types.h>
    #include <sys/time.h>
     
    int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
    select系统调用用于测试文件描述符集合中,是否有一个文件描述符已经处于可读状态或可写状态或错误状态,它将阻塞等待某个文件描述符进入上述状态。
    select函数会在发生如下情况时返回:readfds集合中描述符可读(也就是写入数据了,写完数据后会发送一个可读信号),writefds集合中 有描述符可写或errorfds集合中有描述符错误,如果这三种情况都没有发生,select函数将在timrout指定的超时后返回,这时所有文件描述 符集合将被清空
    下面是一个select系统调用的例子:
    #include <sys/types.h>
    #include <sys/time.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <unistd.h>
    #include <stdlib.h>
    int main()
    {
      char buffer[128];
      int result,nread;
      fd_set inputs,testfds;//定义文件描述符集合
      struct timeval timeout;//定义超时结构体
      FD_ZERO(&inputs);//清零文件描述符集合
      FD_SET(0,&inputs);//把标准输入加到文件描述符集合中
      while(1) {
        testfds = inputs;
        timeout.tv_sec = 2;
        timeout.tv_usec = 500000;
        result = select(FD_SETSIZE,&testfds,(fd_set *)NULL,(fd_set *)NULL,&timeout);//检测是否有标准输入,没有的话每隔2.5秒打印一次timeout
        switch(result) {
          case 0:
            printf("timeout ");
            break;
          case -1:
            perror("select");
            exit(1);
          default:
            if(FD_ISSET(0,&testfds)) {//检测是否有标准输入
              ioctl(0,FIONREAD,&nread);//得到缓冲区里有多少字节要被读取,存到nread中
              if(nread == 0) {
                printf("keyboard done ");
                exit(0);
              }
              nread = read(0,buffer,nread);//从标准输入缓冲区中读取数据
              buffer[nread] = 0;
              printf("read %d from keyboard: %s",nread,buffer);
            }
            break;
        }
      }
    }
    下面是这个程序运行时情况
    root@www:/opt/chengxu# ./select
    timeout
    hello
    read 6 from keyboard: hello
    timeout
    timeout
    fread
    read 6 from keyboard: fread
    keyboard done
    root@www:/opt/chengxu#
    最后来看一个改进的多客户/服务器程序:
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <netinet/in.h>
    #include <sys/time.h>
    #include <sys/ioctl.h>
    #include <unistd.h>
    #include <stdlib.h>
    int main()
    {
      int server_sockfd,client_sockfd;//定义套接字描述符
      int server_len,client_len;
      struct sockaddr_in server_address;//套接字地址结构体
      struct sockaddr_in client_address;//套接字地址结构体
      int result;
      fd_set readfds,testfds;//定义文件描述符集合
      server_sockfd = socket(AF_INET,SOCK_STREAM,0);//创建一个套接字,并返回一个描述符
      server_address.sin_family = AF_INET;//网络介质
      server_address.sin_addr.s_addr = htonl(INADDR_ANY);//要连接到服务器的Ip地址
      server_address.sin_port = htons(9734);//要连接到的端口号
      server_len = sizeof(server_address);
      bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接字
      listen(server_sockfd,5);// 创建套接字队列
      FD_ZERO(&readfds);//清零描述符
      FD_SET(server_sockfd,&readfds);//把server_sockfd描述符加到readfds描述符集合中
      while(1) {
        char ch;
        int fd;
        int nread;
        testfds = readfds;
        printf("server waiting ");
        result = select(FD_SETSIZE,&testfds,(fd_set *)0,(fd_set *)0,(struct timeval *)0);//检测testfds描述符集合是否有变化,没有的话阻塞等待
        if(result < 1) {
          perror("server5");
          exit(1);
        }
        for(fd = 0;fd < FD_SETSIZE;fd++) {//逐个检查描述符
          if(FD_ISSET(fd,&testfds)) {//检测那个描述符集合发生变化,这个很重要
            if(fd == server_sockfd) {//如果是server_sockfd描述符集变化
              client_len = sizeof(client_address);
              client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address,&client_len);//接收连接
              FD_SET(client_sockfd,&readfds);//把客户端的client_sockfd描述符加进描述符集合中,这样就可以用select来检测客户端的描述符了
              printf("adding client on fd %d ",client_sockfd);
            }
              else {
                ioctl(fd,FIONREAD,&nread);//能得到缓冲区里有多少字节要被读取,存进nread中
                if(nread == 0) {
                  close(fd);
                  FD_CLR(fd,&readfds);
                  printf("removing client on fd %d ",fd);
                }
                else {
                  read(fd,&ch,1);
                  sleep(50);
                  printf("serving client on fd %d ",fd);
                  ch++;
                  write(fd,&ch,1);
                }
              }
            }
          }
        }
      }
                        
    这个程序运行如下:
    root@www:/opt/chengxu# ./server5 &
    [6] 7344
    root@www:/opt/chengxu# server waiting
    root@www:/opt/chengxu# ./client2 & ./client2 & ./client2 &
    [7] 7352
    [8] 7353
    [9] 7354
    root@www:/opt/chengxu# server waiting
    server waiting
    server waiting
    char form server = B
    char form server = B
    char form server = B
    [7]   Done                    ./client2
    [8]-  Done                    ./client2
    [9]+  Done                    ./client2
    root@www:/opt/chengxu#
    ....
  • 相关阅读:
    城市生态规划关键技术方法之三:城市生态系统服务功能理论与方法
    AE中用矢量数据剪裁栅格
    城市生态规划的基本原理与程序
    城市生态规划关键技术方法之一:生态系统健康理论与方法
    城市生态规划的基本概念、内容与方法
    终于找到使用Sql Server Management Studio导致蓝屏的罪魁祸首了
    保证相同类型的MDI子窗体只会被打开一次的方法
    甩掉数据字典,让Sql Server数据库也来一个自描述
    新鲜试用IE8 beta2
    让我目瞪口呆的program.exe
  • 原文地址:https://www.cnblogs.com/Ricezhang/p/3735989.html
Copyright © 2020-2023  润新知