【Linux Day15 TCP网络通讯】

TCP网络通讯

TCP编程流程

在这里插入图片描述

接口介绍

  • socket()方法是用来创建一个套接字,有了套接字就可以通过网络进行数据的收发。创建套接字时要指定使用的服务类型,使用 TCP 协议选择流式服务(SOCK_STREAM)。

  • **bind()方法是用来指定套接字使用的 IP 地址和端口。**IP 地址就是自己主机的地址,测试程序时可以使用回环地址“127.0.0.1”。端口是一个 16 位的整形值,一般 0-1024 为知名端口,如 HTTP 使用的 80 号端口。这类端口一般用户不能随便使用。其次,1024-4096 为保留端口,用户一般也不使用。4096 以上为临时端口,用户可以使用。在Linux 上,1024 以内的端口号,只有 root 用户可以使用。

  • **listen()方法是用来创建监听队列。**监听队列有两种,一个是存放未完成三次握手的连接,一种是存放已完成三次握手的连接。listen()第二个参数就是指定已完成三次握手队列的长度。

  • accept()处理存放在 listen 创建的已完成三次握手的队列中的连接。每处理一个连接,则accept()返回该连接对应的套接字描述符。如果该队列为空,则 accept 阻塞。

  • connect()方法一般由客户端程序执行,需要指定连接的服务器端的 IP 地址和端口。该方法执行后,会进行三次握手, 建立连接。

  • send()方法用来向 TCP 连接的对端发送数据。send()执行成功,只能说明将数据成功写入到发送端的发送缓冲区中,并不能说明数据已经发送到了对端。send()的返回值为实际写入到发送缓冲区中的数据长度。

  • recv()方法用来接收 TCP 连接的对端发送来的数据。recv()从本端的接收缓冲区中读取数据,如果接收缓冲区中没有数据,则 recv()方法会阻塞;返回值是实际读到的字节数,如果recv()返回值为 0, 说明对方已经关闭了 TCP 连接。

  • close()方法用来关闭 TCP 连接。此时,会进行四次挥手

客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);assert(sockfd != -1);struct sockaddr_in saddr;memset(&saddr, 0, sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));if (-1 == res){ exit(1);}while (1){char buff[128] = {0};printf("input:\n");fgets(buff, 128, stdin);if (strncmp(buff, "end", 3) == 0){break;}send(sockfd, buff, strlen(buff), 0);memset(buff, 0, 128);recv(sockfd, buff, 127, 0);printf("buff=%s\n", buff);}close(sockfd);exit(0);
}

服务端代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(-1 == sockfd){exit(1);}struct sockaddr_in saddr;memset(&saddr, 0, sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);                   // htons 将主机字节序转换为网络字节saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 回环地址int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));if (-1 == res){exit(1);}res = listen(sockfd, 5);if (-1 == res){exit(1);}struct sockaddr_in caddr;socklen_t len = sizeof(caddr);int n = 0;int c = -1;while (1) // 服务器循环接收客户端连接{char data[128] = {0};if (n == 0){c = accept(sockfd, (struct sockaddr *)&caddr, &len); // 阻塞if (c == -1){printf("accept error ");continue;;}}n = recv(c, data, 127, 0); // 阻塞if (n == 0)                //连接关闭{close(c);printf("client close\n");continue;}else if (n < 0)            //出错{printf("recv error");continue;}printf("n = %d, buff = %s\n", n, data);send(c, "OK", 2, 0);}close(sockfd); exit(0);
}

运行结果:

引入多线程处理并发

服务器端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>void *run(void *arg)
{int c = (int)arg;while (1){char buff[128] = {0};if (recv(c, buff, 127, 0) <= 0){break;}printf("recv(%d)=%s", c, buff);send(c, "ok", 2, 0);}printf("one client over(%d)\n", c);close(c);
}int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd){exit(1);}struct sockaddr_in saddr, caddr;memset(&saddr, 0, sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));if (-1 == res){exit(1);}listen(sockfd, 10);while (1){int len = sizeof(caddr);int c = accept(sockfd, (struct sockaddr *)&caddr, &len);if (c < 0){continue;}printf("accept c = %d\n", c);pthread_t id;pthread_create(&id, NULL, run, (void *)c);}close(sockfd);exit(0);
}

运行结果:

引入fork处理并发

服务器端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>void DealClientLink(int c, struct sockaddr_in caddr)
{while (1){char buff[128] = {0};int n = recv(c, buff, 127, 0);if (n <= 0){break;}printf("%s:%d %s", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port), buff);send(c, "OK", 2, 0);}printf("One Client Close\n");close(c);
}void Signal_Fun(int sign)
{wait(NULL);
}
int main()
{signal(SIGCHLD, Signal_Fun); // 用wait()处理僵死进程int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd){printf("create sockfd error\n");exit(1);}struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));assert(-1 != res);listen(sockfd, 10);while (1){struct sockaddr_in caddr;int len = sizeof(caddr);int c = accept(sockfd, (struct sockaddr *)&caddr, &len);assert(-1 != c);printf("%s:%d Link Success\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));pid_t pid = fork();if (-1 == pid){exit(1);}if (0 == pid){DealClientLink(c,caddr);exit(0);   //必须结束子进程,否则会有多个进程调 accept}else{close(c);  //父子进程都需要关闭 c}}close(sockfd);exit(0);
}

运行结果:

TCP连接状态转变图

三次握手

  • 流程图

  • 使用netstat工具查看状态变化(参考图3-8)

四次挥手

  • 流程图

  • 使用netstat命令查看状态(参考图3-8)

TIME_WAIT的作用

在图3-8中,当客户端连接在收到服务器的结束报文段之后,并没有直接进人CLOSED 状态,而是转移到 TIME_WAIT 状态。在这个状态,客户端连接要等待段长为2MSL(Maximum Segment Life,报文段最大生存时间)的时间,才能完全关闭;MSL是 TCP 报文段在网络中的最大生存时间,标准文档 RFC 1122 的建议值是2 min;

TIME WAIT 状态存在的原因有两点:
  1. 可靠地终止TCP 连接

    当服务器发给客户端的ACK中途丢失,客户端收不到ACK,会重新发送FIN,如果此时服务器已经关闭,无法接收来自客户端的FIN,便会陷入一种“藕断丝连”状态(一方关闭,一方未关闭)。这显然是不合适的,因为TCP 连接是全双工的,双方完成数据交换之后,通信双方都必须断开连接以释放系统资源

  2. 保证让迟来的TCP 报文段有足够的时间被识别并丢弃

    在 Linux 系统上,一个TCP 端口不能被同时打开多次(两次及以上)。当一个TCP 连接处于 TIME_WAIT 状态时,我们将无法立即使用该连接占用着的端口来建立一个新连接。反过来,如果不存在 TIME WAIT 态,则应用序能够立即建立一个和刚关闭的连接相似的连接(这里说的相似,是指它们具有相同的 IP 地址和端口号)。这个新的、和原来相似的连接被称为原来的连接的化身 (incarmation)。新的化身可能接收到属于原来的连接的、携带应用程序数据的 TCP 报文段(迟到的报文段),这显然是不应该发生的。这就是 TIMEWAIT 状态存在的第二个原因。

TCP协议特点

流式服务

TCP 字节流的特点,发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系,应用程序对数据的发送和接收是没有边界限制的。如下图:

TCP连接的可靠性

  • IPV4报文格式:

  • TCP报文格式:

  • 应答机制

  • 超时重传

TCP 传输是可靠的。首先,TCP 协议采用发送应答机制,即发送端发送的每个 TCP 报文段都必须得到接收方的应答,才认为这个 TCP 报文段传输成功。其次,TCP 协议采用超时重传机制,发送端在发送出1个 TCP 报文段之后启动定时器,如果在定时时间内未收到应答,它将重发该报文段。最后,因为 TCP 报文段最终是以 IP数据报发送的,而 数据报到达接收端可能乱序、重复,所以 TCP 协议还会对接收到的 TCP 报文段重排、整理,再交付给应用层。

粘包问题

在流式服务中如上图3-9所示,尽管报文已经按顺序整理好并接受,但是无法分割成正确的信息,就形成了所谓的粘包问题,为了解决此问题,我们可以每次发送时进行标记分割,以便于接收方进行分析和拆分,如下图:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/458158.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[C/C++] -- JSON for Modern C++

JSON for Modern C&#xff08;nlohmann/json&#xff09;是一个流行的 C JSON 库&#xff0c;由德国开发者nlohmann编写。这个库提供了简洁而灵活的 API&#xff0c;使得在C中解析和生成JSON数据变得非常方便。 1.JSON简介 JSON&#xff08;JavaScript Object Notation&…

论文精读--ViT

ViT的提出挑战了CNN在计算机视觉领域的绝对统治地位。ViT不仅在视觉领域开了一个新坑&#xff0c;因为它打破了cv与nlp之间的壁垒&#xff0c;所以还在多模态领域挖了一个大坑 题目是AN IMAGE IS WORTH 16X16 WORDS:TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE&#xff0c;把…

(2)(2.14) SPL Satellite Telemetry

文章目录 前言 1 本地 Wi-Fi&#xff08;费用&#xff1a;30 美元以上&#xff0c;范围&#xff1a;室内&#xff09; 2 蜂窝电话&#xff08;费用&#xff1a;100 美元以上&#xff0c;范围&#xff1a;蜂窝电话覆盖区域&#xff09; 3 手机卫星&#xff08;费用&#xff…

EDM营销平台哪个好?推荐的邮件营销平台?

EDM邮件营销平台有哪些&#xff1f;外贸EDM邮件营销平台有哪些&#xff1f; EDM营销平台已成为企业推广产品和服务的重要工具。但是&#xff0c;面对市场上众多的EDM营销平台&#xff0c;究竟哪个更好呢&#xff1f;下面&#xff0c;蜂邮EDM将从平台功能、用户体验、数据分析和…

SpringMVC原理(设计原理+启动原理+工作原理)

文章目录 前言正文一、设计原理1.1 servlet生命周期简述1.2 设计原理小结 二、启动原理2.1 AbstractHandlerMethodMapping 初始化 --RequestMapping注解解析2.2 DispatcherServlet 的初始化2.3 DispatcherServlet#initHandlerMappings(...) 初始化示例说明 三、工作原理 前言 …

DolphinScheduler本地安装

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

(十四)springboot实战——spring securtity安全框架原理之启动流程

前言 本节内容主要介绍spring securtity的初始化启动流程&#xff0c;spring security是通过EnableWebSecurity注解来启用的&#xff0c;在EnableWebSecurity注解中主要引入了WebSecurityConfiguration、SpringWebMvcImportSelector、OAuth2ImportSelector、HttpSecurityConfi…

存算一体:架构创新,打破算力极限

1 需求背景 在全球数据量呈指数级暴涨&#xff0c;算力相对于AI运算供不应求的现状下&#xff0c;存算一体技术主要解决了高算力带来的高能耗成本矛盾问题&#xff0c;有望实现降低一个数量级的单位算力能耗&#xff0c;在功耗敏感的百亿级AIoT设备上、高能耗的数据中心、自动驾…

NLP_循环神经网络(RNN)

文章目录 RNN结构RNN实战RNN小结 RNN结构 NPLM 在处理长序列时会面临一些挑战。首先&#xff0c;由于它仍然是基于词的模型&#xff0c;因此在处理稀有词汇或者词汇表外的词汇时效果不佳。其次&#xff0c;NPLM不能很好地处理长距离依赖关系。而上面这两个局限&#xff0c;恰恰…

Cookie、Session、Token、JWT的区别

文章目录 概述什么是认证&#xff08;Authentication&#xff09;什么是授权&#xff08;Authorization&#xff09;什么是凭证&#xff08;Credentials&#xff09;Cookiecookie 重要的属性 Sessionsession 认证流程&#xff1a; Cookie 和 Session 的区别TokenRefresh Token …

进程优先级、切换、调度

1. 进程优先级 当多个进程等待访问资源&#xff0c;需要排队&#xff0c;所以优先级可以确定访问资源的先后顺序。有优先级的原因就是资源狼多肉少。 在linux中优先级的本质也就是task_struct中一个整型字段&#xff08;PRI&#xff09;,默认值是80&#xff0c;并且范围是[60,…

【蓝桥杯冲冲冲】[NOIP2017 提高组] 宝藏

蓝桥杯备赛 | 洛谷做题打卡day29 文章目录 蓝桥杯备赛 | 洛谷做题打卡day29[NOIP2017 提高组] 宝藏题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1样例 #2样例输入 #2样例输出 #2提示题解代码我的一些话[NOIP2017 提高组] 宝藏 题目背景 NOIP2017 D2T2 题目描…