热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

再谈Socket粘包问题

众所周知,socket的recv和send次数并不是对应的,比如对方send多次,本机一次就能recv完。因为协议层会自动根据网络状况进行拆分或者合并。但我并不关心对方如何发,也不
    众所周知,socket的recv和send次数并不是对应的,比如对方send多次,本机一次就能recv完。因为协议层会自动根据网络状况进行拆分或者合并。
    但我并不关心对方如何发,也不关心协议层如何处理,我只关心本机网卡到底收到几个包,类似于Wireshark一样,来一个包就显示一个包,不使用驱动只使用Socket编程可以实现吗?

21 个解决方案

#1


另注:只需关心指定端口的TCP数据包情况,并不需要检测流经网卡的所有数据。
又或者说,如何让recv函数根据实际数据包的大小来返回,而不是根据用户指定的buffer大小来返回?

#2


socket可能不行。TCP协议栈是自动处理的,不知道有没有这样的socket设置,如果没有,只能使用pcap之类的来处理了。

#3


从内核和应用的角度来说:

在应用层,多个send可能被一次recv,一个send可能被多次recv,两个send可能被两次recv但中间的界限不同,一切都有可能。所以只能从流的角度来看,包没有意义。

在内核层,一个send可能占用多个包,多个send可能公用一个包,这里的包我指的是ip层的包。

在编程开发中,普通的send recv处理不到ip层,原始套接字可以处理到。wireshark处理的比ip还要低一层,会截取到以太层的包(或其他)。这一层在应用层无法截取到。

#4


不知道有多少前人掉在TCP Socket
send(人多)send(病少)send(财富)
recv(人多病)recv(少财富)
陷阱里面啊!
http://bbs.csdn.net/topics/380167545

#5


4楼的老师不知道这句话复制粘贴多少次了
我说一下我遇到的情况吧
TCP是流式协议,所以会出现粘包的,但是UDP不会
还有就是你可以自己定义“包头+包身”然后根据长度进行接收包吧,我觉得这应该可以,逼格更高的还没遇到过

#6


定长发送 ,定长接收吧

#7


只要控制好 你需要读取的字节长度,就可以了 

比如一个包有10个字节,包头4个字节是长度,包体6个字节是包内容

你首先要读的就是4字节,如果不够4字节就等下一次recv的返回

够了4字节,就能得到包的长度,如果超过4字节,也只取4字节,多出来的内容是包体的,后面再处理

然后这4字节里面的内容,得到是6,表示后面有6个字节的包体

然后就是读6字节,依然是如果不够就等下一次recv的返回

直到够了6字节为止,

如果读了6字节还有多余,就是下一个包的数据

如此一来就不存在粘包的问题

#8


引用 楼主 xth21 的回复:
    众所周知,socket的recv和send次数并不是对应的,比如对方send多次,本机一次就能recv完。因为协议层会自动根据网络状况进行拆分或者合并。
    但我并不关心对方如何发,也不关心协议层如何处理,我只关心本机网卡到底收到几个包,类似于Wireshark一样,来一个包就显示一个包,不使用驱动只使用Socket编程可以实现吗?


你应该调用 windows api (Linux or unix 则要去找它们的 API 了)

Winsock

用它可以写一个简单的 Wireshark

#9


首先:楼主的需求很矛盾,既要获得原始网卡数据包,又说只需关心指定端口的tcp包。为什么这么说呢?

回答这个问题,要先回到教科书的理论:

1. 网卡数据包在最底层,他处在硬件或硬件上面,称为链路层,这一层的网络协议有:以太网RFC894,串行线路协议SLIP(RFC1055),PPP点对点协议...等等;

2. 数据链路层上面是网络层,也就是IP协议所在,是对下面链路层的封装,引入了IP地址和寻址相关的协议。

3. IP上面是TCP协议,属于运输层,也是对下层协议的包装,并引入了错误检测,重发等等机制,TCP保证数据的正确有序。
ip层

对于“数据包”这个名词来说他是一个逻辑包的概念,不同的协议层他的包的概念是不同的。一个IP包就是IP协议的规格所描述的完整逻辑的数据整体,当然TCP协议也对应相应的规格,而网卡的一个数据包我们可以认为他是链路层协议的规格描述的一个完整的逻辑数据体。

当然,在TCP层之上的应用协议,比如HTTP,他的协议对一个完整HTTP数据包的格式也有明确的规定。

定义清楚了神马是“数据包”以后,再来谈怎么抓这个包,

所以你如果要抓一个数据包,你要说明是抓什么协议的数据包。 实际上,在ring3层用socket编程,TCP及TCP以下的数据包你都抓不到,socket抓到的只能称为数据流,或者UDP的消息。跟包无关。要抓包,需要下驱动层,或者说内核层。

当然socket可以抓到TCP上层的应用层协议的包。

再次说明,“数据包” 这个概念是一个逻辑上完整的数据块。不同的协议他的格式不同。完整详细的信息请参考RFC文档或《TCP/IP详解 卷一》

有时候你需要严谨的表达和定义一些概念,你才能往下走,不然各说各话,一个问题,引申出无数无关的回答。


#10


github 搜索 “stickPackage”这个项目

#11


先理解概念,tcp是流式协议,本身就没有“一个包”这个说法,要按包接收的请用udp。不过udp的包的乱序问题比tcp的粘包麻烦多了。tcp只能照自定义的协议处理数据,如前几楼说的先收长度再收数据是最常用的做法,或者是先收定长的结构体也很常见。

#12


仅供参考:
//循环向a函数每次发送200个字节长度(这个是固定的)的buffer,
//a函数中需要将循环传进来的buffer,组成240字节(也是固定的)的新buffer进行处理,
//在处理的时候每次从新buffer中取两个字节打印
#ifdef _MSC_VER
    #pragma warning(disable:4996)
#endif
#include 
#include 
#include 
#ifdef _MSC_VER
    #include 
    #include 
    #include 
    #define  MYVOID             void
    #define  vsnprintf          _vsnprintf
#else
    #include 
    #include 
    #include 
    #define  CRITICAL_SECTION   pthread_mutex_t
    #define  MYVOID             void *
#endif
//Log{
#define MAXLOGSIZE 20000000
#define MAXLINSIZE 16000
#include 
#include 
#include 
char logfilename1[]="MyLog1.log";
char logfilename2[]="MyLog2.log";
static char logstr[MAXLINSIZE+1];
char datestr[16];
char timestr[16];
char mss[4];
CRITICAL_SECTION cs_log;
FILE *flog;
#ifdef _MSC_VER
void Lock(CRITICAL_SECTION *l) {
    EnterCriticalSection(l);
}
void Unlock(CRITICAL_SECTION *l) {
    LeaveCriticalSection(l);
}
void sleep_ms(int ms) {
    Sleep(ms);
}
#else
void Lock(CRITICAL_SECTION *l) {
    pthread_mutex_lock(l);
}
void Unlock(CRITICAL_SECTION *l) {
    pthread_mutex_unlock(l);
}
void sleep_ms(int ms) {
    usleep(ms*1000);
}
#endif
void LogV(const char *pszFmt,va_list argp) {
    struct tm *now;
    struct timeb tb;

    if (NULL==pszFmt||0==pszFmt[0]) return;
    vsnprintf(logstr,MAXLINSIZE,pszFmt,argp);
    ftime(&tb);
    now=localtime(&tb.time);
    sprintf(datestr,"%04d-%02d-%02d",now->tm_year+1900,now->tm_mon+1,now->tm_mday);
    sprintf(timestr,"%02d:%02d:%02d",now->tm_hour     ,now->tm_min  ,now->tm_sec );
    sprintf(mss,"%03d",tb.millitm);
    printf("%s %s.%s %s",datestr,timestr,mss,logstr);
    flog=fopen(logfilename1,"a");
    if (NULL!=flog) {
        fprintf(flog,"%s %s.%s %s",datestr,timestr,mss,logstr);
        if (ftell(flog)>MAXLOGSIZE) {
            fclose(flog);
            if (rename(logfilename1,logfilename2)) {
                remove(logfilename2);
                rename(logfilename1,logfilename2);
            }
        } else {
            fclose(flog);
        }
    }
}
void Log(const char *pszFmt,...) {
    va_list argp;

    Lock(&cs_log);
    va_start(argp,pszFmt);
    LogV(pszFmt,argp);
    va_end(argp);
    Unlock(&cs_log);
}
//Log}
#define ASIZE    200
#define BSIZE    240
#define CSIZE      2
char Abuf[ASIZE];
char Cbuf[CSIZE];
CRITICAL_SECTION cs_HEX;
CRITICAL_SECTION cs_BBB;
struct FIFO_BUFFER {
    int  head;
    int  tail;
    int  size;
    char data[BSIZE];
} BBB;
int No_Loop=0;
void HexDump(int cn,char *buf,int len) {
    int i,j,k;
    char binstr[80];

    Lock(&cs_HEX);
    for (i=0;i         if (0==(i%16)) {
            sprintf(binstr,"%03d %04x -",cn,i);
            sprintf(binstr,"%s %02x",binstr,(unsigned char)buf[i]);
        } else if (15==(i%16)) {
            sprintf(binstr,"%s %02x",binstr,(unsigned char)buf[i]);
            sprintf(binstr,"%s  ",binstr);
            for (j=i-15;j<=i;j++) {
                sprintf(binstr,"%s%c",binstr,('!'             }
            Log("%s\n",binstr);
        } else {
            sprintf(binstr,"%s %02x",binstr,(unsigned char)buf[i]);
        }
    }
    if (0!=(i%16)) {
        k=16-(i%16);
        for (j=0;j             sprintf(binstr,"%s   ",binstr);
        }
        sprintf(binstr,"%s  ",binstr);
        k=16-k;
        for (j=i-k;j             sprintf(binstr,"%s%c",binstr,('!'         }
        Log("%s\n",binstr);
    }
    Unlock(&cs_HEX);
}
int GetFromRBuf(int cn,CRITICAL_SECTION *cs,struct FIFO_BUFFER *fbuf,char *buf,int len) {
    int lent,len1,len2;

    lent=0;
    Lock(cs);
    if (fbuf->size>=len) {
        lent=len;
        if (fbuf->head+lent>BSIZE) {
            len1=BSIZE-fbuf->head;
            memcpy(buf     ,fbuf->data+fbuf->head,len1);
            len2=lent-len1;
            memcpy(buf+len1,fbuf->data           ,len2);
            fbuf->head=len2;
        } else {
            memcpy(buf     ,fbuf->data+fbuf->head,lent);
            fbuf->head+=lent;
        }
        fbuf->size-=lent;
    }
    Unlock(cs);
    return lent;
}
MYVOID thdB(void *pcn) {
    char        *recv_buf;
    int          recv_nbytes;
    int          cn;
    int          wc;
    int          pb;

    cn=(int)pcn;
    Log("%03d thdB              thread begin...\n",cn);
    while (1) {
        sleep_ms(10);
        recv_buf=(char *)Cbuf;
        recv_nbytes=CSIZE;
        wc=0;
        while (1) {
            pb=GetFromRBuf(cn,&cs_BBB,&BBB,recv_buf,recv_nbytes);
            if (pb) {
                Log("%03d recv %d bytes\n",cn,pb);
                HexDump(cn,recv_buf,pb);
                sleep_ms(1);
            } else {
                sleep_ms(1000);
            }
            if (No_Loop) break;//
            wc++;
            if (wc>3600) Log("%03d %d==wc>3600!\n",cn,wc);
        }
        if (No_Loop) break;//
    }
#ifndef _MSC_VER
    pthread_exit(NULL);
#endif
}
int PutToRBuf(int cn,CRITICAL_SECTION *cs,struct FIFO_BUFFER *fbuf,char *buf,int len) {
    int lent,len1,len2;

    Lock(cs);
    lent=len;
    if (fbuf->size+lent>BSIZE) {
        lent=BSIZE-fbuf->size;
    }
    if (fbuf->tail+lent>BSIZE) {
        len1=BSIZE-fbuf->tail;
        memcpy(fbuf->data+fbuf->tail,buf     ,len1);
        len2=lent-len1;
        memcpy(fbuf->data           ,buf+len1,len2);
        fbuf->tail=len2;
    } else {
        memcpy(fbuf->data+fbuf->tail,buf     ,lent);
        fbuf->tail+=lent;
    }
    fbuf->size+=lent;
    Unlock(cs);
    return lent;
}
MYVOID thdA(void *pcn) {
    char        *send_buf;
    int          send_nbytes;
    int          cn;
    int          wc;
    int           a;
    int          pa;

    cn=(int)pcn;
    Log("%03d thdA              thread begin...\n",cn);
    a=0;
    while (1) {
        sleep_ms(100);
        memset(Abuf,a,ASIZE);
        a=(a+1)%256;
        if (16==a) {No_Loop=1;break;}//去掉这句可以让程序一直循环直到按Ctrl+C或Ctrl+Break或当前目录下存在文件No_Loop
        send_buf=(char *)Abuf;
        send_nbytes=ASIZE;
        Log("%03d sending %d bytes\n",cn,send_nbytes);
        HexDump(cn,send_buf,send_nbytes);
        wc=0;
        while (1) {
            pa=PutToRBuf(cn,&cs_BBB,&BBB,send_buf,send_nbytes);
            Log("%03d sent %d bytes\n",cn,pa);
            HexDump(cn,send_buf,pa);
            send_buf+=pa;
            send_nbytes-=pa;
            if (send_nbytes<=0) break;//
            sleep_ms(1000);
            if (No_Loop) break;//
            wc++;
            if (wc>3600) Log("%03d %d==wc>3600!\n",cn,wc);
        }
        if (No_Loop) break;//
    }
#ifndef _MSC_VER
    pthread_exit(NULL);
#endif
}
int main() {
#ifdef _MSC_VER
    InitializeCriticalSection(&cs_log);
    InitializeCriticalSection(&cs_HEX);
    InitializeCriticalSection(&cs_BBB);
#else
    pthread_t threads[2];
    int threadsN;
    int rc;
    pthread_mutex_init(&cs_log,NULL);
    pthread_mutex_init(&cs_HEX,NULL);
    pthread_mutex_init(&cs_BBB,NULL);
#endif
    Log("Start===========================================================\n");

    BBB.head=0;
    BBB.tail=0;
    BBB.size=0;

#ifdef _MSC_VER
    _beginthread((void(__cdecl *)(void *))thdA,0,(void *)1);
    _beginthread((void(__cdecl *)(void *))thdB,0,(void *)2);
#else
    threadsN=0;
    rc=pthread_create(&(threads[threadsN++]),NULL,thdA,(void *)1);if (rc) Log("%d=pthread_create %d error!\n",rc,threadsN-1);
    rc=pthread_create(&(threads[threadsN++]),NULL,thdB,(void *)2);if (rc) Log("%d=pthread_create %d error!\n",rc,threadsN-1);
#endif

    if (!access("No_Loop",0)) {
        remove("No_Loop");
        if (!access("No_Loop",0)) {
            No_Loop=1;
        }
    }
    while (1) {
        sleep_ms(1000);
        if (No_Loop) break;//
        if (!access("No_Loop",0)) {
            No_Loop=1;
        }
    }
    sleep_ms(3000);
    Log("End=============================================================\n");
#ifdef _MSC_VER
    DeleteCriticalSection(&cs_BBB);
    DeleteCriticalSection(&cs_HEX);
    DeleteCriticalSection(&cs_log);
#else
    pthread_mutex_destroy(&cs_BBB);
    pthread_mutex_destroy(&cs_HEX);
    pthread_mutex_destroy(&cs_log);
#endif
    return 0;
}

#13


在发送端,按照网络的最小传输单元[MTU]进行分片,应该可以达到一对一的发送与接收。

#14


千万别糊里糊涂的
首先TCP是安全的连接

发多少东西,发送的顺序,这些到达另外一端顺序,大小都不会变
简单的可以这样一次发送中头部设置4个字节作为整个包的长度
接收的时候recv调用下这个是阻塞函数
收到的字节可以放在一个大大的buf里面,然后进行解析

客户端是recv调用一次,解析一次,如果长度不够,那就继续接收,解析后用memmove把后面的数据移动到前面

#15



来个草图吧,粘包处理的思路,面试的时候,把这个思路说给考官听,基本OK

const int head_len = 4
char buff[0x4000]
char buff_once[1024]
int nRev = 0
while(1)
{
int n = recv(s,buff_once,1024)
if n == (-1)
   break  //socket 出错


memcpy(buff[nRev],buff_once,n)
nRev+=n

if nRev break  //收到的数据不够包头长度 

int nPacket = *(int*)buff
if nPacket>nRev
continue  //收到的数据长度不够一个数据包

DoWork(buff,0,nPacket)   //调用数据处理函数
memmove  调用这个函数,把buff后面的数据移动到最前面移动
nRev -= nPacket
}

#16


if nRev         continue  //收到的数据不够包头长度 

#17


引用 15 楼 henry3695 的回复:
来个草图吧,粘包处理的思路,面试的时候,把这个思路说给考官听,基本OK

const int head_len = 4
char buff[0x4000]
char buff_once[1024]
int nRev = 0
while(1)
{
int n = recv(s,buff_once,1024)
if n == (-1)
   break  //socket 出错


memcpy(buff[nRev],buff_once,n)
nRev+=n

if nRev break  //收到的数据不够包头长度 

int nPacket = *(int*)buff
if nPacket>nRev
continue  //收到的数据长度不够一个数据包

DoWork(buff,0,nPacket)   //调用数据处理函数
memmove  调用这个函数,把buff后面的数据移动到最前面移动
nRev -= nPacket
}

memcpy(buff[nRev],buff_once,n)编译通不过。
面试被毙。

#18


收到的数据长度超过两个包呢?

#19


引用 18 楼 zhao4zhong1 的回复:
收到的数据长度超过两个包呢?


有道理,草图还得优化下

#20



const int head_len = 4
char buff[0x4000]
char buff_once[1024]
int nRev = 0
while(1)
{
    int n = recv(s,buff_once,1024)
    if n == (-1)
       break  //socket 出错
 
     
    memcpy(buff[nRev],buff_once,n)
    nRev+=n
 
    if nRev         continue  //收到的数据不够包头长度 
 
loop1:
;
    int nPacket = *(int*)buff
    if nPacket>nRev
        continue  //收到的数据长度不够一个数据包
 
    DoWork(buff,0,nPacket)   //调用数据处理函数
    memmove  调用这个函数,把buff后面的数据移动到最前面移动
    nRev -= nPacket

//服务器下发出现了粘包问题

    if nRev>=head_len
goto loop1

    
}


//加了 个goto 语句,感觉意思已经到位了

#21


n为0咋办?
n小于-1咋办?
nRev小于0咋办?
nPacket小于0咋办?
出错后怎么重连?
重连前对缓冲区怎么处理?
可否采用环形缓冲区,避免memmove?
……?

推荐阅读
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • javax.mail.search.BodyTerm.matchPart()方法的使用及代码示例 ... [详细]
  • Spring Boot 中配置全局文件上传路径并实现文件上传功能
    本文介绍如何在 Spring Boot 项目中配置全局文件上传路径,并通过读取配置项实现文件上传功能。通过这种方式,可以更好地管理和维护文件路径。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • 本文介绍如何在 Android 中自定义加载对话框 CustomProgressDialog,包括自定义 View 类和 XML 布局文件的详细步骤。 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • MATLAB字典学习工具箱SPAMS:稀疏与字典学习的详细介绍、配置及应用实例
    SPAMS(Sparse Modeling Software)是一个强大的开源优化工具箱,专为解决多种稀疏估计问题而设计。该工具箱基于MATLAB,提供了丰富的算法和函数,适用于字典学习、信号处理和机器学习等领域。本文将详细介绍SPAMS的配置方法、核心功能及其在实际应用中的典型案例,帮助用户更好地理解和使用这一工具箱。 ... [详细]
  • 优化后的标题:深入探讨网关安全:将微服务升级为OAuth2资源服务器的最佳实践
    本文深入探讨了如何将微服务升级为OAuth2资源服务器,以订单服务为例,详细介绍了在POM文件中添加 `spring-cloud-starter-oauth2` 依赖,并配置Spring Security以实现对微服务的保护。通过这一过程,不仅增强了系统的安全性,还提高了资源访问的可控性和灵活性。文章还讨论了最佳实践,包括如何配置OAuth2客户端和资源服务器,以及如何处理常见的安全问题和错误。 ... [详细]
  • Python 程序转换为 EXE 文件:详细解析 .py 脚本打包成独立可执行文件的方法与技巧
    在开发了几个简单的爬虫 Python 程序后,我决定将其封装成独立的可执行文件以便于分发和使用。为了实现这一目标,首先需要解决的是如何将 Python 脚本转换为 EXE 文件。在这个过程中,我选择了 Qt 作为 GUI 框架,因为之前对此并不熟悉,希望通过这个项目进一步学习和掌握 Qt 的基本用法。本文将详细介绍从 .py 脚本到 EXE 文件的整个过程,包括所需工具、具体步骤以及常见问题的解决方案。 ... [详细]
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
  • 在本文中,我们将为 HelloWorld 项目添加视图组件,以确保控制器返回的视图路径能够正确映射到指定页面。这一步骤将为后续的测试和开发奠定基础。首先,我们将介绍如何配置视图解析器,以便 SpringMVC 能够识别并渲染相应的视图文件。 ... [详细]
  • AIX编程挑战赛:AIX正方形问题的算法解析与Java代码实现
    在昨晚的阅读中,我注意到了CSDN博主西部阿呆-小草屋发表的一篇文章《AIX程序设计大赛——AIX正方形问题》。该文详细阐述了AIX正方形问题的背景,并提供了一种基于Java语言的解决方案。本文将深入解析这一算法的核心思想,并展示具体的Java代码实现,旨在为参赛者和编程爱好者提供有价值的参考。 ... [详细]
  • HTML5绘图功能的全面支持与应用
    HTML5绘图功能的全面支持与应用 ... [详细]
  • C#中实现高效UDP数据传输技术
    C#中实现高效UDP数据传输技术 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有