常见的Linux服务端的开发模型有多进程、多线程和IO复用,即select、poll和epoll三种方式,其中现在广泛使用的IO模型主要epoll,关于该模型的性能相较于select和poll要好不少,本文也主要讨论该模型而忽略另外两种IO复用模型。

  多线程相较于多进程开销比较小,但是要注意线程间数据的同步访问。

  epoll模型在Linux2.6内核中引入的,改进了select模型一些明显设计上的明显缺点,具有更高的效率。主要体现在以下几个方面:

  1. epoll的通知机制。不同于select查询整个文件描述符数组,epoll只关心有“动静”(读写异常等情况)的描述符。但是这并不能说select一定比epoll效率低,比如在大部分情况下socket活跃度比较高的情况下,select还是很有优势的。另外socket还可以实现较高精度的us级别的定时器功能(Unix网络编程卷一),此外select创建的开销较小,如果文件描述符较少的情况下,相对epoll也较小。

  2. epoll内核底层的结构是红黑树+链表。红黑树用来保存所有的被管理的文件描述符,每一个文件描述符都在网卡驱动中添加相应的回调函数。网卡监测到相应事件到达之后会调用该回调函数将相应文件描述符的加入链表,因此链表中保存的全是活动的文件描述符。红黑树也保证了文件描述符增改查删都有比较快的速度,比如每一次添加一个文件描述符之前都会先判断其是否在红黑树中,如果存在就不重复插入。

  3. 不同于select采用的内存拷贝如copy_to_user、copy_from_user方式,epoll模式下,内核和用户之间的通信是通过共享内存,直接对内存读写是速度最快的一种IPC(Unix网络编程卷2)。

  4. epoll同时管理的文件描述符的数量几乎不受限制,不同于select的最多2048。

  今天(2016年5月27号)我一直在想为什么不能把多线程和epoll结合起来用,该怎么结合起来用。于是我上网查了下,主要有以下两种做法:

  1. 创建几个线程,然后在每个线程上创建一个epoll_wait IO等待,多个线程共享epoll一个文件描述符。

  2. 每次有数据读写的时候创建一个新线程,数据读写。

  哪一种更好呢?参考文献告诉我是第一种。

  第二种有一个明显的问题:每次有数据来的时候都会重新创建一个新线程来处理数据,创建线程也需要一定的开销(时间和内存),而epoll的引入就是为了尽量消除多线程多进程带入的这种开销。另一方面,子线程处理完之后又要销毁进程,因为如果不销毁线程则会带来内存泄漏,每个线程8M,1000个线程就需要8G。

  第一种方式就是比较完美的了,尽量利用了CPU的全部资源,同时也利用了IO复用的优势:可以在一个线程中完成对多个文件描述符的监测。这是因为多个线程共享了epoll描述符,而epoll的几个函数epoll_ctl、epoll_wait等等都是线程安全的,因此可以再多个线程中复用一个epoll描述符。这就是传说中的reactor模式?

  5月31号增加如下内容:

EPOLLONESHOT (since Linux 2.6.2)Sets the one-shot behavior for the associated file descriptor. This means that after an event is pulled out withepoll_wait(2) the associated file descriptor is internally disabled and no other events will be reported by the epollinterface. The user must call epoll_ctl() with EPOLL_CTL_MOD to rearm the file descriptor with a new event mask.

  这里的意思是,如果使用了EPOLLONESHOT 选项那么就意味着如果一个event在一个线程处理之后,不会再被触发。比如如果采用LT模式同时设置了EPOLLONESHOT ,此时到来一个可读文件描述符,如果在一个线程中对此文件描述符非阻塞读,此时如果没有把数据完全读完,那么不会再被通知了。当然如果设置了ET模式就不用设置此参数,也为在ET模式下本身就会只通知一次。

  下面是github上面一个前辈写的一个参考代码:

/*
* Copyright (C) 2012-2014 www.56.com email: jingchun.zhang AT renren-inc.com; jczhang AT 126.com ; danezhang77 AT gmail.com
*
* 56VFS may be copied only under the terms of the GNU General Public License V3
*/ #include "vfs_so.h"
#include "vfs_init.h"
#include "solib.h"
#include "myepoll.h"
#include "thread.h"
#include "myconfig.h"
#include "fdinfo.h"
#include "global.h"
#include "mybuff.h"
#include "log.h"
#include "watchdog.h"
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/sendfile.h> #if __GNUC__ < 4
static inline void barrier(void) { __asm__ volatile("":::"memory"); }
#else
static inline void barrier(void) { __sync_synchronize (); }
#endif static int epfd;
static int lfd;
static int maxevent;
static struct mylib solib; static int sub_init_signalling(char *so)
{
solib.handle = dlopen(so, RTLD_NOW);
if (solib.handle == NULL)
{
LOG(glogfd, LOG_ERROR, "open %s err %s\n", so, dlerror());
return -1;
}
solib.svc_init = (proc_init)dlsym(solib.handle, "svc_init");
if (solib.svc_init)
{
if (solib.svc_init() < 0)
{
LOG(glogfd, LOG_ERROR, "svc_init ERROR %m!\n");
return -1;
}
}
else
{
LOG(glogfd, LOG_ERROR, "svc_init must be imp!\n");
return -1;
}
solib.svc_pinit = (proc_init)dlsym(solib.handle, "svc_pinit");
solib.svc_initconn = (proc_method)dlsym(solib.handle, "svc_initconn");
solib.svc_recv = (proc_method)dlsym(solib.handle, "svc_recv");
solib.svc_send = (proc_method)dlsym(solib.handle, "svc_send");
solib.svc_finiconn = (proc_fini)dlsym(solib.handle, "svc_finiconn");
solib.svc_timeout = (proc_timeout)dlsym(solib.handle, "svc_timeout" );
solib.svc_send_once = (proc_method)dlsym(solib.handle, "svc_send_once" );
if (solib.svc_recv && solib.svc_send)
return 0;
LOG(glogfd, LOG_ERROR, "svc_send and svc_recv must be imp!\n");
return -1;
} void add_fd_2_efd(int fd)
{
fcntl(fd, F_SETFL, O_RDWR|O_NONBLOCK); struct conn *curconn = &acon[fd];
curconn->fd = fd;
curconn->send_len = 0;
memset(curconn->peerip, 0, sizeof(curconn->peerip));
mybuff_reinit(&(curconn->send_buff));
mybuff_reinit(&(curconn->recv_buff));
uint32_t ip = getpeerip(fd);
ip2str(curconn->peerip, ip);
LOG(glogfd, LOG_DEBUG, "fd [%d] [%s]set ok %d\n", fd, curconn->peerip, curconn->fd);
epoll_add(epfd, fd, EPOLLIN);
} void modify_fd_event(int fd, int events)
{
events = EPOLLIN|events;
epoll_mod(epfd, fd, events);
LOG(glogfd, LOG_DEBUG, "fd [%d] be modify!\n", fd);
} int get_client_data(int fd, char **data, size_t *len)
{
struct conn *curcon = &acon[fd];
if(mybuff_getdata(&(curcon->recv_buff), data, len))
return -1;
return 0;
} int consume_client_data(int fd, size_t len)
{
struct conn *curcon = &acon[fd];
mybuff_skipdata(&(curcon->recv_buff), len);
return 0;
} int set_client_data(int fd, char *buf, size_t len)
{
struct conn *curcon = &acon[fd];
mybuff_setdata(&(curcon->send_buff), buf, len);
return 0;
} int set_client_fd(int fd, int localfd, size_t offset, size_t len)
{
struct conn *curcon = &acon[fd];
mybuff_setfile(&(curcon->send_buff), localfd, offset, len);
return 0;
} static void accept_new()
{
struct sockaddr_in addr;
socklen_t len;
int fd = 0; while (1)
{
fd = accept(lfd, (struct sockaddr *)&addr, (len = sizeof(addr), &len));
if (fd < 0)
break;
if (fd >= maxfds)
{
LOG(glogfd, LOG_ERROR, "fd overflow ![%d] > [%d]\n", fd, maxfds);
close(fd);
continue;
}
if (solib.svc_initconn(fd))
{
LOG(glogfd, LOG_ERROR, "fd init err ![%d] %m\n", fd);
close(fd);
continue;
}
add_fd_2_efd(fd);
}
} void do_close(int fd)
{
if (fd >= 0 && fd < maxfds)
{
struct conn *curcon = &acon[fd];
if (curcon->fd < 0)
LOG(glogfd, LOG_ERROR, "fd %d already be closed %s\n", fd, FUNC); LOG(glogfd, LOG_DEBUG, "%s:%s:%d close fd %d\n", ID, FUNC, LN, fd);
if (solib.svc_finiconn)
solib.svc_finiconn(fd);
barrier();
epoll_del(epfd, fd);
curcon->fd = -1;
close(fd);
}
} static void do_send(int fd)
{
LOG(glogfd, LOG_TRACE, "%s:%s:%d\n", ID, FUNC, LN);
int ret = SEND_ADD_EPOLLIN;
int n = 0;
struct conn *curcon = &acon[fd];
if (curcon->fd < 0)
{
LOG(glogfd, LOG_DEBUG, "fd %d already be closed %s\n", fd, FUNC);
return;
}
int localfd;
off_t start;
char* data;
size_t len;
if(!mybuff_getdata(&(curcon->send_buff), &data, &len))
{
LOG(glogfd, LOG_TRACE, "fd[%d] get len from data [%d]\n", fd, len);
while (1)
{
n = send(fd, data, len, MSG_DONTWAIT | MSG_NOSIGNAL);
if(n > 0)
{
LOG(glogfd, LOG_TRACE, "fd[%d] send len %d, datalen %d\n", fd, n, len);
mybuff_skipdata(&(curcon->send_buff), n);
if (n < len)
ret = SEND_ADD_EPOLLOUT;
curcon->send_len += n;
}
else if(errno == EINTR)
continue;
else if(errno == EAGAIN)
ret = SEND_ADD_EPOLLOUT;
else
{
LOG(glogfd, LOG_ERROR, "%s:%s:%d fd[%d] send err %d:%d:%m\n", ID, FUNC, LN, fd, n, len);
ret = SEND_CLOSE;
}
break;
}
}
if(ret == SEND_ADD_EPOLLIN && !mybuff_getfile(&(curcon->send_buff), &localfd, &start, &len))
{
LOG(glogfd, LOG_TRACE, "fd[%d] get len from file [%d]\n", fd, len);
size_t len1 = len > GSIZE ? GSIZE : len;
while (1)
{
n = sendfile64(fd, localfd, &start, len1);
if(n > 0)
{
mybuff_skipfile(&(curcon->send_buff), n);
LOG(glogfd, LOG_TRACE, "%s:%s:%d fd[%d] send len %d, datalen %d\n", ID, FUNC, LN, fd, n, len1);
if(n < len)
ret = SEND_ADD_EPOLLOUT;
curcon->send_len += n;
}
else if(errno == EINTR)
continue;
else if(errno == EAGAIN)
ret = SEND_ADD_EPOLLOUT;
else
{
LOG(glogfd, LOG_ERROR, "%s:%s:%d fd[%d] send err %d:%d:%m\n", ID, FUNC, LN, fd, n, len);
ret = SEND_CLOSE;
}
break;
}
} switch (ret)
{
case SEND_CLOSE:
do_close(fd);
return;
case SEND_ADD_EPOLLIN:
modify_fd_event(fd, EPOLLIN);
break;
case SEND_ADD_EPOLLOUT:
modify_fd_event(fd, EPOLLOUT);
break;
case SEND_ADD_EPOLLALL:
modify_fd_event(fd, EPOLLOUT|EPOLLIN);
break;
default:
modify_fd_event(fd, EPOLLIN);
break;
} if (n > 0 && solib.svc_send_once)
solib.svc_send_once(fd); if (ret == SEND_ADD_EPOLLIN && solib.svc_send)
if (solib.svc_send(fd) == SEND_CLOSE)
{
LOG(glogfd, LOG_DEBUG, "%s:%s:%d send close %d\n", ID, FUNC, LN, fd);
do_close(fd);
}
} static void do_recv(int fd)
{
struct conn *curcon = &acon[fd];
if (curcon->fd < 0)
{
LOG(glogfd, LOG_ERROR, "fd %d already be closed %s\n", fd, FUNC);
return;
} char iobuf[20480] = {0x0};
int n = -1;
while (1)
{
n = recv(fd, iobuf, sizeof(iobuf), MSG_DONTWAIT);
if (n > 0)
{
LOG(glogfd, LOG_DEBUG, "fd[%d] recv len %d\n", fd, n);
mybuff_setdata(&(curcon->recv_buff), iobuf, n);
if (n == sizeof(iobuf))
{
LOG(glogfd, LOG_DEBUG, "fd[%d] need recv nextloop %d\n", fd, n);
continue;
}
break;
}
if (n == 0)
{
LOG(glogfd, LOG_ERROR, "fd[%d] close %s:%d!\n", fd, ID, LN);
return do_close(fd);
}
if (errno == EINTR)
{
LOG(glogfd, LOG_TRACE, "fd[%d] need recv again!\n", fd);
continue;
}
else if (errno == EAGAIN)
{
LOG(glogfd, LOG_TRACE, "fd[%d] need recv next!\n", fd);
modify_fd_event(fd, EPOLLIN);
break;
}
else
{
LOG(glogfd, LOG_ERROR, "fd[%d] close %s:%d!\n", fd, ID, LN);
return do_close(fd);
}
} if (n <= 0)
return; int ret = solib.svc_recv(fd);
switch (ret)
{
case RECV_CLOSE:
LOG(glogfd, LOG_ERROR, "fd[%d] close %s:%d!\n", fd, ID, LN);
do_close(fd);
break;
case RECV_SEND:
do_send(fd);
break;
case RECV_ADD_EPOLLIN:
modify_fd_event(fd, EPOLLIN);
break;
case RECV_ADD_EPOLLOUT:
modify_fd_event(fd, EPOLLOUT);
break;
case RECV_ADD_EPOLLALL:
modify_fd_event(fd, EPOLLOUT|EPOLLIN);
break;
default:
modify_fd_event(fd, EPOLLIN);
break;
}
} static void do_process(int fd, int events)
{
if(!(events & (EPOLLIN | EPOLLOUT)))
{
LOG(glogfd, LOG_DEBUG, "error event %d, %d\n", events, fd);
do_close(fd);
return;
}
if(events & EPOLLIN)
{
LOG(glogfd, LOG_TRACE, "read event %d, %d\n", events, fd);
do_recv(fd);
}
if(events & EPOLLOUT)
{
LOG(glogfd, LOG_TRACE, "send event %d, %d\n", events, fd);
do_send(fd);
}
} static void set_socket_attr(int fd)
{
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &init_buff_size, sizeof(int));
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &init_buff_size, sizeof(int));
} static void so_thread_entry(void *arg)
{
pthread_detach(pthread_self());
if (solib.svc_pinit)
{
if (solib.svc_pinit() < 0)
{
LOG(glogfd, LOG_ERROR, "svc_pinit ERROR %m!\n");
stop = 1;
return ;
}
}
else
{
LOG(glogfd, LOG_ERROR, "svc_pinit must be imp!\n");
stop = 1;
return ;
}
int n = 0, i = 0;
time_t last = time(NULL);
time_t now = last;
struct epoll_event *pev = (struct epoll_event*)malloc(sizeof(struct epoll_event) * maxevent);
if(pev == NULL)
{
LOG(glogfd, LOG_ERROR, "allocate epoll_event(%d): %m\n", maxevent);
stop = 1;
return ;
}
while (!stop)
{
n = epoll_wait(epfd, pev, maxevent, 1000);
for(i = 0; i < n; i++)
{
if (pev[i].data.fd == lfd)
accept_new();
else
do_process(pev[i].data.fd, pev[i].events);
}
now = time(NULL);
if (now > last + g_config.cktimeout)
{
last = now;
if (solib.svc_timeout)
solib.svc_timeout();
}
}
} int vfs_signalling_thread(void *arg)
{
t_thread_arg *argp = (t_thread_arg *)arg;
if (sub_init_signalling(argp->name))
{
stop = 1;
return -1;
}
if (argp->port > 0)
{
lfd = get_listen_sock(argp->port);
if (lfd < 0)
{
LOG(glogfd, LOG_ERROR, "get_listen_sock err %d\n", argp->port);
stop = 1;
return -1;
}
LOG(glogfd, LOG_DEBUG, "%s listen on %d\n", argp->name, argp->port);
}
maxevent = argp->maxevent;
epfd = epoll_create(maxevent);
if(epfd < 0)
{
LOG(glogfd, LOG_ERROR, "epoll_create(%d): %m\n", maxevent);
stop = 1;
return -1;
}
if (argp->port > 0)
{
if (argp->flag)
set_socket_attr(lfd);
} struct threadstat *thst = get_threadstat();
int event = EPOLLIN;
if (argp->port > 0)
epoll_add(epfd, lfd, event);
LOG(glogfd, LOG_DEBUG, "%s:%s:%d\n", ID, FUNC, LN); int i = 0;
pthread_attr_t attr;
pthread_t tid;
pthread_attr_init(&attr);
int rc; if (argp->threadcount <= 0)
argp->threadcount = 1;
for (; i < argp->threadcount; i++)
{
usleep(5000);
if((rc = pthread_create(&tid, &attr, (void*(*)(void*))so_thread_entry, NULL)) != 0)
{
LOG(glogfd, LOG_ERROR, "pthread_create err %m\n");
stop = 1;
return -1;
}
}
while (!stop)
{
thread_reached(thst);
sleep(200);
}
return 0;
}

参考:

  http://stackoverflow.com/questions/7058737/is-epoll-thread-safe

  https://github.com/jingchunzhang/56vfs/blob/master/network/vfs_so_r.c

  http://stackoverflow.com/questions/14584833/multithreaded-epoll

  http://linux.die.net/man/2/epoll_ctl

linux服务端的网络编程的更多相关文章

  1. Linux下高并发网络编程

      Linux下高并发网络编程 1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时, 最高的并发数量都要受到系统对用户单一进程同时可打 ...

  2. TCP和UDP的区别以及使用python服务端客户端简单编程

    一.TCP.UDP区别总结 1.TCP面向连接(如打电话要先拨号建立连接):UDP是无连接的,即发送数据之前不需要建立连接 2.TCP提供可靠的服务,也就是说,通过TCP连接传送的数据,无差错,不丢失 ...

  3. 随手写了一个linux服务端与window客户端的epoll程序,当做练习把。

    linux服务端:监听链接,处理消息 #include <sys/socket.h>     #include <sys/epoll.h>     #include <n ...

  4. Kafka服务端之网络连接源码分析

    #### 简介 上次我们通过分析KafkaProducer的源码了解了生产端的主要流程,今天学习下服务端的网络层主要做了什么,先看下 KafkaServer的整体架构图 ![file](https:/ ...

  5. Linux IO模型和网络编程模型

    术语概念描述: IO有内存IO.网络IO和磁盘IO三种,通常我们说的IO指的是后两者. 阻塞和非阻塞,是函数/方法的实现方式,即在数据就绪之前是立刻返回还是等待. 以文件IO为例,一个IO读过程是文件 ...

  6. linux服务器开发三(网络编程)

    网络基础 协议的概念 什么是协议 从应用的角度出发,协议可理解为"规则",是数据传输和数据的解释的规则. 假设,A.B双方欲传输文件.规定: 第一次,传输文件名,接收方接收到文件名 ...

  7. 金蝶 K/3 Cloud 服务端控件编程模型

    如下图是服务端已有的控件编程模型

  8. 成功使Linux服务端和Windows客户端建立socket通信

    一.准备工作 1.一台装有虚拟机的Windows7操作系统,虚拟机中装的是CentOS6.5版本的Linux 2.Windows7已经装有java环境 二.编码 使用java编写socket通信的服务 ...

  9. MQTT协议学习及实践(Linux服务端,Android客户端的例子)

    前言 MQTT(Message Queuing Telemetry Transport),是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提 ...

随机推荐

  1. Qt学习之路(1)------Qt常用类用法说明

    Qt常用类 向控制台输出文本 第一个例子,我们采用STL的方式: console.cpp #include <iostream> int main() { std::cout <&l ...

  2. EJB 总结学习(1)

    总结1: 以下面这行代码为例: PersonDaoBeanRemote pdb = (PersonDaoBeanRemote)ctx.lookup("PersonDaoBean/remote ...

  3. RHEL7下安装使用TensorFlow和kcws

    0.安装依赖包 #用pip安装python科学计算库numpy,sklearn,scipysu - wget http://dl.fedoraproject.org/pub/epel/7/x86_64 ...

  4. 前端自动生成/加载CSS

    前言: 1.我很懒! 2.写样式时,很多时候需要单独设置长度.宽度.内间距.外间距等.于是,就会有很多CSS代码会出现很多类似以下的代码: .w20: { width: 20px; } .mt10: ...

  5. hdoj 2087 剪花布条

    剪花布条 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  6. 第一次尝试使用JAVA编写的ATM机程序

    package study; import java.util.Scanner; public class ATM { private static int[] users = { 111111, 2 ...

  7. FZU 2129 子序列个数 (动态规划)

    题意:子序列的定义:对于一个序列a=a[1],a[2],......a[n].则非空序列a'=a[p1],a[p2]......a[pm]为a的一个子序列,其中1<=p1<p2<.. ...

  8. 全文索引(三)lucene 分词 Analyzer

    分词: 将reader通过阅读对象Analyzer字处理,得到TokenStream处理流程被称为分割. 该解释可能是太晦涩.查看示例,这个东西是什么感性的认识. 样品:一段文本"this ...

  9. android 双卡手机发短信/判断手机是否为双卡

    通过反射发现sendMultipartTextMessage多了一个phoneType的参数,这个参数就是这只用GMS还是CDMA卡发送的. phoneType获取方法,iTelephony.getA ...

  10. 谷歌google搜索打不开、谷歌gmail邮箱及相关服务无法登录的解决的方法

    歌打不开 google打不开,与中国大陆封杀有关,可是主要是由于近期googleserver在全球范围内又一次进行了布局调整. 解决的方法是仅仅要改动用户本地计算机hosts文件就能够了. 一.Win ...