文章目录

  • 1.流协议与粘(nian)包
  • 2.粘包产生的原因
  • 4.粘包处理方案
  • 5.readn,writen
  • 6.回射客户/服务器

1.流协议与粘(nian)包

  • tcp是基于字节流的传输服务(字节流是无边界的),像流水一样,无法区分边界,他不能保证对等方一次读操作能够返回多少字节。
    eg:hostA发送两个数据包给hostB,对于hostB来讲,他可能有以下四种情况:例如第(2),一次性读取M1和M2的所有消息,这里M1和M2就粘在一起了。
    第(3)第一次读操作返回M1消息的全部和M2条消息的一部分(M2_1),第二次读操作返回M2条消息的一部分(M2_2)

  • udp是基于消息的报文,是有边界的

2.粘包产生的原因

  • tcp会有粘包问题
    (1)write将应用进程缓冲区的数据拷贝到套接口发送缓冲区中,当应用进程缓冲区的大小超过了套接口发送缓冲区SO_SNDBUF的大小,就有可能产生粘包问题,可能一部分已经发送出去了,对方已经接收,另外一部分才发送套接口发送缓冲区进行发送,对方延迟接收后一部分数据,导致粘包问题,这里的原因是:数据包的分割
    (2)TCP最大段MSS的限制,可能会对发送的消息进行分割,可能会产生粘包问题
    (3)应用层的最大传输单元MTU的限制,若发送的数据包超过MTU,会在IP层进行分组或分片,可能会对发送的消息进行分割,可能会产生粘包问题
    (4)tcp的流控,拥塞控制,延迟发送机制都有可能产生粘包问题

4.粘包处理方案

  • 本质上是要在应用层维护消息与消息的边界
    (1)定长包
    (2)包尾加\r\n(ftp就是这么用的)
    消息若本来就有\r\n,\r\n本来就是消息的一部分的话,则需要转义的方式
    (3)包头加上包体长度
    可以先接收包头,然后通过包头计算出包体的长度,然后才接收包体所对应的数据包
    (4)更复杂的应用层协议

5.readn,writen

  • 具体实现已经在6中回射服务器中了

6.回射客户/服务器

  • (1)以定长的方式收发数据
    NetworkProgramming-master (1)\LinuxNetworkProgramming\P9echosrv.c
    NetworkProgramming-master (1)\LinuxNetworkProgramming\P9echocli.c
==================NetworkProgramming-master (1)\LinuxNetworkProgramming\P9echosrv.c=============================
//
// Created by wangji on 19-7-21.
//#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;struct packet
{int len;char buf[1024];
};#define ERR_EXIT(m) \do  \{   \perror(m);  \exit(EXIT_FAILURE); \} while(0);//参考man 2 read声明写出来的
//ssize_t是无符号整数
ssize_t readn(int fd, void *buf, size_t count)
{size_t nleft = count;   // 剩余字节数ssize_t nread;//已接收字节数char *bufp = (char*) buf;while (nleft > 0){if ((nread = read(fd, bufp, nleft)) < 0){if (errno == EINTR)continue;return  -1;} else if (nread == 0)//表示读取到了EOF,表示对方关闭return count - nleft;//表示剩余的字节数bufp += nread;//读到的nread,要将bufp指针偏移nleft -= nread;}return count;
}//参考man 2 write声明写出来的
ssize_t writen(int fd, const void *buf, size_t count)
{size_t nleft = count;//剩余要发送的字节数ssize_t nwritten;char* bufp = (char*)buf;while (nleft > 0){//write一般不会阻塞,缓冲区数据大于发送的数据,就能够成功将数据拷贝到缓冲区中if ((nwritten = write(fd, bufp, nleft)) < 0){if (errno == EINTR){continue;}return -1;}else if (nwritten == 0){continue;}bufp += nwritten;//已发送字节数nleft -= nwritten;//剩余字节数}return count;
}void do_service(int connfd)
{// char recvbuf[1024];struct packet recvbuf;int n;while (1){memset(&recvbuf, 0, sizeof(recvbuf));int ret = readn(connfd, &recvbuf.len, 4);if (ret == -1){ERR_EXIT("read");}else if (ret < 4){printf("client close\n");break;}n = ntohl(recvbuf.len);ret = readn(connfd, recvbuf.buf, n);if (ret == -1){ERR_EXIT("read");}else if (ret < n){printf("client close\n");break;}fputs(recvbuf.buf, stdout);writen(connfd, &recvbuf, 4+n);}}int main(int argc, char** argv) {// 1. 创建套接字int listenfd;if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {ERR_EXIT("socket");}// 2. 分配套接字地址struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(6666);servaddr.sin_addr.s_addr = htonl(INADDR_ANY);// servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");// inet_aton("127.0.0.1", &servaddr.sin_addr);int on = 1;// 确保time_wait状态下同一端口仍可使用if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0){ERR_EXIT("setsockopt");}// 3. 绑定套接字地址if (bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0) {ERR_EXIT("bind");}// 4. 等待连接请求状态if (listen(listenfd, SOMAXCONN) < 0) {ERR_EXIT("listen");}// 5. 允许连接struct sockaddr_in peeraddr;socklen_t peerlen = sizeof(peeraddr);// 6. 数据交换pid_t pid;while (1){int connfd;if ((connfd = accept(listenfd, (struct sockaddr *) &peeraddr, &peerlen)) < 0) {ERR_EXIT("accept");}printf("id = %s, ", inet_ntoa(peeraddr.sin_addr));printf("port = %d\n", ntohs(peeraddr.sin_port));pid = fork();if (pid == -1){ERR_EXIT("fork");}if (pid == 0)   // 子进程{close(listenfd);do_service(connfd);//printf("child exit\n");exit(EXIT_SUCCESS);}else{//printf("parent exit\n");close(connfd);}}// 7. 断开连接close(listenfd);return 0;
}
==================NetworkProgramming-master (1)\LinuxNetworkProgramming\P9echocli.c=============================
//
// Created by wangji on 19-7-21.
//#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;#define ERR_EXIT(m) \do  \{   \perror(m);  \exit(EXIT_FAILURE); \} while(0);//参考man 2 read声明写出来的
//ssize_t是无符号整数
ssize_t readn(int fd, void *buf, size_t count)
{size_t nleft = count;   // 剩余字节数ssize_t nread;//已接收字节数char *bufp = (char*) buf;while (nleft > 0){if ((nread = read(fd, bufp, nleft)) < 0){if (errno == EINTR)continue;return  -1;} else if (nread == 0)//表示读取到了EOF,表示对方关闭return count - nleft;//表示剩余的字节数bufp += nread;//读到的nread,要将bufp指针偏移nleft -= nread;}return count;
}//参考man 2 write声明写出来的
ssize_t writen(int fd, const void *buf, size_t count)
{size_t nleft = count;//剩余要发送的字节数ssize_t nwritten;char* bufp = (char*)buf;while (nleft > 0){//write一般不会阻塞,缓冲区数据大于发送的数据,就能够成功将数据拷贝到缓冲区中if ((nwritten = write(fd, bufp, nleft)) < 0){if (errno == EINTR){continue;}return -1;}else if (nwritten == 0){continue;}bufp += nwritten;//已发送字节数nleft -= nwritten;//剩余字节数}return count;
}int main(int argc, char** argv) {// 1. 创建套接字int sockfd;if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {ERR_EXIT("socket");}// 2. 分配套接字地址struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(6666);// servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");// inet_aton("127.0.0.1", &servaddr.sin_addr);// 3. 请求链接if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {ERR_EXIT("connect");}// 4. 数据交换char recvbuf[1024]={0};char sendbuf[1024]={0};int n = 0;while (fgets((sendbuf), sizeof(sendbuf), stdin) != NULL)   // 键盘输入获取{//发送定长包,缺点:若发送数据小,但是发的是定长包,会对网络造成负担writen(sockfd, sendbuf,sizeof(sendbuf));//发送和接收都是1024个字节readn(sockfd, recvbuf,sizeof(recvbuf));fputs(recvbuf, stdout);memset(sendbuf, 0, sizeof(sendbuf));memset(recvbuf, 0, sizeof(recvbuf));}// 5. 断开连接close(sockfd);return 0;
}
  • (2)发送的数据包是头部+包体,接收的时候:先接收包头,接收完毕后,将数据包的长度计算出来,再接收对应的长度,对消息与消息之间进行了区分
    NetworkProgramming-master (1)\LinuxNetworkProgramming\P9echosrv3.c
    NetworkProgramming-master (1)\LinuxNetworkProgramming\P9echocli3.c
=====================================NetworkProgramming-master (1)\LinuxNetworkProgramming\P9echosrv3.c=============//
// Created by wangji on 19-7-21.
//#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;struct packet
{int len;char buf[1024];
};#define ERR_EXIT(m) \do  \{   \perror(m);  \exit(EXIT_FAILURE); \} while(0);//参考man 2 read声明写出来的
//ssize_t是无符号整数
ssize_t readn(int fd, void *buf, size_t count)
{size_t nleft = count;   // 剩余字节数ssize_t nread;//已接收字节数char *bufp = (char*) buf;while (nleft > 0){if ((nread = read(fd, bufp, nleft)) < 0){if (errno == EINTR)continue;return  -1;} else if (nread == 0)//表示读取到了EOF,表示对方关闭return count - nleft;//表示剩余的字节数bufp += nread;//读到的nread,要将bufp指针偏移nleft -= nread;}return count;
}//参考man 2 write声明写出来的
ssize_t writen(int fd, const void *buf, size_t count)
{size_t nleft = count;//剩余要发送的字节数ssize_t nwritten;char* bufp = (char*)buf;while (nleft > 0){//write一般不会阻塞,缓冲区数据大于发送的数据,就能够成功将数据拷贝到缓冲区中if ((nwritten = write(fd, bufp, nleft)) < 0){if (errno == EINTR){continue;}return -1;}else if (nwritten == 0){continue;}bufp += nwritten;//已发送字节数nleft -= nwritten;//剩余字节数}return count;
}void do_service(int connfd)
{// char recvbuf[1024];struct packet recvbuf;int n;while (1){memset(&recvbuf, 0, sizeof recvbuf);int ret = readn(connfd, &recvbuf.len, 4);//先接收头部if (ret == -1){ERR_EXIT("read");}else if (ret < 4){printf("client close\n");break;}n = ntohl(recvbuf.len);//转换程主机字节序,包体实际长度nret = readn(connfd, recvbuf.buf, n);//接收包体if (ret == -1){ERR_EXIT("read");}else if (ret < n){printf("client close\n");break;}fputs(recvbuf.buf, stdout);writen(connfd, &recvbuf, 4+n);}}int main(int argc, char** argv) {// 1. 创建套接字int listenfd;if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {ERR_EXIT("socket");}// 2. 分配套接字地址struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof servaddr);servaddr.sin_family = AF_INET;servaddr.sin_port = htons(6666);servaddr.sin_addr.s_addr = htonl(INADDR_ANY);// servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");// inet_aton("127.0.0.1", &servaddr.sin_addr);int on = 1;// 确保time_wait状态下同一端口仍可使用if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0){ERR_EXIT("setsockopt");}// 3. 绑定套接字地址if (bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0) {ERR_EXIT("bind");}// 4. 等待连接请求状态if (listen(listenfd, SOMAXCONN) < 0) {ERR_EXIT("listen");}// 5. 允许连接struct sockaddr_in peeraddr;socklen_t peerlen = sizeof(peeraddr);// 6. 数据交换pid_t pid;while (1){int connfd;if ((connfd = accept(listenfd, (struct sockaddr *) &peeraddr, &peerlen)) < 0) {ERR_EXIT("accept");}printf("id = %s, ", inet_ntoa(peeraddr.sin_addr));printf("port = %d\n", ntohs(peeraddr.sin_port));pid = fork();if (pid == -1){ERR_EXIT("fork");}if (pid == 0)   // 子进程{close(listenfd);do_service(connfd);//printf("child exit\n");exit(EXIT_SUCCESS);}else{//printf("parent exit\n");close(connfd);}}// 7. 断开连接close(listenfd);return 0;
}
=================================NetworkProgramming-master (1)\LinuxNetworkProgramming\P9echocli3.c=============//
// Created by wangji on 19-7-21.
//#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;struct packet
{int len;//包头:存放包体实际的数据长度char buf[1024];//包体
};#define ERR_EXIT(m) \do  \{   \perror(m);  \exit(EXIT_FAILURE); \} while(0);/参考man 2 read声明写出来的
//ssize_t是无符号整数
ssize_t readn(int fd, void *buf, size_t count)
{size_t nleft = count;   // 剩余字节数ssize_t nread;//已接收字节数char *bufp = (char*) buf;while (nleft > 0){if ((nread = read(fd, bufp, nleft)) < 0){if (errno == EINTR)continue;return  -1;} else if (nread == 0)//表示读取到了EOF,表示对方关闭return count - nleft;//表示剩余的字节数bufp += nread;//读到的nread,要将bufp指针偏移nleft -= nread;}return count;
}//参考man 2 write声明写出来的
ssize_t writen(int fd, const void *buf, size_t count)
{size_t nleft = count;//剩余要发送的字节数ssize_t nwritten;char* bufp = (char*)buf;while (nleft > 0){//write一般不会阻塞,缓冲区数据大于发送的数据,就能够成功将数据拷贝到缓冲区中if ((nwritten = write(fd, bufp, nleft)) < 0){if (errno == EINTR){continue;}return -1;}else if (nwritten == 0){continue;}bufp += nwritten;//已发送字节数nleft -= nwritten;//剩余字节数}return count;
}int main(int argc, char** argv) {// 1. 创建套接字int sockfd;if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {ERR_EXIT("socket");}// 2. 分配套接字地址struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(6666);// servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");// inet_aton("127.0.0.1", &servaddr.sin_addr);// 3. 请求链接if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {ERR_EXIT("connect");}// 4. 数据交换
//    char recvbuf[1024];
//    char sendbuf[1024];struct packet recvbuf;struct packet sendbuf;memset(&recvbuf, 0, sizeof(recvbuf));memset(&sendbuf, 0, sizeof(sendbuf));int n = 0;while (fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL)   // 键盘输入获取{n = strlen(sendbuf.buf);//n是包体的长度sendbuf.len = htonl(n); // 主机字节序转换为网络字节序writen(sockfd, &sendbuf, 4+n); // 头部4字节+包体int ret = readn(sockfd, &recvbuf.len, 4); //先接收头部if (ret == -1){ERR_EXIT("read");}else if (ret < 4){printf("server close\n");break;}n = ntohl(recvbuf.len);//转换程主机字节序ret = readn(sockfd, &recvbuf.buf, n);//接收包体if (ret == -1){ERR_EXIT("read");}else if (ret < n){printf("server close\n");break;}fputs(recvbuf.buf, stdout); //将接收到的数据输出// 清空memset(&recvbuf, 0, sizeof(recvbuf));memset(&sendbuf, 0, sizeof(sendbuf));}// 5. 断开连接close(sockfd);return 0;
}
  • 测试:
  • Makefile
.PHONY:clean all
CC=gcc
CFLAGS=-Wall -g
BIN=echosrv echocli
all:$(BIN)
%.o:%.c$(CC) $(CFLAGS) -c $< -o $@
clean:rm -f *.o $(BIN)

(P9)socket编程四:流协议与粘(nian)包,粘包产生的原因,粘包处理方案,readn,writen 6.回射客户/服务器相关推荐

  1. UNIX网络编程卷1 回射客户程序 TCP客户程序设计范式

    本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 下面我会介绍同一个使用 TCP 协议的客户端程序的几个不同版本,分别是停等版本.select ...

  2. 【Python】socket编程——使用UDP协议打造在线时间服务器

    题目

  3. socket 编程:回射客户/服务程序

    参考 <Unix 网络编程> github 地址 unp.h #include <stdio.h> #include <unistd.h> #include < ...

  4. socket编程(四)

    1.流协议与粘包 TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的.这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的.TCP的发送方无法保证 ...

  5. socket编程(十四)

    1.UDP特点 (1)无连接 (2)基于消息的数据传输服务 (3)不可靠 (4)一般情况下UDP更加高效 2.UDP客户/服务基本模型 3.UDP回射客户/服务器 server.cpp #includ ...

  6. linux网络编程之socket编程(六)

    经过一个国庆长假,又有一段时间没有写博文了,今天继续对linux网络编程进行学习,如今的北京又全面进入雾霾天气了,让我突然想到了一句名句:"真爱生活,珍惜生命",好了,言归正传. ...

  7. socket编程(六)

    1.TCP/IP回射客户/服务器 2.TCP是个流协议 (1)TCP是个基于字节流传输的,只维护发送出去多少,确认了多少,没有维护消息与消息之间的边界,因而可能导致粘包问题. (2)粘包问题解决方法是 ...

  8. socket编程(二)

    1.TCP客户/服务器模型 2.回射客户/服务器 3.socket函数 头文件:<sys/socket.h> 功能:创建一个套接字用于通信 原型:int socket(int domain ...

  9. socket编程(五)

    1.read,write与recv,send (1)recv只能接收套接字io (2)recv有Flags选项 (3)recv选项:MSG_OOB接收带外数据,通过紧急指针,TCP选项 (4)recv ...

最新文章

  1. 微服务架构的核心要点和实现原理
  2. 笔记-信息系统开发基础-uml-uml类图关系
  3. 删除所有正在运行和退出的docker实例
  4. 软核,硬核、固核的区别!
  5. 面试精讲之面试考点及大厂真题 - 分布式专栏 04 谈谈你对分布式的理解,为什么引入分布式?
  6. 简谈java的split
  7. CCF201604-3 路径解析(解法二)(100分)(废除!!!)
  8. centos免密登录
  9. Android 还可以走多久?
  10. Android屏幕共享权限,chrome屏幕共享权限
  11. 程序员使用C#编写表白小软件(VS2013)(表白程序)
  12. A3的PDF试卷怎么对半拆分成A4打印?
  13. 链路追踪Logback-ERROR日志邮件发送
  14. 完全指南:在 Linux 中如何打印和管理打印机
  15. Linux HugePage
  16. 帝国cms tag生成html,帝国CMS tags标签多种调用方法
  17. WinXP系统下Opencms的安装与配置
  18. 100个中国传统英文词汇,你会用英语表达吗?
  19. Ubuntu18.04安装libsdl1.2-dev(亲测可行)
  20. macos如何隐藏或取消隐藏文件/文件夹

热门文章

  1. 动态IP与静态ip的区别是什么
  2. 熵,哈夫曼编码,二进制
  3. LC Uniboot相比于常规的LC光纤连接器有什么特点?
  4. cs224w(图机器学习)2021冬季课程学习笔记12 Knowledge Graph Embeddings
  5. android新闻客户端报告,简易的Android新闻客户端
  6. python 字符串删除首尾空格
  7. 华为模拟器ensp ACL技术
  8. Hadoop3.2.1 【 YARN 】源码分析 : LinuxContainerExecutor 浅析 [ 一 ]
  9. USACO 2.1.4 健康的荷斯坦奶牛 Healthy Holsteins
  10. GitHub是什么?GitHub如何使用?