文章目录
  1. 1. 小结

我最初实现的Accept函数是通过accept()函数获取socket描述符,再根据socket描述符构建出一个智能指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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函数的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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_servertest_client拿了出来编译,发现unp_client能连接上test_server,但是test_client却连接不上unp_server。可以确定下来是unp_server的问题。
unp_server当中,我使用

1
cout <<"++++++++++++++++++++++++++++" << endl;

打印出一行断点,发现最后程序就是执行到了这里而执行不下去了,基本可以断定是Accept()函数的问题。

那么显而易见,accept()函数是不会有问题的,就剩下智能指针的问题了。那么我提出了一个猜想:

是不是在unp_server的这段代码:

1
2
3
4
5
6
7
8
9
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函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
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*

1
2
3
4
5
6
7
8
9
10
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:

1
2
//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型:

1
2
3
4
5
6
7
8
9
10
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能力,也就是分析问题的能力。

文章目录
  1. 1. 小结