在编写服务器应用程序时,有时会有这样一种应用场景:后端的业务数据及业务逻辑相同,但但愿给前端应用提供的功能范围及协议方式有些差异。如:前端
场景一:但愿来自于外网的客户端以只读权限访问后端数据,同时但愿来自于内网的客户端能够以读/写方式访问后端数据;后端
场景二:但愿某个网段的客户端以 HTTP 协议访问后端业务,同时但愿某个网段的客户端以私有协议方式访问后端业务。服务器
为了处理上面的应用场景,固然能够写多个服务器程序,每一个服务器程序处理不一样的协议格式和权限范围,但这势必会形成不少冗余代码,增长额外的工做量及出错可能性。acl 的服务器框架模型容许一个服务器进程同时监听多个地址,利用这一点即可以轻松解决上面的应用场景问题,同时大大减小了程序工做量及维护成本。多线程
下面以一个简单的例子,说明如何使用这一特性来处理不一样的协议过程。框架
为了简单起见,本例子使用了 使用 acl 生成向导快速建立服务器程序 文章中介绍的服务器生成向导过程来生成一个简单的 DEMO(假设让该服务器程序监听:127.0.0.1:8088 和 192.168.166.162:8080 两个地址)。假设由服务器生成向导程序生成了服务器模板类型为 master_threads (线程池模型)的程序 echo_server。而后在 echo_server 程序目录下打开 master_service.cpp 源程序,修改 函数 master_service::thread_on_accept ,master_service::thread_on_close 及 master_service::thread_on_read,内容以下:socket
// 当客户端链接流有数据可读/出现异常时的回调函数 bool master_service::thread_on_read(acl::socket_stream* conn) { // 得到客户端链接本地的哪一个监听服务地址,其中 get_local 的参数为 true 表示要求得到 // ip:port 格式的全地址 const char* local_addr = conn->get_local(true); const char* str = (const char*) conn->get_ctx(); logger("connection from local %s on read fd %d, info: %s", local_addr, conn->sock_handle(), str); acl::string buf; // 从客户端读取一行数据 if (conn->gets(buf, false) == false) { logger("get error from client %s, local addr: %s", conn->get_peer(true), local_addr); return false; // 返回 false 通知服务器框架将链接关闭 } // 回写数据 if (conn->write(buf) == -1) { logger("write to client %s error, local addr: %s", conn->get_peer(true), local_addr); return false; } // 返回 true 通知服务器框架继续监控该客户端链接流 return true; } // 当接收到一个客户端链接时的回调函数 bool master_service::thread_on_accept(acl::socket_stream* conn) { // 得到客户端链接本地的哪一个监听服务地址,其中 get_local 的参数为 true 表示要求得到 // ip:port 格式的全地址 const char* local_addr = conn->get_local(true); logger("connect from local addr: %s", local_addr); // 在此处能够根据 local_addr 的不一样来区分不一样的链接请求: if (strcmp(local_addr, "127.0.0.1:8088") == 0) { const char* str = strdup("from 127.0.0.1:8088"); conn->set_ctx(str); } else if (strcmp(local_addr, "192.168.166.162:8080" == 0) { const char* str = strdup("from 192.168.166.162:8080"); conn->set_ctx(str); } else { const char* str = strdup("other addr"); conn->set_ctx(str); } // 设置客户端链接流的读写超时时间(秒) conn->set_rw_timeout(10); return true; } // 当客户端链接关闭前调用的回调函数 void master_service::thread_on_close(acl::socket_stream* conn) { // 释放由 master_service::thread_on_accept 中分配的内存对象 char* str = (char*) conn->get_ctx(); if (str) free(str); }
上面代码逻辑很简单地演示了 acl 服务器框架支持监听多个地址的用处。为了支持不一样的业务功能分流,应用能够在 thread_on_accept 阶段经过 socket_stream::set_ctx(void*) 设置不一样的功能对象,在 thread_on_read 阶段经过 socket_stream::get_ctx() 取出设置的对象,经过对对象的功能判断进行业务功能分流。svn
固然,还有一点不要忘记,还得须要修改该服务器的配置文件,将 master_service 的监听地址改为多个地址,如:127.0.0.1:8088, 192.168.166.162:8080 即:master_service = 127.0.0.1:8088, 192.168.166.162:8080,同时须要将 master_type 值改成 sock,即:master_type = sock。函数
此外,为了在独立方式下测试服务器程序,能够打开 main.cpp 文件,将其中的 addr 的值设为 "127.0.0.1:8088, 192.168.166.162:8080" 便可。测试
下面写一个更加实用一点的例子,能够先设计一个虚类,里面定义一个虚方法,在接收到客户端链接 (thread_on_accept) 时,根据链接地址不一样来建立该虚类的子类实例(这些子类只需实现基类中的虚方法便可),在 thread_on_read 时,经过调用子类实例的虚方法来达到协议分流的目的。以下面的例子:spa
class base { public: base() {} virtual ~base() {} // 纯虚方法,须要子类实现 virtual bool run(acl::socket_stream* conn) = 0; }; class child1 : public base { public: child1() {} ~child1() {} protected: bool run(acl::socket_stream* conn) // 基类虚方法实现 { acl::string buf; // 读一行数据,但第二个参数为 true 表示但愿将 \r\n 自动去掉 if (conn->gets(buf, true) == false) return false; if (conn.format("child1: %s\r\n", buf.c_str()) == -1) return false; return true; } }; class child2 : public base { public: child2() {} ~child2() {} protected: bool run(acl::socket_stream* conn) // 基类虚方法实现 { acl::string buf; // 读一行数据,但第二个参数为 true 表示但愿将 \r\n 自动去掉 if (conn->gets(buf, true) == false) return false; if (conn.format("child2: %s\r\n", buf.c_str()) == -1) return false; return true; } }; ///////////////////////////////////////////////////////////////////////////////// bool master_service::thread_on_read(acl::socket_stream* conn) { // 将流中参数硬转化为 base 类对象 base* obj = (base*) conn->get_ctx(); // 调用基类中的纯虚方法,而其实是调用了子类的方法 // 从而实现了协议分流 return obj->run(conn); } bool master_service::thread_on_accept(acl::socket_stream* conn) { const char* local_addr = conn->get_local(true); if (strcmp(local_addr, "127.0.0.1:8088") = 0) { base* obj = new child1(); conn->set_ctx(obj); return true; } else if (strcmp(local_addr, "127.0.0.1:8080") == 0) { base* obj = new child2(); conn->set_ctx(obj); return true; } else return false; } void master_service::thread_on_close(acl::socket_stream* conn) { base* obj = (base*) conn->get_ctx(); if (obj) delete obj; }
参考:
acl 库下载:https://sourceforge.net/projects/acl/
svn: svn://svn.code.sf.net/p/acl/code/
QQ 群:242722074