// tcp_iocp_serve.cpp
#include <WinSock2.h>
#include <Windows.h>
#include <vector>
#include <iostream>
using namespace std;
#pragma comment(lib, "Ws2_32.lib") // Socket编程需用的动态链接库
#pragma comment(lib, "Kernel32.lib") // IOCP需要用到的动态链接库
/**
* 结构体名称:PER_IO_DATA
* 结构体功能:重叠I/O需要用到的结构体,临时记录IO数据
**/
const int g_nDataBuffSize = 2 * 1024;
typedef struct
{
OVERLAPPED sOverLapped;
WSABUF sDatabuff;
char szBuffer[g_nDataBuffSize];
int nBufferLen;
int nOperationType;
}PER_IO_OPERATEION_DATA, *LPPER_IO_OPERATION_DATA, *LPPER_IO_DATA, PER_IO_DATA;
/**
* 结构体名称:PER_HANDLE_DATA
* 结构体存储:记录单个套接字的数据,包括了套接字的变量及套接字的对应的客户端的地址。
* 结构体作用:当服务器连接上客户端时,信息存储到该结构体中,知道客户端的地址以便于回访。
**/
typedef struct
{
SOCKET socket;
SOCKADDR_STORAGE clientaddr;
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
// 定义全局变量
const int g_nDefaultPort = 6000;
vector < PER_HANDLE_DATA* > g_vctClientGroup; // 记录客户端的向量组
HANDLE g_hMutex = CreateMutex(NULL, FALSE, NULL);
DWORD WINAPI ServerWorkThread(LPVOID lpCompletionPortID);
DWORD WINAPI ServerSendThread(LPVOID lpParam);
// 开始主函数
int main()
{
// 加载socket动态链接库
WORD wVersionRequested = MAKEWORD(2, 2); // 请求2.2版本的WinSock库
WSADATA wsaData; // 接收Windows Socket的结构信息
DWORD dwErr = WSAStartup(wVersionRequested, &wsaData);
if (0 != dwErr)
{
// 检查套接字库是否申请成功
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 hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (NULL == hCompletionPort)
{
// 创建IO内核对象失败
cerr << "CreateIoCompletionPort failed. Error:" << GetLastError() << endl;
system("pause");
return -1;
}
// 创建IOCP线程--线程里面创建线程池
// 确定处理器的核心数量
SYSTEM_INFO sSysInfo;
GetSystemInfo(&sSysInfo);
// 基于处理器的核心数量创建线程
for (DWORD i = 0; i < (sSysInfo.dwNumberOfProcessors * 2); ++i)
{
// 创建服务器工作器线程,并将完成端口传递到该线程
HANDLE hThreadHandle = CreateThread(NULL, 0, ServerWorkThread, hCompletionPort, 0, NULL);
if (NULL == hThreadHandle)
{
cerr << "Create Thread Handle failed. Error:" << GetLastError() << endl;
system("pause");
return -1;
}
CloseHandle(hThreadHandle);
}
// 建立流式套接字
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(g_nDefaultPort);
int nBindResult = bind(srvSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));
if (SOCKET_ERROR == nBindResult)
{
cerr << "Bind failed. Error:" << GetLastError() << endl;
system("pause");
return -1;
}
// 将SOCKET设置为监听模式
int nListenResult = listen(srvSocket, 10);
if (SOCKET_ERROR == nListenResult)
{
cerr << "Listen failed. Error: " << GetLastError() << endl;
system("pause");
return -1;
}
// 开始处理IO数据
cout << "本服务器已准备就绪,正在等待客户端的接入...\n";
// 创建用于发送数据的线程
HANDLE hSendThread = CreateThread(NULL, 0, ServerSendThread, 0, 0, NULL);
while (true)
{
PER_HANDLE_DATA * pPerHandleData = NULL;
SOCKADDR_IN saRemote;
int nRemoteLen = 0;
SOCKET acceptSocket;
// 接收连接,并分配完成端,这儿可以用AcceptEx()
nRemoteLen = sizeof(saRemote);
acceptSocket = accept(srvSocket, (SOCKADDR*)&saRemote, &nRemoteLen);
if (SOCKET_ERROR == acceptSocket)
{
// 接收客户端失败
cerr << "Accept Socket Error: " << GetLastError() << endl;
system("pause");
return -1;
}
// 创建用来和套接字关联的单句柄数据信息结构
pPerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA)); // 在堆中为这个PerHandleData申请指定大小的内存
pPerHandleData->socket = acceptSocket;
memcpy(&pPerHandleData->clientaddr, &saRemote, nRemoteLen);
g_vctClientGroup.push_back(pPerHandleData); // 将单个客户端数据指针放到客户端组中
// 将接受套接字和完成端口关联
CreateIoCompletionPort((HANDLE)(pPerHandleData->socket), hCompletionPort, (DWORD)pPerHandleData, 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->sOverLapped), sizeof(OVERLAPPED));
perIoData->sDatabuff.len = 1024;
perIoData->sDatabuff.buf = perIoData->szBuffer;
perIoData->nOperationType = 0; // read
DWORD dwRecvBytes;
DWORD dwFlags = 0;
WSARecv(pPerHandleData->socket, &(perIoData->sDatabuff), 1, &dwRecvBytes, &dwFlags, &(perIoData->sOverLapped), NULL);
}
system("pause");
return 0;
}
// 开始服务工作线程函数
DWORD WINAPI ServerWorkThread(LPVOID lpParam)
{
HANDLE hCompletionPort = (HANDLE)lpParam;
DWORD dwBytesTransferred = 0;
LPOVERLAPPED lpOverlapped;
LPPER_HANDLE_DATA perHandleData = NULL;
LPPER_IO_DATA perIoData = NULL;
DWORD dwRecvBytes = 0;
DWORD dwFlags = 0;
BOOL bRet = FALSE;
while (true)
{
bRet = GetQueuedCompletionStatus(hCompletionPort, &dwBytesTransferred, (PULONG_PTR)&perHandleData, (LPOVERLAPPED*)&lpOverlapped, INFINITE);
if (bRet == 0)
{
cerr << "GetQueuedCompletionStatus Error: " << GetLastError() << endl;
return -1;
}
perIoData = (LPPER_IO_DATA)CONTAINING_RECORD(lpOverlapped, PER_IO_DATA, sOverLapped);
// 检查在套接字上是否有错误发生
if (0 == dwBytesTransferred)
{
closesocket(perHandleData->socket);
GlobalFree(perHandleData);
GlobalFree(perIoData);
continue;
}
// 开始数据处理,接收来自客户端的数据
WaitForSingleObject(g_hMutex, INFINITE);
cout << "A Client says: " << perIoData->sDatabuff.buf << endl;
ReleaseMutex(g_hMutex);
// 为下一个重叠调用建立单I/O操作数据
ZeroMemory(&(perIoData->sOverLapped), sizeof(OVERLAPPED)); // 清空内存
perIoData->sDatabuff.len = 1024;
perIoData->sDatabuff.buf = perIoData->szBuffer;
perIoData->nOperationType = 0; // read
WSARecv(perHandleData->socket, &(perIoData->sDatabuff), 1, &dwRecvBytes, &dwFlags, &(perIoData->sOverLapped), NULL);
}
return 0;
}
// 发送信息的线程执行函数
DWORD WINAPI ServerSendThread(LPVOID /*lpParam*/)
{
while (true)
{
char szTalk[200] = {0};
gets_s(szTalk);
int nLen = 0;
for (nLen = 0; szTalk[nLen] != '\0'; ++nLen)
{
// 找出这个字符组的长度
}
szTalk[nLen] = '\n';
szTalk[++nLen] = '\0';
cout << "I Say:" << szTalk;
WaitForSingleObject(g_hMutex, INFINITE);
for (int i = 0; i < (int)g_vctClientGroup.size(); ++i)
{
send(g_vctClientGroup[i]->socket, szTalk, 200, 0); // 发送信息
}
ReleaseMutex(g_hMutex);
}
return 0;
}
// tcp_iocp_client.cpp
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <winsock2.h>
#include <Windows.h>
using namespace std;
#pragma comment(lib, "Ws2_32.lib") // Socket编程需用的动态链接库
SOCKET g_sockClient; // 连接成功后的套接字
HANDLE g_hBufferMutex = NULL; // 令其能互斥成功正常通信的信号量句柄
const int g_nDefaultPort = 6000;
DWORD WINAPI SendMessageThread(LPVOID lpParam);
DWORD WINAPI ReceiveMessageThread(LPVOID lpParam);
int main()
{
// 加载socket动态链接库(dll)
WORD wVersionRequested;
WSADATA wsaData; // 这结构是用于接收Wjndows Socket的结构信息的
wVersionRequested = MAKEWORD(2, 2); // 请求2.2版本的WinSock库
int nErr = WSAStartup(wVersionRequested, &wsaData);
if (nErr != 0)
{
// 返回值为零的时候是表示成功申请WSAStartup
return -1;
}
if (LOBYTE(wsaData.wVersion) != 2
|| HIBYTE(wsaData.wVersion) != 2)
{
// 检查版本号是否正确
WSACleanup();
return -1;
}
// 创建socket操作,建立流式套接字,返回套接字号sockClient
g_sockClient = socket(AF_INET, SOCK_STREAM, 0);
if (g_sockClient == INVALID_SOCKET)
{
printf("Error at socket():%ld\n", WSAGetLastError());
WSACleanup();
return -1;
}
// 将套接字sockClient与远程主机相连
// int connect( SOCKET s, const struct sockaddr* name, int namelen);
// 第一个参数:需要进行连接操作的套接字
// 第二个参数:设定所需要连接的地址信息
// 第三个参数:地址的长度
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本地回路地址是127.0.0.1;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(g_nDefaultPort);
while (SOCKET_ERROR == connect(g_sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
{
// 如果还没连接上服务器则要求重连
cout << "服务器连接失败,是否重新连接?(Y/N):";
char choice;
while (cin >> choice && (!((choice != 'Y' && choice == 'N') || (choice == 'Y' && choice != 'N'))))
{
cout << "输入错误,请重新输入:";
cin.sync();
cin.clear();
}
if (choice == 'Y')
{
continue;
}
else
{
cout << "退出系统中...";
system("pause");
return 0;
}
}
cin.sync();
cout << "本客户端已准备就绪,用户可直接输入文字向服务器反馈信息。\n";
send(g_sockClient, "\nAttention: A Client has enter...\n", 200, 0);
g_hBufferMutex = CreateSemaphore(NULL, 1, 1, NULL);
HANDLE hSendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);
HANDLE hReceiveThread = CreateThread(NULL, 0, ReceiveMessageThread, NULL, 0, NULL);
WaitForSingleObject(hSendThread, INFINITE); // 等待线程结束
closesocket(g_sockClient);
CloseHandle(hSendThread);
CloseHandle(hReceiveThread);
CloseHandle(g_hBufferMutex);
WSACleanup(); // 终止对套接字库的使用
printf("End linking...\n");
printf("\n");
system("pause");
return 0;
}
DWORD WINAPI SendMessageThread(LPVOID /*lpParam*/)
{
while (true)
{
string strTalk;
getline(cin, strTalk);
WaitForSingleObject(g_hBufferMutex, INFINITE); // P(资源未被占用)
if ("quit" == strTalk)
{
strTalk.push_back('\0');
send(g_sockClient, strTalk.c_str(), 200, 0);
break;
}
else
{
strTalk.append("\n");
}
printf("\nI Say:(\"quit\"to exit):");
cout << strTalk;
int nSendResult = send(g_sockClient, strTalk.c_str(), /*200*/strTalk.length() + 1, 0); // 发送信息
ReleaseSemaphore(g_hBufferMutex, 1, NULL); // V(资源占用完毕)
if (SOCKET_ERROR == nSendResult)
{
cout << "Send failed.Error:" << WSAGetLastError() << endl;
break ;
}
}
return 0;
}
DWORD WINAPI ReceiveMessageThread(LPVOID /*lpParam*/)
{
while (true)
{
char szRecvBuf[300] = {0};
int nRecvResult = recv(g_sockClient, szRecvBuf, 200, 0);
if (SOCKET_ERROR == nRecvResult)
{
cout << "Recv failed.Error:" << WSAGetLastError() << endl;
break ;
}
WaitForSingleObject(g_hBufferMutex, INFINITE); // P(资源未被占用)
printf("%s Says: %s", "Server", szRecvBuf); // 接收信息
ReleaseSemaphore(g_hBufferMutex, 1, NULL); // V(资源占用完毕)
}
return 0;
}