这几天在学习多线程和网络socket模型,结合人宅老师的C++教程和windows提供的api参考以及网上的一些资料写下这些,记录一下。
服务端代码。
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h>
#include <list>
#include <process.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
struct IoData
{
IoData()
{
ZeroMemory(this, sizeof(IoData));
}
OVERLAPPED OverL;
CHAR buffer[1024];
byte Type;
DWORD Len;
WSABUF wasbuffer;
};
class FClient
{
public:
FClient(SOCKET S, SOCKADDR_IN InSin);
virtual ~FClient() {};
BOOL Recv();
BOOL Send();
public:
SOCKET ClientSocket;
SOCKADDR_IN Sin;
IoData Data;
};
FClient::FClient(SOCKET S, SOCKADDR_IN InSin)
:ClientSocket(S),
Sin(InSin)
{
}
BOOL FClient::Recv()
{
DWORD Flag = 0;
DWORD Len = 0;
Data.Type = 0;
Data.wasbuffer.buf = Data.buffer;
Data.wasbuffer.len = 1024;
//参考https://learn.microsoft.com/zh-cn/windows/win32/api/winsock2/nf-winsock2-wsarecv
if (WSARecv(
ClientSocket,
&Data.wasbuffer,
1,&Len,&Flag,&Data.OverL,NULL)== SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
return false;
}
}
return TRUE;
}
BOOL FClient::Send()
{
DWORD Flag = 0;
DWORD Len = 0;
Data.Type = 1;
Data.wasbuffer.buf = Data.buffer;
Data.wasbuffer.len = strlen(Data.buffer);
//参考https://learn.microsoft.com/zh-cn/windows/win32/api/winsock2/nf-winsock2-wsasend
if (WSASend(
ClientSocket,
&Data.wasbuffer,
1, &Len, &Flag, (LPOVERLAPPED) & Data.OverL, NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
return false;
}
}
return TRUE;
}
list<FClient*>ClientList;
void ListRemove(FClient* Client)
{
//遍历客户端链表,将匹配的进程号排出
for (auto iter = ClientList.begin(); iter != ClientList.end(); iter++)
{
if (Client == (*iter))
{
ClientList.erase(iter);
break;
}
}
}
HANDLE cp = NULL; //创建完成端口
unsigned int __stdcall Run(void* content)
{
for (;;)
{
DWORD IOSize = -1;
LPOVERLAPPED lpOverlapped = NULL;
FClient* Client = NULL;
//队列任务模式
bool Ret = GetQueuedCompletionStatus(
cp, &IOSize, (LPDWORD)&Client, &lpOverlapped, INFINITE
);
if (Client == NULL && lpOverlapped == NULL)
{
break;
}
//排队成功了的话
if(Ret)
{
//检查接受的字节数
if (IOSize == 0)
{
ListRemove(Client); //0则直接移除等待队列
continue;
}
//https://blog.csdn.net/zyhse/article/details/109246875
//CONTAINING_RECORD宏的用途,构造数据
IoData* InData = CONTAINING_RECORD(lpOverlapped, IoData, OverL);
switch (InData->Type)
{
case 0: //接受 客户端发送的信息
{
Client->Data.Len = IOSize;
Client->Data.buffer[IOSize] = '\0';
printf(Client->Data.buffer);
char buffer[1024] = { 0 }; //测试给客户端返回的报文
sprintf_s(buffer, 1024, "hi I'm Server (%d)", Client->ClientSocket);
strcpy(Client->Data.buffer, buffer);
Client->Send();
break;
}
case 1: //发送
{
printf(Client->Data.buffer);
Client->Data.Len = 0;
if (!Client->Recv())
{
ListRemove(Client);
}
break;
}
}
}
else //失败了获取失败信息
{
DWORD Err_msg = GetLastError();
if (Err_msg == WAIT_TIMEOUT)
{
continue;
}
else if(lpOverlapped != NULL)
{
ListRemove(Client);
}
else
{
cout << Err_msg << endl;
break;
}
}
}
return 0;
}
int main()
{
SYSTEM_INFO systeminfo;
GetSystemInfo(&systeminfo);
int CpuNums = systeminfo.dwNumberOfProcessors;
static const int Threadnums = CpuNums * 2;
HANDLE hThreadHandle[128]; //线程的返回句柄
if ((cp = CreateIoCompletionPort(
((HANDLE)(LONG_PTR)-1),
NULL,
0,
0
)) == NULL)
{
return GetLastError();
}
for (int i = 0; i < Threadnums; i++)
{
hThreadHandle[i] = (HANDLE)_beginthreadex(
NULL,// 安全属性, 为NULL时表示默认安全性
0,// 线程的堆栈大小, 一般默认为0
Run, // 所要启动的线程函数
cp, // 线程函数的参数, 是一个void*类型, 传递多个参数时用结构体
0,// 新线程的初始状态,0表示立即执行,CREATE_SUSPENDED表示创建之后挂起
NULL); // 用来接收线程ID
}
//启动windows套接字
WSADATA WsaData;
int err = WSAStartup(MAKEWORD(2, 2), &WsaData);
if (err != 0)
{
return -1;
}
//创建Socket,监听Socket
SOCKET Listen = INVALID_SOCKET;
if ((Listen = WSASocket(
AF_INET,//IPV4
SOCK_STREAM, /*
TCP一种套接字类型,
它通过 OOB 数据传输机制提供排序的可靠双向基于连接的字节流。
此套接字类型使用 Internet 地址系列(AF_INET 或AF_INET6)
的传输控制协议(TCP) SOCK_DGRAM是udp */
0, //协议
NULL,//指向 WSAPROTOCOL_INFO 结构的指针,
//该结构定义要创建的套接字的特征。 如果此参数不为 NULL,
//则套接字将绑定到与指示 的 WSAPROTOCOL_INFO 结构关联的提供程序。
0,
WSA_FLAG_OVERLAPPED//
)) == INVALID_SOCKET)
{
WSACleanup();
return -1;
}
sockaddr_in Sin;
Sin.sin_family = AF_INET;
Sin.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
Sin.sin_port = htons(8888); //端口号
if (bind(Listen, (SOCKADDR*)&Sin, sizeof(Sin)) == SOCKET_ERROR)
{
closesocket(Listen);
WSACleanup();
return -1;
}
if (listen(Listen, SOMAXCONN))
{
closesocket(Listen);
WSACleanup();
return -1;
}
//iocp投递
SOCKET ClientAccpet = INVALID_SOCKET;
SOCKADDR_IN ClientAccpetAddr;
int ClientAccpetAddrLen = sizeof(ClientAccpetAddr);
for (;;)
{
//阻塞进程
ClientAccpet = WSAAccept(Listen, (SOCKADDR*)&ClientAccpetAddr,
&ClientAccpetAddrLen, NULL, 0);
if (ClientAccpet == SOCKET_ERROR)
{
break;
}
FClient* InClient = new FClient(ClientAccpet, ClientAccpetAddr);
ClientList.push_back(InClient);
//绑定完成端口
if (CreateIoCompletionPort(
(HANDLE)ClientAccpet,
cp,
(DWORD)InClient,
0
) == NULL)
{
break;
}
if (!InClient->Recv())
{
ListRemove(InClient);
}
}
//销毁线程池
for (int i = 0; i < Threadnums; i++)
{
PostQueuedCompletionStatus(cp, 0, NULL, NULL);
CloseHandle(hThreadHandle[i]);
}
WaitForMultipleObjects(Threadnums, hThreadHandle, TRUE, INFINITE);
return 0;
}
客户端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
int main()
{
//启动windows套接字
WSADATA WsaData;
int Ret = 0;
if ((Ret = WSAStartup(MAKEWORD(2, 1), &WsaData)) != 0)
{
return -1;
}
for (;;)
{
//创建套接字
SOCKET ClientSocket = socket(
AF_INET,
SOCK_STREAM,
IPPROTO_TCP //tcp协议 IPPROTO_IP IP协议
);
sockaddr_in Sin;
Sin.sin_family = AF_INET;
Sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
Sin.sin_port = htons(8888); //端口号
//检测连接
if (
connect(
ClientSocket,
(SOCKADDR*)&Sin,
sizeof(Sin)
) == SOCKET_ERROR)
{
//错误则关闭套接字
closesocket(ClientSocket);
break;
}
//发报文
char buffer[1024] = { 0 };
sprintf_s(buffer, 1024, "Hello I'm client %d \n", ClientSocket);
send(ClientSocket, buffer, strlen(buffer), 0);
memset(buffer, 0, 1024);
recv(ClientSocket, buffer, sizeof(buffer), 0);//阻塞
printf(buffer);
closesocket(ClientSocket);
}
WSACleanup();
return 0;
}
调试可以看到服务端启动后各个线程通过调用GetQueuedCompletionStatus()进入队列任务等待
服务端能成功接收客户端发来的报文。并反馈报文给客户端。
最终等32个线程跑完后,可以在控制台看到打印结果。