【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所示,尽管报文已经按顺序整理好并接受,但是无法分割成正确的信息,就形成了所谓的粘包问题,为了解决此问题,我们可以每次发送时进行标记分割,以便于接收方进行分析和拆分,如下图:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32L4学习

STM32L4系列是围绕Cortex-M4构建&#xff0c;具有FPU和DSP指令集&#xff0c;主频高达80MHz。 STM32CubeL4简介 STM32Cube 是 ST 提供的一套性能强大的免费开发工具和嵌入式软件模块&#xff0c;能够让开发人员在 STM32 平台上快速、轻松地开发应用。它包含两个关键部分&…

XXE基础知识整理(附加xml基础整理)

全称&#xff1a;XML External Entity 外部实体注入攻击 原理 利用xml进行读取数据时过滤不严导致嵌入了恶意的xml代码&#xff1b;和xss原理雷同 危害 外界攻击者可读取商户服务器上的任意文件&#xff1b; 执行系统命令&#xff1b; 探测内网端口&#xff1b; 攻击内网网站…

NAS系统折腾记 – Emby搭建家庭多媒体服务器

Emby简介 Emby是一款优秀的媒体服务器软件&#xff0c;致力于为用户提供丰富的多媒体体验。通过Emby&#xff0c;您可以方便地在家庭内的各种设备上观看您喜爱的电影、电视剧和其他视频内容。而且&#xff0c;Emby还具备强大的媒体管理功能&#xff0c;让您的影视资源井然有序…

nodejs 事件循环

浏览器的事件循环比较熟悉了&#xff0c;也来了解下 node 的。 参考来源&#xff1a; https://nodejs.org/en/guides/event-loop-timers-and-nexttick/ https://juejin.cn/post/6844903999506923528 事件循环分为 6 个阶段&#xff0c;图中每个框都是一个阶段&#xff0c;每个阶…

Vue服务端渲染

Vue服务端渲染 一、服务端渲染基础 1、概述 我们现在可以使用Vue,React等开发SPA单页面应用&#xff0c;单页面应用的优点&#xff0c;用户体验好&#xff0c;开发效率高&#xff0c;可维护性好等。 缺点&#xff1a;首屏渲染时间长&#xff08;在客户端通过JS来生成html来…

Vue-easy-tree封装及使用

1.使用及安装 下载依赖 npm install wchbrad/vue-easy-tree引入俩种方案 1.在main.js中引入 import VueEasyTree from "wchbrad/vue-easy-tree"; import "wchbrad/vue-easy-tree/src/assets/index.scss" Vue.use(VueEasyTree)2.当前页面引入 import VueEa…

蓝桥杯嵌入式第七届真题(完成) STM32G431

蓝桥杯嵌入式第七届真题(完成) STM32G431 题目 相关文件 main.c /* USER CODE BEGIN Header */ /********************************************************************************* file : main.c* brief : Main program body**********************…

layui-实现上下表,父子表单选加载事件

layui-实现上下表&#xff0c;父子表单选加载事件 代码HTML代码表格数据加载点击主表行&#xff0c;加载子表数据 实现效果图 代码 主子表&#xff0c;实现点击主表的单元格实现选中主表&#xff0c;并加载子表 HTML代码 //主表 <table class"layui-hide" id&q…

日志追踪-Tracing

1. 前言 分布式链路跟踪中有两个重要的概念&#xff1a;跟踪&#xff08;trace&#xff09;和 跨度&#xff08; span)。trace 是请求在分布式系统中的整个链路视图&#xff0c;span 则代表整个链路中不同服务内部的视图&#xff0c;span 组合在一起就是整个 trace 的视图在整个…

中科星图——Sentinel-1_SAR_GRD数据集

数据名称&#xff1a; Sentinel-1_SAR_GRD 数据来源&#xff1a; Copernicus 时空范围&#xff1a; 2022年8月-2023年2月 空间范围&#xff1a; 全国 数据简介&#xff1a; 哨兵1号&#xff08;Sentinel-1&#xff09;卫星是欧洲航天局哥白尼计划&#xff08;GMES&…

全球降水数据产品介绍

一、数据基本概况 降水数据在气象学、水文学、农业、生态学等领域有着广泛的用途。以下是一些降水数据的主要用途&#xff1a; 气象预报和监测&#xff1a; 降水数据是气象预报的重要组成部分&#xff0c;对预测天气、气候和自然灾害&#xff08;如暴雨、洪水&#xff09;至关…

day38 斐波那契数 爬楼梯 使用最小花费爬楼梯

题目1&#xff1a;509 斐波那契数 题目链接&#xff1a;509 斐波那契数 题意 斐波那契数列由0和1开始 后面的每一项数字都是前面两项数字之和 计算F(n) 动态规划 动规五部曲&#xff1a; 1&#xff09;dp数组及下标i的含义 dp[i] : 第i个斐波那契数值 i: 第i个斐…

使用 Visual Studio Code 在远程计算机上调试 PostgreSQL

使用 Visual Studio Code 在远程计算机上调试 PostgreSQL 1. 概述 PostgreSQL 是一个功能强大的开源关系数据库管理系统&#xff0c;适用于各种应用程序。在开发过程中&#xff0c;调试 PostgreSQL 对于识别和解决问题至关重要。在本博客中&#xff0c;我们将手把手教你使用客…

【考研408】操作系统笔记

文章目录 [toc] 计算机系统概述操作系统的基本概念操作系统的概念和特征操作系统的目标和功能&#xff08;**处理器管理、存储器管理、设备管理、文件管理、向用户提供接口、扩充机器**&#xff09; 操作系统的发展与分类操作系统的运行环境操作系统的运行机制 操作系统的体系结…

Node.js-1

Node.js 简介 定义&#xff1a;Node.js 是一个跨平台 JavaScript 运行环境&#xff0c;使开发者可以搭建服务器端的 JavaScript 应用程序 为什么 Node.js 能执行 JS 代码&#xff1a; Chrome 浏览器能执行 JS 代码&#xff0c;依靠的是内核中的 V8引擎&#xff08;即&#x…

【ELK】logstash快速入门

1.概述 1.1.什么是logstash&#xff1f; 之前我们聊了es&#xff0c;并且用docker搭建了一个eskibana的环境。es目前最普遍的用法是用来存储日志的&#xff0c;然后结合kibana对日志做一些可视化的工作。既然要收集日志&#xff0c;就面临着一个问题&#xff1a; 各个系统的…

Linux下新建用户

新建用户 sudo adduser -m username添加密码 sudo passwd username设置权限 sudo vi /etc/sudoers在user privilege这一行&#xff0c;仿照root&#xff0c;另起一行&#xff0c;添加上 设置命令解释器 sudo vi /etc/passwd找到新建用户名&#xff0c;将sh改为bash vi中…

基于知识图谱的少样本和零样本学习综述

论文题目&#xff1a;Zero-Shot and Few-Shot Learning With Knowledge Graphs: A Comprehensive Survey 本文作者&#xff1a;陈矫彦&#xff08;曼彻斯特大学&牛津大学&#xff09;、耿玉霞&#xff08;浙江大学&#xff09;、陈卓&#xff08;浙江大学&#xff09;、Je…

快速理解复杂系统组成学习内容整合

目录 一、复杂系统组成 二、接入系统 (Access System) 三、应用系统 (Application System) 四、基础平台 (Foundation Platform) 五、中间件 (Abundant External Middleware) 六、支撑系统 (Supporting System) 参考文章 一、复杂系统组成 复杂系统是由多个相互关联、相…

【Java程序设计】【C00245】基于Springboot的家政服务管理平台(有论文)

基于Springboot的家政服务管理平台&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的家政服务管理平台 本系统分为前台模块、管理员功能模块、用户功能模块以及服务人员功能模块。 前台模块&#xff1a;系统首页的…