SOCKET IOCP 一
- 引言
- 便于理解小帮手
- Accept 方式的IOCP代码
引言
最近又有空了,但是最近看见很多关于Java netty的东西,大家想到最多的就是NIO,事实上netty在windows下我想应该使用的是select没有iocp(AIO),所以性能会和linux上有很大差距,但这并不妨碍老衲写几篇关于IO的文章。看这篇文章前可以先看看。
- Windows和Linux IO模型简单介绍
- IOCP 简单的完成端口读写文件
循序渐进的方式来看IOCP,如果我直接使用acceptex方式来讲IOCP,可能很多小伙伴会被完成键和OL结构绕晕,所以分2篇写,这篇使用accept监听,但收发使用IOCP
看了之前的文章会了解到,使用IOCP最起码需要2步
1 创建一个完成端口
- CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
2 将刚才创建的完成端口与某个设备进行关联,并传入完成键和OL结构指针
完成键的理解小伙伴们可以将它理解为自定义的消息,比如linux中的信号,我可以自定义某个信号,windows窗口消息中的自定义消息
OL结构我想不必多说了,是异步IO必须存在的结构,里面包含了event对象等。
便于理解小帮手
在iocp传输文件的那一篇文章中,我们自定义了完成键和创建了ol结构,通过主动触发让它工作起来。但是在socket的操作中,我们不会单纯的传一个完成键和ol结构,为了方便理解,我这里说一下重点,不然除了学c/c++的同学,其他语言的同学可能会有点迷。
事实上我们设置的完成键和ol结构,为了节省开销,都是传入指针,HANDLE 本身也可以理解为对象指针。
所以在socket开发中,我们其实可以传入自己定义的结构,这样在接收时通过指针,就能转变为我们自己的结构,而不是单纯传一个数字(完成键)和ol结构指针。事实上我就是这么做的
Accept 方式的IOCP代码
// IOCP_SOCKET.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <WinSock2.h>
#include <Windows.h>
#include <vector>
using namespace std;
#pragma comment(lib, "Ws2_32.lib") // Socket编程需用的库
#pragma comment(lib, "Kernel32.lib") // IOCP需要用到的库
/**
* 自己定义封装OL结构,加了一些东西
**/
const int DataBuffSize = 2 * 1024;
typedef struct
{
OVERLAPPED overlapped;
WSABUF databuff;
char buffer[DataBuffSize];
int BufferLen;
int operationType;
}PER_IO_OPERATEION_DATA, *LPPER_IO_OPERATION_DATA, *LPPER_IO_DATA, PER_IO_DATA;
/**
* 完成键,好让我们知道是哪个socket触发的
**/
typedef struct
{
SOCKET socket;
SOCKADDR_STORAGE ClientAddr;
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
// 定义全局变量
const int DefaultPort = 8080;
vector < PER_HANDLE_DATA* > clientGroup; // 记录客户端的向量组
HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
DWORD WINAPI ServerWorkThread(LPVOID CompletionPortID);
DWORD WINAPI ServerSendThread(LPVOID IpParam);
// 开始主函数
int main()
{
// 加载socket动态链接库
WORD wVersionRequested = MAKEWORD(2, 2); // 请求2.2版本的WinSock库
WSADATA wsaData; // 接收Windows Socket的结构信息
DWORD err = WSAStartup(wVersionRequested, &wsaData);
if (0 != err) { // 检查套接字库是否申请成功
cerr << "Request Windows Socket Library Error!\n";
system("pause");
return -1;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {// 检查是否申请了所需版本的套接字库
WSACleanup();
cerr << "Request Windows Socket Version 2.2 Error!\n";
system("pause");
return -1;
}
// 创建IOCP的内核对象
/**
* 需要用到的函数的原型:
* HANDLE WINAPI CreateIoCompletionPort(
* __in HANDLE FileHandle, // 已经打开的文件句柄或者空句柄,一般是客户端的句柄
* __in HANDLE ExistingCompletionPort, // 已经存在的IOCP句柄
* __in ULONG_PTR CompletionKey, // 完成键,包含了指定I/O完成包的指定文件
* __in DWORD NumberOfConcurrentThreads // 真正并发同时执行最大线程数,一般推介是CPU核心数*2
* );
**/
//创建一个完成端口
HANDLE completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (NULL == completionPort) { // 创建IO内核对象失败
cerr << "CreateIoCompletionPort failed. Error:" << GetLastError() << endl;
system("pause");
return -1;
}
// 创建IOCP线程--线程里面创建线程池
// 确定处理器的核心数量
SYSTEM_INFO mySysInfo;
GetSystemInfo(&mySysInfo);
// 基于处理器的核心数量创建线程
long threadCount = mySysInfo.dwNumberOfProcessors==8 ? 8: 3 +(mySysInfo.dwNumberOfProcessors * 5 / 8)
for (DWORD i = 0; i < threadCount; ++i) {
// 创建服务器工作器线程,并将完成端口传递到该线程
HANDLE ThreadHandle = CreateThread(NULL, 0, ServerWorkThread, completionPort, 0, NULL);
if (NULL == ThreadHandle) {
cerr << "Create Thread Handle failed. Error:" << GetLastError() << endl;
system("pause");
return -1;
}
CloseHandle(ThreadHandle);
}
// 建立流式套接字
SOCKET srvSocket = socket(AF_INET, SOCK_STREAM, 0);
// 绑定SOCKET到本机
SOCKADDR_IN srvAddr;
srvAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
srvAddr.sin_family = AF_INET;
srvAddr.sin_port = htons(DefaultPort);
int bindResult = bind(srvSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));
if (SOCKET_ERROR == bindResult) {
cerr << "Bind failed. Error:" << GetLastError() << endl;
system("pause");
return -1;
}
// 将SOCKET设置为监听模式
int listenResult = listen(srvSocket, 1024);
if (SOCKET_ERROR == listenResult) {
cerr << "Listen failed. Error: " << GetLastError() << endl;
system("pause");
return -1;
}
// 开始处理IO数据
cout << "本服务器已准备就绪,正在等待客户端的接入...\n";
// 创建用于发送数据的线程,就是个客户端而已
HANDLE sendThread = CreateThread(NULL, 0, ServerSendThread, 0, 0, NULL);
while (true) {
//同步Accept
SOCKADDR_IN saRemote;
int RemoteLen;
SOCKET acceptSocket;
// 接收连接,并分配完成端,这儿可以用AcceptEx()
RemoteLen = sizeof(saRemote);
acceptSocket = accept(srvSocket, (SOCKADDR*)&saRemote, &RemoteLen);
if (SOCKET_ERROR == acceptSocket) { // 接收客户端失败
cerr << "Accept Socket Error: " << GetLastError() << endl;
closesocket(acceptSocket);
continue;
}
// 创建用来和套接字关联的单句柄数据信息结构
PER_HANDLE_DATA * PerHandleData = NULL;
PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA)); // 在堆中为这个PerHandleData申请指定大小的内存
PerHandleData->socket = acceptSocket;
memcpy(&PerHandleData->ClientAddr, &saRemote, RemoteLen);
clientGroup.push_back(PerHandleData); // 将单个客户端数据指针放到客户端组中
// 将接受套接字和完成端口关联
CreateIoCompletionPort((HANDLE)(PerHandleData->socket), completionPort, (DWORD)PerHandleData, 0);
// 开始在接受套接字上处理I/O使用重叠I/O机制
// 在新建的套接字上投递一个或多个异步
// WSARecv或WSASend请求,这些I/O请求完成后,工作者线程会为I/O请求提供服务
// 单I/O操作数据(I/O重叠)
LPPER_IO_OPERATION_DATA PerIoData = NULL;
PerIoData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATEION_DATA));
ZeroMemory(&(PerIoData->overlapped), sizeof(OVERLAPPED));
PerIoData->databuff.len = 1024;
PerIoData->databuff.buf = PerIoData->buffer;
PerIoData->operationType = 0; // read
DWORD RecvBytes;
DWORD Flags = 0;
WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
}
system("pause");
return 0;
}
// 开始服务工作线程函数
DWORD WINAPI ServerWorkThread(LPVOID IpParam)
{
HANDLE CompletionPort = (HANDLE)IpParam;
DWORD BytesTransferred;
LPOVERLAPPED IpOverlapped;
LPPER_HANDLE_DATA PerHandleData = NULL;
LPPER_IO_DATA PerIoData = NULL;
DWORD RecvBytes;
DWORD Flags = 0;
BOOL bRet = false;
while (true) {
bRet = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&IpOverlapped, INFINITE);
if (bRet == 0) {
int lastError = GetLastError();
closesocket(PerHandleData->socket);
GlobalFree(PerHandleData);
WaitForSingleObject(hMutex, INFINITE);
cerr << "GetQueuedCompletionStatus Error: " << lastError << endl;
ReleaseMutex(hMutex);
continue;
}
//这行事实上可以不要,因为我们自己封装的头部就是ol结构,只是为了规范
PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped, PER_IO_DATA, overlapped);
// 检查在套接字上是否有错误发生
if (0 == BytesTransferred) {
closesocket(PerHandleData->socket);
GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}
// 开始数据处理,接收来自客户端的数据
WaitForSingleObject(hMutex, INFINITE);
cout << "A Client says: " << PerIoData->databuff.buf << endl;
ReleaseMutex(hMutex);
// 为下一个重叠调用建立单I/O操作数据
ZeroMemory(&(PerIoData->overlapped), sizeof(OVERLAPPED)); // 清空内存
PerIoData->databuff.len = 1024;
PerIoData->databuff.buf = PerIoData->buffer;
PerIoData->operationType = 0; // read
WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
}
return 0;
}
// 发送信息的线程执行函数
DWORD WINAPI ServerSendThread(LPVOID IpParam)
{
while (1) {
char talk[200];
cin.getline(talk, 200);
int len;
for (len = 0; talk[len] != '\0'; ++len) {
// 找出这个字符组的长度
}
talk[len] = '\n';
talk[++len] = '\0';
printf("I Say:");
cout << talk;
WaitForSingleObject(hMutex, INFINITE);
for (UINT i = 0; i < clientGroup.size(); ++i) {
send(clientGroup[i]->socket, talk, 200, 0); // 发送信息
}
ReleaseMutex(hMutex);
}
return 0;
}