基于多反应堆的高并发服务器【C/C++/Reactor】(中)创建并初始化TcpServer实例 以及 启动

本文主要是介绍基于多反应堆的高并发服务器【C/C++/Reactor】(中)创建并初始化TcpServer实例 以及 启动,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

对于一个TcpServer来说,它的灵魂是什么?就是需要提供一个事件循环EventLop(EventLoop),不停地去检测有没有客户端的连接到达,有没有客户端给服务器发送数据,描述的这些动作,反应堆模型能够胜任。当服务器和客户端建立连接之后,剩下的就是网络通信,在通信的时候,需要把接收的数据和要发送的数据存储到一块内存里边,Buffer(Buffer)就是为此量身定制的。另外,如果服务器想和客户端实现并发操作,需要用到多线程。我们提供了线程池ThreadPool(ThreadPool),剩下的事就是把服务器模型里边的代码实现一下,基于服务器的整体流程实现一下TcpConnection

这个TcpConnection就是服务器和客户端建立连接之后,它们是在通信的时候,其实就需要用到Http,如果想要实现一个HttpServer,就需要用到Http协议,再把HttpRequestHttpResponse写出来之后,整个项目的流程就全部走通了。

TcpServer分为几个部分:

  1. 主线程需要有一个Listener(包括端口port,监听的文件描述符listenFd
  2. 主线程的事件循环MainEventLoop(反应堆)
  3. 一个线程池ThreadPool
  4. TcpConnection其实是子线程的任务。服务器与客户端建立连接之后,子线程要是想工作的话,就必须创建一个TcpConnection实例。如果没有这个TcpConnection模块,子线程就不知道在建立连接之后需要做什么事情
struct Listener {int lfd;unsigned short port;
};struct TcpServer {struct Listener* listener; // 监听套接字struct EventLoop* mainLoop; // 主线程的事件循环(反应堆模型)struct ThreadPool* threadPool; // 线程池int threadNum; // 线程数量
};

一、创建并初始化TcpServer实例

(1)初始化监听

// 初始化监听
struct Listener* listenerInit(unsigned short port);
// 初始化监听
struct Listener* listenerInit(unsigned short port) {// 创建一个Listner实例 -> listenerstruct Listener* listener = (struct Listener*)malloc(sizeof(struct Listener));// 1.创建一个监听的文件描述符 -> lfdint lfd = socket(AF_INET,SOCK_STREAM,0); // AF_INET -> (网络层协议:Ipv4) ;SOCK_STREAM -> (传输层协议:流式协议)  ;0 -> :表示使用Tcpif(lfd == -1) {perror("socket");              return -1;}   // 2.设置端口复用int opt = 1;int ret =  setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); if(ret == -1) {perror("setsockopt");return -1;}// 3.绑定struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);// 主机字节序(小端)转成网络字节序(大端) 端口的最大数量:2^16=65536addr.sin_addr.s_addr = INADDR_ANY;// 0.0.0.0 ret = bind(lfd,(struct sockaddr*)&addr,sizeof(addr));if(ret == -1) {perror("bind");return -1;}// 4.设置监听ret = listen(lfd,128);if(ret == -1) {perror("listen");return -1;}listener->lfd = lfd;listener->port = port;return listener;
}

(2)创建并初始化TcpServer实例

TcpServer结构与工作原理 

(一)服务器结构

  1. Listener: 监听特定端口,等待客户端的连接请求。主要包括端口和监听的文件描述符
  2. MainEventLoop主线程事件循环(主线程反应堆): 负责接收和处理来自客户端的请求
  3. ThreadPool线程池: 用于处理并发连接每个新连接都会有一个子线程处理
  4. TcpConnection: 每个客户端连接都会有一个对应的TcpConnection实例,用于处理该连接的通信

(二)初始化步骤

(1)创建服务器实例:申请TcpServer结构体的内存空间

(2)初始化Listener:

  • 指定服务器要绑定的本地端口(unsigned short类型)
  • 初始化监听的文件描述符

(3)初始化MainEventLoop主线程的事件循环或反应堆

(4)ThreadPool线程池初始化:根据需要的子线程数量(threadNum)初始化线程池

(5)返回值: 返回初始化完成的TcpServer的地址给调用者

// 初始化
struct TcpServer* tcpServerInit(unsigned short port,int threadNum);
// 初始化
struct TcpServer* tcpServerInit(unsigned short port,int threadNum) {struct TcpServer* tcp = (struct TcpServer*)malloc(sizeof(struct TcpServer));tcp->listener = listenerInit(port); // 创建listenertcp->mainLoop = eventLoopInit(); // 主线程的事件循环(反应堆模型)tcp->threadPool = threadPoolInit(tcp->mainLoop,threadNum); // 创建线程池tcp->threadNum = threadNum; // 线程数量return tcp;
}

二、启动TcpServer

// 启动服务器(不停检测有无客户端连接)
void tcpServerRun(struct TcpServer* server);
// 启动服务器(不停检测有无客户端连接)
void tcpServerRun(struct TcpServer* server) {// 启动线程池threadPoolRun(server->threadPool);// 初始化一个channel实例struct Channel* channel = channelInit(server->listener->lfd,ReadEvent,acceptConnection,NULL,server);// 添加检测的任务 eventLoopAddTask(server->mainLoop,channel,ADD);// 启动反应堆模型eventLoopRun(server->mainLoop);
}

>>启动服务器(不停检测有无客户端连接)

启动线程池,之后,需要让它处理任务,对于当前的TcpServer来说,是有任务可以处理的。在当前服务器启动之后,需要处理的文件描述符有且只有一个,就是用于监听的文件描述符,因此需要把待检测的文件描述符(用于监听的)添加到(mainLoop)事件循环里边。接着初始化一个channel实例,可调用eventLoopAddTask函数实现添加任务到任务队列。

  • 回顾channelInit函数:Channel模块的封装主要包括文件描述符、事件检测和回调函数。在服务器端,Channel主要用于封装文件描述符,用于监听和通信。事件检测是基于IO多路模型的,当文件描述符对应的事件被触发时,会调用相应的事件处理函数。在Channel结构中,需要指定读事件和写事件对应的回调函数。此外,还有一个data参数用于传递动态数据。
// 定义函数指针
typedef int(*handleFunc)(void* arg);// 定义文件描述符的读写事件
enum FDEvent {TimeOut = 0x01;ReadEvent = 0x02;WriteEvent = 0x04;
};struct Channel {// 文件描述符int fd;// 事件int events;// 回调函数handleFunc readCallback;// 读回调handleFunc writeCallback;// 写回调// 回调函数的参数void* arg;
};// 初始化一个Channel 
struct Channel* channelInit(int fd, int events, handleFunc readFunc, handleFunc writeFunc, void* arg);

 tcpServerRun函数中使用channelInit函数,这个channel实例主要用于封装监听文件描述符(lfd),用于监听。当lfd对应的事件被触发时,会调用相应的事件处理函数。其中它的读事件的回调函数acceptConnection函数:

// 初始化一个channel实例
struct Channel* channel = channelInit(server->listener->lfd,ReadEvent,acceptConnection,NULL,server);
int acceptConnection(void* arg) {struct TcpServer* server = (struct TcpServer*)arg;// 和客户端建立连接int cfd = accept(server->listener->lfd,NULL,NULL);if(cfd == -1) {perror("accept");return -1;}// 从线程池中去取出一个子线程的反应堆实例,去处理这个cfdstruct EventLoop* evLoop = takeWorkerEventLoop(server->mainLoop);// 将cfd放到 TcpConnection中处理tcpConnectionInit(cfd, evLoop);// ...(未完,待补充)return 0;
}
  • 回顾eventLoopAddTask函数:如果把channel放到了mainLoop的任务队列里边,任务队列在处理的时候需要知道对这个节点做什么操作,是添加到检测集合里去,还是从检测集合里删除,还是修改检测集合里的文件描述符的事件。那么对于监听的文件描述符,当然就是添加(ADD
// 添加任务到任务队列
int eventLoopAddTask(struct EventLoop* evLoop,struct Channel* channel,int type);

还有启动反应堆模型

// 启动反应堆模型
eventLoopRun(server->mainLoop);

>>TCP服务器初始化与线程池、事件循环的启动

知识点1:线程池的创建与启动:在初始化TcpServer时,线程池随之被创建。 启动线程池的函数是threadPoolRun函数

知识点2:事件循环的启动与任务添加:服务器启动后,需要将监听的文件描述符添加到事件循环(mainLoop反应堆)中。 添加任务的函数是eventLoopAddTask函数

知识点3:Channel的初始化与使用:获取channel实例需要调用初始化函数channelInit函数。 当读事件触发时,表示有新的客户端连接到达,需要与其建立连接。

 

这篇关于基于多反应堆的高并发服务器【C/C++/Reactor】(中)创建并初始化TcpServer实例 以及 启动的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/576734

相关文章

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

基于Python打造一个可视化FTP服务器

《基于Python打造一个可视化FTP服务器》在日常办公和团队协作中,文件共享是一个不可或缺的需求,所以本文将使用Python+Tkinter+pyftpdlib开发一款可视化FTP服务器,有需要的小... 目录1. 概述2. 功能介绍3. 如何使用4. 代码解析5. 运行效果6.相关源码7. 总结与展望1

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

C/C++错误信息处理的常见方法及函数

《C/C++错误信息处理的常见方法及函数》C/C++是两种广泛使用的编程语言,特别是在系统编程、嵌入式开发以及高性能计算领域,:本文主要介绍C/C++错误信息处理的常见方法及函数,文中通过代码介绍... 目录前言1. errno 和 perror()示例:2. strerror()示例:3. perror(

C++变换迭代器使用方法小结

《C++变换迭代器使用方法小结》本文主要介绍了C++变换迭代器使用方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、源码2、代码解析代码解析:transform_iterator1. transform_iterat

详解C++中类的大小决定因数

《详解C++中类的大小决定因数》类的大小受多个因素影响,主要包括成员变量、对齐方式、继承关系、虚函数表等,下面就来介绍一下,具有一定的参考价值,感兴趣的可以了解一下... 目录1. 非静态数据成员示例:2. 数据对齐(Padding)示例:3. 虚函数(vtable 指针)示例:4. 继承普通继承虚继承5.

C++中std::distance使用方法示例

《C++中std::distance使用方法示例》std::distance是C++标准库中的一个函数,用于计算两个迭代器之间的距离,本文主要介绍了C++中std::distance使用方法示例,具... 目录语法使用方式解释示例输出:其他说明:总结std::distance&n编程bsp;是 C++ 标准

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处