WINSOCK.053.重叠IO模型
Posted oldmao_2001
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WINSOCK.053.重叠IO模型相关的知识,希望对你有一定的参考价值。
文章目录
https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket
基于TCP/IP的网络编程有5种模型:
SELECT模型
事件选择模型
异步选择模型
重叠IO模型
完成端口模型
这次先讲第四种。
重叠IO模型介绍
重叠IO是Windows提供的一种异步读写文件的机制。
如果我们把网络发送消息,读取消息中的消息看成文件,那么SOCKET的本质就是文件操作。正常的读文件,例如recv,是阻塞的,等待协议缓冲区中的全部复制到我们自己定义的buff中,函数才能结束并返回复制的内容,如果多次调用recv函数,那么这些recv函数是依次一个个执行的。写(send)的过程也一样,同一时间只能执行一个send函数,其他的操作只能等。
重叠IO机制则是把上面描述的过程做成异步操作,将的指令以及我们自定义的buff投递给操作系统,然后函数直接返回,由操作系统独立打开一个线程,将数据复制到buff,这个过程,我们的应用可以做别的事情,也就意味我们可以同时投递多个读或写指令,同时进行多个读写操作。
从代码上看,就是将原来的accept、recv、send函数,转化为可异步执行的AcceptEx、WSARecv、WSASend函数。
关于异步读写这个我记得当年教SOCKET的时候有一个钓鱼的例子,不过书没找见了。
重叠IO的由来是由其结构体WSAOVERLAPPED得来
typedef struct _WSAOVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent;
} WSAOVERLAPPED, *LPWSAOVERLAPPED;
该结构体的前四个成员是保留给系统使用的,第五个成员WSAEVENT hEvent是事件对象的句柄。操作完事件句柄后,系统将WSAOVERLAPPED结构体中的第五个成员设置为有信号。
在使用的时候,我们就是将SOCKET和WSAOVERLAPPED绑定后,投递给操作系统,系统会以重叠IO机制处理反馈,反馈方式有两种:事件通知、完成例程。
对于事件通知而言:
1.调用AcceptEx WSARecv WSASend投递
2.操作系统将被完成的操作,事件信号置成有信号
3.调用WSAGetOverlappedResult获取事件信号
对于完成例程而言:
1.调用AcceptEx WSARecv WSASend投递
2.完成后自动调用回调函数
重叠IO模型代码逻辑
先看事件通知的反馈方式
1.创建事件(optional)、SOCKET数组,重叠结构体数组(根据下标来进行对应,相同下标是一组)
2.创建重叠IO模型使用的SOCKET:WSASocket
3.投递AcceptEx
3.1立即完成,此时有客户端连接
3.1.1对客户端套接字投递WSARecv
3.1.1.1有客户端消息,系统空闲,立即完成,跳3.1.1
3.1.1.2无客户端消息,跳3.3
3.1.2根据需求对客户端套接字投递WSASend
3.1.2.1有消息要发送,系统空闲,立即完成,跳3.1.2
3.1.2.2无消息要发送,跳3.3
3.1.3如果需要连接客户端跳3
3.2延迟完成,此时没有客户端连接,跳3.3
3.3循环等待信号(WSAWaitForMultipleEvents)
3.3.1 没信号,继续等
3.3.2 有信号,先获取重叠结构上的信息(WSAGetOverlappedResult)
3.3.2.1 如果信号是服务器端上检测到有客户端连接,跳转3(这个步骤和3.3.2.2不可以调换顺序)
3.3.2.2 如果信号是客户端退出,则关闭客户端SOCKET,并从数组删除客户端的信息
3.3.2.3 如果信号是需要接收信息,跳转3.1.1
3.3.2.4 如果信号是需要发送信息,跳转3.1.2
说明:由于AcceptEx WSARecv WSASend每调用一次,只会处理一次,要处理多次就要多次调用,因此上面有跳转。
重叠IO模型代码实现
1.-5.
和之前一样,重叠IO模型服务器端的SOCKET代码套路的前面部分是一样的:
1.打开网络库
2.校验版本
3.创建SOCKET 这里要用WSASocket来创建
再次强调:WSA是windows socket async的缩写
SOCKET WSAAPI WSASocketA(
int af,
int type,
int protocol,
LPWSAPROTOCOL_INFOA lpProtocolInfo,
GROUP g,
DWORD dwFlags
);
前面几个参数可以参考第一篇简单通信模型,这里不展开介绍。
参数1:地址类型
参数2:套接字类型
参数3:协议类型
参数4:协议属性设置,不用则设置为NULL,可以设置的内容包括:
发送数据是否需要连接
是否保证数据完整到达(选择TCP面向连接的协议或UDP面向传输的协议的处理方式)
参数3填0,那么匹配哪个协议在这里设置,貌似是一个列表,按列表的先后顺序进行匹配
设置传输接收字节数限制
设置套接字权限,设置后进行相应套接字操作的时候会有相应的提示,例如发送数据会触发某个操作,具体操作在参数6里面设置WSA_FLAG_ACCESS_SYSTEM_SECURITY
其他很多保留字段,供以后拓展使用
以上参数4的设置,实际是修改WSAPROTOCOL _INFO结构的指针,具体可以看:
https://docs.microsoft.com/en-us/windows/win32/api/winsock2/ns-winsock2-wsaprotocol_infoa
参数5:保留参数,默认写0(看名字应该是组ID,可以一次操作多个socket,多/组播?)
参数6:指定套接字属性/设置。
这里用:WSA_FLAG_OVERLAPPED,表示创建重叠IO模型的SOCKET
还有别的一些宏:
WSA_FLAG_ACCESS_SYSTEM_SECURITY:套接字权限设置,配合参数4中的设置一起使用
WSA_FLAG_NO_HANDLE_INHERIT:套接字不可继承,这个在多线程SOCKET里面用。在多线程开发中,子线程会继承父线程的SOCKET,即主线程创了一个SOCKET,那么子线程有两种使用方式:
方式 | 方法 |
---|---|
共享 | 直接用父类的,父子都使用这一个SOCKET(只用关闭一次) |
继承(默认) | 把父类的这个SOCKET复制一份,自己用,这两父子用两个SOCKET,但是本质一样(要关闭两次) |
多播(1vs.n)用的:
WSA_FLAG_MULTIPOINT_C_ROOT
WSA_FLAG_MULTIPOINT_C_LEAF
WSA_FLAG_MULTIPOINT_D_ROOT
WSA_FLAG_MULTIPOINT_D_LEAF
返回值:
成功返回可用的SOCKET句柄
失败返回INVALID_SOCKET
具体看:
https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
4.绑定地址与端口
5.开始监听
再往下的步骤就和基本框架有很大不同,详细写
6.投递AcceptEx
这个玩意的流程在上面有写,方便看就直接copy下来:
3.投递AcceptEx
3.1立即完成,此时有客户端连接
3.1.1对客户端套接字投递WSARecv
3.1.1.1有客户端消息,系统空闲,立即完成,跳3.1.1
3.1.1.2无客户端消息,跳3.3
3.1.2根据需求对客户端套接字投递WSASend
3.1.2.1有消息要发送,系统空闲,立即完成,跳3.1.2
3.1.2.2无消息要发送,跳3.3
3.1.3如果需要连接客户端跳3
3.2延迟完成,此时没有客户端连接,跳3.3
3.3循环等待信号(WSAWaitForMultipleEvents)
3.3.1 没信号,继续等
3.3.2 有信号,先获取重叠结构上的信息(WSAGetOverlappedResult)
3.3.2.1 如果信号是服务器端上检测到有客户端连接,跳转3(这个步骤和3.3.2.2不可以调换顺序)
3.3.2.2 如果信号是客户端退出,则关闭客户端SOCKET,并从数组删除客户端的信息
3.3.2.3 如果信号是需要接收信息,跳转3.1.1
3.3.2.4 如果信号是需要发送信息,跳转3.1.2
由于要对多个SOCKET进行操作,因此先要创建SOCKET数组和重叠结构体数组,套路和前面两节的选择模型一样的,数组+个数
#define MAX_COUNT 1024
SOCKET garr_sockAll[MAX_COUNT];//SOCKET数组
OVERLAPPED garr_olpAll[MAX_COUNT];//重叠结构体数组
int gi_count;//数组中SOCEKT的个数,全局变量默认值是0
然后把上面创建好的SOCKET放到数组里面去,事件也要初始化:
garr_sockAll[gi_count] = socketServer;//初始化后的SOCKET放到SOCKET数组里面
garr_olpAll[gi_count].hEvent = WSACreateEvent();//事件初始化
gi_count++;
接下来的投递代码为了简洁,单独将其写成一个函数。先来看看AcceptEx函数
//投递服务器SOCKET句柄,异步接收客户端连接
BOOL AcceptEx(
SOCKET sListenSocket,
SOCKET sAcceptSocket,
PVOID lpOutputBuffer,
DWORD dwReceiveDataLength,
DWORD dwLocalAddressLength,
DWORD dwRemoteAddressLength,
LPDWORD lpdwBytesReceived,
LPOVERLAPPED lpOverlapped
);
参数1:服务器SOCKET句柄
参数2:接收的客户端SOCKET句柄,这个句柄要通过Socket或者WSASocket来创建,然后放在AcceptEx这里相当于原始模型中的accept+bind操作,将客户端的IP和端口号绑定到参数2上。
参数3:缓冲区的指针,接收在新连接上发送的第一个数据。意思是这里可以直接拿到客户端第一次send过来的数据,第二次和之后send的数据就要用WSARecv来接收了。这个参数是一个字符数组,不能设置为NULL。
参数4:上面参数3功能估计是程序猿打瞌睡弄出来bug,太恶搞了,都有recv干嘛还要设置这个功能,难道每个客户端都和服务器YYQ,只发送一次数据,然后为了偷懒弄出来的?因此参数4进行补救,如果参数4设置为0,那么参数3无效,但是不管参数4是否设置为0,参数3都不可设置为NULL。如果要使得参数3生效,参数4则要设置为参数3的长度,此时,客户端连接服务器并发送了一条消息后服务器才产生信号,才能recv到数据。
参数5:The number of bytes reserved for the local address information. This value must be at least 16 bytes more than the maximum address length for the transport protocol in use.因此固定写成:sizeof(struct sockaddr _in)+16
参数6:和参数5一样,只不过存在Remote端,固定写成:sizeof(struct sockaddr _in)+16
简要分析:由于SOCKET操作可以看做是文件操作,因此参数5可以看做是本地硬盘用的地址,参数6可以看做是内存用的地址。
参数7:配合参数3和参数4使用,如果第一次是在这里接收信息,而且立即执行(步骤3.1)接收到了信息,那么参数7将得到信息的长度。
如果不想获取信息的长度,填写NULL即可;否则填写DWORD变量的地址即可。
参数8:服务器SOCKET句柄对应的重叠结构体。注意不要写错为客户端的重叠结构体
返回值:
TRUE:表示执行完成就有客户端到了,accept成功
FALSE:出错,用int acceptexerr = WSAGetLastError()获取错误码并处理
情况1:acceptexerr ==ERROR_IO_PENDING,表示执行成功,但处于异步等待状态,或者此时还没有客户端请求连接;
情况2:其他错误码要根据情况解决。
注意:AcceptEx要加载自己的头文件和库文件:
#include <mswsock.h>//这个头文件要在#include <WinSock2.h>下面
#pragma comment(lib, "mswsock.lib")
//投递AcceptEx
int PostAccept()
{
garr_sockAll[gi_count]=WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED);
garr_olpAll[gi_count].hEvent = WSACreateEvent();//事件初始化
char str[1024] = {0};
DWORD dwRecvCount = 0;
BOOL bRes = AcceptEx(garr_sockAll[0],garr_sockAll[gi_count],str,0,sizeof(struct sockaddr_in)+16,sizeof(struct sockaddr_in)+16,&dwRecvCount,garr_olpAll[0]);
if (bRes == TRUE)
{
gi_count++;
//这里执行3.1
//执行成功,并连接成功
return 0;
}
else
{
int acceptexerr = WSAGetLastError();
if (acceptexerr == ERROR_IO_PENDING)
{
//延迟处理
return 0;
}
else
{
//出错处理
}
}
}
3.1
接下来写上面自定义函数中3.1部分,这里acceptEx执行成功,并有客户端进行连接上来。
这里要用到WSARecv函数,该函数用来投递异步Recv消息
int WSAAPI WSARecv(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
参数1:客户端SOCKET句柄
参数2:接收客户端信息的BUFF,这里不能用自定义的字符串数组,要用:
typedef struct _WSABUF {
ULONG len;
CHAR *buf;
} WSABUF, *LPWSABUF;
参数3:有几个参数2,这里一般是1个
参数4:接收成功,则这里可以设置接收到的信息长度,如果参数6设置的重叠结构体不为空,这里也可以设置为NULL,表示不获取接收到的信息长度
参数5:和recv函数中的参数4意思一样,用于设置WSARecv的标注,把前面的内容拷贝过来:
数据的读取方式。默认是0即可。正常情况下recv根据参数3读取数据缓冲区指定长度的数据后(指定长度大于数据长度则全部读取),数据缓冲区中被读取的数据会清除,把空间留出来给别的消息进来(不清理的话时间长了内存会溢出,数据缓冲区数据结构相当于队列)。
例如数据缓冲区中有如下数据:
a | b | c | d | e | f |
---|
调用recv(socketClient,buff,2,0);从数据缓冲区读取两个字节的数据得到a,b。则变成
c | d | e | f |
---|
这个时候再调用recv(socketClient,buff,2,0);从数据缓冲区读取两个字节的数据得到c,d。
懂得正常逻辑后我们可以看下其他几种模式。
数值 | 含义 |
---|---|
0(默认值) | 从数据缓冲区读取数据后清空被读取的数据 |
MSG_PEEK(不建议使用,内存会爆) | 从数据缓冲区读取数据后不清空被读取的数据 |
MSG_OOB | 接收带外数据,每次可以额外传输1个字节的数据,具体数据内容可以自己定义,这个方法可以用分别调用两次send函数,而且在不同TCP协议规范这个模式还不怎么兼容,因此也不推荐使用 |
MSG_WAITALL | 等待知道系统缓冲区字节数大于等于参数3所指定的字节数,才开始读取 |
如果使用MSG_PEEK模式,那么调用recv(socketClient,buff,2,MSG_PEEK);从数据缓冲区读取两个字节的数据得到a,b。由于不清空被读取的数据,缓冲区还是不变:
a | b | c | d | e | f |
---|
如果再次执行recv(socketClient,buff,2,MSG_PEEK);从数据缓冲区读取两个字节的数据还是得到a,b。
看了一下MSDN,WSARecv比Recv函数要多了2个模式
数值 | 含义 |
---|---|
MSG_PUSH_IMMEDIATE | 该标识只能用在tream-oriented sockets,尽快处理请求,不做延迟,该标识在接收数据较小的时候比较有用,但是当数据较大进行分包传输后,使用该标识会造成后面传输的包会被丢弃,导致传输失败! |
MSG_PARTIAL | 从数据缓冲区读取的数据是客户端发送的部分数据,程序员应该读取完整数据后再进行处理,该标识是由客户端设置的 |
参数6:重叠IO结构体指针
参数7:回调函数,先设置为NULL
返回值:
0:表示执行成功。这里要注意的是和AcceptEx不一样,AcceptEx返回的TRUE
SOCKET_ERROR :出错,用int wasrecverr = WSAGetLastError()获取错误码并处理
情况1:wasrecverr ==ERROR_IO_PENDING,表示执行成功,但处于异步等待状态,或者此时还没有客户端请求连接;
情况2:其他错误码要根据情况解决。
//投递RecvEx
//参数socketIndex是当前SOCKET数组下标
int PostRecv(int socketIndex)
{
WSABUF wsabuff;
wsabuff.buf = gc_recvbuff;
wsabuff.len= MAX_RECV_LENGTH;
DWORD dwRecvedLength;
DWORD dwRecvFlag=0;
int iret = WSARecv(garr_sockAll[socketIndex],&wsabuff,1,&dwRecvedLength,&dwRecvFlag,&garr_olpAll[socketIndex],NULL);
if (iret == 0)
{
//立即完成,执行成功
//收取信息后返回
printf("%s\\n",wsabuf.buf);
memset(gc_recvbuff,0,MAX_RECV_LENGTH);//清空buff
//根据情况投递send
//跳3.1.1继续投递Recv
PostRecv(socketIndex);
return 0;
}
else
{
int wasrecverr = WSAGetLastError();
if (wasrecverr == ERROR_IO_PENDING)
{
//延迟处理
return 0;
}
else
{
//出错处理
}
}
return 0;
}
3.3循环等待/查询事件
DWORD WSAAPI WSAWaitForMultipleEvents(
DWORD cEvents,
const WSAEVENT *lphEvents,
BOOL fWaitAll,
DWORD dwTimeout,
BOOL fAlertable
);
参数1:要等待/查询的事件数量
参数2:要等待/查询的事件句柄
参数3:如果查询一组/多个事件,当参数3为TRUE的时候,要等所有事件都有信号才返回,填FALSE则只要有一个事件有信号就返回
参数4:查询没有信号等待的时间,不等待就写0
参数5:设置线程是否进入alertable wait state,跟多线程有关,目前单线程且是事件通知先FALSE
返回值
整型,具体看
https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsawaitformultipleevents
//accept成功就循环等待事件发生
while(1)
{
for(int i = 0; i < gi_count; i++)
{
//笨方法,一次查询一个事件是否有信号
int nRes=WSAWaitForMultipleEvents(1,&(garr_olpAll[i].hEvent), FALSE,0, FALSE);
if(nRes==WSA_WAIT_FAILED || nRes==WSA_WAIT_TIMEOUT)//查询失败或者超时
{
continue;
}
}
}
3.3.2有信号
这时要获取socket上的具体信号
BOOL WSAAPI WSAGetOverlappedResult(
SOCKET s,
LPWSAOVERLAPPED lpOverlapped,
LPDWORD lpcbTransfer,
BOOL fWait,
LPDWORD lpdwFlags
);
参数1:有信号的SOCKET句柄
参数2:有信号的SOCKET句柄对应的重叠结构的地址
参数3:发送或者接收到的实际字节数,如果得到0,表示客户端下线
参数4:当重叠操作选择了基于事件的完成通知时,设置为TRUE,这里就默认TRUE,设置false的解释如下:
If FALSE and the operation is still pending, the function returns FALSE and the WSAGetLastError function returns WSA_IO_INCOMPLETE.
参数5:不可为NULL,装WSARecv的参数5:lpflags,具体看上面的表
返回值:
TRUE:成功
FALSE:失败,具体错误码看
https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsagetoverlappedresult
这里要注意,如果错误码是10054表示点×关闭窗口,要单独判断释放客户端SOCKET。
//经过查询有信号
//3.3.2有信号
DWORD dwTransfer;//接收或发送的数据长度
DWORD dwFlagrecvpara5;//和recv中的参数5一致
BOOL bret = WSAGetOverlappedResult(garr_sockAll[i],&garr_olpAll[i],&dwTransfer,TRUE,&dwFlagrecvpara5);
if (bret == FALSE)//获取信号失败则跳过
{
continue;
}
//获取信号成功,则分类处理
//0号位代表服务器SOCKET,说明接受连接完成
if(i == 0)
{
}
//长度为0表示客户端正常下线
if(dwTransfer == 0)
{
}
//发送或者接收数据成功
if(dwTransfer != 0)
{
}
注意:WSAGetOverlappedResult没有自带重置信号的功能,要在执行它后面接下面代码重置信号:
WSAResetEvent(garr_olpAll[i].hEvent);
3.3.2.X
下面处理上面代码中if判断里面的内容,这里对应伪代码的3.3.2.X的内容。
3.3.2.1
如果信号是客户端连接,跳转3
实际上这里就是要先收数据,因为连接成功后必定要先收,然后再投递accept
//0号位代表服务器SOCKET,说明接受连接完成,且刚连接上的客户端SOCKET在数组的第gi_count位上
if(i == 0)
{
//执行成功,并连接成功
//走流程3.1的两种情况
//投递recv
PostRecv(gi_count);
gi_count++;//注意这里gi_count++的位置
//再次投递AcceptEx
PostAccept();
continue;//一次处理一个响应,处理完跳出循环后面不用看了
}
3.3.2.2
如果信号是客户端退出,则关闭客户端SOCKET,并从数组删除客户端的信息
//长度为0表示客户端下线
if(dwTransfer == 0)
{
//关闭客户端SOCKET和事件句柄
closesocket(garr_sockAll[i]);
WSACloseEvent(garr_olpAll[i].hEvent);
//从数组中删除客户端SOCKET和事件,这里思路用数组最后一位替换当前元素
garr_sockAll[i] = garr_sockAll[gi_count-1];
garr_olpAll[i] = garr_olpAll[gi_count-1];
gi_count--;
i--;//循环控制变量i回退一位,重新循环当前替换的新元素
}
3.3.2.3、3.3.2.4
3.3.2.3如果信号是需要接收信息,跳转3.1.1
3.3.2.4 如果信号是需要发送信息,跳转3.1.2
这里用gc_recvbuff全局变量来判断是接受还是发送信息
//发送或者接收数据成功
if(dwTransfer != 0)
{
//根据全局变量gc_recvbuff是否为空来判断是否发送数据
if(gc_recvbuff[0] != 0)//不空说明收到数据,应该是recv
{
//立即完成,执行成功
//收取信息后返回
printf("%s\\n",gc_recvbuff);
memset(gc_recvbuff,0,MAX_RECV_LENGTH);//清空buff
//根据情况投递send
//跳3.1.1继续投递Recv
PostRecv(i);
}
else//发送数据
{
//send
}
}
PostSend
上面的代码中还缺少对发送数据的操作进行处理,这里也将其写成一个函数的形式。
主要函数看这里:
https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasend
int WSAAPI WSASend(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
参数1:目标客户端SOCKET句柄
参数2:WSABUF结构体,参考WSARecv
参数3:WSABUF结构体的个数,一般为1
参数4:发送成功后,获取到已发送消息长度的字节数
参数5:同WSARecv参数5,不过不是地址,而是指示标识
参数6:重叠IO结构体地址
参数7:回调函数,先设置为NULL
整体代码
#include <WinSock2.h>
#include <mswsock.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "mswsock.lib")
#define MAX_COUNT 1024//这里上万个SOCKET应该没压力
#define MAX_RECV_LENGTH 1000//接收缓冲区一次最多接收字符串长度
#define MAX_SEND_LENGTH 1000//接收缓冲区一次最多发送字符串长度
SOCKET garr_sockAll[MAX_COUNT];//SOCKET数组
OVERLAPPED garr_olpAll[MAX_COUNT];//重叠结构体数组
int gi_count;//数组中SOCEKT的个数,全局变量默认值是0
char gc_recvbuff[MAX_RECV_LENGTH];//接收缓冲区全局变量,也可以放到main函数中定义,然后PostRecv传址调用
//函数声明
int PostAccept();
int PostRecv(int socketIndex);
int PostSend(int socketIndex);
//清理两个数组,逐个关闭SOCKET和事件
void ClearArr()
{
for(int i = 0;i < gi_count;i++)
{
closesocket(garr_sockAll[i]);//关闭SOCKET
WSACloseEvent(garr_olpAll[i].hEvent);//关闭事件
}
}
BOOL WINAPI cls(DWORD dwCtrlType)
{
switch (dwCtrlType)
{
case CTRL_CLOSE_EVENT :
//释放数组中的事件和SOCKET句柄
ClearArr();
WSACleanup();
break;
}
return TRUE;
}
int main(void)
{
SetConsoleCtrlHandler(cls,TRUE);
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
WORD wdVersion=MAKEWORD(2,2);
WSADATA wdScokMsg;
int nRes = WSAStartup(wdVersion,&wdScokMsg);
if (0 != nRes)
{
switch(nRes)
{
case WSASYSNOTREADY:
printf("解决方案:重启电脑。。。\\n");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库\\n");
break;
case WSAEINPROGRESS:
printf("请重启程序\\n");
break;
case WSAEPROCLIM:
printf("请关闭空闲软件,释放资源来运行程序\\n");
break;
case WSAEFAULT:
break;
}
return 0;
}
//校验版本
if (2!=HIBYTE(wdScokMsg.wVersion)|| 2!=LOBYTE(wdScokMsg.wVersion))
{
printf("版本有问题!\\n");
WSACleanup();
return 0;
}
//这里和前面不一样,要把Socket初始化换成WSASocket
SOCKET socketServer=WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED);
if(INVALID_SOCKET == socketServer)
{
int err=WSAGetLastError();
printf("WSASocket初始化失败,错误码是:%d\\n",err);
//清理网络库,不关闭句柄
WSACleanup();
return 0;
}
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(9527);//用htons宏将整型转为端口号的无符号整型
si.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
if(SOCKET_ERROR==bind(socketServer,(const struct sockaddr *)&si,sizeof(si)))
{
int err = WSAGetLastError();//取错误码
printf("服务器bind失败错误码为:%d\\n",err);
closesocket(socketServer);//释放
WSACleanup();<以上是关于WINSOCK.053.重叠IO模型的主要内容,如果未能解决你的问题,请参考以下文章