今天是闭关的第28天,Linux APUE的学习到目前为止已经经历了一大半,后面或许还会有一部分Linux APUE的其它知识,如Linux 动态库和Linux静态库、Makefile等等。(在本文正式开始讲解之前,先吐槽一下,很多博客的一些博主,不尊重其它博主的原创博客知识产权,抄来抄去,请问意义何在?另外,我还发现很多博主发表博客都是以赚钱为主,这是不太地道的,虽然知识付费无可厚非,但是应该多提倡开源,大家一起学习才能进步)。现在将来带大家一起学习Linux的多路复用,在这里我们将用到三个函数:select()、poll()和epoll()。
现在的操作系统都是采用虚拟存储器,对于32位操作系统而言,它的寻址空间位4G,为了保证用户进程不能直接操作内核,保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间,针对linux来说,将最高的1G供内核使用,称为内核空间(从虚拟地址 0xC0000000 到 0xFFFFFFFF),较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF),供各个进程使用,称为用户空间。
I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。操作系统上的I/O用户空间和内核空间的数据交互,因此I/O操作通常包含以下两个步骤:
①等待网路数据到达网卡 -》读取/写入内核缓冲区
②从内核缓冲区复制数据 –> 用户空间(读)/从用户空间复制数据 -> 内核缓冲区(写)
而判断一个I/O模型是同步还是异步,主要看第二步,数据在用户和内核空间之间复制的时候是不是会阻塞当前进程,如果会则为同步,否则为异步,所谓 I/O 多路复用指的就是 select/poll/epoll 这一系列的多路选择器:支持单一线程同时监听多个文件描述符(I/O 事件),阻塞等待,并在其中某个文件描述符可读写时收到通知。 I/O 复用其实复用的不是 I/O 连接,而是复用线程,让一个 thread of control 能够处理多个连接(I/O 事件)
(1)select多路复用
select()函数允许进程指示内核等待多个事件(文件描述符)中的任何一个发生,并只在有一个或多个事件发生或经历一段指定时间后才唤醒它,然后接下来判断究竟是哪个文件描述符发生了事件并进行相应的处理。
(2)poll多路复用
select()和poll()系统调用的本质一样,前者在BSD UNIX中引入的,后者在System V中引入的。poll()的机制与 select() 类似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll() 没有最大文件描述符数量的限制(但是数量过大后性能也是会下降)。poll() 和 select() 同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
(3)epoll多路复用
poll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
#include
#include
struct timeval
{
long tv_sec; //seconds
long tv_usec; //microseconds
};
FD_ZERO(fd_set* fds) //清空集合
FD_SET(int fd, fd_set* fds) //将给定的描述符加入集合
FD_ISSET(int fd, fd_set* fds) //判断指定描述符是否在集合中
FD_CLR(int fd, fd_set* fds) //将给定的描述符从文件中删除
int select(int max_fd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);
说明: select监视并等待多个文件描述符的属性发生变化,它监视的属性分3类,分别是readfds(文件描述符有数据到来可读)、
writefds(文件描述符可写)、和exceptfds(文件描述符异常)。调用后select函数会阻塞,直到有描述符就绪(有数据可读、可写、
或者有错误异常),或者超时( timeout 指定等待时间)发生函数才返回。当select()函数返回后,可以通过遍历 fdset,来找到
究竟是哪些文件描述符就绪。
1. select函数的返回值是就绪描述符的数目,超时时返回0,出错返回-1;
2. 第一个参数max_fd指待测试的fd的总个数,它的值是待测试的最大文件描述符加1。Linux内核从0开始到max_fd-1扫描文件描述
符,如果有数据出现事件(读、写、异常)将会返回;假设需要监测的文件描述符是8,9,10,那么Linux内核实际也要监测0~7,此时真
正带测试的文件描述符是0~10总共11个,即max(8,9,10)+1,所以第一个参数是所有要监听的文件描述符中最大的+1。
3. 中间三个参数readset、writeset和exceptset指定要让内核测试读、写和异常条件的fd集合,如果不需要测试的可以设置为
NULL;
4. 最后一个参数是设置select的超时时间,如果设置为NULL则永不超时;
需要注意的是待测试的描述集总是从0, 1, 2, ...开始的。 所以, 假如你要检测的描述符为8, 9, 10, 那么系统实际也要
监测0, 1, 2, 3, 4, 5, 6, 7, 此时真正待测试的描述符的个数为11个, 也就是max(8, 9, 10) + 1
在Linux内核有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数中,这也意味着select所用到的FD_SET是有限的,也正是这个
原因select()默认只能同时处理1024个客户端的连接请求:
/linux/posix_types.h:
#define __FD_SETSIZE 1024
基于select的I/O复用模型的是单进程执行可以为多个客户端服务,这样可以减少创建线程或进程所需要的CPU时间片或内存
资源的开销;此外几乎所有的平台上都支持select(),其良好跨平台支持是它的另一个优点。当然它也有两个主要的缺点:
poll函数的原型说明如下:
#include
struct pollfd
{
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
第一个参数用来指向一个struct pollfd类型的数组,每一个pollfd结构体指定了一个被监视的文件描述符,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域,events域中请求的任何事件都可能在revents域中返回。下表列出指定 events 标志以及测试 revents 标志的一些常值:
POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可写.
我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。
如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。
这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。
第二个参数 nfds 指定数组中监听的元素个数;
第三个参数 timeout指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()
一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。
这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。
该函数成功调用时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;
失败时,poll()返回-1,并设置errno为下列值之一:
EBADF 一个或多个结构体中指定的文件描述符无效。
EFAULTfds 指针指向的地址超出进程的地址空间。
EINTR 请求的事件之前产生一个信号,调用可以重新发起。
EINVALnfds 参数超出PLIMIT_NOFILE值。
ENOMEM 可用内存不足,无法完成请求。
在linux 没有实现epoll事件驱动机制之前,我们一般选择用select或者poll等IO多路复用的方法来实现并发服务程序。自
Linux 2.6内核正式引入epoll以来,epoll已经成为了目前实现高性能网络服务器的必备技术,在大数据、高并发、集群等一些名
词唱得火热之年代,select和poll的用武之地越来越有限,风头已经被epoll占尽。
select的缺点:
相比select模型,poll使用链表保存文件描述符,因此没有了监视文件数量的限制,但其他三个缺点依然存在。拿select模型为例,假设我们的服务器需要支持100万的并发连接,则在__FD_SETSIZE 为1024的情况下,则我们至少需要开辟1k个进程才能实现100万的并发连接。除了进程间上下文切换的时间消耗外,从内核/用户空间大量的无脑内存拷贝、数组轮询等,是系统难以承受的。因此,基于select模型的服务器程序,要达到10万级别的并发访问,是一个很难完成的任务。
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
ET (edge-triggered)是高速工作方式,只支持non-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
ET和LT的区别就在这里体现,LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你。而ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET则为边缘触发。LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。
由于epoll的实现机制与select/poll机制完全不同,上面所说的 select的缺点在epoll上不复存在。设想一下如下场景:有100万个客户端同时与一个服务器进程保持着TCP连接。而每一时刻,通常只有几百上千个TCP连接是活跃的(事实上大部分场景都是这种情况)。如何实现这样的高并发?在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,因此,select/poll一般只能处理几千的并发连接。
epoll的设计和实现与select完全不同。epoll通过在Linux内核中申请一个简易的文件系统,把原先的select/poll调用分成了3
个部分:
如此一来,要实现上面说是的场景,只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者
删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数
据,内核也不需要去遍历全部的连接。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BACKLOG 13
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
static inline void printf_usage(char *progname);
int socket_server_init(char *listen_ip, int listen_port);
int main(int argc, char **argv)
{
int listenfd = -1;
int client_fd = -1;
int rv = -1;
int listen_port;
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
char buff[1024];
fd_set rdset;
int i, j;
int found;
int maxfd = 0;
int fds_array[1024];
if (argc < 2)
{
printf("Program usage:%s [Port]\n", argv[0]);
return -1;
}
listen_port = atoi(argv[1]);
if ((listenfd = socket_server_init(NULL, listen_port)) < 0)
{
printf("ERROR: %s server listen on port %d failure\n", argv[0], listen_port);
return -2;
}
printf("%s server start to listen on port %d\n", argv[0], listen_port);
for (i = 0; i < ARRAY_SIZE(fds_array); i++)
{
fds_array[i] = -1;
}
fds_array[0] = listenfd;
for (;;)
{
FD_ZERO(&rdset); //清空集合
for (i = 0; i < ARRAY_SIZE(fds_array); i++)
{
if (fds_array[i] < 0)
continue;
maxfd = fds_array[i] > maxfd ? fds_array[i] : maxfd;
FD_SET(fds_array[i], &rdset);
}
rv = select(maxfd + 1, &rdset, NULL, NULL, NULL);
if (rv < 0)
{
printf("select failure:%s\n", strerror(errno));
break;
}
else if (rv == 0)
{
printf("select failure:%s\n", strerror(errno));
continue;
}
/*判断是否有新的客户端加入进来,listen_fd相当于门卫*/
if (FD_ISSET(listenfd, &rdset))
{
if ((client_fd = accept(listenfd, (struct sockaddr *)&cli_addr, &cli_len)) < 0)
{
printf("accepet new client failure:%s\n", strerror(errno));
continue;
}
found = 0;
/*扫描(遍历)判断是否还有-1(空位置),有的话就把clent_fd加入进去,并且found为1,直接break,跳出for循环,如果未满,最后当i>ARRAY_SIZE(fds_array)就跳出for循环*/
for (i = 0; i < ARRAY_SIZE(fds_array); i++)
{
if (fds_array[i] < 0)
{
printf("accept new client [%d] and add it into array\n", client_fd);
fds_array[i] = client_fd;
found = 1;
break;
}
}
if (!found)
{
printf("accept new client[%d] but full so refuse it\n", client_fd);
close(client_fd);
}
}
else /*在上面的if中就是判断有没有新的客户端连接进来,那么这个else就是已经连接的客户端了*/
{
for (i = 0; i < ARRAY_SIZE(fds_array); i++)
{
if (fds_array[i] < 0 || !FD_ISSET(fds_array[i], &rdset))
continue;
memset(buff, 0, sizeof(buff));
if ((rv = read(fds_array[i], buff, sizeof(buff))) < 0)
{
printf("socket[%d] read failure or get disconnect.\n", fds_array[i]);
close(fds_array[i]);
fds_array[i] = -1;
}
else
{
printf("socket[%d] read get %d bytes data,buff:%s\n", fds_array[i], rv, buff);
for (j = 0; j < rv; j++)
{
buff[j] = toupper(buff[j]);
}
if (write(fds_array[i], buff, rv < 0))
{
printf("socket[%d] write failure: %s\n", fds_array[i], strerror(errno));
close(fds_array[i]);
fds_array[i] = -1;
}
}
}
}
}
CleanUp:
close(listenfd);
return 0;
}
int socket_server_init(char *listen_ip, int listen_port)
{
int listen_fd = -1;
int rv = 0;
int on = 1;
struct sockaddr_in serv_addr;
socklen_t serv_len = sizeof(serv_addr);
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0)
{
printf("socket create failure : %s, listen_fd:[%d]\n", strerror(errno), listen_fd);
return -1;
}
printf("socket create success its listen_fd:[%d]\n", listen_fd);
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(listen_port);
if (!listen_ip) /* Listen all the local IP address */
{
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
}
else /* listen the specified IP address */
{
if (inet_pton(AF_INET, listen_ip, &serv_addr.sin_addr) <= 0)
{
printf("inet_pton() set listen IP address failure.\n");
rv = -2;
goto CleanUp;
}
}
if (bind(listen_fd, (struct sockaddr *)&serv_addr, serv_len) < 0)
{
printf("socket create failure : %s\n", strerror(errno));
return -2;
goto CleanUp;
}
printf("socket:[%d],bind on port[%d]\n", listen_fd, listen_port);
listen(listen_fd, BACKLOG);
CleanUp:
if (rv < 0)
close(listen_fd);
else
rv = listen_fd;
return rv;
}
①客户端连接图
②服务器运行图:
③客户端发送消息与接收消息图:
④服务器接收消息图:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BACKLOG 13
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
static inline void printf_usage(char *progname);
int socket_server_init(char *listen_ip, int listen_port);
int main(int argc, char **argv)
{
int listenfd = -1;
int client_fd = -1;
int rv = -1;
int listen_port;
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
char buff[1024];
fd_set rdset;
int i, j;
int found;
int max;
struct pollfd fds_array[1024];
if (argc < 2)
{
printf("Program usage:%s [Port]\n", argv[0]);
return -1;
}
listen_port = atoi(argv[1]);
if ((listenfd = socket_server_init(NULL, listen_port)) < 0)
{
printf("ERROR: %s server listen on port %d failure\n", argv[0], listen_port);
return -2;
}
printf("%s server start to listen on port %d\n", argv[0], listen_port);
for (i = 0; i < ARRAY_SIZE(fds_array); i++)
{
fds_array[i].fd = -1;
}
fds_array[0].fd = listenfd;
fds_array[0].events = POLLIN;
max = 0;
for (;;)
{
rv = poll(fds_array, max + 1, -1);
if (rv < 0)
{
printf("poll failure:%s\n", strerror(errno));
break;
}
else if (rv == 0)
{
printf("poll failure:%s\n", strerror(errno));
continue;
}
if (fds_array[0].revents & POLLIN)
{
if ((client_fd = accept(listenfd, (struct sockaddr *)&cli_addr, &cli_len)) < 0)
{
printf("accepet new client failure:%s\n", strerror(errno));
continue;
}
found = 0;
/*扫描(遍历)判断是否还有-1(空位置),有的话就把clent_fd加入进去,并且found为1,直接break,跳出for循环,如果未满,最后当i>ARRAY_SIZE(fds_array)就跳出for循环*/
for (i = 0; i < ARRAY_SIZE(fds_array); i++)
{
if (fds_array[i].fd < 0)
{
printf("accept new client [%d] and add it into array\n", client_fd);
fds_array[i].fd = client_fd;
fds_array[i].events = POLLIN;
found = 1;
break;
}
}
if (!found)
{
printf("accept new client[%d] but full so refuse it\n", client_fd);
close(client_fd);
continue;
}
max = i > max ? i : max;
if (--rv < 0) //这句感觉作用不大
continue;
}
else /*在上面的if中就是判断有没有新的客户端连接进来,那么这个else就是已经连接的客户端了*/
{
for (i = 0; i < ARRAY_SIZE(fds_array); i++)
{
if (fds_array[i].fd < 0 || !fds_array[i].revents & POLLIN)
continue;
memset(buff, 0, sizeof(buff));
if ((rv = read(fds_array[i].fd, buff, sizeof(buff))) < 0)
{
printf("socket[%d] read failure or get disconnect.\n", fds_array[i].fd);
close(fds_array[i].fd);
fds_array[i].fd = -1;
if (i == max) //让MAX进行动态变化
max--;
}
else
{
printf("socket[%d] read get %d bytes data,buff:%s\n", fds_array[i].fd, rv, buff);
for (j = 0; j < rv; j++)
{
buff[j] = toupper(buff[j]);
}
if (write(fds_array[i].fd, buff, rv < 0))
{
printf("socket[%d] write failure: %s\n", fds_array[i].fd, strerror(errno));
close(fds_array[i].fd);
fds_array[i].fd = -1;
}
}
}
}
}
CleanUp:
close(listenfd);
return 0;
}
int socket_server_init(char *listen_ip, int listen_port)
{
int listen_fd = -1;
int rv = 0;
int on = 1;
struct sockaddr_in serv_addr;
socklen_t serv_len = sizeof(serv_addr);
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0)
{
printf("socket create failure : %s, listen_fd:[%d]\n", strerror(errno), listen_fd);
return -1;
}
printf("socket create success its listen_fd:[%d]\n", listen_fd);
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(listen_port);
if (!listen_ip) /* Listen all the local IP address */
{
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
}
else /* listen the specified IP address */
{
if (inet_pton(AF_INET, listen_ip, &serv_addr.sin_addr) <= 0)
{
printf("inet_pton() set listen IP address failure.\n");
rv = -2;
goto CleanUp;
}
}
if (bind(listen_fd, (struct sockaddr *)&serv_addr, serv_len) < 0)
{
printf("socket create failure : %s\n", strerror(errno));
return -2;
goto CleanUp;
}
printf("socket:[%d],bind on port[%d]\n", listen_fd, listen_port);
listen(listen_fd, BACKLOG);
CleanUp:
if (rv < 0)
close(listen_fd);
else
rv = listen_fd;
return rv;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BACKLOG 13
#define MAX_EVENTS 512
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
static inline void printf_usage(char *progname);
int socket_server_init(char *listen_ip, int listen_port);
void set_socket_rlimit(void);
int main(int argc, char **argv)
{
int listenfd = -1;
int client_fd = -1;
int rv = -1;
int listen_port;
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
char buff[1024];
fd_set rdset;
int i, j;
int found;
int epollfd;
struct epoll_event event;
struct epoll_event event_array[MAX_EVENTS];
int events;
if (argc < 2)
{
printf("Program usage:%s [Port]\n", argv[0]);
return -1;
}
listen_port = atoi(argv[1]);
set_socket_rlimit();
if ((listenfd = socket_server_init(NULL, listen_port)) < 0)
{
printf("ERROR: %s server listen on port %d failure\n", argv[0], listen_port);
return -2;
}
printf("%s server start to listen on port %d\n", argv[0], listen_port);
if ((epollfd = epoll_create(MAX_EVENTS)) < 0)
{
printf("epoll_create() failure: %s\n", strerror(errno));
return -3;
}
//event.events = EPOLLIN|EPOLLET;
event.events = EPOLLIN;
event.data.fd = listenfd;
/**/
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event) < 0)
{
printf("epoll add listen socket failure: %s\n", strerror(errno));
return -4;
}
for (;;)
{
events = epoll_wait(epollfd, event_array, MAX_EVENTS, -1);
if (events < 0)
{
printf("epoll failure: %s\n", strerror(errno));
break;
}
else if (events == 0)
{
printf("epoll get timeout\n");
continue;
}
for (i = 0; i < events; i++)
{
if ((event_array[i].events & EPOLLERR) || (event_array[i].events & EPOLLHUP))
{
printf("epoll_wait get error on fd[%d]: %s\n", event_array[i].data.fd, strerror(errno));
epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
close(event_array[i].data.fd);
}
/* listen socket get event means new client start connect now */
if (event_array[i].data.fd == listenfd)
{
if ((client_fd = accept(listenfd, (struct sockaddr *)NULL, NULL)) < 0)
{
printf("accept new client failure: %s\n", strerror(errno));
continue;
}
event.data.fd = client_fd;
//event.events = EPOLLIN|EPOLLET;
event.events = EPOLLIN;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, client_fd, &event) < 0)
{
printf("epoll add client socket failure: %s\n", strerror(errno));
close(event_array[i].data.fd);
continue;
}
printf("epoll add new client socket[%d] ok.\n", client_fd);
}
else /* already connected client socket get data incoming */
{
memset(buff, 0, sizeof(buff));
if ((rv = read(event_array[i].data.fd, buff, sizeof(buff))) <= 0)
{
printf("socket[%d] read failure or get disconncet and will be removed.\n",
event_array[i].data.fd);
epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
close(event_array[i].data.fd);
continue;
}
else
{
printf("socket[%d] read get %d bytes data\n", event_array[i].data.fd, rv);
/* convert letter from lowercase to uppercase */
for (j = 0; j < rv; j++)
buff[j] = toupper(buff[j]);
if (write(event_array[i].data.fd, buff, rv) < 0)
{
printf("socket[%d] write failure: %s\n", event_array[i].data.fd, strerror(errno));
epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
close(event_array[i].data.fd);
}
}
}
}
}
CleanUp:
close(listenfd);
return 0;
}
int socket_server_init(char *listen_ip, int listen_port)
{
int listen_fd = -1;
int rv = 0;
int on = 1;
struct sockaddr_in serv_addr;
socklen_t serv_len = sizeof(serv_addr);
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0)
{
printf("socket create failure : %s, listen_fd:[%d]\n", strerror(errno), listen_fd);
return -1;
}
printf("socket create success its listen_fd:[%d]\n", listen_fd);
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(listen_port);
if (!listen_ip) /* Listen all the local IP address */
{
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
}
else /* listen the specified IP address */
{
if (inet_pton(AF_INET, listen_ip, &serv_addr.sin_addr) <= 0)
{
printf("inet_pton() set listen IP address failure.\n");
rv = -2;
goto CleanUp;
}
}
if (bind(listen_fd, (struct sockaddr *)&serv_addr, serv_len) < 0)
{
printf("socket create failure : %s\n", strerror(errno));
return -2;
goto CleanUp;
}
printf("socket:[%d],bind on port[%d]\n", listen_fd, listen_port);
listen(listen_fd, BACKLOG);
CleanUp:
if (rv < 0)
close(listen_fd);
else
rv = listen_fd;
return rv;
}
void set_socket_rlimit(void)
{
struct rlimit limit = {0};
getrlimit(RLIMIT_NOFILE, &limit);
limit.rlim_cur = limit.rlim_max;
setrlimit(RLIMIT_NOFILE, &limit);
printf("set socket open fd max count to %ld\n", limit.rlim_max);
}
Linux的多路复用确实比较难,要把这几套代码理解掉可是真的饿有点难度,大家好好努力吧。