上一篇写的是UDP的,我昨天又写了一个TCP的例子。才发现我有很多东西其实是没有理解。
比如说 GetQueuedIOCompletionStatus()这是一个阻塞函数,那么哪些操作值得他阻塞呢。
客户端的话,需要阻塞的是recv,他不知道什么时候会收到消息,那么就需要阻塞等待。
服务器的话,可能要多一点,比如说先是需要收到客户端的连接,还有也是在recv收消息的时候。
服务器代码框架:
1,创建IOCP句柄
2,启动线程,线程中主要还是调用GetQueuedIOCompletionStatus()阻塞函数,等待
3,启动监听
1,创建socket(方式随便,socket()和WSASocket()都可以)
2,填写本地起监听的地址信息
3,绑定socket和地址
4,listen,起监听
4,投递accept,等待客户端连接
5,结束,结束的时候使用PostQueue的IOCompletionStatus();告诉主程序,我要退出了。
详细代码我就不贴了,然后解释几个我遇到的问题。
客户端的代码框架:
1,创建IOCP句柄
2,启动线程,线程中主要还是调用GetQueuedIOCompletionStatus()阻塞函数,等待
3,connect(),连接服务器
4,连接建立之后,投递recv,就可以接收消息了。
5,结束,结束的时候使用PostQueue的IOCompletionStatus();告诉主程序,我要退出了。
客户端比较简单,服务器需要启动监听之后accept()等待有客户端来主动连接,之后连接建立。
问题一:什么时候调用socket和IOCP句柄的绑定
HANDLE _handle = CreateIoCompletionPort((HANDLE)(sock_srv), _iocp, reinterpret_cast<ULONG_PTR>(&key), 0);
在调用阻塞函数之前即可,比如在服务器中,在accept()之前调用即可。或者是客户端recv之前即可。
比如我之前的错误,以为什么时候都可以,结果在客户端中在connect就绑定了socket和iocp的关系,结果一直报错,就卡在阻塞函数那里,报的错误是向0x00000000写数据错误。所以一定要使用正确。在阻塞函数(accpet和recv)之前调用。
问题二:connect()和accpet()怎么用
他俩都是需要调用一个指针函数,当然指针函数首先需要去获取才可以。那么如何获取呢?来我慢慢解释
SOCKET sock = socket(AF_INET,SOCK_STREAM,0);// 创建socket
LPFN_ACCEPTEX fn_accept_ex = 0;// 申明函数指针,如果是connect,定义为LPFN_CONNECTEX fn_connect_ex = 0;
GUID GuidAcceptEx = WSAID_ACCEPTEX;// 这里如果是connect,那么就填WSAID_CONNECTEX,这些都是对应的
DWORD dwBytes;
WSAIoctl(sock,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidAcceptEx,
sizeof(GuidAcceptEx),
&fn_accept_ex,
sizeof(fn_accept_ex),
&dwBytes,
NULL,
NULL);
closesocket(sock);
下面我再仔细介绍该如何使用。
先介绍accept()的用法。
函数原型:
typedef
BOOL
(PASCAL FAR * LPFN_ACCEPTEX)(
IN SOCKET sListenSocket,// 最先创建的socket
IN SOCKET sAcceptSocket,// 在accpet之前创建的socket
IN PVOID lpOutputBuffer,// 输出信息的内存
IN DWORD dwReceiveDataLength,// 收到的消息长度,一般都只设置成0即可
IN DWORD dwLocalAddressLength,// 本地地址的长度,sizeof(sockaddr_in) + 16
IN DWORD dwRemoteAddressLength,// 对端地址的长度,sizeof(sockaddr_in) + 16。。。至于为什么要加16,我还没搞明白,不过如果不加,那么
// 阻塞函数会报错,错误码为122.但是我试验过,6~16均可以,小于6就会报错
OUT LPDWORD lpdwBytesReceived,
IN LPOVERLAPPED lpOverlapped// 重叠指针
);
今天就因为122的报错纠结了很久,但是为什么是加16,我还需要再请教别人。。。。遗留问题?????
一旦收到链接请求,那么就可以投递接收请求,可以接收数据了。
connect()函数
typedef
BOOL
(PASCAL FAR * LPFN_CONNECTEX) (
IN SOCKET s,// socket
IN const struct sockaddr FAR *name,// 对端地址信息
IN int namelen,// 对端地址长度
IN PVOID lpSendBuffer OPTIONAL,// 链接是并没有数据传送,所以设成NULL即可
IN DWORD dwSendDataLength,// 那么对应的这个值即为0
OUT LPDWORD lpdwBytesSent,// 这个也是NULL就可以
IN LPOVERLAPPED lpOverlapped// 重叠指针
);
这个函数和我们平常使用的connect()很像,也没有什么特别需要讲解的。
总结:
今天学习到的重点:
1.如果绑定socket和IOCP,什么时候需要绑定。我把它理解成,我们在告诉IOCP我们需要开始有阻塞函数了,有点类似select()之前sockt和FD_SET的绑定。
2.解决了122报错。但是具体长度为什么仅仅使用sizeof(sockaddr_in)会被认为说系统分配内存不足,还是个遗留问题。
3.还有一点是GetQueuedIOCompletionStatus()函数的第四个参数,这个地方只需要传一个指针进去即可,可以承载或者说包含OVERLAP的结构体空指针即可。这个值是一个出参,在投递请求时我们填写的带有OVERLAP的结构体中含有的信息,一旦阻塞解除,那么这个指针即可将值带出来。所以带有OVERLAP的结构体可以很灵活,比如说在建立链接中,如果对端地址不明确,那么就可以在这个结构体中含一个是sockaddr_in即可将对端地址带出来。所以使用起来也很方便,很灵活。
之前是我吧IOCP看的太神秘了,其实很简单。
4.如果想要建立服务器与客户端一对多的连接。那么每次连接之前都需要投递一次accept()请求,也方便记录每次的连接信息,主要是socket信息。这部分可以在线程完成。代码逻辑:如果收到accept请求,成功之后:可以投递一次接受请求,表名此连接可以接受消息了。不管成没成功,都进行一次accept()投递请求,这样的话主要用来处理多客户端的情况。
5,为了区分各种消息,比如accpet,recv,exit等等。可以定义一个枚举类型值以示区分。