中科大计网学习记录笔记(十二):TCP 套接字编程

news2025/1/30 15:53:54

前前言:大家看到这一章节的时候一定不要跳过,虽然标题是编程,但实际上是对 socket 的运行机制做了详细的讨论,对理解 TCP 有很大的帮助;但是由于本节涉及到了大量的编程知识,对于一些朋友来说不是很好理解,所以大家看本节的时候强烈建议结合我的这篇笔记来学习,本篇整理了许多详细的案例和解析,也补充了我自己的理解,导致这篇笔记来到了七千多字,工程量很大,如果这篇文章对你有所帮助别忘了留下你的点赞和关注😊😊😊

前言:

学习视频:中科大郑烇、杨坚全套《计算机网络(自顶向下方法 第7版,James F.Kurose,Keith W.Ross)》课程
该视频是B站非常著名的计网学习视频,但相信很多朋友和我一样在听完前面的部分发现信息量过大,有太多无法理解的地方,在我第一次点开的时候也有相同的感受,但经过了一段时间项目的学习,对计网有了更多的了解,所以我准备在这次学习的时候做一些记录并且加入一些我的理解,希望能够帮助到大家。
往期笔记可以看专栏中的内容😊😊😊

文章目录

      • 2.8 TCP 套接字编程
        • 2.8.1 通过 TCP 建立连接的流程
        • 2.8.2 编程案例
        • 2.8.3 总结

2.8 TCP 套接字编程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

💡 Socket 编程:应用进程使用 传输层 提供的服务才能够交换报文,实现应用协议从而实现引用。

  • 在 TCP/ IP 的架构中,应用进程使用 Socket API 来 访问 传输的服务
  • 传输层中 TCP 提供的是可靠的 字节流 的服务,UDP 提供的是不可靠的数据 UDP 数据报的服务
  • 字节流需要上层自己来维护界限,也就是 TCP 提供字节流,但是需要自己来划清有哪些部分
2.8.1 通过 TCP 建立连接的流程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

前期准备: 服务器

  • 服务器进程必须先处于运行的状态

    • 创建欢迎 socket

    • 和本地的 端口 捆绑

    • 在原本创建的 socket 上阻塞式等待接收用户的连接


  1. 客户端

    • 创建客户端本地的 socket,捆绑到本地的端口
      • 与指定的服务器的进程和 IP 地址和端口,发送建立连接的请求
      • 然后这个 socket 会在本地等候连接
  2. 服务器

    • 当服务器接收到用户的请求时,接触阻塞,创建一个新的 socket 值,与客户端通信

    • 原初的监听 socket 并不会消失,它继续存在并可以接收更多的客户端连接请求。服务器端的监听 socket 负责监听和接受连接。

  3. 客户端

    • 客户端收到信息后,确认收到信息,再次向客户端发送报文表示确认链接

💡 上面的过程其实就是简化过的 TCP 三次握手 的过程

  • 上述过程中的端口中,服务器的端口是指定的,就是对外提供服务的端口(每个服务器都有对外提供访问服务的端口)
  • 而客户端的端口可能是随机指定的,比如编写一段 Java 程序跑在 8080 端口,程序向数据库服务器发送请求(作为客户端),数据库服务器提供的访问端口为 3306,返回的数据不会直接发送到Java程序监听的8080端口,而是发回到客户端在连接时使用的随机源端口上。
    • 这个例子是为了让大家更好的理解服务器的客户端,因为在命令执行的时候服务器其实就是发送请求的客户端,而 同时 又作为对外提供服务的服务器。
2.8.2 编程案例

💡 这里使用一个案例来更好的描述 socket 的运转流程

  • 客户端接收一个仅包含小写字母的输入
  • 将输入部分发送给服务器
  • 服务器将其转换为大写并且返回

👉 在开始之前先来介绍两个结构体(存储信息的一种数据结构)

🍀 sockaddr_in:是 C 语言编程中用于表示 Internet (IPv4) 地址的结构体,通常在 BSD sockets API 中使用。

struct sockaddr_in {
    sa_family_t sin_family;   // 地址族,对于 IPv4 应该为 AF_INET(通常定义为2)
    in_port_t   sin_port;     // 端口号(网络字节序,即大端字节序)
    struct in_addr sin_addr;  // IPv4 地址结构体
    unsigned char sin_zero[8]; // 填充字段
};

💡 概念辨析

  • 地址族:地址族用来定义不同类型的网络层协议以及这些协议如何识别并处理网络地址。每一种地址族对应一种特定的网络协议或一系列相关的协议,支持相应的地址格式和相关功能
    • 比如 AF_INET 代表 IPv4 地址族
  • 填充字段:对于不同的网络协议(IPv4、IPv6 等)是将其对应的结构转换为一个通用的结构来进行信息的传递的,所以能够将其转换为这个统一的结构且不会出现匹配问题,就需要一个填充字段来使得更具备兼容性。

再来看这个结构体的存储信息的意义

  • 这个结构体在通信过程中表示的是通信的 一方,当客户端或服务器需要指定一个网络地址进行连接、绑定或接收连接时,就会使用 sockaddr_in 结构体
  • 这个端点既可以是本地端(例如服务器绑定到的IP地址和端口),也可以是远程端(例如客户端要连接的目标服务器的IP地址和端口)。

🍀 hostent:在C语言的网络编程中用于存储与主机名解析相关的信息,它包含了从域名系统(DNS)获取到的一个特定主机的所有相关信息

struct hostent {
    char *h_name;         // 主机的正式名字(规范化主机名)
    char **h_aliases;     // 指向别名列表的指针数组,
    int h_addrtype;       // 地址类型,例如AF_INET(IPv4)、AF_INET6(IPv6)
    int h_length;         // 地址长度,以字节为单位
    char **h_addr_list;   // 指向主机IP地址列表的指针数组
};

💡 这一部分是结合完整的 C 语言代码的演示,不用去看懂代码,熟悉完整的流程即可

🍀 首先,服务器启动程序,并且创建一个 欢迎 socket 来等待客户端的请求:

第一步:变量声明

   	int server_sock, client_sock;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE];

💡 这里先不对每个变量的意义做出解释,后面在流程中一一阐述

第二步:创建 socket

	// 创建 socket  
	if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

💡 解析

  • server_sock,在之前的 socket 部分提到过,上层应用访问 socket 是通过一个整型,这个整型就标识着这个 socket。而调用函数创建一个 socket 返回的就是这个标识。
  • 上述的创建过程包括很多后续的创建过程都是通过 if 创建的,旨在如果创建失败的话会执行 if 中的语句来做失败处理,是一种常见的方式。

第三步:设置服务器地址结构体(也就是上面提到的 sockaddr_in

    // 设置服务器地址结构体
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8000); // 假设监听8000端口
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定到所有网络接口

💡 AF_INET 就是上面提到的 IPv4 协议族

第四步:绑定 socket 到指定的端口

    // 绑定 socket 到指定端口
   if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

💡 这一步就是将创建的 socket 实实在在的绑定到本机的端口上了

  • 在本步骤之前,socket 都是一个系统返回的整型,在本步骤之后它就与本地的端口做了绑定
  • 上面的 sockaddr 就是上面提到的统一的数据结构,绑定等操作不会直接对 server_addr 或者 client_addr 做操作,而是将其转换为 sockaddr 结构。

第五步:开始监听请求

    // 开始监听连接请求
    if (listen(server_sock, 10) == -1) { // 最多允许10个未处理的连接请求排队
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

💡 创建完上面的 socket,也就是常说的 HelloSocket

  • 并且将请求放于队列中等待,这个队列最多存储十个请求,当请求多于十个的时候会拒绝请求。
  • 通过这个命令将 socket 置于监听的状态

🍀 接下来将视角转向客户端

第一步:变量声明

    int client_sock;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];
    char input[BUFFER_SIZE];

第二步:创建 socket

    // 创建 socket
    if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

💡 这个 socket 是标识本地的 socket

  • 与服务器不同的是服务器需要额外的去指定自己的端口(不能随便分配,因为这个端口是要告知客户端来使其访问的)
  • 所以对于客户端的 socket 来说,不需要去手动指定端口,而是在调用 connect()函数与服务器建立连接时,操作系统会 自动 为该socket分配一个可用的临时端口作为源端口(这个端口号通常是随机选择的,范围是1024至65535)。

第三步:设置服务器地址的结构体

    // 设置服务器地址结构体
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        perror("invalid address/ Address not supported");
        exit(EXIT_FAILURE);
    }

💡 服务器的信息是提前告知用户的

  • 中间可能会通过 hostent 来通过域名解析得到 IP 地址
  • 本例子中没有涉及

第四步:与服务器建立连接

// 连接到服务器
if (connect(client_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
    perror("connect failed");
    exit(EXIT_FAILURE);
}

💡 需要传入本地的 socket 和服务器的信息


🍀 继续回到服务器

服务器本地等待连接

 while (1) {
// 接受客户端连接请求
	if ((client_sock = accept(server_sock, (struct sockaddr *)&client_addr ,
                          &client_len)) < 0) {
        perror("accept failed");
        continue;
	}
     // 其他代码
}

💡 这段代码之后服务器的代码都是写在这个 while 函数中的

  • accept 函数会阻塞的等待连接,当 listen 队列中有连接的时候会去除并且处理

🍀 回到客户端来发送信息

发送数据到服务器

    // 发送数据到服务器
    if (send(client_sock, input, strlen(input), 0) < 0) {
        perror("send failed");
        exit(EXIT_FAILURE);
    }

💡 当建立连接后客户端就可以发送信息了

  • 这时候本地的 socket 就与服务器连接上了,通过这个 socket 就可以实现与服务器的通信了

🍀 服务器来处理客户端的请求

    // 接收客户端发送的数据
    ssize_t bytes_received = recv(client_sock, buffer, BUFFER_SIZE - 1, 0);
    if (bytes_received <= 0) {
        perror("recv failed");
        close(client_sock);
        continue;
    }
    buffer[bytes_received] = '\0'; // 确保字符串结束

    // 转换小写为大写
    to_upper(buffer);

    // 发送数据回客户端
    if (send(client_sock, buffer, strlen(buffer), 0) < 0) {
        perror("send failed");
    }

    // 关闭已处理的客户端连接
    close(client_sock);

💡 上述的代码是写在 while 循环中的,表示对一个连接的处理

  • 接下来将信息发送回客户端
  • 注意这时候已经是通过 client_socket 来发送数据了,这个是在上面通过 accept() 函数来返回的 socket,表示一个连接
  • 原本的 Hello Socket 仍然在 listen 状态等候连接

🍀 最后就是客户端接收返回的信息

客户端接收信息

    // 接收服务器发来的数据
    ssize_t bytes_received = recv(client_sock, buffer, BUFFER_SIZE - 1, 0);
    if (bytes_received <= 0) {
    	perror("recv failed");
    } else {
    	buffer[bytes_received] = '\0';
    	printf("Received uppercase word from server: %s\n", buffer);
    }

    // 关闭客户端socket
    close(client_sock);
2.8.3 总结

💡 相信看到这里大家对 socket 的执行流程已经有了大致的把握,这里换个视角做一个总结。

其实整个流程就是 不断调用 socket API 来实现各种服务,这体现了下层为上层通过接口提供服务的特点。

这里来回顾一下上面代码中使用到的 socket API

  1. socket():函数用于创建一个新的socket,参数指定了地址族(AF_INET表示IPv4)、套接字类型(SOCK_STREAM表示TCP流式套接字)和协议(通常为0,由系统选择合适的协议)
    • 用于上面 socket 的创建,比如说客户端和服务器的 socket
    • 当这些信息都不指定的时候返回的就是一个普通的整型,没有任何绑定
    • 这个绑定可以通过后续的 bind 和 connect 来额外附加,最终使用的 socket 一定是绑定了客户端和服务器的全部信息的
    • 所以后续的一部分操作就是针对这个 socket 信息的修补,来让它达到可以使用的状态
  2. bind():绑定一个本地IP地址和端口到服务器socket。server_addr是一个初始化好的sockaddr_in结构体,包含了服务器要监听的IP地址和端口号信息。
    • 这就是服务器修补 socket 的方式,将自己的端口号等信息补充到里面
  3. listen():将服务器socket设置为监听模式,并允许指定数量(在例子中是10个)的未完成连接请求排队等待。
  4. accept():该函数使服务器socket进入阻塞状态,直到有新的客户端连接请求到达。成功建立连接后,返回一个新的socket描述符client_sock,用来与新客户端通信,并通过client_addr获取客户端的地址信息。
    • 这里的 socket 就是一个完完整整可以使用的 socket,通过 accept 函数补充上了客户端的信息
  5. connect():通常用于客户端程序连接到服务器端的 socket
    • 这是客户端用来修补自己 socket 的方式,调用后操作系统会自动分配临时端口信息
  6. send()recv()recv()用于从已连接的socket接收数据,而send()则用于向已连接的socket发送数据。
  7. close():关闭已打开的socket文件描述符,释放系统资源。

这就引出了另一种理解方式:在真正的业务开始之前,所有的操作都是为了使得通信双方获得 完整的 socket

  • 客户端有了服务器的信息所以可以直接获取完整的 socket
  • 而服务器需要等待客户端发送信息给它才能获取完整的 socket,所以服务器的 socket 会有 listen 的状态来等待信息补充完整。

💡 补充:三次握手

  • 三次握手是TCP(Transmission Control Protocol)建立连接的过程,确保了客户端和服务器之间的数据传输通道可靠、有序且无差错。
  • 第一次握手
    • 客户端发送一个SYN(同步序列编号,Synchronize Sequence Numbers)报文段到服务器,其中包含客户端选择的一个初始序号seq=x。此时,客户端进入SYN_SENT状态。
  • 第二次握手
    • 服务器接收到客户端的SYN报文段后,如果同意建立连接,则回应一个SYN+ACK(同步并确认)报文段。这个报文段中包含了服务器的初始序号seq=y,并将确认号ack设置为x+1,表示已接收并期望接下来的数据从x+1开始。服务器进入SYN_RECEIVED状态。
  • 第三次握手
    • 客户端收到服务器的SYN+ACK报文段后,向服务器发送一个ACK(确认)报文段。该报文中确认号ack设置为y+1,表示已经接收到了服务器的SYN,并且期望接下来的数据从y+1开始。客户端同时也将自己的序号seq更新为x+1。

以上面提到的视角看,其实三次握手就是补充 socket 信息的一个过程

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里云香港服务器cn2速度测试和租用价格表

阿里云香港服务器中国香港数据中心网络线路类型BGP多线精品&#xff0c;中国电信CN2高速网络高质量、大规格BGP带宽&#xff0c;运营商精品公网直连中国内地&#xff0c;时延更低&#xff0c;优化海外回中国内地流量的公网线路&#xff0c;可以提高国际业务访问质量。阿里云服务…

『运维备忘录』之 CMD 命令详解

运维人员不仅要熟悉操作系统、服务器、网络等只是&#xff0c;甚至对于开发相关的也要有所了解。很多运维工作者可能一时半会记不住那么多命令、代码、方法、原理或者用法等等。这里我将结合自身工作&#xff0c;持续给大家更新运维工作所需要接触到的知识点&#xff0c;希望大…

Linux-系统资源管理的命令

目录 查看CPU&#xff1a;more /proc/meminfo 查看内存数据&#xff1a;free -m / free -h 查看系统版本&#xff1a;more /etc/issue 查看操作系统的类型&#xff1a;uname -a 查看主机名称&#xff1a;hostname 查看磁盘空间&#xff1a;df -h 查看某个目录空间…

互联网加竞赛 基于设深度学习的人脸性别年龄识别系统

文章目录 0 前言1 课题描述2 实现效果3 算法实现原理3.1 数据集3.2 深度学习识别算法3.3 特征提取主干网络3.4 总体实现流程 4 具体实现4.1 预训练数据格式4.2 部分实现代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习机器视觉的…

【IEEE-Trans】这本TOP刊一点不拖泥带水,审稿很利索!质量也很不错!

【SciencePub学术】 期刊信息简介 IEEE TRANSACTIONS ON INSTRUMENTATION AND MEASUREMENT IF(2022)&#xff1a;5.6&#xff0c;JCR1区&#xff0c;中科院2区TOP 期刊数据指标 ISSN&#xff1a;0018-9456 IF(2022)&#xff1a;5.6 自引率&#xff1a;23.20% 年发文量&a…

好的程序员不该局限技术故步自封,更多去了解产品,运营,销售,推广,公司运作吧

在当今技术迅速发展的时代&#xff0c;作为程序员&#xff0c;我们常常面临着学习速度跟不上技术变化的困扰。每年涌现的新技术、新框架&#xff0c;给我们带来了巨大的挑战。尤其是随着年龄增长&#xff0c;学习能力的下降似乎让我们更加被动。技术的发展也并非一帆风顺&#…

【大厂AI课学习笔记】【2.1 人工智能项目开发规划与目标】(6)特征工程初步

特征工程是一个非常重要的概念&#xff0c;从特征工程可以领会到机器学习的真谛。 特征工程就是从原始数据转换为特征向量的过程。 特征工程的特点&#xff1a; 特征工程是机器学习中很重要的起始步骤&#xff0c;直接影响效果&#xff0c;需要大量的时间。 数据和特征决定了…

NLP深入学习:《A Survey of Large Language Models》详细学习(六)

文章目录 1. 前言2. LLMs 能力与评价方法2.1 基础能力2.1.1 语言生成2.1.2 知识利用率2.1.3 复杂推理 2.2 高阶能力2.3 基准和评估方法2.3.1 评价基准2.3.2 评估方法2.3.3 评估方法优点和不足 3. 参考 1. 前言 最近正在读 LLM 论文的综述&#xff0c;当前采取的策略是部分内容…

【JVM】打破双亲委派机制

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;JVM ⛺️稳中求进&#xff0c;晒太阳 打破双亲委派机制 打破双亲委派机制三种方法 自定义类加载器 ClassLoader包含了四个核心方法 //由类加载器子类实现&#xff0c;获取二进制数据调用…

安装cockpit

1、下载cockpit yum -y install cockpit 下载相关环境 yum install qemu-kvm libvirt libvirt-daemon virt-install virt-manager libvirt-dbus 2、启动libvirtd systemctl start libvirtd.service systemctl enable libvirtd.service 3、设置开机自启动 systemctl enabl…

高程 | 继承与派生(c++)

文章目录 &#x1f4da;继承的概念和语法&#x1f4da;派生类生成过程&#x1f4da;继承权限和继承方式&#x1f407;公有继承&#x1f407;私有继承&#x1f407;保护继承 &#x1f4da;类型转换规则&#x1f4da;派生类构造函数和析构函数&#x1f4da;继承中的静态成员特性&…

并发编程之深入理解JVM并发三大特性

并发编程之深入理解JVM&并发三大特性 并发编程解决的问题 ​ 多线程同步&#xff08;一个线程需要等待另一个线程的结果&#xff0c;一个线程依赖于另一个线程&#xff09;&#xff0c;互斥&#xff08;一个资源只能一个线程使用&#xff09;&#xff0c;分工&#xff08…

华为配置直连二层组网隧道转发示例

配置直连二层组网隧道转发示例 组网图形 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件扩展阅读 业务需求 企业用户通过WLAN接入网络&#xff0c;以满足移动办公的最基本需求。且在覆盖区域内移动发生漫游时&#xff0c;不影响用户的业务使用。 组网需求 AC组…

Open CASCADE学习|布尔运算

目录 1、加法&#xff1a;BRepAlgoAPI_Fuse 2、减法&#xff1a;BRepAlgoAPI_Cut 3、交集&#xff1a;BRepAlgoAPI_Common 4、交线&#xff1a;BRepAlgoAPI_Section 1、加法&#xff1a;BRepAlgoAPI_Fuse #include <gp_Pnt.hxx>#include <BRepPrimAPI_MakeBox.hxx…

计算机网络-数据通信基础

目录 前言 一、数据通信基本概念 二、数据通信相关知识1 总结 前言 正在学习计算机网络体系&#xff0c;把每日所学的知识梳理出来&#xff0c;既能够当作读书笔记&#xff0c;又能分享出来和大家一同学习讨论。 一、数据通信基本概念 基本概念&#xff1a;信源、信道、信宿&…

怎么防止u盘里的东西被拷贝?

随着科技的进步&#xff0c;U盘作为便携式存储设备&#xff0c;在我们的日常工作和生活中扮演着越来越重要的角色。然而&#xff0c;这也带来了一个潜在的安全风险——U盘内的数据可能被未经授权的人员拷贝。 一、了解U盘拷贝数据的常见方式 在探讨如何防止U盘数据被拷贝之前&…

setTimeout及setInterval

setTimeout用法&#xff1a; 过100ms调用函数 setTimeout((a) > {console.log(111,a);}, 100,我是定时器); 参数&#xff1a; 第一个参数&#xff1a;必填&#xff0c;回调函数&#xff1b; 第二个参数&#xff1a;可选&#xff0c;延迟时间&#xff0c;单位ms 第三个参…

Apache Httpd 常见漏洞解析(全)

一、Apache HTTPD 换行解析漏洞 漏洞编号&#xff1a;CVE-2017-15715 Apache HTTPD是一款HTTP服务器&#xff0c;它可以通过mod_php来运行PHP网页。 其2.4.0~2.4.29版本中存在一个解析漏洞。 在解析PHP时&#xff0c;1.php\x0A将被按照PHP后缀进行解析&#xff0c;导致绕过…

springboot第56集:微服务框架,物联网IOT,SQL数据库MySQL底层,AOP收集业务操作日志架构周刊...

单点登录 1.配置代理信息 /*请求登陆的方法*/ "/modelLogin": {//本地服务接口地址&#xff0c;这是测试环境&#xff0c;正式环境需要更改下地址target: "http://127.0.0.1:6776/xxx-auth/",changeOrigin: true,pathRewrite: {"^/modelLogin": …

MATLAB知识点:ismembertol函数(★★☆☆☆)考虑了一定的容差的ismember函数

讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自第3章&#xff1a;课后习题讲解中拓展的函数 在讲解第三…