select函数实现无阻塞的连接、接收、输入发送

————Author:qinfan,Date:2014.10.29

记录自己获得的东西,原理性的东西网上搜一大把。

定义:
int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);
表头文件:
#include
#include
#include
说明:
select()用来等待文件描述词状态的改变。参数n代表最大的文件描述词加1, 参数readfds、writefds 和exceptfds 称为描述词组, 是用来回传该描述词的读, 写或例外的状况。底下的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set); 用来清除描述词组set的全部位
参数:
timeout为结构timeval, 用来设置select()的等待时间, 其结构定义如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
返回值:
如果参数timeout设为NULL则表示select()没有timeout。
错误代码:
执行成功则返回文件描述词状态已改变的个数, 如果返回0代表在描述词状态改变前已超过timeout时间, 当有错误发生时则返回-1, 错误原因存于errno, 此时参数readfds, writefds, exceptfds和timeout的值变成不可预测。
EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足
示例:
常见的程序片段: fs_set readset; FD_ZERO(&readset); FD_SET(fd,&readset); select(fd+1,&readset,NULL,NULL,NULL); if(FD_ISSET(fd,readset){...}

首先介绍select实现无阻塞的连接,接收,发送

#include #include #include #include #include #include /* See NOTES */ #include #include #include /* superset of previous */ #include int main(int argc, char *argv[]) { int listenfd, on=1; if (0 > (listenfd = socket(AF_INET, SOCK_STREAM, 0))) perror("socket"); /*避免bind : Address already in use 的错误*/ if((setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))<0) perror("setsockopt"); struct sockaddr_in server; bzero(&server, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(8888); server.sin_addr.s_addr = htonl(INADDR_ANY); if (0 > bind(listenfd, (struct sockaddr *)&server, sizeof(server))) perror("bind"); listen(listenfd, 20); printf("Listenfd:%d, Waiting for connecting ...\n", listenfd); int i, ret, maxfd, accept_fd=-1; char w_buf[1024], r_buf[1024]; struct sockaddr_in client; bzero(&client, sizeof(client)); socklen_t addrlen=sizeof(client); struct timeval tm; bzero(&tm, sizeof(tm)); fd_set read_fd,tmp_fds;/*用于中间值*/ FD_ZERO(&read_fd); FD_ZERO(&tmp_fds); FD_SET(0, &tmp_fds); maxfd = maxfd > 0 ? maxfd : 0; FD_SET(listenfd, &tmp_fds); maxfd = maxfd > listenfd? maxfd : listenfd; while(1){ tm.tv_sec=3;/*设置3S等待*/ tm.tv_usec=0; /*这个赋值很关键,否则文件描述符集合里面得不到可用值*/ read_fd = tmp_fds; if(-1==select(maxfd+1, &read_fd, NULL,NULL, &tm)){ perror("select"); }else{ for(i=0; i<=maxfd; i++) if(FD_ISSET(i, &read_fd)){ if(listenfd==i) {/*可连接请求到来*/ if(0>(accept_fd=accept(listenfd, (struct sockaddr*)&client, &addrlen))) perror("accept"); printf("Ip:%s, port:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port)); FD_SET(accept_fd, &tmp_fds); maxfd = maxfd > accept_fd ? maxfd : accept_fd; /*可以用链表保存连接的客户端方便后续使用*/ }else if(0==i){ /*输入发送的信息,并发送出去,输入不阻塞 发送的时候只能发送给最后一个连接的客户端 可以自己实现函数实现发送到指定客户端*/ fgets(w_buf, sizeof(w_buf), stdin); if(0>(ret = send(accept_fd, w_buf, strlen(w_buf) + 1, 0))){ perror("send"); } }else{ /*接收客户端的信息*/ if (0>(ret = recv(i, r_buf, sizeof(r_buf), 0))){ perror("recv"); }else{ /*如果客户端异常退出,就清除集合中的描述符并关掉该链接*/ if (0 == ret){ FD_CLR(i, &tmp_fds); close(i); }else{ printf("%s\n", r_buf); } } } } } } close(listenfd); return 0; }

这是一个用select实现无阻塞的服务端,其中可以优化的地方已经说明,可以根据自己的需求添加不同的功能。

由于我的项目中是要求客户端和服务器保持长连接,所以要求客户端在规定的时间间隔内(30S)要发送一个心跳包给服务器,这是一个保活包,如果超过3次没有这个心跳包,即说明客户端异常退出了。服务器需要使用一个线程来检测客户端列表中哪些用户还是处于连接状态,如果离线就清除。在这里遇到一个神奇的事情,在使用SIGALRM信号的时候,会引起select函数被系统调用中断(select:Interrupted system call),后来在网上找到了答案:http://bbs.csdn.net/topics/80048170

select就是一种系统调用,原来select处理中是不能有信号的,在库函数这一行:
if (retval || !__timeout || signal_pending(current)) break; 说明select在处理的时候如果有信号,则直接跳出循环。因为中断服务程序里再有中断,程序会有些 混乱的行为,所以在调用alarm(1)会使select返回-1。

由于select函数天生就不优越,它是采用轮询的方式来检测集合里面的连接符哪些可用,所以在连接的客户端很多时,性能会和客户端的数量程线性反比例下降,而且select的最大连接数也是受系统限制的,虽然可以改,但不能解决本质问题。

#undef __FD_SETSIZE #define __FD_SETSIZE 1024 /* Maximum number of file descriptors in `fd_set'*/ #define FD_SETSIZE __FD_SETSIZE

所以,用于大型的服务器上是不靠谱的,而且又遇到这个系统调用出问题,所以接下来实现一个用epoll函数族实现的服务端!

你可能感兴趣的:(C源码,C源码,select,epoll,服务器,通信)