linux网络编程十三:I/O复用select

来源:互联网 发布:淘宝企业店铺升级流程 编辑:程序博客网 时间:2024/06/11 06:11

最近在看《linux高性能服务器编程》,在此做个日记,以激励自己,同时分享于有需要的朋友。


I/O复用使得程序能够同时监听多个文件描述符,对提高程序的性能至关重要。

通常,网络程序在下列情况下需要使用I/O复用技术:

A.  客户端程序要同时处理多个socket。

B.  客户端程序要同时处理用户输入和网络连接。比如:聊天室

C.  TCP服务器要同时监听socket和连接socket。

D.  服务器要同时处理TCP请求和UDP请求。

E.  服务器要同时监听多个端口, 或者处理多种服务。比如:xinetd服务


虽然I/O复用能同时监听多个文件描述符,但它本身是阴塞的。并且当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能顺序依次处理其中的每个文件描述符,这使得服务器程序看起来像是串行工作。如果要实现并发,只能使用多进程或多线程。


linux下实现I/O复用的系统调用有select、poll、epoll,今天我们讨论select。


1.  select函数

#include <sys/socket.h>int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

nfds参数指定被监听的文件描述符的总数,通常设置为select监听的所有文件描述中最大值+1,因为文件描述符是从0开始的。

readfds, writefds, exceptfds参数分别指可读,可写和异常文件描述符。select调用返回时,内核将修改它们来通知程序哪些文件描述符已经就绪。它们都是fd_set结构指针,它仅包含一个整型数组,该数组的每个元素的每一位(bit)标记一个文件描述符。fd_set能容纳的最大文件描述符数量由FD_SETSIZE指定,这就限制了select能同时处理的文件描述符的总量。

我们使用一系列宏来访问fd_set结构体:

#include <sys/select.h>FD_ZERO(fd_set *fdset);                   //清除fdset所有位FD_SET(int fd, fd_set *fdset);           //设置fdset的位fdFD_CLR(int fd, fd_set *fdset);          //清除fdset的位fdint FD_ISSET(int fd, fd_set *fdset);  //测试fdset的位fd是否被设置

timeout参数设置select的超时时间。这是timeval结构指针,用来告诉内核select等待多久。

struct timeval{        long tv_sec;       \\秒数        long tv_usec;     \\微秒}

如果都为0,则select立即返回。如果给timeval传NULL,则一直阻塞。


调用成功时返回就绪文件描述符的总数。如果在超时时间内没有任何文件描述符就绪,返回0。失败时返回-1,并设置errno。如果在select等待期间,程序收到信号,则立即返回-1,并设置errno为EINTR。


2. 代码,用select同时接收普通数据和外带数据

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <errno.h>#include <assert.h>#include <fcntl.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>int main(int argc, char **argv){if (argc != 3) {fprintf(stderr, "Usage: %s ip port\n", basename(argv[0]));return 1;}const char *ip = argv[1];int port = atoi(argv[2]);printf("ip is %s and port is %d\n", ip, port);int ret = 0;struct sockaddr_in address;bzero(&address, sizeof(address));address.sin_family = AF_INET;address.sin_port = htons(port);inet_pton(AF_INET, ip, &address.sin_addr);int listenfd = socket(PF_INET, SOCK_STREAM, 0);assert(listenfd >= 0);ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));assert(ret != -1);ret = listen(listenfd, 5);assert(ret != -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("error: %s\n", strerror(errno));close(listenfd);}char remote_addr[INET_ADDRSTRLEN];printf("connect with ip: %s and port: %d\n", inet_ntop(AF_INET, &client_address.sin_addr, remote_addr, INET_ADDRSTRLEN), ntohs(client_address.sin_port));char buf[1024];fd_set read_fds;fd_set exception_fds;FD_ZERO(&read_fds);FD_ZERO(&exception_fds);//设置接收处带数据int reuseaddr = 1;setsockopt(connfd, SOL_SOCKET, SO_OOBINLINE, &reuseaddr, sizeof(reuseaddr));while (1) {memset(buf, '\0', sizeof(buf));//每次调用select前都要重新设置,因为事件发生后,文件描述符集合会被内核修改FD_SET(connfd, &read_fds);FD_SET(connfd, &exception_fds);ret = select(connfd + 1, &read_fds, NULL, &exception_fds, NULL);printf("select one\n");if (ret < 0) {printf("selection failed\n");break;}if (FD_ISSET(connfd, &read_fds)) {ret = recv(connfd, buf, sizeof(buf)-1, 0);if (ret <= 0)break;printf("get %d bytes of normal data: %s\n", ret, buf);}else if (FD_ISSET(connfd, &exception_fds)) {//接收外带数据ret = recv(connfd, buf, sizeof(buf)-1, MSG_OOB);if (ret <= 0)break;printf("get %d bytes of oob data: %s\n", ret, buf);}}close(connfd);close(listenfd);return 0;}





0 0
原创粉丝点击