上一篇文章《TCP/IP协议分析》讲述了自己是如何和网络领域的开发扯上关系的。正如从招聘网站上抽出的几个关键词“TCP/IP, Socket, 多线程”可见,协议分析并不是网络开发的主流,通常我们所说的网络编程是socket编程。今天就以我的经历来讲一下socket编程的相关知识。


socket编程的基础知识我就不在这里科普了,大家可以通过相关书籍和资料去了解。

socket编程难吗

在招聘工程师时面试问到某些新人这个问题。他们以为会编写客户端/服务端建立连接正常收发数据的流程就认为自己了解socket开发。真是这样简单调用几个API,理解下三次握手,会使用bind、listen、......就可以了吗?

当然不是,随着客户端数量的增加,少量并发——>大量并发——>海量并发的过程中服务端的处理难度也会随之增加。在服务端编程中非常有名的C10K问题(网络服务在处理数以万计的客户端连接时,往往出现效率低下甚至完全瘫痪)就向我们做了很详细的说明。但是客户端socket编程虽然没有这种并发要求就一定简单吗?我认为不是的。请参看我的这篇文章《客户端网络库实现真的很简单吗》。另外互联网中各种复杂的网络环境也会给我们进行socket编程带来很多困难和挑战。

所以总体来说想要做好socket编程还是有一定难度的。大家很容易从各种招聘渠道了解到精通这个领域的人在市场上很热销,报酬也很可观,而且多为一些互联网巨头和新兴互联网公司所需要。

服务端的网络模型

要想处理我们上面所说的C10K甚至C100K问题,网络模型的选择是非常重要的。那么我们首先要清楚几个概念:阻塞I/O,非阻塞I/O,I/O复用,异步I/O 。网络上关于这个话题的讨论有很多,我说说我的理解吧:

  • 阻塞/非阻塞:进程/线程要访问的数据是否就绪,进程/线程是否需要睡眠等待。

  • 同步/异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞。异步只需要得到I/O操作完成的通知,并不主动读写数据,而是由操作系统去完成数据的读写。

所以可以看出阻塞I/O,非阻塞I/O,I/O复用都属于同步的范畴。

常见的几种服务端网络模型:

  • all connections one thread+blocking I/O:服务端对所有客户端连接只用一个线程去处理。而且网络I/O还是阻塞的。这在多并发情况下就是找死,吞吐率会很低,延迟会非常大。

  • per connection per thread+blocking I/O:服务端线程资源是有限的,而且线程的上下文切换是很耗资源的,随着客户端并发连接的增多系统资源也会慢慢吃紧。但是在这种模型的基础上,有些语言内置的协程(用户级线程)却可以解决资源问题。比如Go routine, Erlang actor。前几天刚发现了一个基于C语言的协程库state thread也可以用来实现用户级线程。

  • thread pool+blocking I/O:线程池的方式依然会受制于服务器CPU个数,自然也不会高效的。

  • non-blocking I/O + I/O multiplexing:网络I/O的多路复用,正是解决C10K问题最基础的方案。但是还可以进一步优化吗?

  • non-blocking I/O + I/O multiplexing + asyc I/O:非常理想、高效的网络模型。windows下是利用完成端口来帮我们实现这个模型,linux下要依赖异步I/O机制,但是究竟是否好用我没尝试过。

从上面又引出了两种高性能的服务端网络编程设计模式:基于I/O事件驱动的Reactor和基于异步I/O驱动的Proactor。其中non-blocking I/O + I/O multiplexing属于Reactor模式。non-blocking I/O + I/O multiplexing + asyc I/O属于Proactor模式。

重点介绍下non-blocking I/O + I/O multiplexing

今天我们所讲的只基于linux平台。毕竟linux平台的服务器占据了市场份额的90%以上。linux下目前最成熟的模型是epoll。(说到这给大家提个问题:epoll和它的老前辈select有什么不同呢?)

刚才讲到non-blocking I/O + I/O multiplexing时,提到过在此模型的基础上我们还可以优化吗?可以的。我们可以采用一个线程对应一个事件循环的机制启用多个线程。毕竟现在的服务器硬件资源是很强大的,我们不要浪费了多cpu资源。这就引出了我们要讲的另一个概念:master—worker模型。字面上的理解就是有一个master负责协调工作,由好几个worker去实际完成工作。在这种编程模型中:也就是有一个master负责将客户端连接分发给不同的worker线程,或者通知连接来了由worker去抢占,实际上由worker去完成客户端连接的读写或逻辑处理工作。这样不但有效利用了服务器的CPU资源,也增加了服务端的吞吐量,降低了客户端的延迟。更详细的讲解请参见我的文章《从master-worker模型看团队管理》 。

一些知名的第三方网络库

站在巨人的肩膀上可以让我们看得更为高远,如果选用了合适的第三方网络库也会使我们的工作事半功倍。

  • ACE学之者生用之者死。这是陈硕老师对ACE库的评价,我觉得很形象。对于学院派理想化的东西去学习收益很大,但是使用就不见得适合自己。

  • boost asio:has a “near STL” statusstackoverflow;

  • Poco: 很全面的库不仅仅有网络库。

  • libev :速度更快,bug更少,特性更多,体积更小;

  • libevent:简单,强大;

网络协议选则

网络编程中还有很重要的一点是客户端/服务端交互数据协议的选择。我们选择的依据有哪些呢?

  1. 网络数据大小——占用带宽,传输效率;

  2. 网络数据安全性——敏感数据的网络安全;

  3. 编码复杂度——序列化和反序列化复杂度,效率,数据结构的可扩展性,可 维护性;

  4. 协议通用性——大众规范;

  • 我们可以以自定义的角度采用TLV结构的二进制协议

  • 可以以提供序列化和反序列化库的的角度采用第三方协议:protocol buffers, json, thrift

  • 可以以选择文本化协议的角度选择xml,json协议。

我的理解是什么呢?请参见文章《网络传输数据格式的选择》

实践中你还常常会遇到

关键词MTU、SO_LINGER、TCPNODELAY、TIMEWAIT、keepalive、串话...这些关键词都代表了某种你需要考虑和处理的网络情况。

辅助工具python、netcat、tcpdump、wireshark...它们都会成为使你事半功倍的巨人。

推荐书籍:

《UNIX网络编程卷1》
《Linux多线程服务端编程》
《构建高性能Web站点》

参考资料

《UNIX网络编程卷1》
《Linux多线程服务端编程》
http://stackoverflow.com/questions/992069/ace-vs-boost-vs-pocohttp://stackoverflow.com/questions/9433864/whats-the-difference-between-libev-and-libevent

我的github项目

高性能tcp网络服务器

基于TCP协议的远程过程调用框架客户端实现