陈硕 (giantchen_AT_gmail)
Blog.csdn.net/Solstice
本文将介绍第一个示例:五个简单 TCP 网络服务协议,包括 echo (RFC 862)、discard (RFC 863)、chargen (RFC 864)、daytime (RFC 867)、time (RFC 868),以及 time 协议的客户端。各协议的功能简介如下:
discard - 丢弃所有收到的数据;
daytime - 服务端 accept 连接之后,以字符串形式发送当前时间,然后主动断开连接;
time - 服务端 accept 连接之后,以二进制形式发送当前时间(从 Epoch 到现在的秒数),然后主动断开连接;我们需要一个客户程序来把收到的时间转换为字符串。
echo - 回显服务,把收到的数据发回客户端;
chargen - 服务端 accept 连接之后,不停地发送测试数据。
剩下的都是例行公事的代码,为节省篇幅,此处从略,请阅读 muduo/examples/simple/daytime。
用 netcat 扮演客户端,运行结果如下:
$ nc 127.0.0.1 2013
2011-02-02 03:31:26.622647 # 服务器返回的时间字符串
剩下的都是例行公事的代码,为节省篇幅,此处从略,请阅读 muduo/examples/simple/time。
用 netcat 扮演客户端,并用 hexdump 来打印二进制数据,运行结果如下:
$ nc 127.0.0.1 2037 | hexdump -C
00000000 4d 48 d0 d5 |MHÐÕ|
00000004
注意其中考虑到了如果数据没有一次性收全,已经收到的数据会暂存在 Buffer 里,以等待下一次机会,程序也不会阻塞。这样即便服务器一个字节一个字节地发送数据,代码还是能正常工作,这也是非阻塞网络编程必须在用户态使用接受缓冲的主要原因。
这是我们第一次用到 TcpClient class,完整的代码如下:
程序的运行结果如下,假设 time server 运行在本机:
$ ./simple_timeclient 127.0.0.1
2011-02-02 04:10:35.181717 4296 INFO pid = 4296 - timeclient.cc:71
2011-02-02 04:10:35.183668 4296 INFO TcpClient::connect[TimeClient] - connecting to 127.0.0.1:2037 - TcpClient.cc:60
2011-02-02 04:10:35.185178 4296 INFO 127.0.0.1:40960 -> 127.0.0.1:2037 is UP - timeclient.cc:39
2011-02-02 04:10:35.185279 4296 INFO Server time = 1296619835, 2011-02-02 04:10:35.000000 - timeclient.cc:56
2011-02-02 04:10:35.185354 4296 INFO 127.0.0.1:40960 -> 127.0.0.1:2037 is DOWN - timeclient.cc:39
这段代码实现的不是行回显(line echo)服务,而是有一点数据就发送一点数据。这样可以避免客户端恶意地不发送换行字符,而服务端又必须缓存已经收到的数据,导致服务器内存暴涨。但这个程序还是有一个安全漏洞,即如果客户端故意不断发生数据,但从不接收,那么服务端的发送缓冲区会一直堆积,导致内存暴涨。解决办法可以参考下面的 chargen 协议。
剩下的都是例行公事的代码,为节省篇幅,此处从略,请阅读 muduo/examples/simple/echo。
练习 1:修改 EchoServer::onMessage(),实现大小写互换。
练习 2:修改 EchoServer::onMessage(),实现 rot13 加密。
剩下的都是例行公事的代码,为节省篇幅,此处从略,请阅读 muduo/examples/simple/chargen。
完整的 chargen 服务端还带流量统计功能,用到了定时器,我们会在下一篇文章里介绍定时器的使用,到时候再回头来看相关代码。
$ nc localhost 2019 | head
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh
"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi
#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij
$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijk
%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijkl
&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm
'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmn
()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno
)*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop
*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopq
前面五个程序都用到了 EventLoop,这其实是个 Reactor,用于注册和分发 IO 事件。Muduo 遵循 one loop per thread 模型,多个服务端(TcpServer)和客户端(TcpClient)可以共享同一个 EventLoop,也可以分配到多个 EventLoop 上以发挥多核多线程的好处。这里我们把五个服务端用同一个 EventLoop 跑起来,程序还是单线程的,功能却强大了很多:
以上几个协议的消息格式都非常简单,没有涉及 TCP 网络编程中常见的分包处理,在下一篇文章讲 Boost.Asio 的聊天服务器时我们再来讨论这个问题。
(待续)
本文转自 陈硕 博客园博客,原文链接:http://www.cnblogs.com/Solstice/archive/2011/02/02/1948839.html,如需转载请自行联系原作者