IOCP 第二篇

上一篇写的是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等等。可以定义一个枚举类型值以示区分。











  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值