epoll 并发接收数据丢失问题

LiquidX 2009-03-04 08:02:55
我在windows上通过批处理快速的循环打开了10多个客户端, 客户端一启动马上就向服务器发送数据,通过观察,发现客户端并发的一起向服务器发送数据频率是0.1秒, 大概22字节的样子, 总是有1~2个客户端发送的数据服务器没有收到(receiveSocketData接口就没有被触发)。
注意:我的测试模式是 服务器收到包则会返回一个包, 客户端也是收到一个包才会发送一个包, 因此那2个客户端是第一次发送服务器就没收到而后就不能继续发往服务器了, 他们的连接是通的。

服务器大概这个样子:

startEpoll()
{
socklen_t socklen;
int n = 0;

if ( !isInit() ){
DEBUG_MSG( ">>you must to init epoll.\n" );
return -1;
}

if ( listen( listener, lisnum ) == -1 )
{
perror( ">>listen" );
return -1;
}
else
DEBUG_MSG( ">>start server of network is successfully!\n" );

/* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */
kdpfd = epoll_create( m_maxEpollSize );
socklen = sizeof( struct sockaddr_in );
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listener;

if ( epoll_ctl( kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0 )
{
fprintf( stderr, ">>epoll set insertion error: fd=%d\n", listener );
return -1;
}
else
DEBUG_MSG( ">>listen socket add epoll successfully!\n");

curfds = 1;
m_isRun = true;

while ( m_isRun ) {
//pthread_testcancel();//设置线程取消点
/* 等待有事件发生 -1表示一直等待 */
nfds = epoll_wait( kdpfd, events, curfds, -1 );

if ( nfds == -1 ) {
perror( ">>epoll_wait" );
return -1;
}

/* 处理所有事件 */
for ( n = 0; n < nfds; ++n ) {
if ( events[ n ].data.fd == listener ) //是主socket的事件发生
{
struct sockaddr_in their_addr;
int new_fd = accept( listener, (struct sockaddr *) &their_addr, &socklen );

//if ( errno == EAGAIN )
// return 0 ;

if ( new_fd < 0 ) {
perror( ">>accept is error! new_fd = 0." );
continue;
}

// 客户端连接通知
if ( 0 != validNewSocket( their_addr ) ){
close( new_fd );
DEBUG_MSG( ">>close a illegal the client, address: %s", inet_ntoa( their_addr.sin_addr ) );
continue;
}

// 把socket设置为非阻塞方式
setnonblocking( new_fd );
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = new_fd;

if ( epoll_ctl( kdpfd, EPOLL_CTL_ADD, new_fd, &ev ) < 0 ) {
fprintf( stderr, ">>to socket '%d' add epoll failed!%s\n", new_fd, strerror( errno ) );
return -1;
}

curfds++;
SocketBufferAssigner::getSingleton().assignSocketBuffer( new_fd );
App::getSingleton().onNewSocketAdd( new_fd, inet_ntoa( their_addr.sin_addr ), ntohs( their_addr.sin_port ) );

DEBUG_MSG( ">>connect from: %s : %d, created socket: %d, currCount : %d\n", inet_ntoa( their_addr.sin_addr ), ntohs( their_addr.sin_port ), new_fd, curfds );
}
else
{
// 接受客户端的数据
int nRet = receiveSocketData( events[ n ].data.fd );
if ( nRet < 1 && errno != 11 )
{
printf( "fdsafsadfdsafsdafdsafsadfsadfasd\n");
curfds--;
epoll_ctl( kdpfd, EPOLL_CTL_DEL, events[ n ].data.fd, &ev);
SocketBufferAssigner::getSingleton().detachSocketBuffer( events[ n ].data.fd );
App::getSingleton().onSocketLeave( events[ n ].data.fd );
}

}
}
}
return 0;
}



// 包接收处理





receiveSocketData( int socketID )
{
char buf[ EPOLL_MAXBUF + 1 ];
bzero( buf, EPOLL_MAXBUF + 1 );

/* 开始处理每个新连接上的数据收发 */
/* 接收客户端的消息 */
int recvlen = recv( socketID, buf, sizeof( buf ), 0 );

if( recvlen < 0 )
{
// 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读
// 在这里就当作是该次事件已处理处.
if(errno == EAGAIN){
DEBUG_MSG( ">>socket %d ==EAGAIN!\n", socketID );
return 1;
}
else
{
DEBUG_MSG( ">>receive data is failed! error code:%d, errstr:'%s', currCount : %d\n", errno, strerror( errno ), curfds );
return -1;
}
}
else if( recvlen == 0 )
{
// 这里表示对端的socket已正常关闭.
DEBUG_MSG( ">>socket %d exit! currCount : %d\n", socketID, curfds );
close( socketID );
return 0;
}
else if ( recvlen == 1 && buf[0] == '0' ){
close( socketID );
close( listener );
exit( 1 );
}
else if( recvlen == sizeof( buf ) )
{
// 需要再次读取
printf("WARNING-----%d, %d\n",recvlen,strlen( (char*)&buf ));
return 1;
}
else
{
static std::vector<int> tmppp;
std::vector<int>::iterator ite = std::find( tmppp.begin(), tmppp.end(), socketID );
if ( ite == tmppp.end() ){
printf("-----------%s, %d\n",buf,socketID);
tmppp.push_back( socketID );
}
//KBPerf* kbp = new KBPerf( "push-socketData" );
//kbp->collect();
// 将所有接收到的消息全部压入环形缓冲区
SocketBufferAssigner::getSingleton().getSocketRecvBuffer( socketID )->push( (char*)&buf, recvlen );
//kbp->getExecInterval();
}
/* 处理每个新连接上的数据收发结束 */
return recvlen;
}
...全文
712 13 打赏 收藏 转发到动态 举报
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhuxueling 2009-03-08
  • 打赏
  • 举报
回复
能帮我找一个可爱点的猪当头像么?谢了。
zhuxueling 2009-03-08
  • 打赏
  • 举报
回复
什么地方都用设计模式,与去哪里都要打出租车是一样的。
该步行时要步行,该坐飞机就坐飞机。
zhuxueling 2009-03-08
  • 打赏
  • 举报
回复
int occur_size;
do{
occur_size = epoll_wait( ep_fd, events, ev_size, -1) > 0);
for( int itr = 0; i <occur_size; ++i){
thread_poll_push( events[i]);
}
}while( occur_size > 0);


void work_for_client( int client_fd){
if( client_fd == server_fd){
// codes here
}else{
// codes here.
async_read( client_fd);
}
}



inline void async_read( int epfd, int client_fd){
epoll_event ev = { EPOLLIN | EPOLLET | EPOLLONESHOT, { (void*) fd}};
if( ::epoll_ctl( ep, EPOLL_CTL_ADD, ev) == -1 && errno == EEXIST)
assert( :epoll_ctl( ep, EPOLL_CTL_ADD, ev) == 0);
}

intline void async_write( int epfd, int clinet_fd){
epoll_event ev = { EPOLLOUT | EPOLLET | EPOLLONESHOT, { (void*) fd}};
if( ::epoll_ctl( ep, EPOLL_CTL_ADD, ev) == -1 && errno == EEXIST)
assert( :epoll_ctl( ep, EPOLL_CTL_ADD, ev) == 0);
}

<NOTE: all client fd events have to register: EPOLLONESHOT, if you want the packages in a queue.>
<well, mutex is not need here, you will not start to recv msg on the same fd, until you have done the last recv package.>

<zhuxueling老兄.... I am a boy of 23.>
zhuxueling 2009-03-06
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 LiquidX 的回复:]
另外再问下
for( int itr = 0; i <occur_size; ++i){
thread_poll_push( events[i]);
}

这个方法你应该使用的是线程池吧?  如果是线程池你如何保证你的数据包的前后顺序问题?例如一个游戏服务器, 数据包的先后顺序是很重要的, 如果有很多在线用户, 线程处理顺序不能保证, 那么底层还要处理很多数据包顺序问题, 消耗又回来了。
[/Quote]

class Mutex{...}
class Lock{...}
class ScopeLock{...}

void work_for_client( int client_fd){
ScopeLock( mutexs[ client_fd]);
//now, work for client_fd, it is locked.
}
LiquidX 2009-03-06
  • 打赏
  • 举报
回复
另外再问下
for( int itr = 0; i <occur_size; ++i){
thread_poll_push( events[i]);
}

这个方法你应该使用的是线程池吧? 如果是线程池你如何保证你的数据包的前后顺序问题?例如一个游戏服务器, 数据包的先后顺序是很重要的, 如果有很多在线用户, 线程处理顺序不能保证, 那么底层还要处理很多数据包顺序问题, 消耗又回来了。
LiquidX 2009-03-06
  • 打赏
  • 举报
回复
class Mutex{...}
class Lock{...}
class ScopeLock{...}

void work_for_client( int client_fd){
ScopeLock( mutexs[ client_fd]);
//now, work for client_fd, it is locked.
}


呵呵 不好意思, 还是有点没搞懂, 还是先谢谢zhuxueling老兄。
还是想问问, 你上面的那段代码主要是使用一个 区域锁, 在work_for_client内导致其他关于client_fd
的线程处于睡眠状态, 但是当work_for_client工作域离开之后, 后面处于睡眠的所有同一个client_fd线程
都会醒来, 那么这个时候哪个线程能够抢到运行权,哪个线程就会发出一个包, 这个地方还是会产生一个无序
的发送, 例如 我有3个线程, 他们如果顺序发的话应该是 发 1, 2, 3到客户端, 按照锁的方式, 线程1锁住了, 并发往客户端一个1, 2和3处于等待, 可能不再一个核心上, 有可能3先抢到执行权, 2还在等, 那么客户端收到的是 1,3,2, 反过来收数据包也是一样的。

如果游戏中是在释放一个技能, 明明是先走后放技能, 现在反过来就会导致违法。



多线程还不是很精通, 请教了。
LiquidX 2009-03-05
  • 打赏
  • 举报
回复
把Server套接字中的ET去掉,这里不该用ET的。

确实解决了我的问题, 谢谢 再问下, 这个结构应该如何优化?

网上查了一下, accept可以单独一个线程来进行, 然后添加到epoll事件里, 还说可以用select混合, 不知道该如何是好。
zhuxueling 2009-03-05
  • 打赏
  • 举报
回复
这个很麻烦。。
我以前用了C++,现在后悔了,全改为C了。
过度的封装,其实带来的是极多的麻烦。。
zhuxueling 2009-03-05
  • 打赏
  • 举报
回复
每次看到Singleton我就超想笑。
zhuxueling 2009-03-05
  • 打赏
  • 举报
回复
其实你这个程序不是完全异步,还是有阻塞的。
accept是个非常麻烦的函数。。 如果server套接字noblock,那将很麻烦。
server套接字触发后,也许一个也不能accept成功,也许能不止一个accept成功。

就算是block也是麻烦的事。。

还有,你的epoll循环里做了太多的事,应该尽量少做,把事件分发到其它线程池里去处理这些事件。
比如:

int occur_size;
do{
occur_size = epoll_wait( ep_fd, events, ev_size, -1) > 0);
for( int itr = 0; i <occur_size; ++i){
thread_poll_push( events[i]);
}
}while( occur_size > 0);
从epoll_wait返回后,要尽快再投入wait中。
zhuxueling 2009-03-05
  • 打赏
  • 举报
回复
把Server套接字中的ET去掉,这里不该用ET的。
  • 打赏
  • 举报
回复
好乱啊,up下
LiquidX 2009-03-04
  • 打赏
  • 举报
回复
晕 我这里排版好好的 怎么贴上来这么乱

startEpoll // 是开始epoll接口, 这是截取的一小段, 程序初始化部分都没问题.


receiveSocketData( int socketID ) // 包接收处理

23,120

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 应用程序开发区
社区管理员
  • 应用程序开发区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧