- muduo的线程模型为one loop per thread+thread pool模型,在前面一篇文章的末尾曾简单的提起过:javascript:void(0)
- 本节以一个Sudoku Solver(数独求解)例子为例,回顾了并发网络服务程序的多种设计方案,并介绍了使用muduo网络库编写多线程服务器的两种最常用手法
- 在后面“muduo编程示例”相关文章会展现muduo在编写单线程并发网络服务程序方面的能力与便捷性。本文先看一看它在多线程方面的表现。本节代码参见:https://github.com/dongyusheng/csdn-code/tree/master/muduo/examples/sudoku
- 假设有这么一个网络编程任务:写一个求解数独的程序(Sudoku Solver),并把它做成一个网络服务
-
Sudoku Solver在很多文章中出现过,例如:
- “分布式系统部署、监控与进程管理的几重境界”
- “muduo Buffer类的设 计与使用”
- “‘多线程服务器的适用场合’例释与答疑”:javascript:void(0)
- 它也可以看成是echo服务的一个变种(附录A“谈一谈网络编程学习经验”把echo列为三大TCP网络编程案例之一)
- 写这么一个程序在网络编程方面的难度不高,跟写echo服务差不多(从网络连接读入一个Sudoku题目,算出答案,再发回给客户),挑战在于怎样做才能发挥现在多核硬件的能力
- 在谈这个问题之前,让我们先写一个基本的单线程版
协议设计
- 一个简单的以\r\n分隔的文本行协议,使用TCP长连接,客户端在不需要服务时主动断开连接
- 其中[id:]表示可选的id,用于区分先后的请求,以支持Parallel Pipelining,响应中会回显请求中的id
- Parallel Pipelining的意义可以参阅:javascript:void(0),或者javascript:void(0)第54页关于out-of-order RPC的介绍
- <81digits>是Sudoku的棋盘,9×9个数字,从左上角到右下角按行扫描,未知数字以0表示。如果Sudoku有解,那么响应是填满数字的棋盘;如果无解,则返回NoSolution
- 实例1:
- 实例2:
- 实例3:
- 基于这个文本协议,我们可以用telnet模拟客户端来测试Sudoku Solver,不需要单独编写Sudoku Client。Sudoku Solver的默认端口号是9981(因为它有9×9=81个格子)
Sudoku Solver程序实现
- Sudoku的求解算法见《谈谈数独(Sudoku)》一文(javascript:void(0)),这不是本文的重点
- 假设我们已经有一个函数能求解Sudoku,它的原型如下:
- 参数为上面提到的的“<81digits>”
- 返回值是“<81digits>”或“NoSolution”
- 这个函数是个pure function,同时也是线程安全的
string sloveSudoku(const string& puzzle);
- 有了这个函数,我们以前面提到的“echo服务的实现”(javascript:void(0))中出现的EchoServer为蓝本,稍加修改就能得到SudokuServer
- 这里只列出最关键的onMessage()函数,完整的代码见https://github.com/dongyusheng/csdn-code/blob/master/muduo/examples/sudoku/server_basic.cc
//onMessage()的主要功能是处理协议格式,并调用solveSudoku()求解问题。这个函数应该能正确处理TCP分包 void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) { LOG_DEBUG << conn->name(); size_t len = buf->readableBytes(); while (len >= kCells + 2) { const char* crlf = buf->findCRLF(); if (crlf) { string request(buf->peek(), crlf); buf->retrieveUntil(crlf + 2); len = buf->readableBytes(); if (!processRequest(conn, request)) { conn->send("Bad Request!\r\n"); conn->shutdown(); break; } } else if (len > 100) // id + ":" + kCells + "\r\n" { conn->send("Id too long!\r\n"); conn->shutdown(); break; } else { break; } } }