socket连接池SocketPool分析(五):Accept函数,进退维谷的困境
来源:互联网 发布:烟草国标网络案件标准 编辑:程序博客网 时间:2024/05/10 21:06
从我的个人博客http://xiongjun.info/2015/12/08/socket5/转过来的。
我最初实现的Accept函数是通过accept()
函数获取socket描述符,再根据socket描述符构建出一个智能指针。
SocketObjPtr SocketObj::Accept() { if (sockFD_ == -1) { // 未经任何初始化的shared_ptr就指向一个NULL,这是一个magic,因为不能直接返回NULL SocketObjPtr tPtr; return tPtr; } struct sockaddr_in sAddr; socklen_t length = sizeof(sAddr); int customFD = accept(sockFD_, (struct sockaddr*)&sAddr, &length); if (customFD == -1) { SocketObjPtr tPtr; return tPtr; } SocketObjPtr tPtr(new SocketObj(customFD)); return tPtr;}
而在我最初的测试代码unp_server
当中是这样子来调用Accept函数的:
while (true) { ret = epoll_wait(efd, ev, EPOLLEVENTS, -1); int ev_fd; for (int i=0; i<ret; ++i) { ev_fd = ev[i].data.fd; //说明有读入,这里要判断一下是从STDIN_FILENO读入还是从socket读入 if (ev_fd == listenfd) { SocketObjPtr sockPtr = listener.Accept(); int sockfd = sockPtr->Get(); //sockPtr->Get()得到socket描述符 tmp.events = EPOLLIN; tmp.data.fd = sockfd; //注册一个事件 epoll_ctl(efd, EPOLL_CTL_ADD, sockfd, &tmp); cout <<"++++++++++++++++++++++++++++" << endl; } else if (ev[i].events & EPOLLIN) { str_echo(ev_fd); } } }
编译之后,先启动unp_server
没问题,那么启动unp_client
之后产生什么结果了呢?
打印出无数的“read error”
我在《socket连接池SocketPool分析(三):《UNPv1》复习(下):IO多路复用》中提到了这个问题:千万不要忘记使用epoll_ctl删除不需要的事件。而我在我的unp_client
上就犯下了这个错误,当我好不容易改正这个错误的时候。才发现了Accept的问题:
unp_client
虽然能够连接上unp_server
,但是却无法发送信息给unp_server
。我把之前自己写的一个非常简单的test_server
和test_client
拿了出来编译,发现unp_client
能连接上test_server
,但是test_client
却连接不上unp_server
。可以确定下来是unp_server
的问题。
在unp_server
当中,我使用
cout <<"++++++++++++++++++++++++++++" << endl;
打印出一行断点,发现最后程序就是执行到了这里而执行不下去了,基本可以断定是Accept()
函数的问题。
那么显而易见,accept()
函数是不会有问题的,就剩下智能指针的问题了。那么我提出了一个猜想:
是不是在unp_server
的这段代码:
if (ev_fd == listenfd) { SocketObjPtr sockPtr = listener.Accept(); int sockfd = sockPtr->Get(); //sockPtr->Get()得到socket描述符 tmp.events = EPOLLIN; tmp.data.fd = sockfd; //注册一个事件 epoll_ctl(efd, EPOLL_CTL_ADD, sockfd, &tmp); cout <<"++++++++++++++++++++++++++++" << endl; }
当中由于使用了智能指针,导致if
作用域结束之后会自动delete掉sockPtr
?
那么我在Accept()
函数当中使用new SocketObj(customFD)
创建的SocketObj对象被析构了,析构函数启动了close()
函数,把这个连接关掉了,从而导致客户端不能发送信息?
为了验证我的这个猜想,我先把智能指针替换成了普通指针:
新的Accept函数:
SocketObj* SocketObj::Accept() { if (sockFD_ == -1) { return NULL; } struct sockaddr_in sAddr; socklen_t length = sizeof(sAddr); int customFD = accept(sockFD_, (struct sockaddr*)&sAddr, &length); if (customFD == -1) { return NULL; } SocketObj* tPtr(new SocketObj(customFD)); return tPtr;}
那么,我在unp_server
当中的代码就要加上delete
了,当然了还要把sockPtr改为SocketPtr*
if (ev_fd == listenfd) { SocketObj* sockPtr = listener.Accept(); int sockfd = sockPtr->Get(); //sockPtr->Get()得到socket描述符 tmp.events = EPOLLIN; tmp.data.fd = sockfd; //注册一个事件 epoll_ctl(efd, EPOLL_CTL_ADD, sockfd, &tmp); cout <<"++++++++++++++++++++++++++++" << endl; delete sockPtr; }
这样一来的话,代码就和使用智能指针的效果是一样的,那么编译之后也就应该产生一样的错误。
编译之后果然产生了一样的错误。
接下来,把unp_server
当中的delete
去掉:
编译再运行,问题果然解决了!
但是这就陷入了一个进退维谷的困境:
如果delete
的话,程序会失败。
如果不delete
的话,会内存泄露。
当然我也尝试过其他的方法。
尝试的方法是把返回的SocketObjPtr变为static:
//SocketObjPtr tPtr(new SocketObj(customFD));static SocketObjPtr tPtr(new SocketObj(customFD));
还是用智能指针来管理,但是指针将放在static
的静态区域,将不在if
的作用域当中,那么就算if
结束了,也不会析构,而且是在程序结束后自动析构。
看起来一箭双雕,两个问题都完美解决了,但是我万万没想到又引入了新的问题—-并发性。
这个服务器将永远只能同时连接一台客户端。
道理其实很简单,static
是静态变量,整个程序只有一个。那么当第二个客户端连接请求到来时,服务器理所应当的调用Accept
函数的时候,遇到了static SocketObjPtr tPtr(new SocketObj(customFD));
的这段代码当然会执行失败。
这是第二个进退维谷的困境了:
使用static
,将毫无并发性
不使用static
,程序会失败。
思前想后,还是退一步,直接返回int型:
int SocketObj::Accept() { if (sockFD_ == -1) { strErrorMessage_ = "can't accept, because sockFD_ = -1"; return -1; } struct sockaddr_in sAddr; socklen_t length = sizeof(sAddr); int customFD = accept(sockFD_, (struct sockaddr*)&sAddr, &length); return customFD;}
小结
这样兜兜转转了一大圈,又回到了仅仅对accept()
函数做一个简单的封装的程序上,好像做了大量的无用功,但是通过这个问题,提升了我自己的debug能力,也就是分析问题的能力。
- socket连接池SocketPool分析(五):Accept函数,进退维谷的困境
- socket连接池SocketPool分析(六):有意思的socket
- socket连接池SocketPool分析(一):概述
- socket连接池SocketPool分析(十):libevent
- socket连接池SocketPool分析(四):socket连接控制SocketObj
- socket连接池SocketPool分析(二):《UNPv1》复习(上):socket函数,三次握手,四次挥手
- socket连接池SocketPool分析(八):并发服务器
- socket连接池SocketPool分析(九):C10k problem
- socket连接池SocketPool分析(七):server连接池与client连接池
- socket连接池SocketPool分析(三):《UNPv1》复习(下):IO多路复用
- socket的accept函数
- Socket的accept函数解析
- socket的accept函数解析
- Socket的accept函数解析
- socket的accept函数解析
- socket的accept函数解析
- socket的accept函数解析
- socket listen和accept函数分析
- socket连接池SocketPool分析(四):socket连接控制SocketObj
- java实现梯度下降算法
- 11、osg中的粒子系统所用到的类以及相关函数
- BF算法(java版本)
- Linux下头文件搜索路径
- socket连接池SocketPool分析(五):Accept函数,进退维谷的困境
- 第13周项目2-Kruskal算法的验证
- [Coursera]算法基础_Week2_枚举_Q1
- 三大框架整合原理及详解<一>
- EBS TABLE LIST
- 链表概述
- linker command failed with exit code 1 (use -v to see invocation)
- iOS UI 01 课堂笔记 -设计模式
- socket连接池SocketPool分析(六):有意思的socket