socket连接池SocketPool分析(五):Accept函数,进退维谷的困境
我最初实现的Accept函数是通过accept()
函数获取socket描述符,再根据socket描述符构建出一个智能指针。
1 | SocketObjPtr SocketObj::Accept() { |
而在我最初的测试代码unp_server
当中是这样子来调用Accept函数的:
1 | while (true) { |
编译之后,先启动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
当中,我使用
1 | cout <<"++++++++++++++++++++++++++++" << endl; |
打印出一行断点,发现最后程序就是执行到了这里而执行不下去了,基本可以断定是Accept()
函数的问题。
那么显而易见,accept()
函数是不会有问题的,就剩下智能指针的问题了。那么我提出了一个猜想:
是不是在unp_server
的这段代码:
1 | if (ev_fd == listenfd) { |
当中由于使用了智能指针,导致if
作用域结束之后会自动delete掉sockPtr
?
那么我在Accept()
函数当中使用new SocketObj(customFD)
创建的SocketObj对象被析构了,析构函数启动了close()
函数,把这个连接关掉了,从而导致客户端不能发送信息?
为了验证我的这个猜想,我先把智能指针替换成了普通指针:
新的Accept函数:
1 | SocketObj* SocketObj::Accept() { |
那么,我在unp_server
当中的代码就要加上delete
了,当然了还要把sockPtr改为SocketPtr*
1 | if (ev_fd == listenfd) { |
这样一来的话,代码就和使用智能指针的效果是一样的,那么编译之后也就应该产生一样的错误。
编译之后果然产生了一样的错误。
接下来,把unp_server
当中的delete
去掉:
编译再运行,问题果然解决了!
但是这就陷入了一个进退维谷的困境:
如果delete
的话,程序会失败。
如果不delete
的话,会内存泄露。
当然我也尝试过其他的方法。
尝试的方法是把返回的SocketObjPtr变为static:
1 | //SocketObjPtr tPtr(new SocketObj(customFD)); |
还是用智能指针来管理,但是指针将放在static
的静态区域,将不在if
的作用域当中,那么就算if
结束了,也不会析构,而且是在程序结束后自动析构。
看起来一箭双雕,两个问题都完美解决了,但是我万万没想到又引入了新的问题—-并发性。
这个服务器将永远只能同时连接一台客户端。
道理其实很简单,static
是静态变量,整个程序只有一个。那么当第二个客户端连接请求到来时,服务器理所应当的调用Accept
函数的时候,遇到了static SocketObjPtr tPtr(new SocketObj(customFD));
的这段代码当然会执行失败。
这是第二个进退维谷的困境了:
使用static
,将毫无并发性
不使用static
,程序会失败。
思前想后,还是退一步,直接返回int型:
1 | int SocketObj::Accept() { |
小结
这样兜兜转转了一大圈,又回到了仅仅对accept()
函数做一个简单的封装的程序上,好像做了大量的无用功,但是通过这个问题,提升了我自己的debug能力,也就是分析问题的能力。