socket编程—select方法使用

0.背景

最近要写一个RPC库,即在客户端向服务端发送请求,服务器计算并返回结果,要求实现服务端能同时接收多个客户端请求但是不能使用线程库,根据提示我知道了可以使用select函数来完成非阻塞方式工作的程序,于是我就开始了select方法的学习。

1.概念

Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序(比如我),他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。
可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。

2.编程

我第一步是准备先熟悉select的简单使用,所以我在我的CentOS服务器上先编辑好如下代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
        int server_fd;
        int client_fd;
        struct sockaddr_in myaddr;
        struct sockaddr_in clientaddr;
        int clientaddr_len=sizeof(clientaddr);
        int maxfdp;
        fd_set fds;
        struct timeval timeout={3,0};
        myaddr.sin_family=AF_INET;
        myaddr.sin_addr.s_addr=htonl(INADDR_ANY);
        myaddr.sin_port=htons(4600);
        char buf[16];
        //socket
        server_fd=socket(AF_INET,SOCK_STREAM,0);
        if(server_fd==-1)
        {
                perror("socket error");
                exit(1);
        }
        //bind
        if(bind(server_fd,(struct sockaddr *)&myaddr,sizeof(myaddr))==-1)
        {
                perror("bind error");
                exit(1);
        }
        //listen
        if(listen(server_fd,20)==-1)
        {
                perror("listen error");
                exit(1);
        }
        printf("listening~\n");
        client_fd=accept(server_fd,(struct sockaddr *)&clientaddr,&clientaddr_len);
        while(1)
        {
                FD_ZERO(&fds);
                FD_SET(server_fd,&fds);
                FD_SET(client_fd,&fds);
                maxfdp=server_fd>client_fd?server_fd+1:client_fd+1;
                switch(select(maxfdp,&fds,&fds,NULL,&timeout))
                {
                        case -1:
                                exit(-1);
                                break;
                        case 0:
                                sleep(2);
                                printf("time out~ \n");
                                break;
                        default:
                                if(FD_ISSET(client_fd,&fds))
                                {
                                        sleep(2);
                                        recv(client_fd,buf,100,0);
                                        printf("receive from client %s\n",buf);
                                }
                }
        }

        close(server_fd);
}

客户端程序比较简单,所以我就不上代码了,这里重点想讨论一下我在完成这个程序中遇到的问题。

3.问题

在完成这个程序之前我看过许多解释select方法的博客,有Windows环境下的,有linux环境下的,但是都没有能正确运行的代码,我想大家也是为了捍卫自己的版权,所以贴的都是伪码,不过也多亏了大家的共享精神我最终才能解决这些小问题。

1)在使用fd_set这个结构体声明变量的时候,我本来写的是:

struct fd_set fds

这也是很多博主的写法,但是我这样写在CentOS下用gcc编译会报错,搜索那个错误提示也没有找到相关联的答案,所以我就去掉了前面的struct关键字,然后一套编译下来,居然连一个警告都没有就通过了。

2)原本我根据别人的代码模仿着写的时候在监听之后并没有写accept这个过程,因为别人都没写,然后编译通过没问题,运行起来的时候就一直处于监听状态了,我在客户端反复执行连接都没有任何反应,所以,别人的方法终究只能作为参考啊,最后还是需要自己动手动脑。

3)我在网上又搜索了一阵子之后,意识到了上面那个问题,所以同样依葫芦画瓢将accept方法写在了死循环里面,但是在FD_ISSET方法判断socket描述符是否可读写时,我又犯了一个错,因为我判断的是server_fd,这个描述符无论有没有连接请求状态都是不变的,所以在调试程序的时候同样无法连通。

最后意识到上述问题之后,我想到,既然select要检测的是变幻的socket描述符,那结合之前accept方法返回的客户端描述符,怎么做方法就很明显了,即用select方法来检测accept返回的文件描述符,也就是有客户端连接请求就可以在select的switch语句中进行发送和接收了,如程序所示。

你可能感兴趣的:(计算机网络)