【美高梅手机版4858】windows和linux套接字中的select机制浅析

By admin in 美高梅手机版4858 on 2019年4月4日

select系统调用的的用处是:在一段钦定的时辰内,监听用户感兴趣的文书讲述符上可读、可写和丰裕等事件。

select系统调用的的用途是:在壹段钦赐的年华内,监听用户感兴趣的文书讲述符上可读、可写和十分等事件。

先来谈谈为啥会冒出select函数,相当于select是消除什么难题的?

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <sys/types.h>
 5 #include <sys/socket.h>
 6 #include <arpa/inet.h>
 7 #include <unistd.h>
 8 
 9 #define PORT 8888
10 #define BACKLOG 20
11 
12 #define MAX1(a,b) (a) > (b) ? (a) : (b)
13 
14 int main (){
15     int ret = 0;
16     int i = 0;
17     int max = 0;
18     int nCurrentSocket = 0;
19     int ss,sc;
20     char buffer[1024];
21     ssize_t size = 0;
22     struct sockaddr_in server_addr;
23     struct sockaddr_in client_addr;
24     int err;
25     pid_t pid;
26     char dst_ip[16]="";
27     
28     ss = socket(AF_INET,SOCK_STREAM,0);
29     if(ss < 0 ){
30         printf("socket error \n");
31         return -1;
32     }
33     
34     bzero(&server_addr,sizeof(server_addr));
35     server_addr.sin_family = AF_INET;
36     server_addr.sin_addr.s_addr = inet_addr("192.168.0.219");
37     server_addr.sin_port = htons(PORT);
38     
39     err = bind(ss,(struct sockaddr*)&server_addr,sizeof(server_addr));
40     if(err < 0 ){
41         printf("bind error\n");
42         return -1;
43     }
44     
45     err = listen (ss , BACKLOG);
46     if(err < 0 ){
47         printf("listen error\n");
48         return -1;
49     }
50     
51     fd_set fdReadFds;
52 
53 
54 
55     FD_ZERO(&fdReadFds);
56     FD_SET(ss,&fdReadFds);
57     max = ss;
58 
59     while(1){
60     
61         ret = select(max + 1, &fdReadFds, NULL, NULL, NULL);
62         if (ret == 0)
63         {
64             continue;
65         }
66         if (ret < 0)
67         {
68             continue;
69         }
70         
71 
72         for (i = 3; i <= max; i++)
73         {
74             nCurrentSocket = i;
75             
76             if (FD_ISSET(nCurrentSocket, &fdReadFds))
77             {
78                 if (nCurrentSocket == ss)
79                 {
80                     socklen_t addrlen= sizeof(struct sockaddr);
81                        sc = accept(ss,(struct sockaddr*)&client_addr,&addrlen);
82                     if(sc < 0)
83                     {
84                         continue;
85                     }
86                     FD_SET(sc,&fdReadFds);
87                     max = MAX1(max,sc);
88                     continue;
89                 }
90                 else
91                 {
92                     memset(buffer,0,1024);
93                                         size = read(sc,buffer,1024);
94                                         printf("==%d==:%d    %s\n",sc,size,buffer);
95                 }    
96             }
97         }
98     }
99 }

select 机制的优势

为什么会出现select模型?

先看一下上面包车型大巴那句代码:

int iResult = recv(s, buffer,1024);

那是用来接收数据的,在默许的隔开分离形式下的套接字里,recv会阻塞在那边,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会回到,不然就会一向不通在这边。在单线程的先后里冒出那种情状会招致主线程(单线程程序里唯有一个暗许的主线程)被封堵,那样任何程序被锁死在那边,假如永
远没多少发送过来,那么程序就会被永远锁死。这么些题材得以用多线程解决,可是在有八个套接字连接的状态下,那不是1个好的选用,扩大性很差。

再看代码:

int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);

这3次recv的调用不管套接字连接上有未有数据能够收到都会立刻重回。原因就在于大家用ioctlsocket把套接字设置为非阻塞形式了。但是你跟踪一下就会意识,在尚未数据的状态下,recv确实是随即重回了,不过也回到了2个不当:WSAEWOULDBLOCK,意思就是伸手的操作未有马到功成做到。

观望此间很两人唯恐会说,那么就再一次调用recv并检讨再次来到值,直到成功截止,不过那样做作用很成难点,花费太大。

select模型的产出正是为了消除上述难点。
select模型的重中之重是利用1种有序的法子,对多个套接字进行合并保管与调度 。

美高梅手机版4858 1

如上所示,用户率先将急需展开IO操作的socket添加到select中,然后阻塞等待select系统调用重临。当数码到达时,socket被激活,select函数再次来到。用户线程正式发起read请求,读取数据并继续执行。

从流程上来看,使用select函数进行IO请求和1块阻塞模型未有太大的界别,甚至还多了丰裕监视socket,以及调用select函数的额外操作,功能更差。不过,使用select现在最大的优势是用户能够在八个线程内同时处理四个socket的IO请求。用户能够登记几个socket,然后不断地调用select读取被激活的socket,即可直达在同三个线程内同时处理多少个IO请求的指标。而在共同阻塞模型中,必须经过10贰线程的法门才能达标那几个指标。

select流程伪代码如下:

{
    select(socket);
    while(1) 
    {
        sockets = select();
        for(socket in sockets) 
        {
            if(can_read(socket)) 
            {
                read(socket, buffer);
                process(buffer);
            }
        }
    }
}

select 机制的优势

何以会现出select模型?

先看一下底下的这句代码:

int iResult = recv(s, buffer,1024);

那是用来接收数据的,在默许的围堵方式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数量读到buffer里后recv函数才会回到,不然就会间接不通在那里。在单线程的先后里涌出那种场馆会招致主线程(单线程程序里只有二个默许的主线程)被封堵,那样全方位程序被锁死在那里,若是永
远没多少发送过来,那么程序就会被永久锁死。这几个题材得以用十贰线程消除,不过在有多个套接字连接的状态下,那不是1个好的选料,扩张性很差。

再看代码:

int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);

那3遍recv的调用不管套接字连接上有未有数据能够接收都会及时再次来到。原因就在于大家用ioctlsocket把套接字设置为非阻塞情势了。然而你跟踪一下就会意识,在尚未数量的状态下,recv确实是立时赶回了,不过也回到了三个不当:WSAEWOULDBLOCK,意思正是伸手的操作未有马到功成做到。

看样子此间很三人唯恐会说,那么就再一次调用recv并检讨再次回到值,直到成功结束,可是这样做作用很成难点,开支太大。

select模型的产出正是为了消除上述难题。
select模型的重中之重是利用一种有序的艺术,对多个套接字举办合并保管与调度 。

美高梅手机版4858 2

如上所示,用户率先将急需展开IO操作的socket添加到select中,然后阻塞等待select系统调用重返。当数码到达时,socket被激活,select函数再次回到。用户线程正式发起read请求,读取数据并继续执行。

从流程上来看,使用select函数实行IO请求和壹道阻塞模型未有太大的界别,甚至还多了增加监视socket,以及调用select函数的额外操作,效能更差。不过,使用select以往最大的优势是用户能够在二个线程内同时处理七个socket的IO请求。用户能够注册七个socket,然后不断地调用select读取被激活的socket,即可直达在同2个线程内同时处理八个IO请求的指标。而在一块儿阻塞模型中,必须经过102线程的办法才能达到那一个指标。

select流程伪代码如下:

{
    select(socket);
    while(1) 
    {
        sockets = select();
        for(socket in sockets) 
        {
            if(can_read(socket)) 
            {
                read(socket, buffer);
                process(buffer);
            }
        }
    }
}

日常使用的recv函数时打断的,也便是假诺未有数据可读,recv就会直接不通在那里,那是只要有其它3个总是过来,就得直白等候,那样实时性就不是太好。

socket与select()函数使用例程

select相关API介绍与运用

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

参数表达:

maxfdp:被监听的文本讲述符的总数,它比有所文件讲述符集合中的公文讲述符的最大值大1,因为文件讲述符是从0开端计数的;

readfds、writefds、exceptset:分别指向可读、可写和11分等事件对应的叙说符集合。

timeout:用于安装select函数的逾期时间,即告诉内核select等待多长期之后就放任等待。timeout
== NULL 表示等待无限长的光阴

timeval结构体定义如下:

struct timeval
{      
    long tv_sec;   /*秒 */
    long tv_usec;  /*微秒 */   
};

重临值:超时再次回到0;战败重临-一;成功重临大于0的整数,这一个平头表示就绪描述符的数量。

以下介绍与select函数相关的宽广的多少个宏:

#include <sys/select.h>   
int FD_ZERO(int fd, fd_set *fdset);   //一个 fd_set类型变量的所有位都设为 0
int FD_CLR(int fd, fd_set *fdset);  //清除某个位时可以使用
int FD_SET(int fd, fd_set *fd_set);   //设置变量的某个位置位
int FD_ISSET(int fd, fd_set *fdset); //测试某个位是否被置位

select使用范例:
当评释了叁个文本讲述符集后,必须用FD_ZERO将全体地方零。之后将大家所感兴趣的叙述符所对应的地方位,操作如下:

fd_set rset;   
int fd;   
FD_ZERO(&rset);   
FD_SET(fd, &rset);   
FD_SET(stdin, &rset);

接下来调用select函数,拥挤堵塞等待文件讲述符事件的赶来;假使超越设定的时光,则不再等待,继续往下执行。

select(fd+1, &rset, NULL, NULL,NULL);

select返回后,用FD_ISSET测试给一定是不是置位:

if(FD_ISSET(fd, &rset)   
{ 
    ... 
    //do something  
}

上边是三个最简单易行的select的施用例子:

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
    fd_set rd;
    struct timeval tv;
    int err;


    FD_ZERO(&rd);
    FD_SET(0,&rd);

    tv.tv_sec = 5;
    tv.tv_usec = 0;
    err = select(1,&rd,NULL,NULL,&tv);

    if(err == 0) //超时
    {
        printf("select time out!\n");
    }
    else if(err == -1)  //失败
    {
        printf("fail to select!\n");
    }
    else  //成功
    {
        printf("data is available!\n");
    }


    return 0;
}

小编们运维该程序同时随便输入①些数额,程序就提醒收到数额了。
美高梅手机版4858 3

select相关API介绍与应用

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

参数表明:

maxfdp:被监听的文本讲述符的总和,它比有所文件讲述符集合中的文件讲述符的最大值大1,因为文件讲述符是从0开始计数的;

readfds、writefds、exceptset:分别指向可读、可写和特别等事件对应的讲述符集合。

timeout:用于安装select函数的逾期时间,即告诉内核select等待多久之后就抛弃等待。timeout
== NULL 表示等待Infiniti长的时刻

timeval结构体定义如下:

struct timeval
{      
    long tv_sec;   /*秒 */
    long tv_usec;  /*微秒 */   
};

重临值:超时再次来到0;战败重回-1;成功重回大于0的平头,那些平头表示就绪描述符的多少。

以下介绍与select函数相关的广泛的多少个宏:

#include <sys/select.h>   
int FD_ZERO(int fd, fd_set *fdset);   //一个 fd_set类型变量的所有位都设为 0
int FD_CLR(int fd, fd_set *fdset);  //清除某个位时可以使用
int FD_SET(int fd, fd_set *fd_set);   //设置变量的某个位置位
int FD_ISSET(int fd, fd_set *fdset); //测试某个位是否被置位

select使用范例:
当评释了1个文书讲述符集后,必须用FD_ZERO将全数职位零。之后将大家所感兴趣的叙说符所对应的地方位,操作如下:

fd_set rset;   
int fd;   
FD_ZERO(&rset);   
FD_SET(fd, &rset);   
FD_SET(stdin, &rset);

然后调用select函数,拥挤堵塞等待文件讲述符事件的来临;假若跨越设定的时日,则不再等待,继续往下实施。

select(fd+1, &rset, NULL, NULL,NULL);

select返回后,用FD_ISSET测试给一定是或不是置位:

if(FD_ISSET(fd, &rset)   
{ 
    ... 
    //do something  
}

上边是3个最简易的select的使用例子:

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
    fd_set rd;
    struct timeval tv;
    int err;


    FD_ZERO(&rd);
    FD_SET(0,&rd);

    tv.tv_sec = 5;
    tv.tv_usec = 0;
    err = select(1,&rd,NULL,NULL,&tv);

    if(err == 0) //超时
    {
        printf("select time out!\n");
    }
    else if(err == -1)  //失败
    {
        printf("fail to select!\n");
    }
    else  //成功
    {
        printf("data is available!\n");
    }


    return 0;
}

咱俩运维该程序同时随便输入一些数码,程序就提示收到多少了。
美高梅手机版4858 4

以此题材的多少个缓解措施:壹.
行使ioctlsocket函数,将recv函数设置成非阻塞的,那样不管套接字上有未有数据都会马上回到,能够重新调用recv函数,那种方法叫做轮询(polling),不过如此效能至极难点,因为,大部分日子实在是许多据可读的,费用时间持续反复实践read系统调用,这样就相比浪费CPU的年月。并且循环之间的区间不佳明显。2.
行使fork,使用多进度来化解,那里终止会比较复杂(待商讨)。
三.使用八线程来缓解,那样制止了截止的复杂,但却须求处理线程之间的一路,在回落复杂性方面这恐怕会以珠弹雀。4.
使用异步IO(待斟酌)。伍.
正是本文所接纳的I/O多路转接(多路复用)–其实就是在套接字阻塞和非阻塞之间做了二个动态平衡,大家誉为半阻塞。

select函数用于在非阻塞中,当3个套接字或一组套接字有时域信号时通报你,系统提供select函数来贯彻多路复用输入/输出模型,原型:
int select(int maxfd,fd_set
*rdset,fd_set *wrset,fd_set *exset,struct timeval
*timeout);

深深了然select模型:

领悟select模型的关键在于精通fd_set,为注脚方便,取fd_set长度为1字节,fd_set中的每壹bit方可对应3个文书讲述符fd。则一字节长的fd_set最大能够对应七个fd。

(1)执行fd_set set; FD_ZERO(&set); 则set用位代表是0000,0000。

(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

(三)若再加入fd=贰,fd=一,则set变为000一,0011

(4)执行select(6,&set,0,0,0)阻塞等待

(伍)若fd=一,fd=二上都发出可读事件,则select重临,此时set变为0000,001一。注意:未有事件发生的fd=5被清空。

听闻上边包车型地铁讨论,能够轻松得出select模型的特点:

(壹)可监察和控制的公文讲述符个数取决与sizeof(fd_set)的值。小编那边服务器上sizeof(fd_set)=512,每bit表示二个文本描述符,则本人服务器上扶助的最大文件讲述符是51二*8=40九陆。据他们说可调,另有说即使可调,但调整上限受于编写翻译内核时的变量值。

(二)将fd参与select监察和控制集的还要,还要再使用四个数据结构array保存放到select监察和控制集中的fd,一是用来再select重返后,array作为源数据和fd_set进行FD_ISSET判断。贰是select重临后会把在此以前参预的但并无事件发生的fd清空,则每便初阶select前都要再度从array取得fd逐Nokia入(FD_ZERO起始),扫描array的还要取得fd最大值maxfd,用于select的首先个参数。

(3)可知select模型必须在select前循环加fd,取maxfd,select重回后使用FD_ISSET判断是或不是有事件发生。

深入掌握select模型:

知晓select模型的关键在于精晓fd_set,为求证方便,取fd_set长度为1字节,fd_set中的每一bit方可对应3个文书讲述符fd。则一字节长的fd_set最大能够对应八个fd。

(1)执行fd_set set; FD_ZERO(&set); 则set用位代表是0000,0000。

(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

(三)若再参加fd=二,fd=1,则set变为0001,001一

(四)执行select(6,&set,0,0,0)阻塞等待

(5)若fd=一,fd=二上都发生可读事件,则select重回,此时set变为0000,001壹。注意:没有事件发生的fd=伍被清空。

依据下面的议论,可以轻松得出select模型的性状:

(1)可监控的公文讲述符个数取决与sizeof(fd_set)的值。小编这边服务器上sizeof(fd_set)=51二,每bit表示三个文书描述符,则自个儿服务器上帮忙的最大文件讲述符是51二*八=409陆。听大人讲可调,另有说即使可调,但调整上限受于编写翻译内核时的变量值。

(贰)将fd加入select监察和控制集的还要,还要再选用1个数据结构array保存放到select监察和控制集中的fd,壹是用于再select再次来到后,array作为源数据和fd_set进行FD_ISSET判断。2是select重回后会把原先参与的但并无事件产生的fd清空,则每趟初阶select前都要再一次从array取得fd逐中兴入(FD_ZERO先河),扫描array的同时取得fd最大值maxfd,用于select的首先个参数。

(叁)可知select模型必须在select前循环加fd,取maxfd,select再次来到后选用FD_ISSET判断是不是有事件发生。

透过对select的开始摸底,在windows和linux下的落到实处小有分别,所以分开来写。那里先写windows下的select机制。

 

用select处理带外数据

互连网程序中,select能处理的格外景况只有1种:socket上收取到带外数据。

怎么是带外数据?

带外数据(out—of—band data),有时也叫做加快数据(expedited data),
是指接连双方中的一方爆发重要工作,想要火速地通报对方。
那种文告在曾经排队等候发送的任何“普通”(有时称为“带内”)数据此前发送。
带外数据陈设为比经常数据有更高的先期级。
带外数据是炫耀到存活的接连中的,而不是在客户机和服务器间再用1个接连。

大家写的select程序日常都以用以收纳普通数据的,当大家的服务器须求同时收取普通数据和带外数据,我们怎么着运用select实行拍卖两岸呢?

下面给出三个小demo:

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>


int main(int argc, char* argv[])
{
    if(argc <= 2)
    {
        printf("usage: ip address + port numbers\n");
        return -1;
    }

    const char* ip = argv[1];
    int port = atoi(argv[2]);

        printf("ip: %s\n",ip);
        printf("port: %d\n",port);

    int ret = 0;
    struct sockaddr_in address;
    bzero(&address,sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&address.sin_addr);
    address.sin_port = htons(port);

    int listenfd = socket(PF_INET,SOCK_STREAM,0);
    if(listenfd < 0)
    {
        printf("Fail to create listen socket!\n");
        return -1;
    }

    ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address));
    if(ret == -1)
    {
        printf("Fail to bind socket!\n");
        return -1;
    }

    ret = listen(listenfd,5); //监听队列最大排队数设置为5
    if(ret == -1)
    {
        printf("Fail to listen socket!\n");
        return -1;
    }

    struct sockaddr_in client_address;  //记录进行连接的客户端的地址
    socklen_t client_addrlength = sizeof(client_address);
    int connfd = accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
    if(connfd < 0)
    {
        printf("Fail to accept!\n");
        close(listenfd);
    }

    char buff[1024]; //数据接收缓冲区
    fd_set read_fds;  //读文件操作符
    fd_set exception_fds; //异常文件操作符
    FD_ZERO(&read_fds);
    FD_ZERO(&exception_fds);

    while(1)
    {
        memset(buff,0,sizeof(buff));
        /*每次调用select之前都要重新在read_fds和exception_fds中设置文件描述符connfd,因为事件发生以后,文件描述符集合将被内核修改*/
        FD_SET(connfd,&read_fds);
        FD_SET(connfd,&exception_fds);

        ret = select(connfd+1,&read_fds,NULL,&exception_fds,NULL);
        if(ret < 0)
        {
            printf("Fail to select!\n");
            return -1;
        }


        if(FD_ISSET(connfd, &read_fds))
        {
            ret = recv(connfd,buff,sizeof(buff)-1,0);
            if(ret <= 0)
            {
                break;
            }

            printf("get %d bytes of normal data: %s \n",ret,buff);

        }
        else if(FD_ISSET(connfd,&exception_fds)) //异常事件
        {
            ret = recv(connfd,buff,sizeof(buff)-1,MSG_OOB);
            if(ret <= 0)
            {
                break;
            }

            printf("get %d bytes of exception data: %s \n",ret,buff);
        }

    }

    close(connfd);
    close(listenfd);


    return 0;
}

用select处理带外数据

互连网程序中,select能处理的很是景况只有壹种:socket上收到到带外数据。

什么样是带外数据?

带外数据(out—of—band data),有时也号称加速数据(expedited data),
是指接连双方中的一方发生首要事务,想要急迅地打招呼对方。
这种公告在早就排队等候发送的别的“普通”(有时称为“带内”)数据在此之前发送。
带外数据布置为比常见数据有更高的事先级。
带外数据是炫耀到现有的连蒲月的,而不是在客户机和劳务器间再用二个三番五次。

作者们写的select程序日常都是用来吸收接纳普通数据的,当大家的服务器必要同时收纳普通数据和带外数据,大家什么选择select举行拍卖双边呢?

下边给出二个小demo:

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>


int main(int argc, char* argv[])
{
    if(argc <= 2)
    {
        printf("usage: ip address + port numbers\n");
        return -1;
    }

    const char* ip = argv[1];
    int port = atoi(argv[2]);

        printf("ip: %s\n",ip);
        printf("port: %d\n",port);

    int ret = 0;
    struct sockaddr_in address;
    bzero(&address,sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&address.sin_addr);
    address.sin_port = htons(port);

    int listenfd = socket(PF_INET,SOCK_STREAM,0);
    if(listenfd < 0)
    {
        printf("Fail to create listen socket!\n");
        return -1;
    }

    ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address));
    if(ret == -1)
    {
        printf("Fail to bind socket!\n");
        return -1;
    }

    ret = listen(listenfd,5); //监听队列最大排队数设置为5
    if(ret == -1)
    {
        printf("Fail to listen socket!\n");
        return -1;
    }

    struct sockaddr_in client_address;  //记录进行连接的客户端的地址
    socklen_t client_addrlength = sizeof(client_address);
    int connfd = accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
    if(connfd < 0)
    {
        printf("Fail to accept!\n");
        close(listenfd);
    }

    char buff[1024]; //数据接收缓冲区
    fd_set read_fds;  //读文件操作符
    fd_set exception_fds; //异常文件操作符
    FD_ZERO(&read_fds);
    FD_ZERO(&exception_fds);

    while(1)
    {
        memset(buff,0,sizeof(buff));
        /*每次调用select之前都要重新在read_fds和exception_fds中设置文件描述符connfd,因为事件发生以后,文件描述符集合将被内核修改*/
        FD_SET(connfd,&read_fds);
        FD_SET(connfd,&exception_fds);

        ret = select(connfd+1,&read_fds,NULL,&exception_fds,NULL);
        if(ret < 0)
        {
            printf("Fail to select!\n");
            return -1;
        }


        if(FD_ISSET(connfd, &read_fds))
        {
            ret = recv(connfd,buff,sizeof(buff)-1,0);
            if(ret <= 0)
            {
                break;
            }

            printf("get %d bytes of normal data: %s \n",ret,buff);

        }
        else if(FD_ISSET(connfd,&exception_fds)) //异常事件
        {
            ret = recv(connfd,buff,sizeof(buff)-1,MSG_OOB);
            if(ret <= 0)
            {
                break;
            }

            printf("get %d bytes of exception data: %s \n",ret,buff);
        }

    }

    close(connfd);
    close(listenfd);


    return 0;
}

select的大体思想:将三个套接字放在1个成团里,然后统一检查这一个套接字的情状(可读、可写、分外等),调用select后,会更新那几个套接字的情况,然后做判断,借使套接字可读,就实施read操作。那样就高明地制止了不通,达到同时处理三个再而三的目标。当然要是未有事件发生,select会平素不通,如若不想直接让它等待,想去处理任何工作,可以设置贰个最大的等候时间。

所在的头文件为:#include
<sys/time.h> 和#include <unistd.h>

用select来消除socket中的多客户难点

地点提到过,,使用select以往最大的优势是用户可以在二个线程内同时处理三个socket的IO请求。在网络编制程序中,当提到到多客户走访服务器的场合,我们首先想到的点子正是fork出八个经过来处理每种客户连接。今后,大家一样能够动用select来拍卖多客户难题,而不用fork。

劳务器端

#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);//建立服务器端socket 
    server_address.sin_family = AF_INET; 
    server_address.sin_addr.s_addr = htonl(INADDR_ANY); 
    server_address.sin_port = htons(8888); 
    server_len = sizeof(server_address); 
    bind(server_sockfd, (struct sockaddr *)&server_address, server_len); 
    listen(server_sockfd, 5); //监听队列最多容纳5个 
    FD_ZERO(&readfds); 
    FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中
    while(1) 
    {
        char ch; 
        int fd; 
        int nread; 
        testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量 
        printf("server waiting\n"); 

        /*无限期阻塞,并测试文件描述符变动 */
        result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) 0); //FD_SETSIZE:系统默认的最大文件描述符
        if(result < 1) 
        { 
            perror("server5"); 
            exit(1); 
        } 

        /*扫描所有的文件描述符*/
        for(fd = 0; fd < FD_SETSIZE; fd++) 
        {
            /*找到相关文件描述符*/
            if(FD_ISSET(fd,&testfds)) 
            { 
              /*判断是否为服务器套接字,是则表示为客户请求连接。*/
                if(fd == server_sockfd) 
                { 
                    client_len = sizeof(client_address); 
                    client_sockfd = accept(server_sockfd, 
                    (struct sockaddr *)&client_address, &client_len); 
                    FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中
                    printf("adding client on fd %d\n", client_sockfd); 
                } 
                /*客户端socket中有数据请求时*/
                else 
                { 
                    ioctl(fd, FIONREAD, &nread);//取得数据量交给nread

                    /*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */
                    if(nread == 0) 
                    { 
                        close(fd); 
                        FD_CLR(fd, &readfds); //去掉关闭的fd
                        printf("removing client on fd %d\n", fd); 
                    } 
                    /*处理客户数据请求*/
                    else 
                    { 
                        read(fd, &ch, 1); 
                        sleep(5); 
                        printf("serving client on fd %d\n", fd); 
                        ch++; 
                        write(fd, &ch, 1); 
                    } 
                } 
            } 
        } 
    } 

    return 0;
}

客户端

//客户端
#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <unistd.h> 
#include <stdlib.h>
#include <sys/time.h>

int main() 
{ 
    int client_sockfd; 
    int len; 
    struct sockaddr_in address;//服务器端网络地址结构体 
     int result; 
    char ch = 'A'; 
    client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket 
    address.sin_family = AF_INET; 
    address.sin_addr.s_addr = inet_addr("127.0.0.1");
    address.sin_port = htons(8888); 
    len = sizeof(address); 
    result = connect(client_sockfd, (struct sockaddr *)&address, len); 
    if(result == -1) 
    { 
         perror("oops: client2"); 
         exit(1); 
    } 
    //第一次读写
    write(client_sockfd, &ch, 1); 
    read(client_sockfd, &ch, 1); 
    printf("the first time: char from server = %c\n", ch); 
    sleep(5);

    //第二次读写
    write(client_sockfd, &ch, 1); 
    read(client_sockfd, &ch, 1); 
    printf("the second time: char from server = %c\n", ch);

    close(client_sockfd); 

    return 0; 
}

运行流程:

客户端:运维->连接服务器->发送A->等待服务器复苏->收到B->再发B给服务器->收到C->停止

服务器:启动->select->收到A->发A+1回去->收到B->发B+1过去

测试:大家先运转服务器,再运营客户端
美高梅手机版4858 5

用select来缓解socket中的多客户难点

地方提到过,,使用select今后最大的优势是用户能够在三个线程内同时处理四个socket的IO请求。在互联网编制程序中,当提到到多客户走访服务器的场所,大家先是想到的不二等秘书籍便是fork出五个进程来拍卖各样客户连接。以往,我们1样能够应用select来拍卖多客户难题,而不用fork。

劳务器端

#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);//建立服务器端socket 
    server_address.sin_family = AF_INET; 
    server_address.sin_addr.s_addr = htonl(INADDR_ANY); 
    server_address.sin_port = htons(8888); 
    server_len = sizeof(server_address); 
    bind(server_sockfd, (struct sockaddr *)&server_address, server_len); 
    listen(server_sockfd, 5); //监听队列最多容纳5个 
    FD_ZERO(&readfds); 
    FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中
    while(1) 
    {
        char ch; 
        int fd; 
        int nread; 
        testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量 
        printf("server waiting\n"); 

        /*无限期阻塞,并测试文件描述符变动 */
        result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) 0); //FD_SETSIZE:系统默认的最大文件描述符
        if(result < 1) 
        { 
            perror("server5"); 
            exit(1); 
        } 

        /*扫描所有的文件描述符*/
        for(fd = 0; fd < FD_SETSIZE; fd++) 
        {
            /*找到相关文件描述符*/
            if(FD_ISSET(fd,&testfds)) 
            { 
              /*判断是否为服务器套接字,是则表示为客户请求连接。*/
                if(fd == server_sockfd) 
                { 
                    client_len = sizeof(client_address); 
                    client_sockfd = accept(server_sockfd, 
                    (struct sockaddr *)&client_address, &client_len); 
                    FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中
                    printf("adding client on fd %d\n", client_sockfd); 
                } 
                /*客户端socket中有数据请求时*/
                else 
                { 
                    ioctl(fd, FIONREAD, &nread);//取得数据量交给nread

                    /*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */
                    if(nread == 0) 
                    { 
                        close(fd); 
                        FD_CLR(fd, &readfds); //去掉关闭的fd
                        printf("removing client on fd %d\n", fd); 
                    } 
                    /*处理客户数据请求*/
                    else 
                    { 
                        read(fd, &ch, 1); 
                        sleep(5); 
                        printf("serving client on fd %d\n", fd); 
                        ch++; 
                        write(fd, &ch, 1); 
                    } 
                } 
            } 
        } 
    } 

    return 0;
}

客户端

//客户端
#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <unistd.h> 
#include <stdlib.h>
#include <sys/time.h>

int main() 
{ 
    int client_sockfd; 
    int len; 
    struct sockaddr_in address;//服务器端网络地址结构体 
     int result; 
    char ch = 'A'; 
    client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket 
    address.sin_family = AF_INET; 
    address.sin_addr.s_addr = inet_addr("127.0.0.1");
    address.sin_port = htons(8888); 
    len = sizeof(address); 
    result = connect(client_sockfd, (struct sockaddr *)&address, len); 
    if(result == -1) 
    { 
         perror("oops: client2"); 
         exit(1); 
    } 
    //第一次读写
    write(client_sockfd, &ch, 1); 
    read(client_sockfd, &ch, 1); 
    printf("the first time: char from server = %c\n", ch); 
    sleep(5);

    //第二次读写
    write(client_sockfd, &ch, 1); 
    read(client_sockfd, &ch, 1); 
    printf("the second time: char from server = %c\n", ch);

    close(client_sockfd); 

    return 0; 
}

运营流程:

客户端:运营->连接服务器->发送A->等待服务器苏醒->收到B->再发B给服务器->收到C->甘休

服务器:启动->select->收到A->发A+1回去->收到B->发B+1过去

测试:大家先运营服务器,再运维客户端
美高梅手机版4858 6

/***********************************************************************************************************/

 

select总结:

select本质上是透过安装或许检查存放fd标志位的数据结构来进展下一步处理。那样所推动的通病是:

壹、单个进程可监视的fd数量被限定,即能监听端口的尺寸有限。壹般的话那几个数额和系统内部存款和储蓄器关系十分大,具体数据能够cat/proc/sys/fs/file-max察看。30人机暗中同意是拾2四个。陆拾8位机暗中同意是204八.

贰、
对socket进行扫描时是线性扫描,即利用轮询的格局,作用较低:当套接字相比多的时候,每一次select()都要由此遍历FD_SETSIZE个Socket来成功调度,不管哪个Socket是活泼的,都遍历1次。那会浪费广大CPU时间。若是能给套接字注册某些回调函数,当她们龙腾虎跃时,自动达成有关操作,那就幸免了轮询,那多亏epoll与kqueue做的。

三、要求珍贵三个用来存放在多量fd的数据结构,那样会使得用户空间和基础空间在传递该组织时复制费用大。

select总结:

select本质上是透过安装也许检查存放fd标志位的数据结构来开始展览下一步处理。那样所推动的短处是:

1、单个进度可监视的fd数量被限定,即能监听端口的轻重缓急有限。1般的话那些数据和种类内部存款和储蓄器关系不小,具体数目能够cat/proc/sys/fs/file-max察看。三十一个人机私下认可是拾贰拾7个。66位机暗中同意是204捌.

二、
对socket进行围观时是线性扫描,即采取轮询的点子,成效较低:当套接字比较多的时候,每一遍select()都要通过遍历FD_SETSIZE个Socket来形成调度,不管哪个Socket是活跃的,都遍历一次。这会浪费广大CPU时间。假使能给套接字注册有个别回调函数,当他俩活跃时,自动完毕相关操作,那就防止了轮询,这正是epoll与kqueue做的。

三、须求保险二个用来存放大批量fd的数据结构,那样会使得用户空间和水源空间在传递该协会时复制花费大。

下边具体讲讲函数的参数,参见MSDN的表明:

   参数maxfd是急需监视的最大的文书讲述符值+壹;rdset,wrset,exset分别对应于供给检测的可读文件讲述符的成团,可写文件讲述符的集
合及万分文件讲述符的集纳。struct
timeval结构用于描述1段时间长度,就算在那个时刻内,供给监视的讲述符未有事件发生则函数重临,再次回到值为0。 

 

 

[cpp]【美高梅手机版4858】windows和linux套接字中的select机制浅析。 view
plaincopy

    对于fd_set类型通过上边多少个宏来操作:
    FD_ZERO(fd_set
*fdset) 将钦点的公文讲述符集清空,在对文件讲述符集合实行安装前,必须对其进展发轫化,假使不清空,由于在系统一分配配内部存款和储蓄器空间后,日常并不作清空处理,所以结果是不可见的。
    FD_SET(fd_set
*fdset) 用于在文件讲述符集合中扩充二个新的公文讲述符。
    FD_CLR(fd_set
*fdset) 用于在文件讲述符集合中删除一个文本讲述符。
    FD_ISSET(int fd,fd_set
*fdset) 用于测试钦点的文书讲述符是不是在该集合中。

 

 

  1. int select(  
  2.   _In_     int nfds,  
  3.   _Inout_  fd_set *readfds,  
  4.   _Inout_  fd_set *writefds,  
  5.   _Inout_  fd_set *exceptfds,  
  6.   _In_     const struct timeval *timeout  
  7. );  

UNIX系统常常会在头文件<sys/select.h>中定义常量FD_SETSIZE,它是数据类型fd_set的叙说字数量,其值日常是拾二四,那样就能代表<拾二四的fd。

函数的再次回到值,表示准备好的套接字的个数,假设是0,则表示尚未八个备选好(超时正是壹种境况),假设是-一(SOCKET_E卡宴RO奥迪Q3),表示有错误发生,能够使用WSAGetLastError.aspx)()函数来赢得错误代码,从而知道是怎么样错误。

 

 

 

函数的参数,第三个是输入参数nfds,表示满意条件的套接字的个数,windows下能够安装为0,因为fd_set结构体中早已蕴涵了那个参数,这么些参数已经是多余的了,之所以还留存,只是是为着与FreeBSD包容。

好了在研讨了一番关于fd_set的音讯之后,再回到对select函数的接头上来吗。

第二三四参数都是输入输出参数(值-结果参数,输入和输出会不壹样),表示套接字的可读、可写和足够三种状态的汇集。调用select之后,要是钦定套接字不可读可能不可写,就会从相应队列中清除,那样就能够断定什么套接字可读只怕可写。 

 

证美赞臣下,那里的可读性是指:要是有客户的连接请求到达,套接口正是可读的,调用accept能够立即成功,而不发生堵塞;若是套接口接收队列缓冲区中的字节数大于0,调用recv可能recvfrom就不会堵塞。可写性是指,可以向套接字发送数据(套接字创制成功后,正是可写的)。当然不是套接字可写就会去发送数据,就像是还是不是探望机子就去打电话一样,而是由打电话的要求了,才去看电话是还是不是可打;可读就不1致了,电话响了,自然要去接电话(除非,你有事忙可能不想接,一般都以要接的)。可读已经包罗了缓冲区中有数量可以读取,可写只是表明了缓冲区有空间让您写,你需不需求写就要看你有未有数据要写了.有关那些,就是指1些奇怪景况,本身用的比较少,未来用到了,再过来补上。

    功用:测试钦点的fd可读?可写?有非常条件待处理?
    readset  用来检查可读性的一组文件讲述字。
    writeset 用来检查可写性的一组文件讲述字。
    exceptset用来检查是还是不是有卓殊条件出现的公文讲述字。(注:不包蕴错误)
    timeout  用于描述壹段时长,要是在这么些时间内,须要监视的叙说符未有事件发生则函数再次回到,重回值为0。

第六个参数是等待的最大日子,是贰个结构体:struct timeval,它的定义是:

 

 

    对于select函数的效果简单来讲正是对文件fd做二个测试。测试结果有二种或许:
    一.timeout=NULL(阻塞:select将直接被封堵,直到有个别文件讲述符上产生了轩然大波)
    贰.timeout所指向的布局设为非零时间(等待固定时间:如若在钦定的时间段里有事件时有发生或然时间耗尽,函数均重临)
    三.timeout所指向的构造,时间设为0(非阻塞:仅检查实验描述符集合的情景,然后马上赶回,并不等待外部事件的发出)

[cpp] view
plaincopy

 

 

 

  1. /* 
  2. * Structure used in select() call, taken from the BSD file sys/time.h. 
  3. */  
  4. struct timeval {  
  5.         long    tv_sec;         /* seconds */  
  6.         long    tv_usec;        /* and microseconds */  
  7. };  

 

现实到秒和神秘,依照等待的时光长度能够分成不等待、等待一定时间、一向守候。对应的装置分别为,(0,0)是不等待,那是select是非阻塞的,(x,y)最大等待时间x秒y微妙(若是有事件就会提早再次回到,而不延续伺机),NULL表示一直等候,直到有事件时有产生。这里能够将timeout分别设置成0(不打断)或许一微妙(阻塞非常短的时日),然后观看CPU的使用率,会意识安装成非阻塞后,CPU的使用率已下载就上升到了3/6左右,那样能够见到非阻塞占用CPU很多,但利用率不高。

重临值:重返对应位依旧为一的fd的总和。注意啊:只有这三个可读,可写以及有不行条件待处理的fd位仍旧为一。不然为0哦。

 

举个例证,比如recv(),
在尚未数量来临调用它的时候,你的线程将被封堵,假使数据直接不来,你的线程就要打断很久.那样分明不佳。所以使用select来查看套节字是不是可读(也正是是或不是有数据读了)

步骤如下——
socket s;
…..
fd_set set;
while(1)
{
FD_ZERO(&set);//将您的套节字集合清空
FD_SET(s,
&set);//参加你感兴趣的套节字到集结,那里是三个读数据的套节字s
select(0,&set,NULL,NULL,NULL);//检查套节字是还是不是可读,
//很多场合下就是是或不是有数量(注意,只是说过多气象)
//那里select是不是出错未有写
if(FD_ISSET(s, &set) //检查s是或不是在这么些集合里面,
{ //select将更新这么些集合,把在那之中不可读的套节字去掉
//只保留符合条件的套节字在那个集合里面
recv(s,…);
}
//do something here
}

/***********************************************************************************************************/

 

跟select同盟使用的多少个宏和fd_set结构体介绍:

套接字描述符为了方便管理是置身1个集合里的,那些集合是fd_set,它的现实定义是:

 

[cpp] view
plaincopy

 

  1. typedef struct fd_set {  
  2.         u_int   fd_count;               /* how many are SET? */  
  3.         SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */  
  4. } fd_set;  

fd_count是集聚中早已安装的套接口描述符的多寡。fd_array数组保存已经设置的套接口描述符,个中FD_SETSIZE的概念是:

 

 

[cpp] view
plaincopy

 

  1. #ifndef FD_SETSIZE  
  2. #define FD_SETSIZE      64  
  3. #endif /* FD_SETSIZE */  

以此私下认可值在1般的程序中已经够用,如果急需,能够将其更改为更大的值。

 

聚拢的管住操作,比如成分的清空、出席、删除以及判断成分是还是不是在集聚中都是用宏来完毕的。多个宏是:

 

[html] view
plaincopy

 

  1. FD_ZERO(*set)  
  2. FD_SET(s, *set)  
  3. FD_ISSET(s, *set)  
  4. FD_CLR(s, *set)  

上面一一介绍那几个宏的效益和定义:

 

FD_ZERO(*set),是把集合清空(开端化为0,确切的说,是把集合中的成分个数早先化为0,并不改动描述符数组).使用集合前,必须用FD_ZERO早先化,不然集合在栈上作为机关变量分配时,fd_set分配的将是随机值,导致不可预测的难题。它的宏定义如下:

 

[cpp] view
plaincopy

 

  1. #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)  

FD_SET(s,*set),向聚集中投入二个套接口描述符(尽管该套接口描述符s没在汇集中,并且数组中曾经设置的个数小于最大个数时,就把该描述符到场到聚集中,集合元素个数加一)。那里是将s的值直接放入数组中。它的宏定义如下:

 

 

[cpp] view
plaincopy

 

  1. #define FD_SET(fd, set) do { \  
  2.     if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) \  
  3.         ((fd_set FAR *)(set))->fd_array[((fd_set FAR *)(set))->fd_count++]=(fd);\  
  4. } while(0)  

FD_ISSET(s,*set),检查描述符是或不是在联谊中,若是在联谊中回到非0值,不然再次回到0.
它的宏定义并不曾交到具体贯彻,但贯彻的思绪一点也不细略,正是摸索集合,判断套接字s是或不是在数组中。它的宏定义是:

 

 

[cpp] view
plaincopy

 

  1. #define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))  

FD_CLR(s,*set),从集合中移出三个套接口描述符(比如三个套接字连接中断后,就活该移除它)。达成思路是,在数组集合中找到呼应的描述符,然后把后边的叙说依次前移1个岗位,末了把描述符的个数减一.
它的宏定义是:

 

 

[cpp] view
plaincopy

 

  1. #define FD_CLR(fd, set) do { \  
  2.     u_int __i; \  
  3.     for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count ; __i++) { \  
  4.         if (((fd_set FAR *)(set))->fd_array[__i] == fd) { \  
  5.             while (__i < ((fd_set FAR *)(set))->fd_count-1) { \  
  6.                 ((fd_set FAR *)(set))->fd_array[__i] = \  
  7.                     ((fd_set FAR *)(set))->fd_array[__i+1]; \  
  8.                 __i++; \  
  9.             } \  
  10.             ((fd_set FAR *)(set))->fd_count–; \  
  11.             break; \  
  12.         } \  
  13.     } \  
  14. } while(0)  

/***********************************************************************************************************/

 

迄今停止,一些基础的点基本就讲完了,然后交由差不离流程和3个示范:

1.调用FD_ZERO来开头化套接字状态;

2.调用FD_SET将感兴趣的套接字描述符插手集合中(每回循环都要重新出席,因为select更新后,会将一部分从未满意条件的套接字移除队列);

三.安装等待时间后,调用select函数–更新套接字的动静;

4.调用FD_ISSET,来判定套接字是或不是有对应情形,然后做相应操作,比如,若是套接字可读,就调用recv函数去接收数据。

关键技术:套接字队列和情形的象征与拍卖。

server端得程序如下(套接字管理类别3个很要紧的效率正是保存套接字描述符,因为accept获得的套接字描述符会覆盖掉原来的套接字描述符,而readfs中的描述符在select后会删除那个套接字描述符):

 

[cpp] view
plaincopy

 

  1. // server.cpp :   
  2. //程序中插手了套接字管理连串,那样管理起来更为显明、方便,当然也得以不要这一个事物  
  3.   
  4. #include “winsock.h”  
  5. #include “stdio.h”  
  6. #pragma comment (lib,”wsock32.lib”)  
  7. struct socket_list{  
  8.     SOCKET MainSock;  
  9.     int num;  
  10.     SOCKET sock_array[64];  
  11. };  
  12. void init_list(socket_list *list)  
  13. {  
  14.     int i;  
  15.     list->MainSock = 0;  
  16.     list->num = 0;  
  17.     for(i = 0;i < 64;i ++){  
  18.         list->sock_array[i] = 0;  
  19.     }  
  20. }  
  21. void insert_list(SOCKET s,socket_list *list)  
  22. {  
  23.     int i;  
  24.     for(i = 0;i < 64; i++){  
  25.         if(list->sock_array[i] == 0){  
  26.             list->sock_array[i] = s;  
  27.             list->num += 1;  
  28.             break;  
  29.         }  
  30.     }  
  31. }  
  32. void delete_list(SOCKET s,socket_list *list)  
  33. {  
  34.     int i;  
  35.     for(i = 0;i < 64; i++){  
  36.         if(list->sock_array[i] == s){  
  37.             list->sock_array[i] = 0;  
  38.             list->num -= 1;  
  39.             break;  
  40.         }  
  41.     }  
  42. }  
  43. void make_fdlist(socket_list *list,fd_set *fd_list)  
  44. {  
  45.     int i;  
  46.     FD_SET(list->MainSock,fd_list);  
  47.     for(i = 0;i < 64;i++){  
  48. 美高梅手机版4858,        if(list->sock_array[i] > 0){  
  49.             FD_SET(list->sock_array[i],fd_list);  
  50.         }  
  51.     }  
  52. }  
  53. int main(int argc, char* argv[])  
  54. {  
  55.     SOCKET s,sock;  
  56.     struct sockaddr_in ser_addr,remote_addr;  
  57.     int len;  
  58.     char buf[128];  
  59.     WSAData wsa;  
  60.     int retval;  
  61.     struct socket_list sock_list;  
  62.     fd_set readfds,writefds,exceptfds;  
  63.     timeval timeout;        //select的最多等待时间,防止一向等候  
  64.     int i;  
  65.     unsigned long arg;  
  66.   
  67.     WSAStartup(0x101,&wsa);  
  68.     s = socket(AF_INET,SOCK_STREAM,0);  
  69.     ser_addr.sin_family = AF_INET;  
  70.     ser_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);  
  71.     ser_addr.sin_port = htons(0x1234);  
  72.     bind(s,(sockaddr*)&ser_addr,sizeof(ser_addr));  
  73.   
  74.     listen(s,5);  
  75.     timeout.tv_sec = 5;     //假如套接字集合中在一s内未有数量,select就会再次回到,超时select重回0  
  76.     timeout.tv_usec = 0;  
  77.     init_list(&sock_list);  
  78.     FD_ZERO(&readfds);  
  79.     FD_ZERO(&writefds);  
  80.     FD_ZERO(&exceptfds);  
  81.     sock_list.MainSock = s;  
  82.     arg = 1;  
  83.     ioctlsocket(sock_list.MainSock,FIONBIO,&arg);  
  84.     while(1){  
  85.         make_fdlist(&sock_list,&readfds);  
  86.         //make_fdlist(&sock_list,&writefds);  
  87.         //make_fdlist(&sock_list,&exceptfds);  
  88.   
  89.         retval = select(0,&readfds,&writefds,&exceptfds,&timeout);     //超过那个时刻,就不封堵在此间,重临三个0值。  
  90.         if(retval == SOCKET_ERROR){  
  91.             retval = WSAGetLastError();  
  92.             break;  
  93.         }  
  94.         else if(retval == 0) {  
  95.             printf(“select() is time-out! There is no data or new-connect coming!\n”);  
  96.             continue;  
  97.         }  
  98.         if(FD_ISSET(sock_list.MainSock,&readfds)){  
  99.             len = sizeof(remote_addr);  
  100.             sock = accept(sock_list.MainSock,(sockaddr*)&remote_addr,&len);  
  101.             if(sock == SOCKET_ERROR)  
  102.                 continue;  
  103.             printf(“accept a connection\n”);  
  104.             insert_list(sock,&sock_list);  
  105.         }  
  106.         for(i = 0;i < 64;i++){  
  107.             if(sock_list.sock_array[i] == 0)  
  108.                 continue;  
  109.             sock = sock_list.sock_array[i];  
  110.             if(FD_ISSET(sock,&readfds)){  
  111.                 retval = recv(sock,buf,128,0);  
  112.                 if(retval == 0){  
  113.                     closesocket(sock);  
  114.                     printf(“close a socket\n”);  
  115.                     delete_list(sock,&sock_list);  
  116.                     continue;  
  117.                 }else if(retval == -1){  
  118.                     retval = WSAGetLastError();  
  119.                     if(retval == WSAEWOULDBLOCK)  
  120.                         continue;  
  121.                     closesocket(sock);  
  122.                     printf(“close a socket\n”);  
  123.                     delete_list(sock,&sock_list);   //连接断开后,从队列中移除该套接字  
  124.                     continue;  
  125.                 }  
  126.                 buf[retval] = 0;  
  127.                 printf(“->%s\n”,buf);  
  128.                 send(sock,”ACK by server”,13,0);  
  129.             }  
  130.             //if(FD_ISSET(sock,&writefds)){  
  131.             //}  
  132.             //if(FD_ISSET(sock,&exceptfds)){  
  133.               
  134.         }  
  135.         FD_ZERO(&readfds);  
  136.         FD_ZERO(&writefds);  
  137.         FD_ZERO(&exceptfds);  
  138.     }  
  139.     closesocket(sock_list.MainSock);  
  140.     WSACleanup();  
  141.     return 0;  
  142. }  

 

关于linux下的select跟windows下的区分还有待学习。

 

参照书籍:

《WinSock网络编制程序经络》第贰九章

《UNIX环境高级编制程序》

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 美高梅手机版4858 版权所有