编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 2.1域名规范和解析流程
  • 2.2Mac和IP身份的标识
  • 2.3HTTPDNS设计思想
  • 2.4TCP详细基础介绍
  • 2.5UDP详细基础介绍
  • 2.6Socket套接字设计
  • 2.7传输包的设计和原理
  • 2.8网络七层设计由来

2.6Socket套接字设计

目录介绍

  • 01.Socket简单介绍
  • 02.Socket工作图解
  • 03.Socket建立网络连接
  • 04.TCP协议的Socket
  • 05.UDP协议的Socket
  • 06.服务器接更多项目
  • 07.数据包传输流程介绍

01.网络编程三要素

  • A:IP地址:InetAddress:
    • 网络中设备的标识,不易记忆,可用主机名。网络中每一台计算机的唯一标识,通过IP地址找到指定的计算机。
  • B:端口号:
    • 用于标识进程的逻辑地址,不同进程的标识
  • C:传输协议:
    • 通讯的规则常见协议:TCP,UDP
  • 举个例子
    • 用生活中的例子说明:假如我要和小杨逗比说话,首先我要到小杨逗比的住址找到小杨逗比(相当于通过IP找到指定计算机);之后我要和小杨逗比说话,小杨逗比用耳朵听我说(相当于用端口接收);而我们对话不能使用鸟语,需要作出规定彼此都要使用都能听懂的普通话(这就是协议的作用了)。博客

02.IP地址

  • IP地址:是网络中计算机的唯一标识,通过IP地址可以找到指定计算机。
  • 假如有一个192.168.26.254 的IP地址,如何理解呢?
    • 它在网络中其实是这样用四个字节表示的:11000000 10101000 00011010 11111110,使用0、1表示,而且中间没有点,因为这种表示形式不容易记(还要计算二进制数),所以使用192.168.26.254的形式,这种形式叫做“点分十进制”。也因为一个字节最大值为255,所以组成点分十进制的四个数字每个都不能超过255。
  • IP地址的组成:
    • IP地址由网络号段(不可变)和主机地址(可变)组成(表示形式:IP地址 = 网络号码+主机地址)。

03.端口号

  • 物理端口 网卡口
  • 逻辑端口 我们指的就是逻辑端口
    • a:每个网络程序都会有一个逻辑端口
    • b:用于标识进程的逻辑地址,不同进程的标识
    • c:有效端口:0~65535,其中0~1024系统使用或保留端口。
  • 如何查看端口号
    • 通常说的都是逻辑端口,用360的流量防火墙可以看到进程的端口号:
    • image
      image

04.传输协议

  • 协议是定义的通信规则,一般有TCP协议和UDP协议。博客
  • UDP
    • 将数据源和目的封装成数据包中,不需要建立连接;
    • 每个数据报的大小在限制在64k;
    • 因无连接,是不可靠协议;
    • 不需要建立连接,速度快
  • TCP
    • 建立连接,形成传输数据的通道;
    • 在连接中进行大数据量传输;
    • 需要连接所以是可靠协议;
    • 必须建立连接,效率会稍低

01.Socket简单介绍

  • Socket如何理解
    • Socket 这个名字很有意思,可以作插口或者插槽讲。虽然我们是写软件程序,但是你可以想象为弄一根网线,一头插在客户端,一头插在服务端,然后进行通信。所以在通信之前,双方都要建立一个 Socket。
    • 在建立 Socket 的时候,应该设置什么参数呢?Socket 编程进行的是端到端的通信,往往意识不到中间经过多少局域网,多少路由器,因而能够设置的参数,也只能是端到端协议之上网络层和传输层的。
    • 在网络层,Socket 函数需要指定到底是 IPv4 还是 IPv6,分别对应设置为 AF_INET 和 AF_INET6。另外,还要指定到底是 TCP 还是 UDP。还记得咱们前面讲过的,TCP 协议是基于数据流的,所以设置为 SOCK_STREAM,而 UDP 是基于数据报的,因而设置为 SOCK_DGRAM。
  • Socket简单介绍
    • Socket就是为网络服务提供的一种机制
    • 通信的两端都有Socket
    • 网络通信其实就是Socket间的通信
    • 数据在两个Socket间通过IO传输
    • 玩Socket主要就是记住流程,代码查文档就行
    • Socket的简单使用的话应该都会,两个端各建立一个Socket,服务端的叫ServerSocket,然后建立连接即可。

02.Socket工作图解

  • 如下所示
    • image
      image

03.Socket建立网络连接

  • Socket建立网络连接的步骤是什么?
    • 建立Socket连接至少需要一对套接字,其中一个运行与客户端--ClientSocket,一个运行于服务端--ServiceSocket
      • 1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
      • 2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。注意:客户端的套接字必须描述他要连接的服务器的套接字,
      • 指出服务器套接字的地址和端口号,然后就像服务器端套接字提出连接请求。
      • 3、连接确认:当服务器端套接字监听到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述
      • 发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务端套接字则继续处于监听状态,继续接收其他客户端套接字的连接请求。

04.TCP协议的Socket

  • TCP 的服务端要先监听一个端口,一般是先调用 bind 函数,给这个 Socket 赋予一个 IP 地址和端口。为什么需要端口呢?要知道,你写的是一个应用程序,当一个网络包来的时候,内核要通过 TCP 头里面的这个端口,来找到你这个应用程序,把包给你。为什么要 IP 地址呢?有时候,一台机器会有多个网卡,也就会有多个 IP 地址,你可以选择监听所有的网卡,也可以选择监听一个网卡,这样,只有发给这个网卡的包,才会给你。
  • 当服务端有了 IP 和端口号,就可以调用 listen 函数进行监听。在 TCP 的状态图里面,有一个 listen 状态,当调用这个函数之后,服务端就进入了这个状态,这个时候客户端就可以发起连接了。
  • 在内核中,为每个 Socket 维护两个队列。一个是已经建立了连接的队列,这时候连接三次握手已经完毕,处于 established 状态;一个是还没有完全建立连接的队列,这个时候三次握手还没完成,处于 syn_rcvd 的状态。
  • 接下来,服务端调用 accept 函数,拿出一个已经完成的连接进行处理。如果还没有完成,就要等着。
  • 在服务端等待的时候,客户端可以通过 connect 函数发起连接。先在参数中指明要连接的 IP 地址和端口号,然后开始发起三次握手。内核会给客户端分配一个临时的端口。一旦握手成功,服务端的 accept 就会返回另一个 Socket。
  • 这是一个经常考的知识点,就是监听的 Socket 和真正用来传数据的 Socket 是两个,一个叫作监听 Socket,一个叫作已连接 Socket。
  • 连接建立成功之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。
  • 这个图就是基于 TCP 协议的 Socket 程序函数调用过程。
    • image
      image
  • 说 TCP 的 Socket 就是一个文件流,是非常准确的。因为,Socket 在 Linux 中就是以文件的形式存在的。除此之外,还存在文件描述符。写入和读出,也是通过文件描述符。
  • 在内核中,Socket 是一个文件,那对应就有文件描述符。每一个进程都有一个数据结构 task_struct,里面指向一个文件描述符数组,来列出这个进程打开的所有文件的文件描述符。文件描述符是一个整数,是这个数组的下标。
  • 这个数组中的内容是一个指针,指向内核中所有打开的文件的列表。既然是一个文件,就会有一个 inode,只不过 Socket 对应的 inode 不像真正的文件系统一样,保存在硬盘上的,而是在内存中的。在这个 inode 中,指向了 Socket 在内核中的 Socket 结构。
  • 在这个结构里面,主要的是两个队列,一个是发送队列,一个是接收队列。在这两个队列里面保存的是一个缓存 sk_buff。这个缓存里面能够看到完整的包的结构。看到这个,是不是能和前面讲过的收发包的场景联系起来了?
  • 整个数据结构我也画了一张图。
    • image
      image

05.UDP协议的Socket

  • 对于 UDP 来讲,过程有些不一样。UDP 是没有连接的,所以不需要三次握手,也就不需要调用 listen 和 connect,但是,UDP 的的交互仍然需要 IP 和端口号,因而也需要 bind。UDP 是没有维护连接状态的,因而不需要每对连接建立一组 Socket,而是只要有一个 Socket,就能够和多个客户端通信。也正是因为没有连接状态,每次通信的时候,都调用 sendto 和 recvfrom,都可以传入 IP 地址和端口。
  • 这个图的内容就是基于 UDP 协议的 Socket 程序函数调用过程。
    • image
      image

06.服务器接更多项目

  • 会了这几个基本的 Socket函数之后,你就可以轻松地写一个网络交互的程序了。就像上面的过程一样,在建立连接后,进行一个 while 循环。客户端发了收,服务端收了发。
  • 当然这只是万里长征的第一步,因为如果使用这种方法,基本上只能一对一沟通。如果你是一个服务器,同时只能服务一个客户,肯定是不行的。这就相当于老板成立一个公司,只有自己一个人,自己亲自上来服务客户,只能干完了一家再干下一家,这样赚不来多少钱。
  • 那作为老板你就要想了,我最多能接多少项目呢?当然是越多越好。我们先来算一下理论值,也就是最大连接数,系统会用一个四元组来标识一个 TCP 连接。
    {本机 IP, 本机端口, 对端 IP, 对端端口}
  • 服务器通常固定在某个本地端口上监听,等待客户端的连接请求。因此,服务端端 TCP 连接四元组中只有对端 IP, 也就是客户端的 IP 和对端的端口,也即客户端的端口是可变的,因此,最大 TCP 连接数 = 客户端 IP 数×客户端端口数。对 IPv4,客户端的 IP 数最多为 2 的 32 次方,客户端的端口数最多为 2 的 16 次方,也就是服务端单机最大 TCP 连接数,约为 2 的 48 次方。
  • 当然,服务端最大并发 TCP 连接数远不能达到理论上限。首先主要是文件描述符限制,按照上面的原理,Socket 都是文件,所以首先要通过 ulimit 配置文件描述符的数目;另一个限制是内存,按上面的数据结构,每个 TCP 连接都要占用一定内存,操作系统是有限的。
  • 所以,作为老板,在资源有限的情况下,要想接更多的项目,就需要降低每个项目消耗的资源数目。

6.1 将项目外包给其他公司

  • 将项目外包给其他公司(多进程方式)
    • 这就相当于你是一个代理,在那里监听来的请求。一旦建立了一个连接,就会有一个已连接 Socket,这时候你可以创建一个子进程,然后将基于已连接 Socket 的交互交给这个新的子进程来做。就像来了一个新的项目,但是项目不一定是你自己做,可以再注册一家子公司,招点人,然后把项目转包给这家子公司做,以后对接就交给这家子公司了,你又可以去接新的项目了。
  • 这里有一个问题是,如何创建子公司,并如何将项目移交给子公司呢?
    • 在 Linux 下,创建子进程使用 fork 函数。
    • 通过名字可以看出,这是在父进程的基础上完全拷贝一个子进程。在 Linux 内核中,会复制文件描述符的列表,也会复制内存空间,还会复制一条记录当前执行到了哪一行程序的进程。显然,复制的时候在调用 fork,复制完毕之后,父进程和子进程都会记录当前刚刚执行完 fork。这两个进程刚复制完的时候,几乎一模一样,只是根据 fork 的返回值来区分到底是父进程,还是子进程。如果返回值是 0,则是子进程;如果返回值是其他的整数,就是父进程。
  • 进程复制过程我画在这里。
    • image
      image
  • 因为复制了文件描述符列表,而文件描述符都是指向整个内核统一的打开文件列表的,因而父进程刚才因为 accept 创建的已连接 Socket 也是一个文件描述符,同样也会被子进程获得。
  • 接下来,子进程就可以通过这个已连接 Socket 和客户端进行互通了,当通信完毕之后,就可以退出进程,那父进程如何知道子进程干完了项目,要退出呢?还记得 fork 返回的时候,如果是整数就是父进程吗?这个整数就是子进程的 ID,父进程可以通过这个 ID 查看子进程是否完成项目,是否需要退出。

6.2 将项目转包给独立的项目组

  • 方式二:将项目转包给独立的项目组(多线程方式)
    • 上面这种方式你应该也能发现问题,如果每次接一个项目,都申请一个新公司,然后干完了,就注销掉这个公司,实在是太麻烦了。毕竟一个新公司要有新公司的资产,有新的办公家具,每次都买了再卖,不划算。
    • 于是你应该想到了,我们可以使用线程。相比于进程来讲,这样要轻量级的多。如果创建进程相当于成立新公司,购买新办公家具,而创建线程,就相当于在同一个公司成立项目组。一个项目做完了,那这个项目组就可以解散,组成另外的项目组,办公家具可以共用。
    • 在 Linux 下,通过 pthread_create 创建一个线程,也是调用 do_fork。不同的是,虽然新的线程在 task 列表会新创建一项,但是很多资源,例如文件描述符列表、进程空间,还是共享的,只不过多了一个引用而已。
    • image
      image
  • 新的线程也可以通过已连接 Socket 处理请求,从而达到并发处理的目的。
    • 上面基于进程或者线程模型的,其实还是有问题的。新到来一个 TCP 连接,就需要分配一个进程或者线程。一台机器无法创建很多进程或者线程。有个C10K,它的意思是一台机器要维护 1 万个连接,就要创建 1 万个进程或者线程,那么操作系统是无法承受的。如果维持 1 亿用户在线需要 10 万台服务器,成本也太高了。
    • 其实 C10K 问题就是,你接项目接的太多了,如果每个项目都成立单独的项目组,就要招聘 10 万人,你肯定养不起,那怎么办呢?

6.3 一个项目组支撑多个项目

  • 方式三:一个项目组支撑多个项目(IO 多路复用,一个线程维护多个 Socket)
    • 当然,一个项目组可以看多个项目了。这个时候,每个项目组都应该有个项目进度墙,将自己组看的项目列在那里,然后每天通过项目墙看每个项目的进度,一旦某个项目有了进展,就派人去盯一下。
    • 由于 Socket 是文件描述符,因而某个线程盯的所有的 Socket,都放在一个文件描述符集合 fd_set 中,这就是项目进度墙,然后调用 select 函数来监听文件描述符集合是否有变化。一旦有变化,就会依次查看每个文件描述符。那些发生变化的文件描述符在 fd_set 对应的位都设为 1,表示 Socket 可读或者可写,从而可以进行读写操作,然后再调用 select,接着盯着下一轮的变化。。

6.4 一个项目组支撑多个项目

  • 方式四:一个项目组支撑多个项目(IO 多路复用,从“派人盯着”到“有事通知”)
    • 上面 select 函数还是有问题的,因为每次 Socket 所在的文件描述符集合中有 Socket 发生变化的时候,都需要通过轮询的方式,也就是需要将全部项目都过一遍的方式来查看进度,这大大影响了一个项目组能够支撑的最大的项目数量。因而使用 select,能够同时盯的项目数量由 FD_SETSIZE 限制。
    • 如果改成事件通知的方式,情况就会好很多,项目组不需要通过轮询挨个盯着这些项目,而是当项目进度发生变化的时候,主动通知项目组,然后项目组再根据项目进展情况做相应的操作。
    • 能完成这件事情的函数叫 epoll,它在内核中的实现不是通过轮询的方式,而是通过注册 callback 函数的方式,当某个文件描述符发送变化的时候,就会主动通知。
    • image
      image
  • 如图所示,假设进程打开了 Socket m, n, x 等多个文件描述符,现在需要通过 epoll 来监听是否这些 Socket 都有事件发生。其中 epoll_create 创建一个 epoll 对象,也是一个文件,也对应一个文件描述符,同样也对应着打开文件列表中的一项。在这项里面有一个红黑树,在红黑树里,要保存这个 epoll 要监听的所有 Socket。
  • 当 epoll_ctl 添加一个 Socket 的时候,其实是加入这个红黑树,同时红黑树里面的节点指向一个结构,将这个结构挂在被监听的 Socket 的事件列表中。当一个 Socket 来了一个事件的时候,可以从这个列表中得到 epoll 对象,并调用 call back 通知它。
  • 这种通知方式使得监听的 Socket 数据增加的时候,效率不会大幅度降低,能够同时监听的 Socket 的数目也非常的多了。上限就为系统定义的、进程打开的最大文件描述符个数。因而,epoll 被称为解决 C10K 问题的利器。

07.数据包传输流程介绍

7.1 先看一个案例

  • 假设甲给乙发送邮件,内容为:“早上好”。而从 TCP/IP 通信上看,是从一台计算机 A 向另一台计算机 B 发送邮件。我们通过这个例子来讲解一下 TCP/IP 通信的过程。
    • image
      image

7.2 数据包的发送处理

  • 1.应用程序处理
    • 启动应用程序新建邮件,将收件人邮箱填好,再由键盘输入“早上好”,鼠标点击“发送”按钮就可以开始 TCP/IP 的通信了。
    • 首先,应用程序会对邮件内容进行编码处理,例如:UTF-8,GB2312 等。这些编码相当于 OSI 的表示层功能。应用在发送邮件的那一刻建立 TCP 连接,从而利用这个 TCP 连接发送数据。它的过程首先是将应用的数据发送给下一层的 TCP,在做实际的转发处理。
  • 2.TCP 模块处理
    • TCP根据应用的提示,负责建立连接,发送数据以及断开连接。TCP 提供将应用层发来的数据顺利发送至对端的可靠传输。
    • 为了实现 TCP 的这一功能,需要在应用层数据的前端附加一个 TCP 的首部。TCP 的首部中包括源端口号和目标端口号、序号。随后将附加了 TCP 首部的包再发送给 IP。
  • 3.IP 模块处理
    • IP 将 TCP 传过来的 TCP 首部和 TCP 数据合起来当做自己的数据,并在 TCP 首部的前端加上自己的 IP 首部。IP 首部中包含接收端 IP 地址,发送端 IP 地址。随后 IP 包将被发送给连接这些路由器或主机网络接口的驱动程序,以实现真正的发送数据。
  • 4.网络接口(以太网驱动)的处理
    • 从 IP 传过来的 IP 包,对于以太网卡来说就是数据。给这些数据附加上以太网首部并进行发送处理。以太网首部中包含接收端 MAC 地址,发送端 MAC 地址,以太网类型,以太网数据协议。根据上述信息产生的以太网数据将被通过物理层传输给接收端。

7.3 数据包的接收处理

  • 1.网络接口(以太网驱动)的处理

    • 主机收到以太网包以后,首先从以太网的包首部找到 MAC 地址判断是否为发给自己的包。如果不是发给自己的则丢弃数据,如果是发给自己的则将数据传给处理 IP 的子程序。
  • 2.IP 模块处理

    • IP 模块收到 IP 包首部以及后面的数据部分以后,也做类似的处理。如果判断得出包首部的 IP 地址与自己的 IP 地址匹配,则可接受数据并从中查找上一层的协议。并将后面的数据传给 TCP 或者 UDP 处理。对于有路由的情况下,接收端的地址往往不是自己的地址,此时需要借助路由控制表,从中找出应该送到的主机或者路由器以后再进行转发数据。
  • 3.TCP 模块处理

    • 在 TCP 模块中,首先会校验数据是否被破坏,然后检查是否在按照序号接收数据。最后检查端口号,确定具体的应用程序。数据接收完毕后,接收端则发送一个“确认绘制”给发送端。数据被完整地接收以后,会传给由端口号识别的应用程序。
  • 4.应用程序处理

    • 接收端应用程序会直接接收发送端发送的数据。通过解析数据可以获知邮件的内容信息。
  • 03丨套接字和地址:像电话和电话号码一样理解它们

    • https://blog.csdn.net/qq_37756660/article/details/133862889
  • 04 | TCP三次握手:怎么使用套接字格式建立连接?

    • https://blog.csdn.net/qq_37756660/article/details/133868598
  • 12 | 连接无效:使用Keep-Alive还是应用心跳来检测?

    • https://blog.csdn.net/qq_37756660/article/details/133883617
贡献者: yangchong211
上一篇
2.5UDP详细基础介绍
下一篇
2.7传输包的设计和原理