IOCP模型编程注意点

乱序问题

问题描述:客户端依次发送Msg1,Msg2,Msg3,服务器的逻辑处理的顺序不一定是Msg1,Msg2,Msg3

产生原因:

 三个线程同时从IOCP中读取Msg1, Msg2,与Msg3。由于TCP本身消息传递的有序性,所以,在IOCP队列内,Msg1-Msg2-Msg3保证了有序性(注意:数据在IOCP队列中数据顺序肯定是对的,乱序问题的产生在线程GetQueuedCompletionStatus之后到交给逻辑处理层的过程中)。假设我们最多允许3个线程,3个线程分别从IOCP中取出Msg1,Msg2与Msg3,然后三个线程都会将各自取到的消息投递到逻辑层处理。在逻辑处理层的实现,我们不应该假定Msg1-Msg2-Msg3顺序,原因其实很简单,在GetQueuedCompletionStatus后到交给逻辑处理层的时间段内,三个线程被操作系统调度的先后次序是不确定的,所以在到达逻辑处理层,Msg1,Msg2与Msg3的次序也就是不确定的。所以,逻辑处理层的实现,必须考虑消息乱序的情况,必须考虑多线程环境下的程序实现。需要注意到是,这个乱序根本问题是由于多线程的调度产生的,与投递多少个WSARecv是没有关系到,就算你同时投递多个WSARecv,数据在IOCP队列中数据顺序肯定是对的。(我说的顺序对,指的是和数据到达网卡的顺序是一致的,IOCP通知你处理的顺序是对的,但是你每部分处理时间不一样,导致交给上层的时候顺序就不一定了)

解决方法:

解决之前先解释一个问题,就是WSARecv和WSASend的有序性。例如:服务器端for循环3次依次投递多个WSARecv,投递时所对应的接收缓冲区分别是buf1,buf2,buf3,网卡上依次来了3次数据data1,data2,data3,(这3组数据是客户端的一个data数据按照顺序拆分出来的)那么能够保证的是这三组数据,存放的顺序一定是先放buf1,再放buf2,再放buf3.(会有这种情况:data1太大buf1不够了,data1的一部分在buf2中。但能够保证的是,分别将buf1,buf2,buf3中的接收到的数据取出再组合,肯定就是data数据),不会出现data2在buf1中,而data1却在buf2中,或者其他乱序的情况都不会出现。
有了上述前提,我们只需要在投递WSARecv时为每一个buf进行编号,然后创建一个接收缓冲区,per-sock-handle中记录当前要读的编号和要安排下次投递的编号,接收到数据时,根据buf编号和per-sock-handle中的编号来决定是要放入缓冲区(按照编号顺序存放)还是直接交给逻辑层。


粘包问题

问题描述:

这是tcp编程大多数情况不得不考虑的问题,(短链接和数据只管顺序收发(例如文件传输)的不用考虑)
不知道有多少前人掉在TCP Socket
send(人多)send(病少)send(财富)
recv(人多病)recv(少财富)
陷阱里面啊!
假设接收缓冲区4K,客户端先发送5K的数据(全是a),然后再次发送2K的数据(全是b),结果是服务器端先收到4K的数据(全是a),然后收到3K的数据(1K的a + 2K的b),这时我们要正确区分原来的完整数据是什么(5K的a 和 2K的b),重新组合后交给逻辑层处理

产生原因:

TCP流式传送数据

解决办法:

基本思路是按格式发送数据,一般为  固定消息头+数据总长度+数据内容   首先将待处理的接收数据流(长度设为m)强行转换成预定的结构数据形式,并从中取出结构数据长度字段,而后根据n计算得到第一包数据长度。
1)若n<m,则表明数据流包含多包数据,从其头部截取n个字节存入临时缓冲区,剩余部分数据依此继续循环处理,直至结束。
2)若n=m,则表明数据流内容恰好是一完整结构数据,直接将其存入临时缓冲区即可。
3)若n>m,则表明数据流内容尚不够构成一完整结构数据,需留待与下一包数据合并后再行处理。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
IOCP是一种高效的I/O多路复用模型,适用于Windows系统上的网络编程。下面是一个简单的使用IOCP模型的C代码示例,实现了一个简单的回显服务器: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #include <windows.h> #define BUFSIZE 1024 typedef struct _PER_HANDLE_DATA { SOCKET Socket; SOCKADDR_STORAGE ClientAddr; } PER_HANDLE_DATA, *LPPER_HANDLE_DATA; typedef struct _PER_IO_DATA { OVERLAPPED Overlapped; WSABUF DataBuf; char Buffer[BUFSIZE]; int OperationType; } PER_IO_DATA, *LPPER_IO_DATA; DWORD WINAPI WorkerThread(LPVOID lpParam); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET ListenSocket, AcceptSocket; SOCKADDR_STORAGE LocalAddr, ClientAddr; DWORD Flags; LPPER_HANDLE_DATA PerHandleData; LPPER_IO_DATA PerIoData; DWORD RecvBytes, SendBytes; DWORD BytesTransferred; DWORD ThreadId; HANDLE CompletionPort, ThreadHandle; int i; // 初始化Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup failed with error: %d\n", WSAGetLastError()); return 1; } // 创建完成端口 CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (CompletionPort == NULL) { printf("CreateIoCompletionPort failed with error: %d\n", GetLastError()); return 1; } // 创建监听套接字 ListenSocket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); if (ListenSocket == INVALID_SOCKET) { printf("socket failed with error: %d\n", WSAGetLastError()); return 1; } // 绑定地址并监听 memset(&LocalAddr, 0, sizeof(LocalAddr)); LocalAddr.ss_family = AF_INET6; ((SOCKADDR_IN6*)&LocalAddr)->sin6_port = htons(12345); ((SOCKADDR_IN6*)&LocalAddr)->sin6_addr = in6addr_any; if (bind(ListenSocket, (SOCKADDR*)&LocalAddr, sizeof(LocalAddr)) == SOCKET_ERROR) { printf("bind failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); return 1; } if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) { printf("listen failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); return 1; } // 创建工作线程 for (i = 0; i < 2; i++) { ThreadHandle = CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &ThreadId); if (ThreadHandle == NULL) { printf("CreateThread failed with error: %d\n", GetLastError()); return 1; } CloseHandle(ThreadHandle); } // 接受连接并关联到完成端口 while (1) { AcceptSocket = accept(ListenSocket, (SOCKADDR*)&ClientAddr, NULL); if (AcceptSocket == INVALID_SOCKET) { printf("accept failed with error: %d\n", WS

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Barry__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值