socket编程以及select、epoll、poll示例详解
socket编程
socket这个词可以表示很多概念,在TCP/IP协议中“IP地址 + TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP + 端口号”就称为socket。在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么两个socket组成的socket pair就唯一标识一个连接。
预备知识
网络字节序:内存中多字节数据相对于内存地址有大端小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分,所以发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接收到的字节按内存从低到高的顺序保存,因此网络数据流的地址应该规定:先发出的数据是低地址,后发出的数据是高地址。TCP/IP协议规定网络数据流应该采用大端字节序,即低地址高字节。所以发送主机和接收主机是小段字节序的在发送和接收之前需要做字节序的转换。
为了使网络程序具有可移植性可以调用以下函数进行网络字节数的转换。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
socket地址数据类型及相关函数
sockaddr数据结构
IPv6和UNIXDomain Socket的地 址类型分别定义为常数AF_INET、AF_INET6、AF_UNIX。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的 内容。因此,socket API可以接受各种类型的sockaddr结构体指针做参数,例如bind、accept、connect等函数,这些函数的参数应该设计成void *类型以便接受各种类型的指 针,但是sock API的实现早于ANSI C标准化,那时还没有空指针类型这些函数的参数都⽤用struct sockaddr 类型表示,在传递参数之前要强制类型转换一下。
本次只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表⽰示32位的IP 地址。但是我们通常⽤用点分十进制的字符串表示IP 地址,以下函数可以在字符串表⽰示 和in_addr表⽰示之间转换。也就是说可以将字符串转换成in_addr类型,也可以将本地字节转换成网络字节。相反由同样有从网络转换到本地的函数具体的用法我们下面的代码中来看。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);
struct in_addr inet_makeaddr(int net, int host);
in_addr_t inet_lnaof(struct in_addr in)
tcpsocket 实现
实现模型:
1.服务器端 socket -> bind -> listen -> accept(阻塞,三次握手)-> send。
2.客户端 socket -> connect(阻塞,三次握手)-> rcv。
函数介绍:
int socket(int family, int type, int protocol)
family :指定协议的类型本次选择AF_INET(IPv4协议)。
type:网络数据类型,TCP是面向字节流的—SOCK_STREAM.
protocol:前两个参数一般确定了协议类型通常传0.
返回值:成功返回套接字符。
失败返回-1设置相关错误码。
int bind(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)
sockfd : socket函数成功时候返回的套接字描述符。
servaddr :服务器的IP和端口。
addrlen : 长度(sizeof(servaddr))。
返回值:成功返回0
失败返回-1,并设置相关错误码。
int listen(int sockfd, int backlog)
sockfd: socket函数成功时候返回的套接字描述符。
backlog : 内核中套接字排队的最大个数。
返回值:成功返回0
失败返回-1,并设置相关错误码。
int accept(int sockfd, const struct sockaddr *servaddr, socklen_t *addrlen)
sockfd : socket函数成功时候返回的套接字描述符。
servaddr : 输出型参数,客户端的ip和端口。
addrlen : 长度(sizeof(servaddr))。
返回值:成功:从监听套接字返回已连接套接字
失败:失败返回-1,并设置相关错误码。
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)
sockfd:函数返回的套接字描述符
servaddr :服务器的IP和端口
addrlen : 长度(sizeof(servaddr))。
返回值:成功返回0
失败返回-1,并设置相关错误码
实现代码
//server.c #include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<string.h> static usage(const char* proc) { printf("Usage:%s[local-ip][local-port]\n",proc); } static int start_up(const char *local_ip,int local_port) { //1.create sock ); ) { perror("socket"); close(sock); exit(); } //2,createbind struct sockaddr_in local; bzero(&local,sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(local_port); local.sin_addr.s_addr = inet_addr(local_ip); ) { perror("bind"); close(sock); exit(); } //3.listen ) < ) { perror("listen"); close(sock); exit(); } return sock; } int main(int argc, char *argv[]) { ) { usage(argv[]); } ],atoi(argv[])); struct sockaddr_in client; socklen_t len = sizeof(client); ) { int new_sock = accept(sock,(struct sockaddr*)&client, &len); ) { perror("accept"); close(sock); ; } printf("client_ip:%s client_port:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); ]; ) { ssize_t s = read(new_sock,buf,); ) { buf[s] = ; printf("client say# %s",buf); } ) { printf("client quit!\n"); break; } write(new_sock,buf, strlen(buf)); } } close(sock); ; }
//client.c #include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<string.h> static void usage(const char* proc) { printf("Usage:%s [server-ip] [server-port]",proc); } int main(int argc,char *argv[]) { ) { usage(argv[]); } ); ) { perror("socket"); ; } struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[])); server.sin_addr.s_addr = inet_addr(argv[]); ) { perror("connect"); ; } ) { printf("please Entry#"); fflush(stdout); ]; ssize_t s = read(,buf,); )//read success { buf[s] = ; } write(sock,buf,strlen(buf)); ssize_t _s = read(sock,buf,); ) { buf[_s - ] = ; printf("server echo# %s\n",buf); } } close(sock); ; }
上述代码只可以处理单个用户,为了可以处理多个用户请求我们可以编写多进程或者多线程的TCP套接字。完整代码如下。
对于上述代码就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即在性能上并不是合理的选择,因此我们需要提高代码的性能。下面介绍三种常用的高性能套接字编程方法。
I/O多路复用之select函数
select函数预备知识
struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作。
(1) FD_CLR(inr fd,fd_set* set):用来清除描述词组set中相关fd 的位
(2)FD_ISSET(int fd,fd_set *set):用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set):用来设置描述词组set中相关fd的位
2.struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。
FD_ZERO(fd_set *set);用来清除描述词组set的全部位
select函数介绍
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
1
maxfdp : 需要监视的最大文件描述符加1。
readfds、writefds、errorfds:分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合。
timeout:等待时间,这个时间内,需要监视的描述符没有事件
发⽣生则函数返回,返回值为0。设为NULL 表示阻塞式等待,一直等到有事件就绪,函数才会返回,0表示非阻塞式等待,没有事件就立即返回,大于0表示等待的时间。
返回值:大于0表示就绪时间的个数,等于0表示timeout等待时间到了,小于0表示调用失败。
select函数原理
select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这⾥里等待,直到被监视的文件句柄有一个或多个发⽣生了状态改变。关于文件句柄,其实就是⼀一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr。
1.我们通常需要额外定义一个数组来保存需要监视的文件描述符,并将其他没有保存描述符的位置初始化为一个特定值,一般为-1,这样方便我们遍历数组,判断对应的文件描述符是否发生了相应的事件。
2.采用上述的宏操作FD_SET(int fd,fd_set*set)遍历数组将关心的文件描述符设置到对应的事件集合里。并且每次调用之前都需要遍历数组,设置文件描述符。
3.调用select函数等待所关心的文件描述符。有文件描述符上的事件就绪后select函数返回,没有事件就绪的文件描述符在文件描述符集合中对应的位置会被置为0,这就是上述第二步的原因。
4.select 返回值大于0表示就绪的文件描述符的个数,0表示等待时间到了,小于0表示调用失败,因此我们可以遍历数组采用FD_ISSET(int fd,fd_set *set)判断哪个文件描述符上的事件就绪,然后执行相应的操作。
采用select的tcp socket实现代码。
#include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<string.h> #include<sys/time.h> static void Usage(const char* proc) { printf("%s [local_ip] [local_port]\n",proc); } ]; static int start_up(const char* _ip,int _port) { ); ) { perror("socket"); exit(); } struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = inet_addr(_ip); ) { perror("bind"); exit(); } ) < ) { perror("listen"); exit(); } return sock; } int main(int argc,char* argv[]) { ) { Usage(argv[]); ; } ],atoi(argv[])); ; fd_set rfds; fd_set wfds; array[] = listensock; ; ]); for(; i < array_size;i++) { array[i] = -; } ) { FD_ZERO(&rfds); FD_ZERO(&wfds); ;i < array_size;++i) { ) { FD_SET(array[i],&rfds); FD_SET(array[i],&wfds); if(array[i] > maxfd) { maxfd = array[i]; } } } ,&rfds,&wfds,NULL,NULL)) { : { printf("timeout\n"); break; } : { perror("select"); break; } default: { ; for(; j < array_size; ++j) { && FD_ISSET(array[j],&rfds)) { //listensock happened read events struct sockaddr_in client; socklen_t len = sizeof(client); int new_sock = accept(listensock,(struct sockaddr*)&client,&len); )//accept failed { perror("accept"); continue; } else//accept success { printf("get a new client%s\n",inet_ntoa(client.sin_addr)); fflush(stdout); ; for(; k < array_size;++k) { ) { array[k] = new_sock; if(new_sock > maxfd) maxfd = new_sock; break; } } if(k == array_size) { close(new_sock); } } }//j == 0 && FD_ISSET(array[j], &rfds)) { //new_sock happend read events ]; ssize_t s = read(array[j],buf,); )//read success { buf[s] = ; printf("clientsay#%s\n",buf); if(FD_ISSET(array[j],&wfds)) { char *msg = "HTTP/1.0 200 OK <\r\n\r\n<html><h1>yingying beautiful</h1></html>\r\n"; write(array[j],msg,strlen(msg)); } } == s) { printf("client quit!\n"); close(array[j]); array[j] = -; } else { perror("read"); close(array[j]); array[j] = -; } }//else j != 0 } break; } } } ; }
client端同上面tcpsocket端相同。
select的优缺点
优点:
(1)select的可移植性好,在某些unix下不支持poll.
(2)select对超时值提供了很好的精度,精确到微秒,而poll式毫秒。
缺点:
(1)单个进程可监视的fd数量被限制,默认是1024。
(2)需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。
(3)对fd进行扫描时是线性扫描,fd剧增后,IO效率降低,每次调用都对fd进行线性扫描遍历,随着fd的增加会造成遍历速度慢的问题。
(4)select函数超时参数在返回时也是未定义的,考虑到可移植性,每次超时之后进入下一个select之前都要重新设置超时参数。
I/O多路复用之poll函数
poll函数预备知识
不同于select函数poll采用一个pollfd指针向内核传递需要关心的描述符及其相关事件。
fd : 需要关心的文件描述符
events : 需要关心的事件,合法事件如下
POLLIN 有数据可读。
POLLRDNORM 有普通数据可读。
POLLRDBAND 有优先数据可读。
POLLPRI 有紧迫数据可读。
POLLOUT 写数据不会导致阻塞。
POLLWRNORM 写普通数据不会导致阻塞。
POLLWRBAND 写优先数据不会导致阻塞。
POLLMSGSIGPOLL 消息可用。
revents : 关心的事件就绪时 revents会被设置成上述对应的事件,除此之外还可能设置为如下内容。
POLLER 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起事件。
POLLNVAL 指定的文件描述符非法。
poll函数介绍
#include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
参数介绍:
fds : 对应上述介绍的结构体指针
nfds : 标记数组中结构体元素的总个数。
timeout : 超时时间 ,等于0表示非阻塞式等待,小于0表示阻塞式等待,大于0表示等待的时间。
返回值:
成功时返回fds数组中事件就绪的文件描述符的个数
返回0表示超时时间到了。
返回-1表示调用失败,对应的错误码会被设置。
EBADF 一个或多个结构体中指定的文件描述符无效。
EFAULTfds 指针指向的地址超出进程的地址空间。
EINTR 请求的事件之前产生一个信号,调用可以重新发起。
EINVALnfds 参数超出PLIMIT_NOFILE值。
ENOMEM 可用内存不足,无法完成请求。
poll函数实现原理
(1)将需要关心的文件描述符放进fds数组中
(2)调用poll函数
(3)函数成功返回后根据返回值遍历fds数组,将关心的事件与结构体中的revents相与判断事件是否就绪。
(4)事件就绪执行相关操作。
poll实现tcpsocket代码
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<poll.h> static void usage(const char *proc) { printf("%s [local_ip] [local_port]\n",proc); } int start_up(const char*_ip,int _port) { ); ) { perror("socket"); ; } ; setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = inet_addr(_ip); ) { perror("bind"); ; } ) < ) { perror("listen"); ; } return sock; } int main(int argc, char*argv[]) { ) { usage(argv[]); ; } ],atoi(argv[])); ]; peerfd[].fd = sock; peerfd[].events = POLLIN; ; int ret; ]); ; ; for(; i < maxsize; ++i) { peerfd[i].fd = -; } ) { switch(ret = poll(peerfd,nfds,timeout)) { : printf("timeout...\n"); break; : perror("poll"); break; default: { ].revents & POLLIN) { struct sockaddr_in client; socklen_t len = sizeof(client); int new_sock = accept(sock,\ (struct sockaddr*)&client,&len); printf("accept finish %d\n",new_sock); ) { perror("accept"); continue; } printf("get a new client\n"); ; for(; j < maxsize; ++j) { ) { peerfd[j].fd = new_sock; break; } } if(j == maxsize) { printf("to many clients...\n"); close(new_sock); } peerfd[j].events = POLLIN; > nfds) nfds = j + ; } ;i < nfds;++i) { if(peerfd[i].revents & POLLIN) { printf("read ready\n"); ]; ssize_t s = read(peerfd[i].fd,buf, \ ); ) { buf[s] = ; printf("client say#%s",buf); fflush(stdout); peerfd[i].events = POLLOUT; } ) { close(peerfd[i].fd); peerfd[i].fd = -; } else { } }//i != 0 else if(peerfd[i].revents & POLLOUT) { char *msg = "HTTP/1.0 200 OK \ <\r\n\r\n<html><h1> \ yingying beautiful \ </h1></html>\r\n"; write(peerfd[i].fd,msg,strlen(msg)); close(peerfd[i].fd); peerfd[i].fd = -; } else { } }//for }//default break; } } ; }
客户端同上。
poll函数的优缺点
优点:
(1)不要求计算最大文件描述符+1的大小。
(2)应付大数量的文件描述符时比select要快。
(3)没有最大连接数的限制是基于链表存储的。
缺点:
(1)大量的fd数组被整体复制于内核态和用户态之间,而不管这样的复制是不是有意义。
(2)同select相同的是调用结束后需要轮询来获取就绪描述符。
I/O多路复用之epoll函数
epoll函数预备知识
epoll函数是多路复用IO接口select和poll函数的增强版本。显著减少程序在大量并发连接中只有少量活跃的情况下CPU利用率,他不会复用文件描述符集合来传递结果,而迫使开发者每次等待事件之前都必须重新设置要等待的文件描述符集合,另外就是获取事件时无需遍历整个文件描述符集合,只需要遍历被内核异步唤醒加入ready队列的描述符集合就行了 。
epoll函数相关系统调用
int epoll_create(int size);
生成一个epoll函数专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的 socket fd 上是否发生以及发生了什么事件。 size 就是你在这个 Epoll fd 上能关注的最大 socket fd 数,大小自定,只要内存足够。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event );
控制文件描述符上的事件,包括注册,删除,修改等操作。
epfd : epoll的专用描述符。
op : 相关操作,通常用以下宏来表示
event : 通知内核需要监听的事件,
EPOLL_CTL_ADD:注册新的fd到epfd中; EPOLL_CTL_MOD:修改已经注册的fd的监听事件; EPOLL_CTL_DEL:从epfd中删除⼀一个fd;
fd : 需要监听的事件。结构体格式如下:
events的合法参数如下
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭); EPOLLOUT:表示对应的文件描述符可以写; EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这⾥里应该表⽰示有带外数据到来); EPOLLERR:表示对应的文件描述符发生错误; EPOLLHUP:表示对应的文件描述符被挂断; EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于⽔水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听⼀一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加⼊入到EPOLL队列里。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epfd : epoll特有的文件描述符
events :从内核中的就绪队列中拷贝出就绪的文件描述符。不可以是空指针,内核只负责将数据拷贝到这里,不会为我们开辟空间。
maxevent : 高速内核events有多大,一般不能超过epoll_create传递的size,
timeout : 函数超时时间,0表示非阻塞式等待,-1表示阻塞式等待,函数返回0表示已经超时。
- epoll函数底层实现过程
首先epoll_create创建一个epoll文件描述符,底层同时创建一个红黑树,和一个就绪链表;红黑树存储所监控的文件描述符的节点数据,就绪链表存储就绪的文件描述符的节点数据;epoll_ctl将会添加新的描述符,首先判断是红黑树上是否有此文件描述符节点,如果有,则立即返回。如果没有, 则在树干上插入新的节点,并且告知内核注册回调函数。当接收到某个文件描述符过来数据时,那么内核将该节点插入到就绪链表里面。epoll_wait将会接收到消息,并且将数据拷贝到用户空间,清空链表。对于LT模式epoll_wait清空就绪链表之后会检查该文件描述符是哪一种模式,如果为LT模式,且必须该节点确实有事件未处理,那么就会把该节点重新放入到刚刚删除掉的且刚准备好的就绪链表,epoll_wait马上返回。ET模式不会检查,只会调用一次
- epoll实现tcpsocket代码
#include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<string.h> #include<sys/epoll.h> static Usage(const char* proc) { printf("%s [local_ip] [local_port]\n",proc); } int start_up(const char*_ip,int _port) { ); ) { perror("socket"); exit(); } struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = inet_addr(_ip); ) { perror("bind"); exit(); } )< ) { perror("listen"); exit(); } return sock; } int main(int argc, char*argv[]) { ) { Usage(argv[]); ; } ],atoi(argv[])); ); ) { perror("epoll_create"); ; } struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = sock; ) { perror("epoll_ctl"); ; } ;//epoll_wait return val ]; ; ) { ,timeout)) { : printf("timeout...\n"); break; : perror("epoll_wait"); break; default: { ; for(; i < evnums; ++i) { struct sockaddr_in client; socklen_t len = sizeof(client); if(evs[i].data.fd == sock \ && evs[i].events & EPOLLIN) { int new_sock = accept(sock, \ (struct sockaddr*)&client,&len); ) { perror("accept"); continue; }//if accept failed else { printf("Get a new client[%s]\n", \ inet_ntoa(client.sin_addr)); ev.data.fd = new_sock; ev.events = EPOLLIN; epoll_ctl(epollfd,EPOLL_CTL_ADD,\ new_sock,&ev); }//accept success }//if fd == sock else if(evs[i].data.fd != sock && \ evs[i].events & EPOLLIN) { ]; ssize_t s = read(evs[i].data.fd,buf,); ) { buf[s] = ; printf("client say#%s",buf); ev.data.fd = evs[i].data.fd; ev.events = EPOLLOUT; epoll_ctl(epollfd,EPOLL_CTL_MOD, \ evs[i].data.fd,&ev); }//s > 0 else { close(evs[i].data.fd); epoll_ctl(epollfd,EPOLL_CTL_DEL, \ evs[i].data.fd,NULL); } }//fd != sock else if(evs[i].data.fd != sock \ && evs[i].events & EPOLLOUT) { char *msg = "HTTP/1.0 200 OK <\r\n\r\n<html><h1>yingying beautiful </h1></html>\r\n"; write(evs[i].data.fd,msg,strlen(msg)); close(evs[i].data.fd); epoll_ctl(epollfd,EPOLL_CTL_DEL, \ evs[i].data.fd,NULL); }//EPOLLOUT else { } }//for }//default break; }//switch }//while ; }
epoll函数的优缺点
优点:
epoll的优点:
(1)支持一个进程打开大数目的socket描述符(FD)
select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显 然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完 美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。
(2)IO效率不随FD数目增加而线性下降
传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是”活跃”的, 但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对”活跃”的socket进行 操作—这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有”活跃”的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个”伪”AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的—比如一个高速LAN环境,epoll并不比select/poll有什么效率,相 反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
(3)使用mmap加速内核与用户空间的消息传递。
这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就 很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。
(4)内核微调
这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。 比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小 — 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手 的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网 卡驱动架构。
socket编程以及select、epoll、poll示例详解的更多相关文章
- Linux C Socket编程发送结构体、文件详解及实例
利用Socket发送文件.结构体.数字等,是在Socket编程中经常需要用到的.由于Socket只能发送字符串,所以可以使用发送字符串的方式发送文件.结构体.数字等等. 本文:http://www.c ...
- socket编程的同步、异步与阻塞、非阻塞示例详解
socket编程的同步.异步与阻塞.非阻塞示例详解之一 分类: 架构设计与优化 简介图 1. 基本 Linux I/O 模型的简单矩阵 每个 I/O 模型都有自己的使用模式,它们对于特定的应用程序 ...
- (转)Linux Network IO Model、Socket IO Model - select、poll、epoll
Linux Network IO Model.Socket IO Model - select.poll.epoll 原文:https://www.cnblogs.com/LittleHann/p/ ...
- Java网络编程和NIO详解6:Linux epoll实现原理详解
Java网络编程和NIO详解6:Linux epoll实现原理详解 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO h ...
- Linux下高并发socket最大连接数所受的各种限制(详解)
1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每 ...
- python中的tcp示例详解
python中的tcp示例详解 目录 TCP简介 TCP介绍 TCP特点 TCP与UDP的不同点 udp通信模型 tcp客户端 tcp服务器 tcp注意点 TCP简介 TCP介绍 TCP协议 ...
- Spring Boot 2.x 快速入门(下)HelloWorld示例详解
上篇 Spring Boot 2.x 快速入门(上)HelloWorld示例 进行了Sprint Boot的快速入门,以实际的示例代码来练手,总比光看书要强很多嘛,最好的就是边看.边写.边记.边展示. ...
- SpringBoot与PageHelper的整合示例详解
SpringBoot与PageHelper的整合示例详解 1.PageHelper简介 PageHelper官网地址: https://pagehelper.github.io/ 摘要: com.gi ...
- AngularJS select中ngOptions用法详解
AngularJS select中ngOptions用法详解 一.用法 ngOption针对不同类型的数据源有不同的用法,主要体现在数组和对象上. 数组: label for value in a ...
随机推荐
- Civil 3D 2017本地化中VBA程序移植到2018版中
中国本地化包简直就是一块鸡肋, 但对于某些朋友来说还真离不了: 可惜中国本地化包的推出一直滞后, 在最新版软件出来后1年多, 本地化还不一定能够出来, 即使出来了, 也只能是购买了速博服务的用户才能得 ...
- Civil 3D 二次开发 创建AutoCAD对象—— 00 ——
不积跬步无以至千里,不积小流无以成江海.虽然创建一条直线.添加一个图层这样的小程序没有什么实际意义(内部命令很简单就可以完成),但对于初学二次开发的您来说,这可是一大步,这一步跨出去,您就跨进了二次开 ...
- Git秘钥生成以及Gitlab配置
安装Git:详见http://www.cnblogs.com/xiuxingzhe/p/9300905.html 开通gitlab(开通需要咨询所在公司的gitlab管理员)账号后,本地Git仓库和g ...
- ajax提交 返回中文乱码问题
接口返回数据相关 使用@ResponseBody后返回NUll 说明:刚把后台运行起来,兴高采烈的测试接口数据,结果无论如何都是返回null, 最终通过各种百度,发现原来是没有引入关键的Jar包. 解 ...
- Android 自定义ListView 修改数据
当我们修改了 自定义ListView,如何更新界面上的控件呢? 两种方法: 1 重新绑定adapter (不推荐) Adapter_InboundPO adapter =(Adapter_Inboun ...
- HDU4624 Endless Spin 【最大最小反演】【期望DP】
题目分析: 题目是求$E(MAX_{i=1}^n(ai))$, 它等于$E(\sum_{s \subset S}{(-1)^{|s|-1}*min(s))} = \sum_{s \subset S}{ ...
- Yahoo Programming Contest 2019 补题记录(DEF)
D - Ears 题目链接:D - Ears 大意:你在一个\(0-L\)的数轴上行走,从整数格出发,在整数格结束,可以在整数格转弯.每当你经过坐标为\(i-0.5\)的位置时(\(i\)是整数),在 ...
- 【XSY1594】棋盘控制 概率DP
题目描述 给你一个\(n\times m\)的棋盘,每次随机在棋盘上放一个国际象棋中的车,不能和以前放的重叠.每个车可以控制当前行和当前列.当所有行和所有列都被控制时结束游戏.问你结束时期望放了多少个 ...
- php插入日志到数据库,对象转json
打印插入日志数据到库 M()->table("t_log")->data(array( 'id'=>'6'.time(), 't'=> json_encod ...
- Markdown 使用技巧
懒得复制,直接贴网页吧 懒得复制,直接贴网页吧*2 懒得复制,直接贴网页吧*3