东阳的学习笔记
POSIX分配文件描述符的方式容易串话
因为 POSIX 标准要求每次打开新文件(含socket
)的时候,必须使用当前最小可用的文件描述符号码。这样可能导致串话。
多线程程序如何避免串话
在单线程程序中,可以通过某种全局表来避免串话。但是这种方法显然不适合多线程场景!
在C++中,要解决串话问题,很简单:RAII
。只需要将文件描述符对象包装在 Socket 对象中。在析构函数里关闭文件描述符即可。
服务器程序中不应该关闭标准输出和标准错误
因为有些第三方库会在紧急情况下往 stdout 和 stderr 打印错误信息。若将其关闭,若这两个文件描述符被网络链接占用,那么他们可能会收到莫名其妙的信息。
正确的做法是将其重定向到文件而不是关闭。
对象生命期管理(使用shared_ptr来管理TcpConnection)
手动delete可能是危险的!比如空悬指针等,
特别是在网络编程环境下,对象的生命期不可预料
- 现代C++的一个特点是对象生命期管理的进步,体现在不需要手工delete对象
- 在网络编程中:
- ****有的对象是长命的(例如TcpServer):****长命的对象的生命期往往和整个程序一样长,那就很容易处理,直接使用全局对象(或scoped_ptr) 或者做成main()的栈上对象都行
- 有的对象是短命的(例如TcpConnection):
- **对于短命的对象,其生命期不一定完全由我们控制,**比如对方客户端断开了某个TCP socket,它对应的服务端进程中的TcpConnection对象(其必然是个heap对象,不可能是stack对象)的生命也即将走到尽头
- 但是这时我们并不能立刻delete这个对象,因为其他地方可能还持有它的引用,贸然delete会造成空悬指针。 只有确保其他地方没有持有该对象的引用的时候,才能安全地销毁对象,这自然会用到
引用计数
- 在多线程程序中,安全地销毁对象不是一件轻而易举的事情,见前面的“线程安全的对象生命期管理”相关文章
- 在非阻塞网络编程中,我们常常要面临这样一种场景:
- 从某个TCP 连接A收到了一个request,程序开始处理这个request;处理可能要花一 定的时间,为了避免耽误(阻塞)处理其他request,程序记住了发来request的TCP连接,在某个线程池中处理这个请求。在处理完之后,会把response发回TCP连接A
- 但是,在处理request的过程中,客户端断开了TCP连接A,而另一个客户端刚好创建了新连接B。我们的程序不能只记住TCP连接A的文件描述符,而应该持有封装socket连接的TcpConnection对象,保证在处理request期间TCP连接A的文件描述符不会被关闭。或者持有TcpConnection对象的弱引用(weak_ptr),这样能知道socket连接在处理request期间是否已经关闭了,fd=8的文件描述符到底是“前世”还是“今生”。
- 否则的话,旧的TCP连接A一断开,TcpConnection对象销毁,关闭了旧的文件描述符(RAII),而且新连接B的socket文件描述符有可能等于之前断开的TCP连接(这是完全可能的,POSIX要求每次新建文件描述符时选取当前最小的可用的整数)。当程序处理完旧连接的request时,就有可能把response发给新的TCP连接B,造成串话
- 为了应对这种情况,防止访问失效的对象或者发生网络串话, muduo使用shared_ptr来管理TcpConnection的生命期。
这是唯一一个采用引用计数方式管理生命期的对象
。如果不用shared_ptr,我想不出其他安全且高效的办法来管理多线程网络服务端程序中的并发连接