完成端口与AcceptEx
关于IOCP网上到处都是资料,说的也很详细。我在这里就不再多说了,这只是本人在学习IOCP时的笔记,和配合AcceptEx写的一个极小的服务端程序。由于刚刚接触ICOP加上本人刚毕业不到一年,所以里面的理解或观点可能有误,还请大家多多批评!
VC6.0开发,旨在体现IOCP的架构,忽略细节,服务程序的功能只是接收客户连接,接着接收到客户数据,然后原封不动的返回给客户!
下面这段话,如果不感兴趣,可以跳过去,直接看代码...
先说IOCP,其实思路很清晰:
1.声明一个数据结构,用来存放客户套接字和客户信息2.声明一个包含OVERLAPPED字段的I/O结构3.创建完成端口4.创建服务线程5.接收客户端连接请求6.关联这个套接字到完成端口中7.服务线程中不断的等待I/O结果,在结果中提供服务和根据需要发起另一个异步操作。按照这个思路很快的写出了一个服务程序,但是遇到了下面的问题:1.WSAGetLastError()返回10045,找了半天才发现发起重叠操作时候,WSARecv中flag参数没有初始化,需要初始化赋值为0。2.在GetQueuedCompletionStatus中,没有错误,但总是返回读取的字数为0。I/O重叠结构中也收不到任何字符。这时候我就在这里用了一下recv()函数,在recv中却可以收到来自客户端发送的数据。难道每次都要自己recv()?肯定不是!如果那样还用扩展的I/O结果何用。一定是哪里指定了接收的数目,而自己不小心指定为0了,所以没有接收数据。找了半天果然如此。在发起重叠操作时候,扩展的I/O中WSABUF的赋值有问题。我的错误:wsaBuf.len = (I/O结构).len;改为: wsaBuf.len = (I/O结构).len = DATABUF_SIZE;修改之后终于可以接收和发送数据了。
为什么要用AcceptEx?
在学习IOCP时,看到一位大神写的文章,他用客户端开了3W个线程同时连接服务端和发送数据,我好奇就也开了3W个线程去同时连接服务端,结果很多都printf连接失败的信息!再看看大神的文章,再搜一下AcceptEx。对比accept,觉得AcceptEx确实很强大。AcceptEx和accept主要的区别就在于接收套接字:accept函数是等待客户连接进来之后才创建套接字,虽然在我们看到的就是一个socket函数,但是在函数背后,系统应该会消耗不少资源,因为它要打通一个和外界通讯的路。如果大量套接字并发接入,难免有的套接字不能及时创建和接收。AcceptEx则是事先创建好套接字,坐等客户端的连接就行了。但是,AcceptEx相比accept确实复杂了很多。原来一句accept就可以解决的,现在却要为AcceptEx做很多服务,但是只要理清思路,这个做起来也是很从容的。1.创建一个监听套接字2.将监听套接字关联到完成端口中3.对监听套接字调用bind()、listen()4.通过WSAIoctl获取AcceptEx、GetAcceptExSockaddrs函数的指针5.创建一个用于接收客户连接的套接字6.用获取到的AcceptEx函数指针发起用于接收连接的异步操作7.服务器接收到连接的套接字,设置一下它的属性(有人说没有必要)。用这个接收到的套接字去发起重叠的I/O操作。8.多次重复5,6就是多次发起接收连接的异步操作的过程。对于第4步,为什么要获取AcceptEx的指针,而不是直接就调用AcceptEx这个函数呢?网上找到的资料是这么说的:Winsock2的其他供应商不一定会实现AcceptEx函数。同样情况也包括的其他Microsoft的特定APIs如TransmitFile,GetAcceptExSockAddrs以及其他Microsoft将在以后版本的windows里。
在运行WinNT和Win2000的系统上,这些APIs在Microsoft提供的DLL(mswsock.dll)里实现,可以通过链接mswsock.lib或者通过WSAioctl的SIO_GET_EXTENSION_FUNCTION_POINTER操作动态调用这些扩展APIs.
未获取函数指针就调用函数(如直接连接mswsock.lib并直接调用AcceptEx)的消耗是很大的,因为AcceptEx实际上是存在于Winsock2结构体系之外的。每次应用程序常试在服务提供层上(mswsock之上)调用AcceptEx时,都要先通过WSAIoctl获取该函数指针。如果要避免这个很影响性能的操作,应用程序最好是直接从服务提供层通过WSAIoctl先获取这些APIs的指针。
这样一来,大家就不觉得这个复杂的函数WSAloctl那么让人心烦了吧!至于调用失败后所返回的错误代码,百度百科中介绍的很详细!
使用AcceptEx后:
在使用AcceptEx后,并发2000个套接字去连接客户端,不再出现连接失败的消息了。但是,你肯定会说人家3W个,你这2000个不能说明问题。开始我也一直在尝试同时并发3W个线程,可是发现公司机器最多时候也就1573个连接,家里笔记本差不多2000个。这是怎么会事呢?于是搜资料查到一个进程最多可以开启的理论线程数是2048个线程,而且实际情况下通常小于这个值,这样在一个进程里面怎么可能有3W个连接啊!忍不住好奇就下了http://blog.csdn.net/piggyxp/article/details/6922277大神的IOCP客户端demo,发现并不是同时并发3W个,用任务管理器看并发最多时候线程数并没有超过1K(无意冒犯大神,只是个人的愚见,我学习IOCP也是大部分都是从大神的文章中学习到的,所以先要感谢大神的奉献,同时如果(不是如果,是肯定)我的理解有错误,希望大家不吝赐教,多多批评,鄙人一定感激万分)。为了验证IOCP是否有那么强的能力,我的客户端没有做成连接到服务端一个套接字,再创建一个线程,传递套接字到线程的方式。而是,主线程直接创建2000个线程,在每个线程中去连接服务器(觉得这样更能体现并发连接),多开几个客户端,每个客户端的连接数为最大线程数,服务端同时处理的连接数为12562(开更多的线程连接数更多,有兴趣的可以试一下)。下面是360的流量管理下面的截图:
我注释掉了接收数据后printf接收到的数据,因为发现如果连接过多,一直printf服务器就挂掉了,不知道改成mfc会不会好点...
下面是服务器代码:
[cpp] view plain copy
#include "stdafx.h"
#include <Afx.h>
#include <Windows.h>
#include <Winsock2.h>
#pragma comment(lib, "WS2_32.lib") #include <mswsock.h> //微软扩展的类库 #define DATA_BUFSIZE 100
#define READ 0
#define WRITE 1
#define ACCEPT 2 DWORD g_count = 0;
//扩展的输入输出结构
typedef struct _io_operation_data
{ OVERLAPPED overlapped; WSABUF databuf; CHAR buffer[DATA_BUFSIZE]; BYTE type; DWORD len; SOCKET sock;
}IO_OPERATION_DATA, *LP_IO_OPERATION_DATA; //完成键
typedef struct _completion_key
{ SOCKET sock; char sIP[30]; //本机测试,IP都是127.0.0.1,没啥意思,实际写时候这个值填的是端口号
}COMPLETION_KEY, *LP_COMPLETION_KEY; ///
//完成端口句柄
HANDLE g_hComPort = NULL;
BOOL g_bRun = FALSE; BOOL AcceptClient(SOCKET sListen); //发起接收连接操作
BOOL Recv(COMPLETION_KEY *pComKey, IO_OPERATION_DATA *pIO); //发起接收操作
BOOL Send(COMPLETION_KEY *pComKey, IO_OPERATION_DATA *pIO); //发起发送操作 //处理重叠结果
BOOL ProcessIO(IO_OPERATION_DATA *pIOdata, COMPLETION_KEY *pComKey); //
//服务线程
DWORD WINAPI ServerWorkerThread( LPVOID pParam ); //
LPFN_ACCEPTEX lpfnAcceptEx = NULL; //AcceptEx函数指针
LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs; //加载GetAcceptExSockaddrs函数指针 ///
//监听套接字,其实也不一定要是全局的。用于接收到连接后继续发起等待连接操作。
SOCKET g_sListen; int main(int argc, char* argv[])
{ g_bRun = TRUE; //创建完成端口 g_hComPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0 ); if( g_hComPort == NULL ) { printf("Create completionport error! %d\n", WSAGetLastError() ); return 0; } //创建服务线程 SYSTEM_INFO sysInfor; GetSystemInfo( &sysInfor ); int i=0; for(i = 0; i < sysInfor.dwNumberOfProcessors * 2; i++)
// if(true) { HANDLE hThread; DWORD dwThreadID; hThread = CreateThread( NULL, 0, ServerWorkerThread, g_hComPort, 0, &dwThreadID ); CloseHandle( hThread ); } //加载套接字库 WSADATA wsData; if( 0 != WSAStartup( 0x0202, &wsData ) ) { printf("加载套接字库失败! %d\n", WSAGetLastError() ); g_bRun = FALSE; return 0; } //等待客户端连接 //先创建一个套接字用于监听 SOCKET sListen = WSASocket( AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED ); g_sListen = sListen; //将监听套接字与完成端口绑定 LP_COMPLETION_KEY pComKey; //完成键 pComKey = (LP_COMPLETION_KEY) GlobalAlloc ( GPTR, sizeof(COMPLETION_KEY) ); pComKey->sock = sListen; CreateIoCompletionPort( (HANDLE)sListen, g_hComPort, (DWORD)pComKey, 0 ); //监听套接字绑定监听 SOCKADDR_IN serAdd; serAdd.sin_addr.S_un.S_addr = htonl(INADDR_ANY); serAdd.sin_family = AF_INET; serAdd.sin_port = htons( 6000 ); bind( sListen, (SOCKADDR*)&serAdd, sizeof(SOCKADDR) ); listen( sListen, 5 ); if( sListen == SOCKET_ERROR ) { goto STOP_SERVER; } / //使用WSAIoctl获取AcceptEx函数指针 if( true ) { DWORD dwbytes = 0; //Accept function GUID GUID guidAcceptEx = WSAID_ACCEPTEX; if( 0 != WSAIoctl( sListen, SIO_GET_EXTENSION_FUNCTION_POINTER, &guidAcceptEx, sizeof(guidAcceptEx), &lpfnAcceptEx, sizeof(lpfnAcceptEx), &dwbytes, NULL, NULL) ) { //百度百科,有关该函数的所有返回值都有! } // 获取GetAcceptExSockAddrs函数指针,也是同理 GUID guidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS; if( 0 != WSAIoctl( sListen, SIO_GET_EXTENSION_FUNCTION_POINTER, &guidGetAcceptExSockaddrs, sizeof(guidGetAcceptExSockaddrs), &lpfnGetAcceptExSockaddrs, sizeof(lpfnGetAcceptExSockaddrs), &dwbytes, NULL, NULL) ) { } } //发起接收的异步操作 for(i=0; i<2000; i++ ) { AcceptClient(sListen); } //不让主线程退出 while( g_bRun ) { Sleep(1000); } STOP_SERVER: closesocket( sListen ); g_bRun = FALSE; WSACleanup(); return 0;
} /
//服务线程
DWORD WINAPI ServerWorkerThread( LPVOID pParam )
{ HANDLE completionPort = (HANDLE)pParam; DWORD dwIoSize; COMPLETION_KEY *pComKey; //完成键 LP_IO_OPERATION_DATA lpIOoperData; //I/O数据 //用于发起接收重叠操作 BOOL bRet; while( g_bRun ) { bRet = FALSE; dwIoSize = -1; pComKey = NULL; lpIOoperData = NULL; bRet = GetQueuedCompletionStatus( g_hComPort, &dwIoSize, (LPDWORD)&pComKey, (LPOVERLAPPED*)&lpIOoperData,INFINITE ); if( !bRet ) { DWORD dwIOError = GetLastError(); if( WAIT_TIMEOUT == dwIOError ) { continue; } else if( NULL != lpIOoperData ) { CancelIo( (HANDLE)pComKey->sock ); //取消等待执行的异步操作 closesocket(pComKey->sock); GlobalFree( pComKey ); } else { g_bRun = FALSE; break; } } else { if( 0 == dwIoSize && (READ==lpIOoperData->type || WRITE==lpIOoperData->type) ) { printf("客户断开了连接!\n"); CancelIo( (HANDLE)pComKey->sock ); //取消等待执行的异步操作 closesocket(pComKey->sock); GlobalFree( pComKey ); GlobalFree( lpIOoperData ); continue; } else { ProcessIO( lpIOoperData, pComKey ); } } } return 0;
} BOOL ProcessIO(IO_OPERATION_DATA *pIOoperData, COMPLETION_KEY *pComKey)
{ if( pIOoperData->type == READ ) { //打印接收到的内容 // char ch[100] = { 0 }; // sprintf(ch, "%s : %s", pComKey->sIP, pIOoperData->buffer); // printf( ch ); Send( pComKey, pIOoperData ); //将接收到的内容原封不动的发送回去 } else if( pIOoperData->type == WRITE ) { Recv( pComKey, pIOoperData ); //发起接收操作 } else if( pIOoperData->type == ACCEPT ) { //使用GetAcceptExSockaddrs函数 获得具体的各个地址参数. printf("accept sucess!\n"); setsockopt( pIOoperData->sock, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char*)&(pComKey->sock), sizeof(pComKey->sock) ); LP_COMPLETION_KEY pClientComKey = (LP_COMPLETION_KEY) GlobalAlloc ( GPTR, sizeof(COMPLETION_KEY) ); pClientComKey->sock = pIOoperData->sock; SOCKADDR_IN *addrClient = NULL, *addrLocal = NULL; int nClientLen = sizeof(SOCKADDR_IN), nLocalLen = sizeof(SOCKADDR_IN); lpfnGetAcceptExSockaddrs(pIOoperData->buffer, 0, sizeof(SOCKADDR_IN)+16, sizeof(SOCKADDR_IN)+16, (LPSOCKADDR*)&addrLocal, &nLocalLen, (LPSOCKADDR*)&addrClient, &nClientLen); sprintf(pClientComKey->sIP, "%d", addrClient->sin_port ); //cliAdd.sin_port ); printf(pClientComKey->sIP ); CreateIoCompletionPort( (HANDLE)pClientComKey->sock, g_hComPort, (DWORD)pClientComKey, 0 ); //将监听到的套接字关联到完成端口 Recv( pClientComKey, pIOoperData ); // char s[30] = {0}; // sprintf( s, "%d\n", g_count++ ); // printf(s); //接收到一个连接,就再发起一个异步操作! AcceptClient( g_sListen ); } return TRUE;
} BOOL AcceptClient(SOCKET sListen)
{ DWORD dwBytes; LP_IO_OPERATION_DATA pIO; pIO = (LP_IO_OPERATION_DATA) GlobalAlloc (GPTR, sizeof(IO_OPERATION_DATA)); pIO->databuf.buf = pIO->buffer; pIO->databuf.len = pIO->len = DATA_BUFSIZE; pIO->type = ACCEPT; //先创建一个套接字(相比accept有点就在此,accept是接收到连接才创建出来套接字,浪费时间. 这里先准备一个,用于接收连接) pIO->sock = WSASocket( AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED ); //调用AcceptEx函数,地址长度需要在原有的上面加上16个字节 //向服务线程投递一个接收连接的的请求 BOOL rc = lpfnAcceptEx( sListen, pIO->sock, pIO->buffer, 0, sizeof(SOCKADDR_IN)+16, sizeof(SOCKADDR_IN)+16, &dwBytes, &(pIO->overlapped) ); if( FALSE == rc ) { if( WSAGetLastError() != ERROR_IO_PENDING ) { printf("%d", WSAGetLastError() ); return false; } } return true;
} BOOL Recv(COMPLETION_KEY *pComKey, IO_OPERATION_DATA *pIOoperData)
{ DWORD flags = 0; DWORD recvBytes = 0; ZeroMemory( &pIOoperData->overlapped, sizeof(OVERLAPPED) ); pIOoperData->type = READ; pIOoperData->databuf.buf = pIOoperData->buffer; pIOoperData->databuf.len = pIOoperData->len = DATA_BUFSIZE; if( SOCKET_ERROR == WSARecv( pComKey->sock, &pIOoperData->databuf, 1, &recvBytes, &flags, &pIOoperData->overlapped, NULL) ) { if( ERROR_IO_PENDING != WSAGetLastError() ) { printf("发起重叠接收失败! %d\n", GetLastError() ); return FALSE; } } return TRUE;
} BOOL Send(COMPLETION_KEY *pComKey, IO_OPERATION_DATA *pIOoperData)
{ DWORD flags = 0; DWORD recvBytes = 0; ZeroMemory( &pIOoperData->overlapped, sizeof(OVERLAPPED) ); pIOoperData->type = WRITE; pIOoperData->databuf.len = 100; if( SOCKET_ERROR == WSASend( pComKey->sock, &pIOoperData->databuf, 1, &recvBytes, flags, &pIOoperData->overlapped , NULL) ) { if( ERROR_IO_PENDING != WSAGetLastError() ) { printf("发起发送重叠接收失败!\n"); return FALSE; } } return TRUE;
}
对于客户端就更简单了,只是创建线程,请求连接,发送数据,接收数据
[cpp] view plain copy
#include "stdafx.h"
#include <Afx.h>
#include <Windows.h>
#include <Winsock2.h>
#pragma comment(lib, "WS2_32.lib") DWORD WINAPI Thread(LPVOID lParam); int main(int argc, char* argv[])
{ WSADATA dwData; WSAStartup( 0x0202, &dwData ); for( int i = 0; i < 2000; i++ ) { HANDLE hThread = NULL; hThread = CreateThread(NULL, 0, Thread, NULL, 0, 0); CloseHandle(hThread); hThread = NULL; } while( true ) { Sleep(100); } return 0;
} DWORD WINAPI Thread(LPVOID lParam)
{ SOCKET sock = socket( AF_INET, SOCK_STREAM, 0 ); SOCKADDR_IN serAddr; serAddr.sin_family = AF_INET; serAddr.sin_port = htons(6000); serAddr.sin_addr.S_un.S_addr = inet_addr(_T("127.0.0.1")); int reVal = connect( sock, (SOCKADDR*)&serAddr, sizeof(SOCKADDR) ); if( reVal==SOCKET_ERROR ) { printf("cannot client SERVER! %d\n", WSAGetLastError()); return 0; } int i=0; char buf[100] = _T("光阴的故事!\n"); while( true ) { if( SOCKET_ERROR == send( sock, buf, 100, 0 ) ) { printf("cannot SEND message to server! %d\n", WSAGetLastError()); break; } memset( buf, 0, strlen(buf) ); //清空一下,体现是接收到的数据 if( SOCKET_ERROR == recv( sock, buf, 100, 0 ) ) { printf("cannot RECV message to server! %d\n", WSAGetLastError()); break; } // printf( buf ); Sleep(3000); } closesocket(sock); return 0;
}
[cpp] view plain copy
将代码贴到编译器中即可,也可以下载这个demo http://download.csdn.net/detail/u010025913/7250965
代码漏洞百出,希望大家多多批评指教!
转载于:https://blog.51cto.com/norman20000/2044073
完成端口与AcceptEx相关推荐
- AcceptEx与完成端口(IOCP)结合实例
前言 在windows平台下实现高性能网络服务器,iocp(完成端口)是唯一选择.编写网络服务器面临的问题有:1 快速接收客户端的连接.2 快速收发数据.3 快速处理数据.本文主要解决第一个问题. A ...
- uicolor swift_Swift中的UIColor
uicolor swift UIColor (UIColor) An object that stores color data and sometimes opacity. 存储颜色数据和有时不透明 ...
- AcceptEx函数与完成端口的结合使用例子
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 这是在学 ...
- 使用AcceptEx方法的完成端口
#include <iostream>#include <winsock2.h>#include <ws2tcpip.h>#include <mswsock. ...
- AcceptEx获取远程ip和端口
iocp模型的tcp服务端若采用AcceptEx接受连接, 在有客户端连接后要获取客户端的ip和端口信息 流程: AcceptEx 在工作线程收到客户端连接时 复制listensocket的信息到新客 ...
- 用完成端口开发大响应规模的Winsock应用程序
通常要开发网络应用程序并不是一件轻松的事情,不过,实际上只要掌握几个关键的原则也就可以了--创建和连接一个套接字,尝试进行连接,然后收发数据.真正难的是要写出一个可以接纳少则一个,多则数千个连接的网络 ...
- Windows完成端口的理解
最近研究了一下完成端口,看了一篇奇文http://blog.csdn.net/piggyxp/article/details/6922277 , 在仔细研读之后,调试了代码.或许是我资历尚浅,发现自己 ...
- WinSock完成端口I/O模型
关于重叠I/O,参考<WinSock重叠I/O模型>:关于完成端口的概念及内部机制,参考译文<深度探索I/O完成端口>. 完成端口对象取代了WSAAsyncSelect中的消息 ...
- IOCP配合AcceptEx的例子
本程序在VS2003编译器编译运行.在6.0下可能需要稍加修改. #include <iostream> #include <winsock2.h> #include < ...
最新文章
- HashMap 的使用
- 「技术人生」:什么是技术一号位?
- [css] z-index有时不起作用的原因是什么?怎么解决?
- 模糊聚类算法(FCM)和硬聚类算法(HCM)的VB6.0实现及
- c# 封装“度分秒”与弧度之间的转换 以及datagridview控件的应用
- SVN快速上手使用(适用于新入职同事)
- 微信小程序 获取cookie 以及设置 cookie
- Java后台框架篇--Spring与Restful风格API接口开发
- vmware16安装mac虚拟机的unlocker
- cat环境搭建及springboot项目接入
- css绘制三角形以及梯形
- 计算机软件实习每日学习打卡(4)20201206
- Scrum团队: I-型人 vs T- 型人
- Shell IFS变量
- 【高等数学】常见的均值不等式
- oop三大特性--封装性
- python银行账户资金交易管理办法_Python实现银行账户资金交易管理系统
- 马彩未解禁:中国互联网损失2000亿
- ArcGIS加载ArcToolbox工具
- 2060显卡驱动最新版本_荣耀猎人游戏本V700 游戏本行业新生主力军-荣耀 ——快科技(驱动之家旗下媒体)-...
热门文章
- 使用灵曜内网穿透 免费实现外网访问内网Vue
- dijkstra算法_Dijkstra算法详细(单源最短路径算法)
- oracle Flashback详解
- 灰色预测GM(1,1)模型的java实现代码
- TCPIp是不是一种计算机语言,GO语言tcpip 编程
- Oracle里面containskey,C#中的ContainsKey()方法
- element popover源码
- 阿语python4-1 django框架v5.0第3.1节视图介绍和项目准备
- 等保测评之服务器未配置登录失败锁定策略及登录连接超时自动退出策略
- 【渗透】Redis 未授权访问漏洞利用(三种利用方式)