TCP/IP协议是一种流协议,流协议是字节流,只有开始和结束,包与包之间没有边界,所以容易产生粘包,但是不会丢包。
UDP/IP协议是数据报,有边界,不存在粘包,但是可能丢包。
产生粘包问题的原因
.SQ_SNDBUF套接字本身有缓冲区(发送缓冲区,接收缓冲区)
.tcp传送的网络数据最大值MSS大小限制
.链路层也有MTU(最大传输单元)大小限制,如果数据包大于>MTU要在IP层进行分片,导致消息分割。(可以简单的认为MTU是MSS加包头数据)
.tcp的流量控制和拥塞控制,也可能导致粘包
.tacp延迟发送机制等等
结论:TCP/IP协议,在传输层没有处理粘包问题,必须由程序员处理

粘包的解决方案--本质上是要在应用层维护消息与消息的边界
.定包长
.包尾加\r\n(比如ftp协议)
.包头加上包体长度
.更复杂的应用层协议
粘包的几种状态

//粘包解决方案--包头加上包体长度
//服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> typedef struct _packet
{
int len; //定义包体长度
char buf[]; //定义包体
} Packet; /*
* fd:文件描述符
* buf:数据缓存区
* count:读取字符数
* */
ssize_t readn(int fd, const void *buf, ssize_t count)
{
//定义临时指针变量
char *pbuf = (char *)buf;
//定义每次已读数据
ssize_t nread = ;
//定义剩余数据
ssize_t lread = count;
while (lread > )
{
nread = read(fd, pbuf, lread);
/*
* 情况分析:假设b缓冲区buf足够大
* 如果nread==count,说明数据正好被读完
* nread<count,说明数据没有被读完,这种情况就是由于粘包产生的
* socket只接收了数据的一部分,TCP/IP协议不可能出现丢包情况
* nread==0,说明对方关闭文件描述符
* nread==-1,说明read函数报错
* nread>count,这种情况不可能存在
* */
if (nread == -)
{
//read()属于可中断睡眠函数,需要做信号处理
if (errno == EINTR)
continue;
perror("read() err");
return -;
} else if (nread == )
{
printf("client is closed !\n");
//返回已经读取的字节数
return count - lread;
}
//重新获取 剩余的 需要读取的 字节数
lread = lread - nread;
//指针后移
pbuf = pbuf + nread;
}
return count;
} /* fd:文件描述符
* buf:数据缓存区
* count:读取字符数
* */
ssize_t writen(int fd, const void *buf, ssize_t count)
{
//定义临时指针变量
char *pbuf = (char *)buf;
//每次写入字节数
ssize_t nwrite = ;
//剩余未写字节数
ssize_t lwrite = count;
while (lwrite > )
{
nwrite = write(fd, pbuf, lwrite);
if (nwrite == -)
{
if (errno == EINTR)
continue;
perror("write() err");
return -;
} else if (nwrite == )
{
printf("client is closed !\n");
//对方关闭文件描述符,返回已经写完的字节数
return count - lwrite;
}
lwrite -= nwrite;
pbuf += nwrite;
}
return count;
} int main(int arg, char *args[])
{
//create socket
int listenfd = socket(AF_INET, SOCK_STREAM, );
if (listenfd == -)
{
perror("socket() err");
return -;
}
//reuseaddr
int optval = ;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
== -)
{
perror("setsockopt() err");
return -;
}
//bind
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(listenfd, (struct sockaddr *) &addr, sizeof(addr)) == -)
{
perror("bind() err");
return -;
}
//listen
if(listen(listenfd,SOMAXCONN)==-)
{
perror("listen() err");
return -;
}
//accept
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn = accept(listenfd, (struct sockaddr *)&peeraddr,&peerlen);
if (conn == -)
{
perror("accept() err");
return -;
}
Packet _packet;
while ()
{
memset(&_packet, , sizeof(_packet));
//获取报文自定义包头
int rc = readn(conn, &_packet.len, );
if (rc == -)
{
exit();
} else if (rc < )
{
exit();
}
//把网络字节序转化成本地字节序
int n = ntohl(_packet.len);
//获取报文自定义包体
rc = readn(conn, _packet.buf, n);
if (rc == -)
{
exit();
} else if (rc < n)
{
exit();
}
//打印报文数据
fputs(_packet.buf, stdout);
//将原来的报文数据发送回去
printf("发送报文的长度%d\n", + n);
rc = writen(conn, &_packet, + n);
if (rc == -)
{
exit();
} else if (rc < + n)
{
exit();
}
}
return ;
}
//粘包解决方案--包头加上包体长度
//客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> typedef struct _packet
{
int len; //定义包体长度
char buf[]; //定义包体
} Packet; /*
* fd:文件描述符
* buf:数据缓存区
* count:读取字符数
* */
ssize_t readn(int fd, const void *buf, ssize_t count)
{
//定义临时指针变量
char *pbuf = (char *)buf;
//定义每次已读数据
ssize_t nread = ;
//定义剩余数据
ssize_t lread = count;
while (lread > )
{
nread = read(fd, pbuf, lread);
/*
* 情况分析:假设b缓冲区buf足够大
* 如果nread==count,说明数据正好被读完
* nread<count,说明数据没有被读完,这种情况就是由于粘包产生的
* socket只接收了数据的一部分,TCP/IP协议不可能出现丢包情况
* nread==0,说明对方关闭文件描述符
* nread==-1,说明read函数报错
* nread>count,这种情况不可能存在
* */
if (nread == -)
{
//read()属于可中断睡眠函数,需要做信号处理
if (errno == EINTR)
continue;
perror("read() err");
return -;
} else if (nread == )
{
printf("client is closed !\n");
//返回已经读取的字节数
return count - lread;
}
//重新获取 剩余的 需要读取的 字节数
lread = lread - nread;
//指针后移
pbuf = pbuf + nread;
}
return count;
} /* fd:文件描述符
* buf:数据缓存区
* count:读取字符数
* */
ssize_t writen(int fd, const void *buf, ssize_t count)
{
//定义临时指针变量
char *pbuf = (char *)buf;
//每次写入字节数
ssize_t nwrite = ;
//剩余未写字节数
ssize_t lwrite = count;
while (lwrite > )
{
nwrite = write(fd, pbuf, lwrite);
if (nwrite == -)
{
if (errno == EINTR)
continue;
perror("write() err");
return -;
} else if (nwrite == )
{
printf("client is closed !\n");
//对方关闭文件描述符,返回已经写完的字节数
return count - lwrite;
}
lwrite -= nwrite;
pbuf += nwrite;
}
return count;
} int main(int arg, char *args[])
{
//create socket
int sockfd = socket(AF_INET, SOCK_STREAM, );
if (sockfd == -)
{
perror("socket() err");
return -;
}
//connect
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -)
{
perror("connect() err");
return -;
}
int rc = , numx = ;
Packet _packet;
memset(&_packet, , sizeof(_packet));
while (fgets(_packet.buf, sizeof(_packet.buf), stdin) != NULL)
{
//发送数据
numx = strlen(_packet.buf);
//将本地字节转化成网络字节序
_packet.len = htonl(numx);
rc = writen(sockfd, &_packet, + numx);
if (rc == -)
{
return -;
} else if (rc < + numx)
{
return -;
}
//接收数据
memset(&_packet, , sizeof(_packet));
//获取包头
rc = readn(sockfd, &_packet.len, );
if (rc == -)
{
return -;
} else if (rc < )
{
return -;
}
//将网络字节转化成本地字节
numx = ntohl(_packet.len);
//printf("接收数据的大小是%d\n",numx);
//获取包体
rc = readn(sockfd, &_packet.buf, numx);
if (rc == -)
{
return -;
} else if (rc < numx)
{
return -;
}
//打印包体
fputs(_packet.buf,stdout);
memset(&_packet, , sizeof(_packet));
}
return ;
}

Linux 网络编程详解四(流协议与粘包)的更多相关文章

  1. TCP/UDP Linux网络编程详解

    本文主要记录TCP/UDP网络编程的基础知识,采用TCP/UDP实现宿主机和目标机之间的网络通信. 内容目录 1. 目标2.Linux网络编程基础2.1 嵌套字2.2 端口2.3 网络地址2.3.1 ...

  2. Linux 网络编程详解五(TCP/IP协议粘包解决方案二)

    ssize_t recv(int s, void *buf, size_t len, int flags); --与read相比,只能用于网络套接字文件描述符 --当flags参数的值设置为MSG_P ...

  3. Linux 网络编程详解九

    TCP/IP协议中SIGPIPE信号产生原因 .假设客户端socket套接字close(),会给服务器发送字节段FIN: .服务器接收到FIN,但是没有调用close(),因为socket有缓存区,所 ...

  4. Linux 网络编程详解一(IP套接字结构体、网络字节序,地址转换函数)

    IPv4套接字地址结构 struct sockaddr_in { uint8_t sinlen;(4个字节) sa_family_t sin_family;(4个字节) in_port_t sin_p ...

  5. Linux 网络编程详解二(socket创建流程、多进程版)

    netstat -na | grep " --查看TCP/IP协议连接状态 //socket编程提高版--服务器 #include <stdio.h> #include < ...

  6. Linux 网络编程详解十一

    /** * read_timeout - 读超时检测函数,不含读操作 * @fd:文件描述符 * @wait_seconds:等待超时秒数,如果为0表示不检测超时 * 成功返回0,失败返回-1,超时返 ...

  7. Linux 网络编程详解八

    TCP/IP协议三次握手机制 TCP/IP是全双工通道,两端都可以读写,三次握手机制就是验证TCP/IP是否是全双工通道 1.客户端调用connect()函数,阻塞客户端进程,客户端向服务器发送数据包 ...

  8. Linux 网络编程详解十二

    UDP的特点 --无连接 --基于消息的数据传输服务 --不可靠 --UDP更加高效 UDP注意点 --UDP报文可能会丢失,重复 --UDP报文可能会乱序 --UDP缺乏流量控制(UDP缓冲区写满之 ...

  9. Linux 网络编程详解十

    select int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *tim ...

随机推荐

  1. java文件复制函数

    在写代码中,经常会需要复制文件.这时候就可以把这些函数放到工具类里面. 下面是一个复制文件的函数: public class CopyFileUtil { private static String ...

  2. jetty for linux 启用日志

    jetty7.8 文档 :https://wiki.eclipse.org/Jetty jetty9 文档: http://www.eclipse.org/jetty/documentation/cu ...

  3. 【转】JavaScript 异步进化史

    前言 JS 中最基础的异步调用方式是 callback,它将回调函数 callback 传给异步 API,由浏览器或 Node 在异步完成后,通知 JS 引擎调用 callback.对于简单的异步操作 ...

  4. 使用sql server profilter跟踪sql

    最近在研究EF延迟加载和贪婪加载的用法时,想要查看Linq生成的sql.一开始通过VS-->调试-->窗口-->IntelliTrace事件,来查看生成的sql,并不是十分准确.然后 ...

  5. CSS之旅——第三站 强大的伪选择器

    说到伪选择器,真的让我体会到了CSS的无比强大,强大到自己貌似都不认识CSS了,有点C# 6.0中一些语法糖带给我们的震撼...首先 我们可以在VS里面提前预览一下. 可以看到,上面的伪类有很多很多, ...

  6. 神奇的decimal,也许面试会问到哦~

    这段时间忙的像狗一样,写博客的事情也就耽搁了,继续扯,为什么说decimal神奇呢,大家都知道decimal是基元类型,但是 这个decimal类型在IL中居然没有相应的IL指令,也就是说CLR根本不 ...

  7. 【Python】-【类解析】--【脚本实例】

    通过脚本事例,解析下Python中类的几个概念在脚本中的应用 脚本如下: ++++++++++++++++++++++++++++++++++++++++ #!/usr/bin/env python# ...

  8. C#显示SQL语句格式

    --SQL SERVER生成测试环境: Create database Test; go USE [Test] GO if OBJECT_ID('Tab','U') is not null drop ...

  9. 知道创宇研发技能表v3.1

    by @知道创宇(www.knownsec.com) @余弦 & 404团队 后续动态请关注微信公众号:Lazy-Thought 说明 关于知道创宇 知行合一 | 守正出奇 知道创宇是一家黑客 ...

  10. C语言中链表任意位置怎么插入数据?然后写入文件中?

    链表插入示意图:(图是个人所画)因为链表指针指来指去,难以理解,所以辅助画图更加方便. 插入某个学号后面图: 定义的结构体: struct student { ]; //学生学号 ]; //学生姓名 ...