当前位置: 技术问答>linux和unix
一个关于EPOLL的问题,一直没有办法进行解决!请大侠们来帮帮忙啊!
来源: 互联网 发布时间:2017-02-01
本文导语: 我编写的epoll的时候发现一个很怪异的问题。 我在windows写编写的客户端程序来链接LINUX下用epoll写的服务端程序。 当进行少量的客户端链接时,客户端发送数据可以被正确的接收到。 当进行大量的客户端链接时(300...
我编写的epoll的时候发现一个很怪异的问题。
我在windows写编写的客户端程序来链接LINUX下用epoll写的服务端程序。
当进行少量的客户端链接时,客户端发送数据可以被正确的接收到。
当进行大量的客户端链接时(300个客户端),客户端发送的数据在epoll接收的时候就会很慢,而且有的时候无法接收完全。
我的实现是这样的。
主进程创建一个wait线程,用来专门等待EPOLL的事件,代码如下:
fds = epoll_wait(lpThreadObj->Parent->m_epollEvents->epoll_fd, lpThreadObj->Parent->m_epollEvents->epoll_events, lpThreadObj->Parent->m_epollEvents->maxfds, 0);
if (fds m_state)
{
sem_post(&lpThreadObj->Wait_exit_sem);
return NULL;
}
continue;
}
else
{
for(int i = 0; i Parent->m_epollEvents->epoll_events[i].data.ptr;
if(pClient == NULL) continue;
if((pClient->Socket == lpThreadObj->Parent->m_ListenClient->Socket) && (pClient->Socket & EPOLLIN))
{
sem_post(&lpThreadObj->Accept_Work_sem);
continue;
}
if(lpThreadObj->Parent->m_epollEvents->epoll_events[i].events & EPOLLIN)
{
gettimeofday(&pClient->m_ActiveTick, NULL);
//加入接收队列中进行等待处理
lpThreadObj->addRecvQueue(pClient, OPER_RECEIVE);
}
else if(lpThreadObj->Parent->m_epollEvents->epoll_events[i].events & EPOLLOUT)
{
//printf("EPOLLOUT = %d i = %dn", pClient->SocketHandle, i);
}
}
}
sem_post(&lpThreadObj->Accept_Work_sem);会触发一个事件,另外会有一个CEpollAcceptWorkThread线程专门来进行accept
并对每个客户端的链接对象添加监控事件EPOLLIN|EPOLLOUT|EPOLLET。代码如下:
struct epoll_event event;
event.data.fd = pSocket->Socket;
event.data.ptr = pSocket;
event.events = EPOLLIN|EPOLLOUT|EPOLLET;
int iRet = epoll_ctl(m_epollEvents->epoll_fd, EPOLL_CTL_ADD, pSocket->Socket, &event);
lpThreadObj->addRecvQueue(pClient, OPER_RECEIVE);函数是将读取请求加入到一个待处理队列中。有3个读取线程从此队列获得读取请求,并进行读取。
少量的数据的时候一切都是正常的,当300个链接以上接收数据就和客户端发送的数据量不一致了。
我的环境是:
EPOLL在UBUNTU 11.10系统下开发,UBUNTU 安装在windows下的虚拟机中。
客户端在windows下
我在windows写编写的客户端程序来链接LINUX下用epoll写的服务端程序。
当进行少量的客户端链接时,客户端发送数据可以被正确的接收到。
当进行大量的客户端链接时(300个客户端),客户端发送的数据在epoll接收的时候就会很慢,而且有的时候无法接收完全。
我的实现是这样的。
主进程创建一个wait线程,用来专门等待EPOLL的事件,代码如下:
fds = epoll_wait(lpThreadObj->Parent->m_epollEvents->epoll_fd, lpThreadObj->Parent->m_epollEvents->epoll_events, lpThreadObj->Parent->m_epollEvents->maxfds, 0);
if (fds m_state)
{
sem_post(&lpThreadObj->Wait_exit_sem);
return NULL;
}
continue;
}
else
{
for(int i = 0; i Parent->m_epollEvents->epoll_events[i].data.ptr;
if(pClient == NULL) continue;
if((pClient->Socket == lpThreadObj->Parent->m_ListenClient->Socket) && (pClient->Socket & EPOLLIN))
{
sem_post(&lpThreadObj->Accept_Work_sem);
continue;
}
if(lpThreadObj->Parent->m_epollEvents->epoll_events[i].events & EPOLLIN)
{
gettimeofday(&pClient->m_ActiveTick, NULL);
//加入接收队列中进行等待处理
lpThreadObj->addRecvQueue(pClient, OPER_RECEIVE);
}
else if(lpThreadObj->Parent->m_epollEvents->epoll_events[i].events & EPOLLOUT)
{
//printf("EPOLLOUT = %d i = %dn", pClient->SocketHandle, i);
}
}
}
sem_post(&lpThreadObj->Accept_Work_sem);会触发一个事件,另外会有一个CEpollAcceptWorkThread线程专门来进行accept
并对每个客户端的链接对象添加监控事件EPOLLIN|EPOLLOUT|EPOLLET。代码如下:
struct epoll_event event;
event.data.fd = pSocket->Socket;
event.data.ptr = pSocket;
event.events = EPOLLIN|EPOLLOUT|EPOLLET;
int iRet = epoll_ctl(m_epollEvents->epoll_fd, EPOLL_CTL_ADD, pSocket->Socket, &event);
lpThreadObj->addRecvQueue(pClient, OPER_RECEIVE);函数是将读取请求加入到一个待处理队列中。有3个读取线程从此队列获得读取请求,并进行读取。
少量的数据的时候一切都是正常的,当300个链接以上接收数据就和客户端发送的数据量不一致了。
我的环境是:
EPOLL在UBUNTU 11.10系统下开发,UBUNTU 安装在windows下的虚拟机中。
客户端在windows下
|
肯定是编码问题,注意检查这几个:
1,write和read的返回值是否检查了,因为返回值可能小于请求值或者直接返回-1。
2,你用的ET,编码时是否read/write到发生EAGAIN为止,不读干净就会漏掉数据。、
3,不应该始终注册EPOLLOUT,因为只要网卡不满,永远都会触发EPOLLOUT事件,也就是即便没有任何消息,你的程序都会不停的循环。
1,write和read的返回值是否检查了,因为返回值可能小于请求值或者直接返回-1。
2,你用的ET,编码时是否read/write到发生EAGAIN为止,不读干净就会漏掉数据。、
3,不应该始终注册EPOLLOUT,因为只要网卡不满,永远都会触发EPOLLOUT事件,也就是即便没有任何消息,你的程序都会不停的循环。
|
你讲讲你的服务端架构吧, 就这点代码来看的话是主线程将触发事件的CLIENT放入队列,利用信号量唤醒工作线程,光这么点东西要注意的细节就很多。
一个CLIENT会不会被多次放入队列中? 如果你没有考虑这个情况, 那么你就肯定没有考虑到同一个CLIENT被不同的线程同时处理的情况。
这种靠线程的架构, 要不就是事件I/O线程读数据拆包,然后交给线程池去处理,之后再通知事件I/O线程发回数据,一个client只能被某一个线程负责,不能一会给这个线程一会给那个线程,这样包序是一个大麻烦,并发也没法处理,你应该理解。
依靠线程的架构说白了就一种,具体一点就是两种。
一种是每个线程负责若干socket的事件监听,这适合CPU多的,非计算的网络通信,主线程只负责accept。
一种是每个线程负责一个socket的处理,适合计算密集的网络通信,主线程还是只负责accept。
你这种写法我看命名就觉得有问题,因为你的主线程肯定不是从accept之后就撒手不管了,而是主线程监听,不停地将client分发到线程,这是不好的。
应该accept之后就将client分发到某个线程,从此改client的死活都由那个线程负责了。
你的问题就是每次client有事件都分发一次,请问如果数据密集了,同一个client在短期内多次来数据,被分发到不同的线程并发处理,请问怎么read怎么send? 毫无办法。
一个CLIENT会不会被多次放入队列中? 如果你没有考虑这个情况, 那么你就肯定没有考虑到同一个CLIENT被不同的线程同时处理的情况。
这种靠线程的架构, 要不就是事件I/O线程读数据拆包,然后交给线程池去处理,之后再通知事件I/O线程发回数据,一个client只能被某一个线程负责,不能一会给这个线程一会给那个线程,这样包序是一个大麻烦,并发也没法处理,你应该理解。
依靠线程的架构说白了就一种,具体一点就是两种。
一种是每个线程负责若干socket的事件监听,这适合CPU多的,非计算的网络通信,主线程只负责accept。
一种是每个线程负责一个socket的处理,适合计算密集的网络通信,主线程还是只负责accept。
你这种写法我看命名就觉得有问题,因为你的主线程肯定不是从accept之后就撒手不管了,而是主线程监听,不停地将client分发到线程,这是不好的。
应该accept之后就将client分发到某个线程,从此改client的死活都由那个线程负责了。
你的问题就是每次client有事件都分发一次,请问如果数据密集了,同一个client在短期内多次来数据,被分发到不同的线程并发处理,请问怎么read怎么send? 毫无办法。
|
你的I/O模型有问题。
三楼说的已经很详细了。
三楼说的已经很详细了。
|
可以看看 Unix环境高级编程
|
2,你用的ET,编码时是否read/write到发生EAGAIN为止,不读干净就会漏掉数据。、
3,不应该始终注册EPOLLOUT,因为只要网卡不满,永远都会触发EPOLLOUT事件,也就是即便没有任何消息,你的程序都会不停的循环。
对于2, 实际上处理得当,无需要等到EAGAIN,也不会出现漏掉数据的情况,至少我没出现过。
对于3,ET 不仅对EPOLLIN有效,同样对EPOLLOUT也是有效果的,所以不会出现不停循环的问题,可以看这里
http://www.chineselinuxuniversity.net/patches/18934.shtml 还有ET的描述,是从非出发态突变到触发态,并没说仅针对EPOLLIN事件。
楼主的模型是,一个线程处理epoll,实际读写交由IO线程处理,我觉得可能的问题是同步问题。
我举个例子:
int ret = recv();
if(ret == EAGAIN)
{
//执行某些处理
//在这里,套接口有可能又恢复成可读状态
}
3,不应该始终注册EPOLLOUT,因为只要网卡不满,永远都会触发EPOLLOUT事件,也就是即便没有任何消息,你的程序都会不停的循环。
对于2, 实际上处理得当,无需要等到EAGAIN,也不会出现漏掉数据的情况,至少我没出现过。
对于3,ET 不仅对EPOLLIN有效,同样对EPOLLOUT也是有效果的,所以不会出现不停循环的问题,可以看这里
http://www.chineselinuxuniversity.net/patches/18934.shtml 还有ET的描述,是从非出发态突变到触发态,并没说仅针对EPOLLIN事件。
楼主的模型是,一个线程处理epoll,实际读写交由IO线程处理,我觉得可能的问题是同步问题。
我举个例子:
int ret = recv();
if(ret == EAGAIN)
{
//执行某些处理
//在这里,套接口有可能又恢复成可读状态
}