完成端口是对重叠IO的直接优化
在重叠IO上进行优化:
1.创建完成端口
HANDLE WINAPI CreateIoCompletionPort(
_In_ HANDLE FileHandle, // 填 INVALID_HANDLE_VALUE
_In_opt_ HANDLE ExistingCompletionPort, // 对现有i/o完成端口或空的句柄。 填NULL
_In_ ULONG_PTR CompletionKey, // 填0
_In_ DWORD NumberOfConcurrentThreads // 填0 表示默认CPU核数
);
2.绑定完成端口
HANDLE WINAPI CreateIoCompletionPort(
_In_ HANDLE FileHandle, // 将完成端口与SOCKET绑定在一起
_In_opt_ HANDLE ExistingCompletionPort, // 完成端口的变量
_In_ ULONG_PTR CompletionKey, // 再次传递socketServer 也可以传递一个下标(做编号) 与系统接收到的对应的数据关联在一起
_In_ DWORD NumberOfConcurrentThreads // 忽略此参数 填0
);
3.创建线程
HANDLE CreateThread( // 函数功能创建一根线程
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程句柄是否能被继承 NULL(指定线程的执行权限 NULL默认)
SIZE_T dwStackSize, // 线程栈大小 填0,系统使用默认大小(1M 1兆大小)以字节位单位
LPTHREAD_START_ROUTINE lpStartAddress, // 线程函数地址 线程函数
__drv_aliasesMem LPVOID lpParameter, // 外部给线程传递数据
DWORD dwCreationFlags, // 填 0 则线程立即执行
LPDWORD lpThreadId // 线程ID 可以填NULL
);
4.获取系统线程
// 声明计算机的体系结构体对象
SYSTEM_INFO SystemInfo;
// 获取系统信息
GetSystemInfo(&SystemInfo);
// 获取线程数量
DWORD nProsseceCount = SystemInfo.dwNumberOfProcessors;
5.处理线程函数
// 5.线程函数
DWORD WINAPI ThreadProc( LPVOID lpParameter);
6.从消息队列中取消息
// 尝试从指定的输入完成端口删除输入完成数据包的队列
BOOL GetQueuedCompletionStatus( // 从消息队列中取一个一个
HANDLE CompletionPort, // 完成端口 要从主函数中传递进来
LPDWORD lpNumberOfBytesTransferred, // 接收或发送得字节数
PULONG_PTR lpCompletionKey, // 完成端口函数得参数3传递进来的
LPOVERLAPPED *lpOverlapped, // 重叠结构
DWORD dwMilliseconds // 等待时间 当没有客户端响应时候,通知队列里什么都没有,填 INFINITE进行一直等待
);
服务端代码:
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mswsock.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "Mswsock.lib")
/*
重叠IO:
重叠IO是Windows提供的一种异步读写文件的机制
正常读写文件(socket本质就是文件操作),如recv,是阻塞的,
等协议缓存区中的数据全部复制进行自定义的buffer字符数组里,
函数才结束并返回复制的个数,写也一样,同一时间只能读写一个,其他的都被阻塞,
只能等读写操作完成之后阻塞才被解除
重叠IO机制读写,将读的指令以及自定义的buffer投给操作系统,然后函数直接返回,操作系统独立开个线程,
将数据复制进自定义buffer,数据复制期间,我们不用管,读写过程变成了异步,可以同时投递多个读写操作
重叠IO 是将accept recv send 优化成了异步过程,被AcceptEx WSARecv WSASend 函数代替了
重叠IO 是对基本C/S模型的直接优化
异步选择模型、事件选择模型、重叠IO模型的区别:
异步选择模型
把消息与socket绑在一起,然后系统以消息机制处理反馈
事件选择模型
把事件与socket绑在一起,然后系统以事件机制处理反馈
重叠IO模型
把重叠结构与socket绑在一起,然后系统以重叠IO机制处理反馈
重叠IO--事件通知逻辑:
调用AcceptEx WSARecv WSASend投递
被完成的操作,事件信号置成有信号
调用WSAWaitForMultipleEvents获取事件信号
完成端口:
完成端口也是Windows的一种机制,是在重叠IO模型基础上的优化
完成端口与重叠IO的区别:
事件通知:无序,要不停的进行事件询问循环,
缺点:延迟高,做了很多无用功
完成例程:每个客户端都有一个线程去调用回调函数
缺点:线程数量太多,
完成端口:1.模仿消息队列,创建一个通知队列,系统创建
2.创建最佳数量的线程,
优点:保证有序,不做无用功
充分利用CPU的性能
完成端口代码逻辑:
原理:
将重叠套接字(客户端+服务端)与一个完成端口(一个类型的变量)绑定在一起
使用AcceptEx WSARecv WSASend 投递请求
当系统异步完成请求,就会把通知存进一个队列,我们就叫它通知队列,该队列由操作系统创建,维护
完成端口可以理解为这个队列的头,我们通过GetQueuedCompletionStatus从队列头往外拿,一个一个处理
代码:
创建完成端口 CreateloCompletionPort
将完成端口与socket绑定 CreateloCompletionPort
创建指定数目的线程 CPU核数一样 CreateThread
线程内部 GetQueuedCompletionStatus
分类处理
使用AcceptEx WSARecv WSASend 投递请求
主线程阻塞
*/
// 声明socket数组用于装所有的socket
SOCKET g_allsock[1024];
// 声明重叠结构体数组
OVERLAPPED g_allOlp[1024];
// 声明记录装socket的个数
int g_count;
// 声明接收客户端的信息的缓存区
char g_str[1024];
// 声明变量用于获取完成端口
HANDLE hPort;
// 声明 获取线程个数
DWORD nProsseceCount;
// 声明获取线程的指针
HANDLE* g_Thread; // 用于动态申请内存空间,通过获取的线程个数创建相应的线程
// 定义一个标记 用于判断是否继续进行循环
BOOL g_flag = TRUE;
// 声明 封装AcceptEx函数
int PostAcceptEx();
// 声明 封装WSARecv函数
int PostWSARecv(int index);
// 声明 将WSASend封装成函数
int PostWSASend(int index);
// 将关闭所有的socket和对应的重叠IO事件对象封装成函数
void Clear();
// 与 SetConsoleCtrlHandler 函数一起使用的应用程序定义的函数。
// 控制台进程使用此函数来处理由进程接收的控制信号。
// 收到信号后,系统会在进程中创建一个新线程来执行该函数
BOOL WINAPI fun(DWORD dwCtrlType);
// 1.打开网络库并检验版本
void OpenAndCheckVersion();
// 2.创建服务端socket
SOCKET CreateSocket();
// 3.绑定端口与ip地址
void Bind(SOCKET sock);
// 4.进行监听
void Listen(SOCKET sock);
// 8.线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter);
int main()
{
// 监视窗口关闭按钮
SetConsoleCtrlHandler(fun, TRUE);
// 1.打开网络库并检验版本
OpenAndCheckVersion();
// 2.创建服务端socket
SOCKET socketServer = CreateSocket();
// 3.绑定端口与ip地址
Bind(socketServer);
// 将socket装进socket结合中
g_allsock[g_count] = socketServer;
// 创建事件,将对应的事件装进结合中
g_allOlp[g_count].hEvent = WSACreateEvent();
// 注意记录的个数要++
g_count++;
// 5.创建完成端口 CreateIoCompletionPort
// 参数1:如果指定了INVALID_HANDLE_VALUE 该函数将创建一个i/o完成端口,而不将其与文件句柄关联起来
// 参数2:对现有i/o完成端口或空的句柄, 参数3:填0,
// 参数4:操作系统可以允许并发处理i/o完成端口的i/o完成数据包的最大线程数,如果完成端口参数不是NULL 则将忽略此参数
// 参数4填0 表示默认CPU核数
hPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
// 判断完成端口是否成功创建
if (NULL == hPort)
{
// 获取错误码
int a = GetLastError();
printf("创建完成端口失败,该错误码为:%d\n", a);
// 关闭socket 和 对应的重叠IO事件对象
closesocket(socketServer);
WSACloseEvent(g_allOlp[g_count].hEvent);
// 关闭网络库
WSACleanup();
return -1;
}
// 6.进行绑定完成端口 CreateIoCompletionPort
// 参数1:将完成端口与SOCKET绑定在一起, 参数2:完成端口的变量, 、
// 参数3:再次传递socketServer 也可以传递一个下标(做编号)与系统接收到的数据关联在一起
// 参数4:忽略此参数 填0
HANDLE hPort1 = CreateIoCompletionPort((HANDLE)socketServer, hPort, 0, 0);
// 判断返回值是否与CreateIoCompletionPort函数参数2完成端口的变量相等
if (hPort1 != hPort)
{
// 获取错误码
int a = GetLastError();
printf("创建完成端口失败,该错误码为:%d\n", a);
// 关闭socket 和 对应的重叠IO事件对象
closesocket(socketServer);
WSACloseEvent(g_allOlp[g_count].hEvent);
// 关闭完成端口
CloseHandle(hPort);
// 关闭网络库
WSACleanup();
return -1;
}
// 4.进行监听
Listen(socketServer);
// 将AcceptEx 封装成函数
// 判断AcceptEx返回值
if (0 != PostAcceptEx())
{
// 出错了
Clear();
// 清理网络库
WSACleanup();
return 0;
}
// 7.创建线程
// 自动获取系统线程个数
// 声明计算机的体系结构体对象
SYSTEM_INFO SystemInfo;
// 获取系统信息
GetSystemInfo(&SystemInfo);
// 获取线程个数
nProsseceCount = SystemInfo.dwNumberOfProcessors;
// 动态申请内存空间 注意要乘上获取的线程个数
g_Thread = (HANDLE*)malloc(sizeof(HANDLE) * nProsseceCount);
// 判空
if (NULL == g_Thread)
{
printf("g_Thread malloc fail\n");
return -1;
}
// 通过获取的线程数量进行创建线程
for (int i = 0; i < nProsseceCount; i++)
{
// 参数1:线程句柄是否能被继承, 参数2:线程栈大小, 参数3:线程函数地址(线程函数), 参数4:填0 则线程立即执行, 参数5:线程ID,可以填NULL
g_Thread[i] = CreateThread(NULL, 0, ThreadProc, hPort, 0, NULL);
// 判断是否成功创建线程
if (NULL == g_Thread[i])
{
// 获取错误码
int a = GetLastError();
printf("创建线程失败, 获取的错误码为%d\n", a);
// 关闭socket 和 对应的重叠IO事件对象
closesocket(socketServer);
WSACloseEvent(g_allOlp[g_count].hEvent);
// 关闭完成端口
CloseHandle(hPort);
// 关闭网络库
WSACleanup();
return -1;
}
}
// 主线程阻塞
while (1)
{
Sleep(1000); // 单位毫秒
}
// 释放线程
for (int i = 0; i < nProsseceCount; i++)
{
CloseHandle(g_Thread[i]);
}
// 释放动态申请的线程变量
free(g_Thread);
// 关闭socket和对应的重叠IO事件对象
Clear();
// 关闭完成端口
CloseHandle(hPort);
// 清理网络库
WSACleanup();
system("pause");
return 0;
}
// 将关闭所有的socket和对应的重叠IO事件对象封装成函数
void Clear()
{
// 关闭所有的socket和对应的重叠IO事件对象
for (int i = 0; i < g_count; i++)
{
closesocket(g_allsock[i]);
WSACloseEvent(g_allOlp[i].hEvent);
}
}
// 与 SetConsoleCtrlHandler 函数一起使用的应用程序定义的函数
BOOL WINAPI fun(DWORD dwCtrlType)
{
switch (dwCtrlType)
{
case CTRL_CLOSE_EVENT:
// 关闭socket和对应的重叠IO事件对象
Clear();
// 将标记置成 FALSE
g_flag = FALSE;
// 释放线程
for (int i = 0; i < nProsseceCount; i++)
{
CloseHandle(g_Thread[i]);
}
// 释放动态申请的线程变量
free(g_Thread);
// 关闭完成端口
CloseHandle(hPort);
break;
}
return TRUE;
}
// 1.打开网络库并检验版本
void OpenAndCheckVersion()
{
// 1,打开网络库
WORD version = MAKEWORD(2, 2); //设置要使用的库的版本 MAKEWORD(主版本,副版本);
WSADATA WSAData;
int n = WSAStartup(version, &WSAData);
// 检验是否成功打开网络库
if (0 != n)
{
// 打开网络库失败,通过n 返回的错误码,给出提示
switch (n)
{
case WSASYSNOTREADY:
printf("重启电脑试试,或者检查ws_2_32库是否存在\n");
break;
case WSAVERNOTSUPPORTED:
printf("当前库版本号不支持,尝试更换版本试试\n");
break;
case WSAEPROCLIM:
printf("已达到对Windows套接字实现支持的任务数量的限制\n");
break;
case WSAEINPROGRESS:
printf("正在阻止Windows Sockets 1.1操作,当前函数运行期间,因为某些原因造成阻塞\n");
break;
case WSAEFAULT:
printf("lpWASData参数不是有效的指针,参数写错了\n");
break;
default:
break;
}
}
// 2.校验版本
if (2 != HIBYTE(WSAData.wVersion) || 2 != LOBYTE(WSAData.wVersion))
{
// 打开版本号 2.2 失败
printf("打开版本号 2.2 失败, 正在退出程序...\n");
// 关闭网络库
WSACleanup();
// 退出程序
return;
}
}
// 2.创建服务端socket
SOCKET CreateSocket()
{
// 3.创建套接字 socket 重叠IO 要将socket改成 WSASocket
/*SOCKET WSAAPI WSASocketA(// 创建一个用于异步操作的SOCKET
int af, // 地址的类型 AF_INET
int type, // 套接字类型 SOCK_STREAM
int protocol, // 协议的类型 IPPROTO_TCP
LPWSAPROTOCOL_INFOA lpProtocolInfo, // 设置套接字详细的属性 这里直接填NULL
GROUP g, // 一组socket的组ID,一次想操作多个socket 这里填0
DWORD dwFlags // 指定套接字属性 这里填 WSA_FLAG_OVERLAPPED 创建一个供重叠IO模型使用的socket
);*/
SOCKET sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);;
// 检验是否成功创建套接字
if (INVALID_SOCKET == sock)
{
// 创建套接字失败
// 获取错误码
int error = WSAGetLastError();
printf("创建套接字失败,返回错误号为:%d\n", error);
// 关闭网络库
WSACleanup();
// 退出程序
return 0;
}
return sock;
}
// 3.绑定端口与ip地址
void Bind(SOCKET sock)
{
// 4.绑定地址与端口号
// 创建bind() 函数所需的第二个参数的结构体,该结构体装 地址类型,IP地址,端口号
struct sockaddr_in server_s;
server_s.sin_family = AF_INET; // 地址类型
server_s.sin_port = htons(12345); // 端口号
server_s.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // ip地址
int isbind = bind(sock, (struct sockaddr*)&server_s, sizeof(server_s));
// 检验绑定地址与端口号是否成功
if (SOCKET_ERROR == isbind)
{
// 绑定地址与端口号失败
// 获取错误号
int error = WSAGetLastError();
printf("绑定端口号和IP地址失败,返回错误号为:%d\n", error);
// 关闭套接字
closesocket(sock);
// 关闭网络库
WSACleanup();
// 退出程序
return;
}
}
// 4.进行监听
void Listen(SOCKET sock)
{
// 5.进行监听
if (SOCKET_ERROR == listen(sock, SOMAXCONN))
{
// 套接字中断了侦听传入连接的状态
// 获取错误号
int error = WSAGetLastError();
printf("监听失败,返回错误号为:%d\n", error);
// 关闭套接字
closesocket(sock);
// 关闭网络库
WSACleanup();
// 退出程序
return;
}
}
// 定义 封装AcceptEx函数
int PostAcceptEx()
{
/*BOOL AcceptEx( // 投递服务器socket,异步接收连接
SOCKET sListenSocket, // 服务器socket
SOCKET sAcceptSocket, // 连接服务器的客户端的socket
PVOID lpOutputBuffer, // 缓存区的指针,接收在新连接上发送的第一个数据
DWORD dwReceiveDataLength, // 设置为0,则表示取消了3的功能
DWORD dwLocalAddressLength, // 为本地地址信号保留的字节数。此值必须至少比使用的传输协议的最大地址长度16个字节
DWORD dwRemoteAddressLength, // 为远程地址信息保留的字节数。此值必须至少比使用的传输协议的最大地址长度16个字节,不能为0
LPDWORD lpdwBytesReceived, // 该函数可以接收第一次客户端发来的信息,如果这个刚好是调用时候接收到了,也即立即接收到了(客户端连接的同时发送了信息),这个时候装着接收到的字节数
LPOVERLAPPED lpOverlapped // 重叠结构体
);*/
// 创建AcceptEx函数中参数2所需的socket,连接服务器的客户端的socket
g_allsock[g_count] = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
// 创建对应的重叠IO事件对象
g_allOlp[g_count].hEvent = WSACreateEvent();
// 定义AcceptEx函数所需的缓存区字符串数组
char str[1024] = { 0 };
// 定义AcceptEx函数中参数7所需的字节
DWORD dwRecvied;
BOOL nAcc = AcceptEx(g_allsock[0], g_allsock[g_count], str, 0, sizeof(struct sockaddr_in) + 16,
sizeof(struct sockaddr_in) + 16, &dwRecvied, &g_allOlp[0]);
// 判断 AcceptEx的返回值
// 调用WSAGetLastError函数查错误码
int a = WSAGetLastError();
if (ERROR_IO_PENDING != a)
{
// 延迟等待 没有客户端进行链接请求
return 1;
}
return 0;
}
// 定义 封装WSARecv函数 参数用于标记是哪个socket和对应的重叠IO事件对象
int PostWSARecv(int index)
{
/*int WSAAPI WSARecv( // 函数作用:投递异步接收信息
SOCKET s, // 客户端socket
LPWSABUF lpBuffers, // 接收后的新存储buffer 结构体WSABUF
DWORD dwBufferCount, // 是参数2是WSABUF结构体的个数
LPDWORD lpNumberOfBytesRecvd, // 接收成功的话,这里装着成功接收到的字节数
LPDWORD lpFlags, // 指向用于修改WSARecv函数调用行为的标准的指针
LPWSAOVERLAPPED lpOverlapped, // 重叠结构
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 回调函数 这里填NULL
);*/
// 声明函数WSARecv中参数2所需的结构体
WSABUF wsaBuf;
// 给结构体成员赋值
wsaBuf.buf = g_str; // 指向字符数组的指针
wsaBuf.len = sizeof(g_str); // 字节数
// 声明函数WSARecv中参数4所需的成功接收到的字节数
DWORD NumberOfBytesRecvd;
// 声明函数WSARecv中参数5所需的用于修改WSARecv函数调用行为的标准的指针
DWORD Flags = 0;
// WSARecv函数的参数1:客户端的socket,参数6:客户端的重叠IO事件对象结构
int nWSAR = WSARecv(g_allsock[index], &wsaBuf, 1, &NumberOfBytesRecvd, &Flags, &g_allOlp[index], NULL);
// 获取错误码
int a = WSAGetLastError();
if (WSA_IO_PENDING != a)
{
// 延迟完成
return 1;
}
return 0;
}
// 定义 将WSASend封装成函数
int PostWSASend(int index)
{
/*int WSAAPI WSASend( // 投递异步发送信息
SOCKET s, // 客户端的socket
LPWSABUF lpBuffers, // 接收后的信息存buffer 结构体WSABUF
DWORD dwBufferCount, // 是参数2结构体WSABUF的个数
LPDWORD lpNumberOfBytesSent, // 接收成功的话,这里装着发送的字节数
DWORD dwFlags, // 函数调用行为的标记
LPWSAOVERLAPPED lpOverlapped, // 重叠结构
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 回调函数 这里直接填NULL
);*/
// 声明函数WSASend所需的结构体
WSABUF wsaBuf;
// 给结构体成员赋值
wsaBuf.buf = "成功调用send";
wsaBuf.len = sizeof(wsaBuf.buf);
// 声明WSASend函数中参数4 所需的接收字节数
DWORD NumberOfBytesSent;
// 声明WSASend函数中参数5 所需的函数调用行为的的标记
DWORD dwFlags = 0;
int nWSAS = WSASend(g_allsock[index], &wsaBuf, 1, &NumberOfBytesSent, dwFlags, &g_allOlp[index], NULL);
if (0 == nWSAS)
{
// 立即完成
// 打印数据
printf("send ok\n");
return 0;
}
else
{
// 获取错误码
int a = WSAGetLastError();
if (WSA_IO_PENDING == a)
{
// 延迟完成
return 0;
}
else
{
return a;
}
}
}
// 8.线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
// 通过GetQueuedCompletionStatus 函数获取消息队列消息
// 定义GetQueuedCompletionStatus函数中参数1所需的 完成端口,通过线程函数参数传递进来
HANDLE port = (HANDLE)lpParameter;
// 声明GetQueuedCompletionStatus函数中参数2所需的 接收或发送的字节数 可以通过该参数判断客户端是否下线
DWORD NumberOfBytesTransferred;
// 声明GetQueuedCompletionStatus函数中参数3所需的 完成端口函数的参数
ULONG_PTR index;
// 声明GetQueuedCompletionStatus函数中参数4所需的 重叠结构
LPOVERLAPPED lpOverlapped;
// 循环获取任务 进行分类处理
while (g_flag)
{
// GetQueuedCompletionStatus从消息队列一个一个中取消息
// 参数1:完成端口 要从主函数中传递进来,参数2:接收或发送的字节数,参数3:完成端口函数的参数3传递进来的,参数4:重叠结构,
// 参数5:等待时间,当没有客户端响应时,通知队列里面什么都没有,填INFINITE进行一直等待
BOOL nFlag = GetQueuedCompletionStatus(port, &NumberOfBytesTransferred, &index, &lpOverlapped, INFINITE);
// 判断是否成功获取消息
if (FALSE == nFlag)
{
// 获取错误码
int a = GetLastError();
// 判断错误码
if (64 == a)
{
printf("Client force close\n");
}
printf("GetQueuedCompletionStatus error:%d\n", a);
continue;
}
// 进行分类处理
// 通过GetQueuedCompletionStatus函数参数3
// 判断index下标是否为0 如果为0,代表是服务端,否则是客户端
if (0 == index)
{
// 处理服务端
// 1.将服务端socket绑定 -- 创建完成端口
HANDLE hPort1 = CreateIoCompletionPort((HANDLE)g_allsock[g_count], hPort, g_count, 0);
// 判断是否成功连接完成端口
if (hPort != hPort1)
{
// 获取错误码
int a = GetLastError();
printf("CreateIoCompletionPort error:%d\n", a);
// 释放socket
closesocket(g_allsock[g_count]);
continue;
}
// 绑定之后进行accept
printf("accept success\n");
//
PostWSASend(g_count);
// 服务端接收链接,之后进行接收数据(recv),或是发送数据操作(send)
// 1.调用WSARecv 将WSARecv封装成函数
PostWSARecv(g_count); // 注意参数要填g_count 因为客户端socket装到 g_allsock[g_count] 中
// 2.根据情况进行WSASend 这里就不写了
// 3.有效个数++,当接收客户端的连接之后,要将客户端socket装进结合中,记录装进结合socket的个数就要进行++,
g_count++; // 每装一个socket和对应的重叠IO事件对象到相应的数组中,记录的个数就要++
// 自己调用自己 接收完客户端之后,进行等待其他客户端的连接请求
PostAcceptEx();
}
else
{
// 处理客户端
// 判断客户端是否下线 通过GetQueuedCompletionStatus函数参数2
if (0 == NumberOfBytesTransferred)
{
// 客户端下线
printf("Client close\n");
// 关闭socket
closesocket(g_allsock[index]);
// 关闭对应的重叠IO 事件对象
WSACloseEvent(g_allOlp[index].hEvent);
// 赋0 置空
g_allsock[index] = 0;
g_allOlp[index].hEvent = NULL;
}
else
{
// 进行收发消息
// 判断接收缓存区的第一个字节数
if (0 != g_str[0])
{
// 接收消息
printf("%s\n", g_str);
// 清空缓存区
memset(g_str, 0, 1024);
PostWSARecv(index);
}
else
{
continue;
}
}
}
}
return 0;
}
客户端代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Winsock.h>
#pragma comment(lib,"ws2_32.lib")
// 1.打开网络库并校验版本
void OpenAndCheckVersion()
{
// 1,打开网络库
WORD version = MAKEWORD(2, 2); //设置要使用的库的版本 MAKEWORD(主版本,副版本);
WSADATA WSAData;
int n = WSAStartup(version, &WSAData);
// 检验是否成功打开网络库
if (0 != n)
{
// 打开网络库失败,通过n 返回的错误码,给出提示
switch (n)
{
case WSASYSNOTREADY:
printf("重启电脑试试,或者检查ws_2_32库是否存在\n");
break;
case WSAVERNOTSUPPORTED:
printf("当前库版本号不支持,尝试更换版本试试\n");
break;
case WSAEPROCLIM:
printf("已达到对Windows套接字实现支持的任务数量的限制\n");
break;
case WSAEINPROGRESS:
printf("正在阻止Windows Sockets 1.1操作,当前函数运行期间,因为某些原因造成阻塞\n");
break;
case WSAEFAULT:
printf("lpWASData参数不是有效的指针,参数写错了\n");
break;
default:
break;
}
}
// 2.校验版本
if (2 != HIBYTE(WSAData.wVersion) || 2 != LOBYTE(WSAData.wVersion))
{
// 打开版本号 2.2 失败
printf("打开版本号 2.2 失败, 正在退出程序...\n");
// 关闭网络库
WSACleanup();
// 退出程序
return;
}
}
// 2.创建服务端socket
SOCKET CreateSocket()
{
// 3.创建套接字 socket 创建服务端的socket
SOCKET socketSer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 检验是否成功创建套接字
if (INVALID_SOCKET == socketSer)
{
// 创建套接字失败
// 获取错误码
int error = WSAGetLastError();
printf("创建套接字失败,返回错误号为:%d\n", error);
// 关闭网络库
WSACleanup();
// 退出程序
return 0;
}
return socketSer;
}
// 3.连接服务端
void ConnectToServer(SOCKET sock)
{
// 4.连接到服务器
// 创建connect() 函数中参数2所需的结构体 用来装IP地址和端口号
sockaddr_in client_s;
//给结构体成员赋值
client_s.sin_family = AF_INET; //IP地址类型
client_s.sin_port = htons(12345); //端口号
client_s.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int nCon = connect(sock, (struct sockaddr*)&client_s, sizeof(client_s));
// 检验是否成功连接到服务端
if (SOCKET_ERROR == nCon)
{
// 连接服务端失败
// 获取错误码
int error = WSAGetLastError();
printf("连接服务端失败,返回错误号为:%d\n", error);
// 关闭套接字
closesocket(sock);
// 关闭网络库
WSACleanup();
// 退出程序
return;
}
}
// 4.与服务端收发消息
void RecvAndSend(SOCKET sock)
{
// 只收一次 信息
// 定义接收服务端信息的缓存区
char recvBuf[1024] = { 0 };
int res = recv(sock, recvBuf, 1024, 0);
// 判断是否成功接收到信息
if (0 == res)
{
printf("服务端下线了\n");
return;
}
else if (SOCKET_ERROR == res)
{
// 获取错误码
int error = WSAGetLastError();
printf("连接服务端失败,返回错误号为:%d", error);
printf("\n");
return;
}
else
{
// 打印信息
printf("服务端say: %s\n", recvBuf);
}
printf("我是客户端,正在发送信息,请稍等...\n");
// 5.与服务端进行收发信息
while (1)
{
char sendBuf[1024] = { 0 };
scanf_s("%s", sendBuf, 1024);
// 设置输入 0 时自动退出
if ('0' == sendBuf[0])
{
break; //退出循环
}
// 发信息
send(sock, sendBuf, 1024, 0);
}
}
int main()
{
// 1.打开网络库并校验版本
OpenAndCheckVersion();
// 2.创建服务端socket
SOCKET socketServer = CreateSocket();
// 3.连接服务端
ConnectToServer(socketServer);
// 4.与服务端收发消息
RecvAndSend(socketServer);
// 关闭套接字
closesocket(socketServer);
// 关闭网络库
WSACleanup();
system("pause");
return 0;
}
程序运行结果: