TCP/IP网络编程学习笔记(十二)

  1. 重叠IO模型(Overlapped IO)中,IO操作本身是异步的(当然也是非阻塞的),与调用IO函数的线程无关。也就是说,调用IO的线程只是发起IO操作,IO操作本身是由其他线程完成的,调用IO的线程可以同时干其他事情。不过,当确认IO操作是否完成时,默认还是需要调用IO的线程来确认。如果添加了回调函数,也就是IO操作完成后自动执行一个后处理函数,此时这个后处理函数也是由刚才发起IO请求的线程来执行的,并且只有当发起IO请求的线程执行了等待函数(比如WaitForSingleObjectEx()WaitForMultipleObjectsEx()WSAWaitForMultipleEvents()SleepEx())时,且最后一个实参传入TRUE时(也就是将线程置为可等待状态(alertable wait)),此线程才会去执行回调函数。否则,回调函数是不会被执行的。因此,执行回调函数时,原来代码必须处于阻塞状态;

  2. Windows下重叠IO模型的TCP/IP通信实现方式:

    • 使用WSASocket()函数创建一个支持重叠IO模型的socket句柄,其中前三个形参与socket()函数一模一样,第四和第五个参数一般默认设为nullptr和0,最后一个参数一定要设置为WSA_FLAG_OVERLAPPED,表示创建的套接字支持重叠IO模型;
    • 使用WSACreateEvent()创建一个手动重置事件WSAEVENT句柄,此函数默认将手动重置事件初始状态设置为non-signaled。注意,结束时一定要使用WSACloseEvent()来关闭这个事件(因为也是句柄,所以必须主动关闭);
    • 创建一个WSABUF结构体,用于存储发送或接受的数据。注意,不管是用于发送数据还是用于接收数据,其内部的buf成员必须要设置为一个可用的内存空间,不能不设置。另外,一般发送和接收函数要传入的是一个WSABUF的数组指针,所以可以定义一个数组,这样可以同时传递多个缓存区的数据,或者将接收的数据存放于多个缓存区;
    • 创建一个WSAOVERLAPPED结构体,用来辅助实现重叠IO。注意,创建完后,一定要通过memset()将结构体初始化为0。然后,可以将上述创建的手动重置事件存放到结构体的hEvent成员里,这样,后面就可以通过相关的事件等待函数来等待这个事件变成signaled状态,就表示相关的IO操作完成了。此外,此结构体中还有一个Pointer成员,可以用来传递相关的数据(比如可以将socket句柄通过此结构传给回调函数,回调函数中可以通过传过来的socket执行额外的通信操作);
    • 按照正常的流程建立TCP/IP连接;
    • 使用WSASend()或者WSARecv()来实现重叠IO通信。注意,为了实现重叠IO通信,这两个函数中的lpOverlapped形参不能传入nullptr,必须传入一个WSAOVERLAPPED的地址,如果传入的是nullptr,则将会以阻塞模式工作,而不再是异步IO。另外,如果要向多个客户端发送或接收数据,必须每个连接的socket都准备一个WSAOVERLAPPED结构体,互不干扰;
    • 通过上面的两个函数提交发送或者接收数据的请求,具体的发送和接收不是本线程来处理的,本线程此时可以干其他事情;
    • 如果需要获取发送和接收数据的状态或者结果(比如判断是否在处于发送中,或者打印接收的数据,等),那么可以使用WSAGetOverlappedResult()函数来查看状态或者查看发送或接收了多少个数据。注意,如果IO还在执行过程中,则WSASend()或者WSARecv()也会立即返回,但是返回的值是SOCKET_ERROR,此时通过WSAGetLastError()获取错误码,如果错误码是WSA_IO_PENDING,则表示IO操作还在异步执行中。此时可以通过WSAGetOverlappedResult()来等待其执行完成,当然也可以通过WSAWaitForMultipleEvents()来等待上面创建的手动重置事件,来确保重叠IO已经完成;
    • 此外,也可以通过异步过程调用的方式来处理当重叠IO完成后的事情(比如解析读取到的数据或者回复对方相关数据,等)。此时需要在WSASend()或者WSARecv()函数中的最后一个形参中传入一个回调函数。注意,如果想让回调函数可以被执行,则主逻辑代码中在执行WSASend()或者WSARecv()后的某个位置必须使用一个阻塞函数(如SleepEx())来阻塞主逻辑代码的执行,这样此线程才会执行回到函数。这种情况下,如果回调函数被执行完成了,那么主逻辑中的阻塞函数将会返回一个WAIT_IO_COMPLETION的数值,表示回调函数执行完成了。此时主逻辑代码就可以继续执行了(所以一般这里的阻塞函数可以设置为阻塞无限长时间)。

    精简一下就是:WSASocket() -> WSACreateEvent() -> WSABUF, WSAOVERLAPPED -> WSASend()或WSARecv() -> WSAGetLastError() -> WSAGetOverlappedResult() 或 WSAWaitForMultipleEvents() 或 SleepEx()

  3. 回调函数的签名为(必须加上CALLBACK):

    void CALLBACK CompletionRoutine(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags);
    

    其中,第一个参数为错误信息,如果IO操作正常结束,则其值为0。第二个参数是对于的IO操作实际传输的字节数,注意,如果此时回调函数是由WSASend()来触发的,且第二个参数得到的值为0,则表示对方发送了EOF。第三个参数为对应的IO操作使用的WSAOVERLAPPED结构体;

  4. WSASend()或者WSARecv()调用后,不管IO操作有没有完成,这两个函数都会返回。如果返回的值是SOCKET_ERROR并且通过WSAGetLastError()得到的错误码是WSA_IO_PENDING,则表示IO操作还未完成。如果这两个函数返回0了,那么形参中获取的实际传输字节数是有效的,如果IO操作未完成就返回了,那么形参中存储的实际传输字节数是无效的,此时如果想获取实际传输字节数,需要通过WSAGetOverlappedResult()或者通过回调函数来得到;

  5. 等待IO操作结束有三个方式,第一个是使用WSAWaitForMultipleEvents()来等待对应的手动重置事件,第二种方式是使用WSAGetOverlappedResult()来等待对应的socket的IO操作完成,第三种是使用SleepEx()或者第1点中列出的几个函数来无限阻塞,并通过回调函数的返回(从而使SleepEx()或者第1点中列出的几个函数返回,且返回值为WAIT_IO_COMPLETION)来等待IO结束。如果使用SleepEx()那么就不需要创建一个手动重置事件了。如果使用的是回调函数的方式,那么就不再需要将手动重置事件赋值给WSAOVERLAPPED结构体的hEvent成员了(因为此时Wait…函数不再是等到此事件变为signaled状态后才返回,而是无限等待,直到回调函数执行完成,Wait…函数后就会返回)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值