关于一次close socket函数bug事故引发的思考

前因后果

昨天一下午肝完了作业1-12全部部分,就剩下13了。这次作业整体上较为简单,就是写一个webserver,然后用各种IO和IPC去实现多线程,多进程以及线程和进程的通信。这完全属于是烂大街项目,所以没啥难度,但是作业还是很有趣的。

在里面需要实现,线程安全的阻塞队列,进程池,线程池,以及共享内存线程间通信和进程间互斥量通信,非常的nice。非常羡慕哥大本科生就能上这么好的课。

然后我一下午写完了1-12,就在写13的时候,发现了一个问题。下文会详述。

问题背景

这个任务需要我们去fork若干个线程处于阻塞状态,然后每个进程和父进程之间通过一个socketpair进行通信。父进程accept()新进来的fd,然后把这些fd用轮询的方式分发给子进程,然后子进程再去处理这些request。所以这里就涉及到了如何让子进程知道自己是通过哪个socketpair进行通信,这个可以通过在fork的时候做一下标记实现。

int cur[limit] = {0};
int fds[limit][2];

for(int i = 0; i < limit; i++){
  socketpair(AF_UNIX, SOCK_STREAM, 0, fds[i]);
  pid_t p = fork();
  if(p == 0){
    cur[i] = 1;
    close(fds[i][0]);
    break;
  }else{
    close(fds[i][1]);
  }
}
//父进程
while(main_pid == getpid()){
  int fd = accept(xxx);
  sendFd(fd, fds[now][0])
}

//子进程
while(main_pid != getpid()){
  int fd = accept(xxx);
  recvFd(fd, fds[now][1]);
}

然后只需要看pair里面哪个位置被为1就能判断出自己所在的进程。 到这里没啥问题。然后下面的任务是需要用sendmsg()和recvmsg()发送和接收被接收的fd。然后我就通过main_pid进行标识判断主进程和非主进程,分别处理。

那么对于主进程就是发送fd,子进程只要一直抢占式接收fd就行了,我原来以为到这一步就完了。

然后通过外界工具访问,发现程序在进行调用之后产生了永久的阻塞。我以为是哪个地方写崩了,遂计划Ctrl + C一下,然后再来。
诡异的是,当我结束程序之后,webserver反而成功处理了请求!! 这说明webserver不是crash了,而是多半在某个地方卡住了

下意识想到是sendmsg函数和recvmsg函数阻塞的结果。但是我既然send了,按照TCP的可靠通信机制,我应该能在最短时间收到啊,为什么没有收到呢?长时间debug无果,很烦。

排查思路改为消息滞留,因为sendmsg肯定用了系统的缓冲区,那么我们怎么让他即使发送呢?

我尝试了n种方法,最后想到,如果发送信息的请求进来是一次性的,那我可以在sendmsg之前把accept的fd给close了,反正tcp有连接队列,这条就是随手写的优化。然而令人吃惊的一幕发生了:我的webserver开始正常工作了!!!

int cur[limit] = {0};
int fds[limit][2];

for(int i = 0; i < limit; i++){
  socketpair(AF_UNIX, SOCK_STREAM, 0, fds[i]);
  pid_t p = fork();
  if(p == 0){
    cur[i] = 1;
    close(fds[i][0]);
    break;
  }else{
    close(fds[i][1]);
  }
}
//父进程
while(main_pid == getpid()){
  int fd = accept(xxx);
  sendFd(fd, fds[now][0])
}

//子进程
while(main_pid != getpid()){
  int fd = accept(xxx);
  recvFd(fd, fds[now][1]);
  close(fd); //新加的一行
}

那么这是什么原理呢?原来我发现sendmsg和recvmsg和f系函数一样,都采用了缓冲区进行缓冲,那么这个缓冲区的访问就需要某种同步机制来协调。在accept的时候,缓冲区被新来的连接加锁,表示正在发送数据,这个时候sendmsg由于操作的是同一块缓冲区,那么这个时候因为有连接正在操作缓冲区所以被阻塞了,这也就解释了为什么我的webserver会被挂起而不是收到执行!

那么我们在close(fd)之后,锁释放,sendmsg抢占到了锁,就开始发送,然后recvmsg顺利收到信息,开始服务。

总结下来自己还是经验不够,我后来发现java的很多IO系函数都存在这个问题,是C++的封装太好了,使得我忽略了底层的逻辑,算是被上了一课吧。

posted @ 2023-02-08 14:20  tiany7  阅读(77)  评论(0编辑  收藏  举报