Socket本质、实战演示两个进程建立TCP连接通信的过程

news2024/9/24 3:27:03

文章目录

    • Socket是什么
      • 引入面试题, 使你更深刻的理解四元组
    • Socket网络通信大体流程
    • 实战演示TCP连接建立过程
      • 需要用到的linux 查看网络的一些命令
      • 测试的程序
      • 一些准备工作
      • 启动服务端, 并没有调用accept
      • 启动客户端
      • 开启服务accept

Socket是什么

通俗来说,Socket是套接字,是一种编程接口,类似于电话插口,通过Socket可以进行网络通信, 但是这种很不容易让人理解.

其实本质来说Socket就是四原组,包括 客户端ip: 客户端端口 + 服务端ip: 服务端端口, 其实就是一个对应关系, 通过这个四原组就可以唯一的确定一个数据包来自哪里,要发送到哪里.
从而可以使得不同服务器上的不同进程进行网络通信, 而数据不会乱掉, 这就是Socket.

Socket是TCP协议层的, 是内核级别的, 这个怎么理解?
在我们使用Socket编程时,服务需要执行accept()方法进行监听, 但是即使我们不执行这个方法, 也会进行三次握手,直接建立tcp连接, 内核会直接帮我们做这件事情, 这个后面有程序验证.
在这里插入图片描述

引入面试题, 使你更深刻的理解四元组

现在有一个客户端,IP地址用AIP代替, 还有一个服务端, IP地址是CIP
而通信其实是两个进程间的通信,我们知道服务端进程在启动的时候需要绑定监听一个端口, 比如是XPORT.
而客户端在发起建立连接的时候其实也会随机起一个端口, 比如是BPORT, 那双方建立连接的时候就会有一个四元组. 这个对应关系既在客户端存在,也在服务端存在
在这里插入图片描述

  • 问题一: 服务端在建立连接后是否需要为客户端随机分配一个端口
    不需要的, 因为当建立连接后, 不论是客户端还是服务端, 内核都会为这个连接分配资源, 在资源中都存储了这个对应关系(四元组), 所以不论是对于客户端还是服务端, 只要这个对应关系有, 这个对应关系是一个唯一标识, 就没有必要再分配一个端口号了.双方就可以进行通信.

  • 问题二: 同一个客户端, 能不能启多个不同的进程去连接同一个服务端的某一个进程
    是可以的, 比如客户端启了三个进程,占用的端口分别是BPORT,CPORT,DPORT, 那其实就会形成三个不同的四原组. 这三个都是唯一的, 自然不会影响.在这里插入图片描述

  • 问题三: 一个服务端的某个进程, 连接了很多不同的客户端, 那是怎么区分每个数据包是来自哪个客户端呢
    其实在每个数据包中, 都会有这个对应关系, 也就是这个四元组, 这样就很容器区分了

  • 问题四:默认linux机器能使用的端口是65535个, 那假如一个客户端起了很多的进程, 连接的都是同一个服务器的80端口, 把65535个端口都用了, 那现在这个客户端能不能再使用端口去连接相同服务器的其他端口 或者 不同服务器的其他端口
    都是可以的, 因为客户端可以重复使用这些端口, 只要生成的四元组是唯一的, 比如以下, 某个客户端的B,C,D端口已经连接了某个服务端的X端口, 现在又用客户端的B,C,D端口连接了服务端的Y端口, 这六组对应关系都是唯一的, 就能够确定数据来源哪, 发送到哪, 那自然是没问题的
    在这里插入图片描述

  • 问题五: 那客户端可以使用相同的端口,但是服务端一个进程已经用了80端口, 再起一个程序去占用80端口就会报异常, 这是为什么呢
    因为服务端是ServerSocket, ServerSocket还有些特殊, 需要先开启监听Listen, 开启监听后等待客户端的连接,
    那假如服务端有两个程序A,B都使用了80端口开启监听等待连接, 此时有个客户端想要和A程序通信, 发起三次握手中第一次连接请求, 直接发给了80端口, 此时能分清是要和哪个程序建立连接吗? 此时是分不清的.

  • 再举个生活中的通俗例子, 你有一个电话号码, 可以用这个电话号码打给市场监督管理局, 这个市场监督管理局的电话一定是唯一的, 不然你就会分不清, 但是你可以用同样的电话号码打给住建局, 那你其实就是客户端, 可以用相同的号码, 而公家单位市场监管局,住建键局这些,他们是服务端, 电话号码都是唯一的, 不能使用相同的.

Socket网络通信大体流程

接下来大致理一下不同机器上两个应用程序通过Socket通信流程

  1. 服务端开启监听, 在内核中生成一个LISTEN并绑定端口
  2. 客户端开启连接后, 两个机器内核开始进行三次握手, 建立连接
  3. 建立连接之后会给其分配资源, 比如说分配buffer, 未来我们读写数据就是和这个buffer交互
    还会在内核中保存这个四元祖, 也就是这个映射关系
  4. 为对应程序分配TCP文件描述符 , 将来应用程序就可以通过这个文件描述符找到对应的四元组, 从而找到buffer,进行数据的读写.
    在这里插入图片描述

实战演示TCP连接建立过程

需要用到的linux 查看网络的一些命令

lsof -p 进程id  --------- 查看某个进程的文件描述符,包括tcp这些
netstat -natp -------  可以查看一个tcp连接建立的过程
tcpdump ------   可以对tcp连接进行抓包

测试的程序

客户端 -> ServerClientTest

public class ServerClientTest {
    public static void main(String[] args) {
        try {
            Socket client = new Socket("192.168.68.2", 9090);
            client.setTcpNoDelay(true);
            client.setSendBufferSize(20);

            OutputStream outputStream = client.getOutputStream();

            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

            while(true){
                String line = reader.readLine();
                if(line != null){
                    byte[] bytes = line.getBytes();
                    for (byte b : bytes) {
                        outputStream.write(b);  //注意这里面没有调用flush
                    }
                }
            }


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

服务端 -> ServerSocketTest

public class ServerSocketTest {
    public static void main(String[] args) {
        ServerSocket server = null;

        try {
            server = new ServerSocket();
            server.bind(new InetSocketAddress(9090));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("server up use 9090");
        while(true){
            try {
                //阻塞,暂时不执行accept,分水岭
                System.in.read();
                //真正开启监听
                Socket client = server.accept();
                System.out.println("client port: "+client.getPort());

                new Thread(() -> {
                    while(true){
                        try {
                            InputStream in = client.getInputStream();
                            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                            char[] data = new char[1024];
                            int num = reader.read(data);

                            if(num>0){
                                System.out.println("client read some data :"+new String(data));
                            }else if(num==0){
                                System.out.println("client read data nothing......");
                            }else{
                                System.out.println("client read data error......");
                            }

                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }


        }

    }
}

需要注意,在 服务端启动程序后,如果不进行任何输入, 程序会阻塞在这里, 不会调用accept()方法

在这里插入图片描述

一些准备工作

对9090这个端口进行抓包
在这里插入图片描述
查看当前机器的所有的TCP连接,并没有9090端口的
在这里插入图片描述

启动服务端, 并没有调用accept

在这里插入图片描述
启动服务端,但是还没有调用accept, 内核会主动注册一个LISTEN,绑定端口号
有了这个LISTEN之后, 客户端就可以连进来了
在这里插入图片描述
同时使用jps 命令查看刚刚启动的服务端的进程id是3907
使用lsof -p 3907, 看看这个进程的文件描述符情况,可以看到有一个监听状态的文件描述符
在这里插入图片描述

启动客户端

在这里插入图片描述

注意此时,服务端还没有调用accept方法,启动服务端后没有做任何操作, 还阻塞在这行代码
在这里插入图片描述

使用tcpdump查看抓包, 发现已经经过三次握手
在这里插入图片描述
使用netstat查看所有的TCP以及连接状态, 发现9090端口已经和一个客户端建立了连接, 但是这个Socket还没有分配给任何程序去使用
但是内核里面已经有它了
在这里插入图片描述

开启服务accept

这里随便输入了一个回车, 跳过了 System.in.read(); 这行代码
在这里插入图片描述
程序里面真正有了一个tcp的文件描述符
在这里插入图片描述
内核中的socket也真正分配给了对应程序去使用

在这里插入图片描述
至此, 两个进程建立TCP连接通信的过程已经完毕.

到这里, 你应该真正能明白Socket最主要就是四元组, 明白Socket是TCP协议层的, 是内核级别的.

今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.
在这里插入图片描述

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

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

相关文章

文件IO练习

一、用read函数完成文件大小计算 #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, const char *argv[]) {int fd open("./1.tx…

子组件因多次监听导致重复调用接口问题

问题原因&#xff1a; 有多个tab切换页面&#xff0c;因为内容一致&#xff0c;写了一个公共组件&#xff0c;这个组件我每次监听当前tab点击的index值&#xff0c;因为有3个tab&#xff0c;就会监听三次&#xff0c;所以每个tab里对应的接口就会相应的调用3次接口&#xff0c…

分布式ID性能评测:CosId VS 美团 Leaf

环境 MacBook Pro (M1)JDK 17JMH 1.36运行在本机 Docker 内的 mariadb:10.6.4 运行 CosId SegmentChainId 模式&#xff0c;基准测试代码&#xff1a; Benchmarkpublic long generate() {return segmentChainId.generate();}Leaf 基准测试代码&#xff1a; Benchmarkpublic l…

如何进行软件回归测试

什么是软件回归测试&#xff0c;如何进行回归测试&#xff0c;进行回归测试时有哪些常用的方法&#xff1f; 回归测试是指修改了旧代码后&#xff0c;重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误的一种测试方法。回归测试是指重复以前的全部或部分的相同功能…

Java版本spring cloud + spring boot企业电子招投标系统源代码

&#xfeff;项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&…

07 定时器处理非活动连接(上)

07 定时器处理非活动连接&#xff08;上&#xff09; 基础知识 非活跃&#xff0c;是指客户端&#xff08;这里是浏览器&#xff09;与服务器端建立连接后&#xff0c;长时间不交换数据&#xff0c;一直占用服务器端的文件描述符&#xff0c;导致连接资源的浪费。 定时事件&a…

kotlin 编写一个简单的天气预报app(三)broadcast换成eventbus

使用eventbus替换broadcast 将从Broadcast切换到EventBus有以下几个好处&#xff1a; 解耦性&#xff1a;通过使用EventBus&#xff0c;您可以实现组件之间的解耦。传统的Broadcast机制需要发送方和接收方明确知道对方的存在&#xff0c;并且需要在代码中设置Intent过滤器和广…

c++ | 动态链接库 | 小结

//环境 linux c //生成动态链接库 //然后调用动态链接库中的函数//出现的问题以及解决//注意在win和在linux中调用动态链接库的函数是不一样的//在要生成链接库的cpp文件中比如以后要调用本文件中的某个函数&#xff0c;需要extern "c" 把你定的函数“再封装”避免重…

java实现日期拆分的方法

java实现日期拆分的方法 本文实例讲述了java实现日期拆分的方法。分享给大家供大家参考。具体如下&#xff1a; 如&#xff1a;计算6-1至6-5之间的日期天数及具体日期&#xff0c;预期的结果是得到&#xff1a; 6-1 6-2 6-3 6-4 6-5 以下是我利用java 日历类做的实现&am…

XtarBackup 8.0.33-28 prepare 速度提升 20 倍!

在这篇博文中&#xff0c;我们将描述 Percona XtraBackup 8.0.33-28 的改进&#xff0c;这显著减少了备份准备所需的时间&#xff0c;以便进行恢复操作。 Percona XtraBackup 中的这一改进显着缩短了新节点加入 Percona XtraDB 集群&#xff08;PXC&#xff09; 所需的时间。 …

多模光模块中Lens透镜的关键作用

随着现代通信技术的飞速发展&#xff0c;多模光模块已经成为光通信系统中不可或缺的关键组件。这些模块可实现高速、高容量的数据传输&#xff0c;广泛应用于数据中心、局域网和广域网等领域。在多模光模块中&#xff0c;透镜作为其中的重要组成部分&#xff0c;扮演着至关重要…

一些网络知识总结(自用)

一些网络知识总结&#xff08;自用&#xff09; 1. 进制的转换 所有进制转换成十进制就是把字面值*权数 比如16进制 5AEF 转成10机制话就是 1* 151614321064 * 5 2. ip地址网段的概念&#xff0c;可用ip&#xff0c;广播地址 比如一个ip为10.1.1.1/24那么他的网络号就是前…

C++继承特性(3)——类的默认成员函数

目录 前情回顾&#xff1a; 一.构造函数和析构函数&#xff1a; 情况1&#xff1a;子类没有写构造和析构函数时&#xff1a; 运行结果&#xff1a; 构造函数&#xff1a; 析构函数&#xff1a; 情况2&#xff1a;父类的构造函数并没有为成员变量初始化赋值&#xff0c;而…

论文阅读-BotPercent: Estimating Twitter Bot Populations from Groups to Crowds

目录 摘要 引言 方法 数据集 BotPercent架构 实验结果 活跃用户中的Bot数量 Bot Population among Comment Sections Bot Participation in Content Moderation Votes Bot Population in Different Countries’ Politics 论文链接&#xff1a;https://arxiv.org/pdf/23…

BES2700 SDK绝对时间获取方法

1 代码 2 实验 log 需要换算下

CLIP-GCD: Simple Language Guided Generalized Category Discovery(论文翻译)

CLIP-GCD: Simple Language Guided Generalized Category Discovery 摘要1 介绍2 相关工作2.1 NCD2.2 无监督聚类2.3 自监督和多模态预训练 3 方法3.1 GCD 问题设置3.2 我们的方法3.2.1 使用CLIP 在GCD 4 实验4.1 模型架构细节4.2 数据集和评估4.3 和最先进水平比较4.4 分析4.5…

echarts柱状图横坐标文字过长的解决办法

背景&#xff1a;echarts图中横坐标显示的文字过长&#xff0c;导致字都堆积在一块如下图所示 解决办法 一&#xff1a;可以尝试修改‘axisLabel’的‘rotate’和‘interval’参数&#xff0c;‘rotate’参数可以设置标签的旋转角度&#xff0c;可以避免标签之间的重叠&#x…

9.环境对象和回调函数

9.1环境对象 指的是函数内部特殊的变量this&#xff0c;它代表着当前函数运行时所处的环境 作用&#xff1a; 弄清楚this的指向&#xff0c;可以让我们代码更简洁 ➢函数的调用方式不同&#xff0c;this指代的对象也不同 ➢[谁调用&#xff0c;this 就指代谁] 是判断this指向的…

三十一章 uboot顶层Makefile详解

编译后的uboot源码文件 1、 arch 文件夹 这个文件夹里面存放着和架构有关的文件&#xff0c;进入后打开arm文件 2、 board 文件夹 board 文件夹就是和具体的板子有关的&#xff0c;打开此文件夹&#xff0c;里面全是不同的板子&#xff0c;毫无疑问正 点原子的开发板肯定也在…

如何使用Postman创建Mock Server?

这篇文章将教会大家如何利用 Postman&#xff0c;通过 Mock 的方式测试我们的 API。 什么是 Mock Mock 是一项特殊的测试技巧&#xff0c;可以在没有依赖项的情况下进行单元测试。通常情况下&#xff0c;Mock 与其他方法的主要区别就是&#xff0c;用于取代代码依赖项的模拟对…