Linux网络编程实验——线程池实现服务器

  本实验是按照《Linux高性能服务器编程》15.5节实现的。原理很简单,就是利用epoll+多线程对客户请求进行处理。代码已经放在了git仓库,下面记录了几个我遇到的问题。

1.异常表现:在进行压测时,服务器会触发许多对-1文件描述符的异常关闭。
  这个问题我排查了很久,最后发现原来是因为自己不熟悉多线程编程而导致的。
  对于每一个客户连接我都使用一个实体类来表示,其中也包括这个连接的文件描述符也就是connectFd。每一次处理完之后我当然要在server端对连接进行主动关闭所以就有了下面代码中的close。下面是正确的代码形式,但在存在bug的代码中,我的connectFd = -1是写在close之后的,bug就此埋下。
  其实在我手动不断刷新请求的情况下服务器是能够正常工作的,但怪就怪在进行压测的时候原本正常的程序就会出现异常表现,从而我们可以确定:客户连接的快慢是导致bug出现的主要原因。
  最后排查出来的结果就是文件描述符的修改不应该放在close之后,因为close了文件描述符之后系统就会有可能将下一个连接的描述符取为与被关闭的描述符相同的值,而在我的设计中相同的值意味着访问了相同的实体。若客户连接过快,则新连接符就会在connectFd=-1之前,close之后执行,如此一来新连接的connectFd就会被设置为-1,也就是说前一个运行较慢的线程将后一个线程中的数据进行了修改从而产生了这样子意想不到的错误。
  知道了真正的问题所在后解决方案自然就很简单了。

void HttpConnection::process()
{
    accept_request();
    
    int tmp = connectFd;
    connectFd = -1;
    processed = false;
    //做对这个连接的最后收尾工作
    printf("thread %u:close %d\n",pthread_self() , tmp);
    if (close(tmp) != 0)
    {
        printf("HttpConnection:fd:%d errno %d:%s\n", 
            tmp, errno, strerror(errno));//我也不知道要怎么处理这种close错误,就暂时让它报个错吧
    }
}

2.异常表现:进行压测时候并发了一定数量的连接所有的工作线程便阻塞不再工作。
   这是个本质上和上一个是相同但是表现形式不同的问题。之前问题的解决让我发现会出现多个线程多次进入同一个客户连接处理流程的情况,为了避免类型情形的发生,我在实体类中添加了processed字段以表示该连接是否被其它线程处理。若是,则不会将其加入到工作队列中。可惜没有吸取上次的教训,我把processed=false又写到了close之后,出现的情况就是比较旧的连接已经被关闭,新连接迅速抢占上一个连接的fd成为下一个要被处理的任务,而新连接已经被创建,旧连接的收尾工作却还没有完成,从而使得新连接的processed被置为false而永远无法被加入到工作队列中接受处理。多次反复这种情形的出现便会使得所有工作线程因为得不到任务处理而一直处于阻塞状态等待新任务的加入,最后的结果就是系统被卡死住无法继续。

3.因为信号处理函数必须被写成可重入函数所以我用write代替printf对被处理的信号号码进行打印。但是因为用习惯了printf而将在printf上使用的习惯照搬到了write上面,所以这样的错误导致输出的东西一度误导困惑了我很久。一定要记住write一定是按照char进行输出的。

4.对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭.但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出。

总结:多线程的收尾工作务必要非常小心,一旦处理不当便会对后续操作产生非常恶劣的影响。

git仓库

你可能感兴趣的:(Linux网络编程实验——线程池实现服务器)