高性能并发TCP网络服务-IOCP框架修正VC2008版本

高性能并发TCP网络服务 IOCP框架修正VC2008版本
本文介绍了一个整合epoll、iocp和kqueue三种模型的高性能并发TCP网络服务框架,支持高并发、多CPU、跨平台。详细解释了如何在Windows、Linux和FreeBSD平台上使用此框架,并提供了测试代码和平台适配说明。

http://www.rosoo.net/a/200807/7092.html

高性能并发TCP网络服务-IOCP框架修正VC2008版本

罗索客 发布于 2008-07-28 11:09 点击: 5656次 
来自:网络博客
从Source Code里可发现,此工程整合的epoll,iocp及kqueue三种模型,应该是非常有用的一个东东(如果ACE能够把它的那些封装出来的每个东东都独立出来就太好了),但由于时间关系未经测试。等测试OK,再来Update具体状况。
TAG: IOCP   IOCP代码  

高性能并发TCP网络服务IOCP框架修正VC2008版本

从Source Code里可发现,此工程整合的epoll,iocp及kqueue三种模型,应该是非常有用的一个东东(如果ACE能够把它的那些封装出来的每个东东都独立出来就太好了),但由于时间关系未经测试。等测试OK,再来Update具体状况。

点击浏览该文件

高并发TCP网络服务框架用Windows的IOCP、Linux的epoll、FreeBSD的kqueue写了一个支持高并发、多CPU、跨平台的TCP网络服务框架。测试下载netfrm.v2.rar,解压缩得到netfrm.v2目录,里面有netfrm.v2.vcproj和src目录。测试代码在src/main.cpp。

#include
#include "./lance/ldebug.h"
#include "./lance/tcpsrv.hpp"
#include "./lance/systm.h"
class MyClient : public lance::net::Client {
public: void OnConnect() { printf("OnConnect: fd=x, ip=%d, port=%d\\n", fd, ip, port); recv(data, 255); }
public: void OnDisconnect() { printf("OnDisconnect: fd=x, ip=%d, port=%d\\n", fd, ip, port); }
public: void OnRecv(int len) { data[len] = 0x00; printf("OnRecv: fd=x, data=%s\\n", fd, data); if (data[0] == 'a') { printf("user exit command\\n"); close(); } recv(data, 255); }
public: char data[256]; };

int main(char * args[]) {
lance::net::TCPSrv srv;
srv.ip = 0;
srv.port = 1234;
srv.ptr = NULL;
srv.backlogs = 10;
srv.threads = 1;
srv.scheds = 0;
srv.start();
while(true) { lance::systm::sleep(2000); }
return 0;
}

测试代码绑定本机所有IP地址,在1234端口开启网络服务,接收客户端发送的字符串,并将这些字符串打印到控制台上。

Windows平台

在Windows XP SP2下用vs2003编译测试通过。
用vs2003打开netfrm.v2.vcproj,然后编译、运行,会弹出控制台窗口。在Windows开始菜单->运行->cmd,启动Windows命令窗口,输入telnet 127.0.0.1 1234,回车连接到测试网络服务,如果一切正常,网络服务控制台窗口将显示连接信息,可以在Windows命令窗口随便输入信息,这时网络服务控制台窗口将打印输入的信息。如下图所示:图1 输入字符a表示断开网络连接。

Linux平台

Linux在Red Hat Enterprise Linux 4下测试通过,其他Linux平台需要Linux 2.6及以上支持epoll的内核。
首先转到src目录: $ cd src
编译: $ make –f Makefile.linux clean all
这时会在当前目录生成tcpsrv.0.1.bin的可执行文件,
执行: $ ./ tcpsrv.0.1.bin 再打开一个命令行窗口,
测试: $ telnet 127.0.0.1 1234
输入字符串并回车,刚才执行tcpsrv.0.1.bin的窗口将打印连接和字符串信息。输入a开头的字符串将断开连接。

FreeBSD平台

FreeBSD在FreeBSD 6.2下测试通过,其他BSD平台需要支持kqueue的内核。
首先转到src目录: $ cd src
编译: $ make –f Makefile.freebsd clean all 这时会在当前目录生成tcpsrv.0.1.bin的可执行文件,
执行: $ ./ tcpsrv.0.1.bin 再打开一个命令行窗口,
测试: $ telnet 127.0.0.1 1234 输入字符串并回车,刚才执行tcpsrv.0.1.bin的窗口将打印连接和字符串信息。
输入a开头的字符串将断开连接。

使用目录结构:

src |---lance |---tcpsrv.hpp
主要接口文件
|---iocptcpsrv.hpp Windows IOCP网络服务实现文件
|---eptcpsrv.hpp Linux epoll网络服务实现文件
|---kqtcpsrv.hpp FreeBSD kqueue网络服务实现文件
在某种平台下使用时,src/lance/tcpsrv.hpp必须,其他文件根据平台而定。

首先,创建一个Client类,这个类必须继承lance::net::Client,重载事件通知方法。
// Client对象类,当连接建立时自动创建,当连接断开时自动销毁
class MyClient : public lance::net::Client {
// 连接建立时被调动
public: void OnConnect() { printf("OnConnect: fd=x, ip=%d, port=%d\\n", fd, ip, port);
// 通知调度系统接收数据
// 数据这时并没有真正接收,当客户端有数据发送来时
// 调度器自动接收数据,然后通过OnRecv通知数据接收完成
recv(data, 255);
}
// 连接断开时被调用
public: void OnDisconnect() {
printf("OnDisconnect: fd=x, ip=%d, port=%d\\n", fd, ip, port); }
// 当有数据被接收时调用,接收的实际数据长度为len
public: void OnRecv(int len) { data[len] = 0x00; printf("OnRecv: fd=x, data=%s\\n", fd, data);
// 断开连接命令
if (data[0] == 'a') { printf("user exit command\\n");
// 通知调度系统断开连接,当调度系统处理完成后才真正断开连接
close();
}
// 通知调度系统接收数据
// 数据这时并没有真正接收,当客户端有数据发送来时
// 调度器自动接收数据,然后通过OnRecv通知数据接收完成
recv(data, 255);
}
// 数据缓冲区
public: char data[256];
};

然后创建一个lance::net::TCPSrv的实例,这个实例负责调度网络服务。
具体代码参考src/main.cpp,lance::net::Client的OnConnect、OnRecv、OnDisconnect都由工作线程池处理,所以里面可以进行IO操作而不会影响系统响应。

int main(char * args[]) {
lance::net::TCPSrv srv;
// 设置监听套接字绑定的IP
// 0为绑定所有本机可用IP地址
srv.ip = 0;
// 监听端口
srv.port = 1234;
// 绑定的对象或资源指针
// MyClient里面可以通过srv->ptr获取这个指针
srv.ptr = NULL;
// 监听套接字连接队列长度
srv.backlogs = 10;
// 处理线程池线程数
srv.threads = 1;
// 调度器线程数,通常是本机CPU数的2倍
// 0表示自动选择
srv.scheds = 0;
// 启动网络服务
srv.start();
// 循环,保证进程不退出
while(true) {
lance::systm::sleep(2000);
}
return 0;
}

Windows平台的预编译宏是LANCE_WIN32。
Linux平台的预编译宏是LANCE_LINUX。
FreeBSD平台的预编译宏是LANCE_FREEBSD。
Windows平台编译需要使用WIN32_LEAN_AND_MEAN和_WIN32_WINNT=0x0500预编译宏来避免Winsock2和Windows头文件的冲突,否则会产生大量类型重定义错误。

 #define EPOLL_MAX_NFDS 10000
// max sockets queried by epoll.
#define EPOLL_MAX_EVENTS 100
// max events queried by epoll.
#define EPOLL_MAX_QUEUE 1024
// max events in cache queue.
Linux平台有额外三个预编译宏,参考src/lance/eptcpsrv.hpp:

FreeBSD平台有额外三个预编译宏,参考src/lance/kqtcpsrv.hpp:
#define KQUEUE_MAX_NFDS 10000
// max sockets queried by kqueue.
#define KQUEUE_MAX_EVENTS 100
// max events queried by kqueue.
#define KQUEUE_MAX_QUEUE 1024
// max events in cache queue.

Windows IOCP设计首先用户接口部分,由两个类lance::net:TCPSrv,lance::net::Client。
lance::net::TCPSrv管理监听套接字、事件调度和事件处理。
lance::net::Client管理连接套接字。
lance::net::TCPSrv由lance::net::Listener、lance::net::Scheduler、lance::net::Processor组成。他们之间的关系如下:

图2

Listener管理监听套接字,有单独的线程执行,当有连接到来时,创建一个Client的对象实例,然后通过IOCP系统调用通知调度器有连接到来,参考src/lance/iocptcpsrv.hpp
template
void Scheduler::push(T * clt) {
::PostQueuedCompletionStatus(iocp, 0, (ULONG_PTR)clt, NULL);
}

Scheduler实际并不做很多事情,只是封装IOCP句柄,Windows的IOCP功能很丰富,包括管理事件队列和多CPU支持,所以Scheduler只是一个IOCP的映射。
Processor管理线程池,这些线程池是工作线程,他们[FS:PAGE]轮询Scheduler的IOCP,从中取出系统事件,IOCP里面有三种事件,一种是客户端连接事件,一种是客户端数据事件,最后一种是连接断开事件,当有事件到来时,会得到Client对象的指针clt,Client的event包含了事件类型,参考src/lance/iocptcpsrv.hpp:

template
DWORD WINAPI Processor::run(LPVOID param) {
Processor& procor = *(Processor *)param;
Scheduler& scheder = *procor.scheder;
HANDLE iocp = scheder.iocp;
DWORD ready;
ULONG_PTR key;
WSAOVERLAPPED * overlap;
while (true) {
::GetQueuedCompletionStatus(iocp, &ready, &key, (LPOVERLAPPED *)&overlap, INFINITE);
T * clt = (T *)key;
switch(clt->event) {
case T::EV_RECV: {
if (0 >= ready) {
clt->event = T::EV_DISCONNECT;
::PostQueuedCompletionStatus(iocp, 0, (ULONG_PTR)clt, NULL);
} else {
clt->OnRecv(ready);
}
}
break;
case T::EV_CONNECT: {
if (NULL == ::CreateIoCompletionPort((HANDLE)clt->fd, iocp, (ULONG_PTR)clt, 0)) {
::closesocket(clt->fd);
delete clt;
} else {
clt->OnConnect();
}
}
break;
case T::EV_DISCONNECT: {
clt->OnDisconnect();
::closesocket(clt->fd);
delete clt;
}
break;
case T::EV_SEND:
break;
}
}
return 0;
}
 

 所以Client::OnConnect、Client::OnRecv、Client::OnDisconnect都在工作线程中进行,这些处理过程中都可以有IO等耗时操作,一个连接的阻塞不会影响其他连接的响应速度。
Client的其他方法Client::recv、Client::send和Client::close。
Client::recv是一个异步接收数据的方法,这个方面只是告诉IOCP想要接收客户端的数据,然后立即返回,由IOCP去负责接收数据,有数据收到时,Processor的工作线程会收到Client::EV_RECV的消息,Processor会调用Client::OnRecv进行通知。
Client::send是发送消息的函数,这个函数是阻塞调用,等待消息发送成功后才返回。
Client::close是主动断开客户端连接的方法,这个方法不会直接调用closesocket(fd),而是调用shutdown(fd),shutdown(fd)会向Scheduler触发一个Client::EV_DISCONNECT的事件,然后Processor调用Client::OnDisconnect通知连接断开,执行完Client::OnDisconnect后,由Processor调用closesocket(fd)真正断开连接,这样设计一方面满足任何情况下OnDisconnect都被调用,另一方面因为操作系统会重用已经关闭的套接字fd,所以只有当OnDisconnect执行完毕后才真正调用closesocket让操作系统回收fd,可以避免使用无效的套接字或者挪用其他连接的套接字。

Linux epoll和FreeBSD kqueue设计

Linux epoll和FreeBSD kqueue的机制几乎一样,只有函数名字和个数不一样,所以一起分析,并且简写为Linux。因为Linux不像Windows一样会管理事件队列和多CPU支持,所以Linux需要额外实现事件队列和多CPU支持。
Linux下用户接口跟Windows一样,有lance::net::TCPSrv和lance::net::Client,因为跨平台,所以他们提供的接口功能和意义也一样,参考Windows一节。
lance::net::TCPSrv管理连接套接字、事件队列、多CPU支持、事件调度和事件处理。
lance::net::TCPSrv由Listener、Scheduler、Processor、Queue组成。他们之间关系图如下:图3
Listener管理监听套接字,有连接到来时创建一个Client的实例clt,初始化Client::event为Client::EV_CONNECT,然后将clt放入调度器,调度器为clt选择一个合适的epoll/kqueue进行绑定,然后将clt放入事件队列Queue等待被Processor执行。

Scheduler管理epoll/kqueue,为了支持多CPU,一个Scheduler可能管理多个epoll/kqueue,通过lance::net::TCPSrv::scheds进行设置,当lance::net::TCPSrv::scheds大于1时,Scheduler将创建scheds个线程,每个线程管理一个epoll/kqueue。当Listener提交一个新的clt时,Scheduler顺序选择一个epoll/kqueue进行绑定,这是最简单的均等选择算法,epoll/kqueue会检查绑定的clt的数据接收和连接断开事件,如果有事件,会把产生这个事件的clt放入事件队列Queue等待被Processor执行,并且设置clt的套接字为休眠状态,因为epoll/kqueue为状态触发,如果事件在被Processor处理前不休眠,会再次被触发,这样Queue将被迅速填满。多CPU时,依靠多个epoll/kqueue能有效利用这些CPU。参考eptcpsrv.hpp:

template
void Scheduler::push(T * clt) {
clt->epfd = epers[epoff].epfd;
epoff = (epoff+1 == scheds)?0:(epoff+1);
queue.in();
while (queue.full()) {
queue.fullWait();
}
if (queue.empty()) {
queue.emptyNotify();
}
queue.push(clt);
queue.out();
}

Queue是有限缓冲队列,有队列最大长度EPOLL_MAX_QUEUE/KQUEUE_MAX_QUEUE,有限缓冲队列结构如下: 图4

Queue采用monitor模式,使用pthread_mutex_t lock保护临界区,使用pthread_cond_t emptySignal做队列由空到不空的通知,也就是唤醒消费者可以处理队列,使用pthread_cond_t fullSignal做队列由满到不满的通知,也就是唤醒生产者可以填充队列,这里Scheduler是生产者,Processor是消费者。
有时epoll/kqueue会一次产生多个事件,如果先前队列为空,那么需要通知Processor可以处理事件,因为emptySignal.notify只能一次唤醒一个线程,为了更加高效的处理事件,应该使用emptySignal.broadcast唤醒所有工作线程。如果epoll/kqueue一次只产生了一个事件,并且先前队列为空,那么只需要使用emptySignal.notify唤醒一个工作线程而不应该使用emptySignal.broadcast唤醒工作线程,因为只有一个事件,所以只有一个线程会处理事件,而其他线程会空转一次消耗资源。如果epoll/kqueue产生了事件,但是队列不为空,那么不需要唤醒工作线程的操作,因为队列不为空的时候,没有任何工作线程处于等待状态。代码参考eptcpsrv.hpp/Queue。
Processor跟Windows基本一样,Processor从Queue取出事件,然后根据clt->event事件类型调用响应的事件通知函数。 Client::recv也是一个请求接收数据的过程,并不实际接收数据,当有数据到来时,Processor的工作线程负责接收数据,然后调用Client::OnRecv通知响应的连接对象。
Cleint::send是一个同步阻塞函数,等待数据真正发送完成后再返回。 Client::close跟Windows类似,只是调用shutdown来触发断开消息,然后处理流程跟Windows一致。

[此贴子已经被作者于2008-7-28 14:36:13编辑过]
(落鹤生)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自: 罗索实验室 [ http://www.rosoo.net/a/200807/7092.html]
标题SpringBoot智能在线预约挂号系统研究AI更换标题第1章引言介绍智能在线预约挂号系统的研究背景、意义、国内外研究现状及论文创新点。1.1研究背景与意义阐述智能在线预约挂号系统对提升医疗服务效率的重要性。1.2国内外研究现状分析国内外智能在线预约挂号系统的研究与应用情况。1.3研究方法及创新点概述本文采用的技术路线、研究方法及主要创新点。第2章相关理论总结智能在线预约挂号系统相关理论,包括系统架构、开发技术等。2.1系统架构设计理论介绍系统架构设计的基本原则和常用方法。2.2SpringBoot开发框架理论阐述SpringBoot框架的特点、优势及其在系统开发中的应用。2.3数据库设计与管理理论介绍数据库设计原则、数据模型及数据库管理系统。2.4网络安全与数据保护理论讨论网络安全威胁、数据保护技术及其在系统中的应用。第3章SpringBoot智能在线预约挂号系统设计详细介绍系统的设计方案,包括功能模块划分、数据库设计等。3.1系统功能模块设计划分系统功能模块,如用户管理、挂号管理、医生排班等。3.2数据库设计与实现设计数据库表结构,确定字段类型、主键及外键关系。3.3用户界面设计设计用户友好的界面,提升用户体验。3.4系统安全设计阐述系统安全策略,包括用户认证、数据加密等。第4章系统实现与测试介绍系统的实现过程,包括编码、测试及优化等。4.1系统编码实现采用SpringBoot框架进行系统编码实现。4.2系统测试方法介绍系统测试的方法、步骤及测试用例设计。4.3系统性能测试与分析对系统进行性能测试,分析测试结果并提出优化建议。4.4系统优化与改进根据测试结果对系统进行优化和改进,提升系统性能。第5章研究结果呈现系统实现后的效果,包括功能实现、性能提升等。5.1系统功能实现效果展示系统各功能模块的实现效果,如挂号成功界面等。5.2系统性能提升效果对比优化前后的系统性能
在金融行业中,对信用风险的判断是核心环节之一,其结果对机构的信贷政策和风险控制策略有直接影响。本文将围绕如何借助机器学习方法,尤其是Sklearn工具包,建立用于判断信用状况的预测系统。文中将涵盖逻辑回归、支持向量机等常见方法,并通过实际操作流程进行说明。 一、机器学习基本概念 机器学习属于人工智能的子领域,其基本理念是通过数据自动学习规律,而非依赖人工设定规则。在信贷分析中,该技术可用于挖掘历史数据中的潜在规律,进而对未来的信用表现进行预测。 二、Sklearn工具包概述 Sklearn(Scikit-learn)是Python语言中广泛使用的机器学习模块,提供多种数据处理和建模功能。它简化了数据清洗、特征提取、模型构建、验证与优化等流程,是数据科学项目中的常用工具。 三、逻辑回归模型 逻辑回归是一种常用于分类任务的线性模型,特别适用于二类问题。在信用评估中,该模型可用于判断借款人是否可能违约。其通过逻辑函数将输出映射为0到1之间的概率值,从而表示违约的可能性。 四、支持向量机模型 支持向量机是一种用于监督学习的算法,适用于数据维度高、样本量小的情况。在信用分析中,该方法能够通过寻找最佳分割面,区分违约与非违约客户。通过选用不同核函数,可应对复杂的非线性关系,提升预测精度。 五、数据预处理步骤 在建模前,需对原始数据进行清理与转换,包括处理缺失值、识别异常点、标准化数值、筛选有效特征等。对于信用评分,常见的输入变量包括收入水平、负债比例、信用历史记录、职业稳定性等。预处理有助于减少噪声干扰,增强模型的适应性。 六、模型构建与验证 借助Sklearn,可以将数据集划分为训练集和测试集,并通过交叉验证调整参数以提升模型性能。常用评估指标包括准确率、召回率、F1值以及AUC-ROC曲线。在处理不平衡数据时,更应关注模型的召回率与特异性。 七、集成学习方法 为提升模型预测能力,可采用集成策略,如结合多个模型的预测结果。这有助于降低单一模型的偏差与方差,增强整体预测的稳定性与准确性。 综上,基于机器学习的信用评估系统可通过Sklearn中的多种算法,结合合理的数据处理与模型优化,实现对借款人信用状况的精准判断。在实际应用中,需持续调整模型以适应市场变化,保障预测结果的长期有效性。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值