在编写服务器应用程序时,有时会有这样一种应用场景:后端的业务数据及业务逻辑相同,但希望给前端应用提供的功能范围及协议方式有些差别。如:
场景一:希望来自于外网的客户端以只读权限访问后端数据,同时希望来自于内网的客户端可以以读/写方式访问后端数据;
场景二:希望某个网段的客户端以 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,内容如下:
// 当客户端连接流有数据可读/出现异常时的回调函数 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() 取出设置的对象,通过对对象的功能判断进行业务功能分流。
当然,还有一点不要忘记,还得需要修改该服务器的配置文件,将 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 时,通过调用子类实例的虚方法来达到协议分流的目的。如下面的例子:
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/
github 地址:https://github.com/acl-dev/acl
QQ 群:242722074
使用 acl::master_threads 类编写多进程多线程服务器程序
相关推荐
这个可执行文件通常是用C#或其他支持.NET框架的语言编写的,其主要职责是处理客户端请求,接收来自Unity客户端的视频流数据,并将这些数据通过网络发送到Web浏览器。 运行"webserver.exe"的过程通常包括以下几个...
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终将简单易用的接口和性能高效、功能稳定的系统提供给用户。...
通过这些面试题,我们可以看出作为一名互联网Java工程师需要掌握的知识非常广泛,涵盖从基础的Java编程到MyBatis的ORM框架使用,再到分布式服务架构ZooKeeper的深入理解和Dubbo分布式服务框架的设计和使用,甚至还...
根据给出的文件信息,我们可以了解到Java互联网工程师面试题包含了多个技术栈的知识点,下面我会详细地介绍这些技术栈的面试题内容以及知识点。 首先是MyBatis相关的面试题,MyBatis是一个优秀的持久层框架,它支持...
12. 数据同步:确保多个ZooKeeper节点之间的数据一致性。 13. 事务的顺序一致性:ZooKeeper保证分布式事务的顺序性。 14. ZooKeeper的部署模式:单机模式、伪集群模式和集群模式。 15. ZooKeeper集群支持动态添加...
0011 试图使用不正确的格式加载程序。 0012 访问代码无效。 0013 数据无效。 0014 可用的存储区不足,无法完成该操作。 0015 系统找不到指定的驱动器。 0016 无法删除该目录。 0017 系统...
Phalcon是一个全功能的C语言编写的PHP框架,它采用了很多现代web应用程序开发的最佳实践。Phalcon的特性涵盖了从简单到复杂的多种需求,包括但不限于模型视图控制器(MVC)架构、依赖注入、路由、视图、模板引擎、...
- **二级缓存**:SqlSessionFactory级别的缓存,多个SqlSession共享。 #### 24、什么是MyBatis的接口绑定?有哪些实现方式? - **接口绑定**:允许我们将接口和XML映射文件绑定在一起,使得接口方法可以直接调用...
14. **传递多个参数**:可以使用`@Param`注解、Map对象或者Java Bean作为参数传递。 15. **Mybatis动态SQL**:动态SQL允许根据不同的条件生成不同的SQL语句,包括`<if>`, `<choose>`, `<where>`, `<set>`等标签。 ...
ChatAgentJADE是一个基于JADE(Just Another DECENTralized Object)框架的聊天代理系统,主要使用Java语言进行开发。JADE是为实现多代理系统(Multi-Agent System, MAS)而设计的一个开源平台,它提供了分布式环境...
1. **使用Dubbo原因**:Dubbo是一个高性能、轻量级的Java RPC框架,主要用于快速构建分布式应用。 2. **整体架构设计**:Dubbo的架构分为服务提供者、注册中心、服务消费者等层级。 3. **通信框架**:默认使用...
- ZooKeeper采用主从复制(Paxos-like)的架构,由一个或多个服务器节点组成集群。 - 集群中的每个节点都保存整个数据树的状态,通过选举算法确定一个领导者节点,负责处理所有写操作。 3. **基本概念** - **...
从提供的文件内容来看,这份文档是一份为准备互联网Java工程师面试的题集,涉及了多个Java领域的技术栈,包括但不限于MyBatis、ZooKeeper、Dubbo等。以下是按照文件内容整理出的知识点: ### MyBatis知识点 1. **...
6. **多线程与并发**:处理多个客户端连接时,可能会用到多线程或多进程模型,以实现并行处理。 7. **心跳与保活机制**:维持客户端的连接状态,检测并处理死连接。 通过分析这个源码,你可以了解MQTT协议的实现...
Spring Security 的前身是 Acegi Security,在被纳入 Spring 项目之后,它经历了多个版本的迭代。Spring Security 3.0 是一个重要的里程碑,它引入了许多新特性,并且对原有的架构进行了优化。 **1.3 发行版本号** ...
- 配置Jetty服务器使用预认证的Realm。 - **示例代码**: 配置Jetty的Realm。 - **19.2 配置Spring Security** - 配置Spring Security支持预认证。 - **示例代码**: 在`spring-security.xml`中配置预认证。 **...
例如,后端可能使用Node.js、Python或其他服务端语言,而前端可能使用React、Vue或Angular等框架,利用它们提供的数据绑定和组件化特性来简化开发。 总结起来,`wdTree`是一个结合了权限赋值和树形列表的工具或组件...
- **配置服务器使用双向加密:** 配置服务器支持双向SSL/TLS加密。 - **配置X509认证:** 配置Spring Security支持X509证书认证。 - **第24章:使用NTLM登录(无法成功登录)** - **配置NTLM认证:** 尝试配置...