C/C++ socket编程教程之九:TCP的粘包问题以及数据的无边界性

C/C++ socket编程教程之九:TCP的粘包问题以及数据的无边界性

上节我们讲到了socket缓冲区和数据的传递过程,可以看到数据的接收和发送是无关的,read()/recv() 函数不管数据发送了多少次,都会尽可能多的接收数据。也就是说,read()/recv() 和 write()/send() 的执行次数可能不同。

例如,write()/send() 重复执行三次,每次都发送字符串"abc",那么目标机器上的 read()/recv() 可能分三次接收,每次都接收"abc";也可能分两次接收,第一次接收"abcab",第二次接收"cabc";也可能一次就接收到字符串"abcabcabc"。

假设我们希望客户端每次发送一位学生的学号,让服务器端返回该学生的姓名、住址、成绩等信息,这时候可能就会出现问题,服务器端不能区分学生的学号。例如第一次发送 1,第二次发送 3,服务器可能当成 13 来处理,返回的信息显然是错误的。

这就是数据的“粘包”问题,客户端发送的多个数据包被当做一个数据包接收。也称数据的无边界性,read()/recv() 函数不知道数据包的开始或结束标志(实际上也没有任何开始或结束标志),只把它们当做连续的数据流来处理。

下面的代码演示了粘包问题,客户端连续三次向服务器端发送数据,服务器端却一次性接收到所有数据。

服务器端代码 server.cpp:

#include <stdio.h>
#include <windows.h>
#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll

#define BUF_SIZE 100

int main(){
    WSADATA wsaData;
    WSAStartup( MAKEWORD(2, 2), &wsaData);

    //创建套接字
    SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0);

    //绑定套接字
    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
    sockAddr.sin_family = PF_INET;  //使用IPv4地址
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    sockAddr.sin_port = htons(1234);  //端口
    bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));

    //进入监听状态
    listen(servSock, 20);

    //接收客户端请求
    SOCKADDR clntAddr;
    int nSize = sizeof(SOCKADDR);
    char buffer[BUF_SIZE] = {0};  //缓冲区
    SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);

    Sleep(10000);  //注意这里,让程序暂停10秒

    //接收客户端发来的数据,并原样返回
    int recvLen = recv(clntSock, buffer, BUF_SIZE, 0);
    send(clntSock, buffer, recvLen, 0);

    //关闭套接字并终止DLL的使用
    closesocket(clntSock);
    closesocket(servSock);
    WSACleanup();

    return 0;
}

客户端代码 client.cpp:

#pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll
#define BUF_SIZE 100
int main(){
    //初始化DLL
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    //向服务器发起请求
    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
    sockAddr.sin_family = PF_INET;
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    sockAddr.sin_port = htons(1234);

    //创建套接字
    SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));

    //获取用户输入的字符串并发送给服务器
    char bufSend[BUF_SIZE] = {0};
    printf("Input a string: ");
    gets(bufSend);
    for(int i=0; i<3; i++){
        send(sock, bufSend, strlen(bufSend), 0);
    }
    //接收服务器传回的数据
    char bufRecv[BUF_SIZE] = {0};
    recv(sock, bufRecv, BUF_SIZE, 0);
    //输出接收到的数据
    printf("Message form server: %s\n", bufRecv);

    closesocket(sock);  //关闭套接字
    WSACleanup();  //终止使用 DLL

    system("pause");
    return 0;
}

原文地址:https://www.cnblogs.com/leijiangtao/p/12050398.html

时间: 2024-11-06 23:45:18

C/C++ socket编程教程之九:TCP的粘包问题以及数据的无边界性的相关文章

TCP的粘包问题以及数据的无边界性

服务端: #include <stdio.h> #include <winsock2.h> #pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll   #define BUF_SIZE 100   int main(int argc, char *argv[]){     //初始化 DLL     WSADATA wsaData;     WSAStartup( MAKEWORD(2, 2), &wsa

TCP通信粘包问题分析和解决

TCP通信粘包问题分析和解决(全) 在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小.数据量小的数据,合并成一个大的数据块,然后进行封包.这样,接收端,就难于分辨出来了,必须提供科学的拆包机制. 对于UDP,不会使用块的合并优化算法,这样,实际上目前认为,是由于UDP支持的是一对多的模式,

Linux下的socket编程实践(四)TCP的粘包问题和常用解决方案

TCP粘包问题的产生 由于TCP协议是基于字节流并且无边界的传输协议, 因此很有可能产生粘包问题.此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段.若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,但是接收方并不知道要一次接收多少字节的数据,这样接收方就收到了粘包数据.具体可以见下图: 假设主机A send了两条消息M1和M2 各10k 给主机B,由于主机B一次提取的字节数

python socket编程实现的简单tcp迭代server

与c/c++ socket编程对照见http://blog.csdn.net/aspnet_lyc/article/details/38946915 server: import socket PORT = 9999 BACKLOG = 5 MAXLINE = 1024 listenfd = socket.socket(socket.AF_INET,socket.SOCK_STREAM) listenfd.bind(('',PORT)) listenfd.listen(BACKLOG) whil

tcp的粘包和拆包示例以及使用LengthFieldFrameDecoder来解决的方法

粘包和拆包是什么? TCP协议是一种字节流协议,没有记录边界,我们在接收消息的时候,不能人为接收到的数据包就是一个整包消息 当客户端向服务器端发送多个消息数据的时候,TCP协议可能将多个消息数据合并成一个数据包进行发送,这就是粘包 当客户端向服务器端发送的消息过大的时候,tcp协议可能将一个数据包拆成多个数据包来进行发送,这就是拆包 以下一netty为例,展示一下tcp粘包和拆包的例子: ServerBusinessHanler: import io.netty.buffer.ByteBuf;

tcp协议粘包问题

粘包问题是tcp协议流式传输数据的方式导致的 举例: from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(('127.0.0.1', 8081)) # 通信循环 while True: cmd=input('>>: ').strip() if len(cmd) == 0:continue client.send(cmd.encode('utf-8')) cmd_res=client.recv(102

关于TCP的粘包和拆包

问题产生 一个完整的业务可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这个就是TCP的拆包和封包问题. 下面可以看一张图,是客户端向服务端发送包: 1. 第一种情况,Data1和Data2都分开发送到了Server端,没有产生粘包和拆包的情况. 2. 第二种情况,Data1和Data2数据粘在了一起,打成了一个大的包发送到Server端,这个情况就是粘包. 3. 第三种情况,Data2被分离成Data2_1和Data2_2,并且Data2_1在Data1之前到

python3 tcp的粘包现象和解决办法

服务器端 import socket sk = socket.socket() sk.bind(("127.0.0.1", 6666)) sk.listen() conn, address = sk.accept() def my_send(msg): bs = msg.encode("utf-8") len_str = format(len(bs), "04d") # 定长4位 conn.send(len_str.encode("ut

[Go] 轻量服务器框架tcp的粘包问题 封包与拆包

tcp传输的数据是以流的形式传输的,因此就没有办法判断到哪里结束算是自己的一个消息,这样就会出现粘包问题,多个包粘在一起了 可以使用这样一个自定义的形式来解决,一个消息分为 head+body  head包括数据的长度和数据编号 , 长度和编号都是uint32类型 也就是32位 占有4个字节 , 总共head占有8个字节 封装一个消息的结构体,作为一个数据实体,比如下面这个,编号 数据 数据长度  三个属性 package znet type Message struct { Id uint32