跳过正文

Socket到底是什么

Socket Udp Tcp
目录

初识Socket
#

Socket对初学者来说可能有点神秘,像个黑盒子。本文将从零开始,用一个简单的比喻解释socket是什么,以及它在网络编程中的原理和内核实现。

想象一个插头插座。插头插入插座后,电器就能与电源连接,获得电力。Socket在网络编程中就像这个插座,英文也叫socket,它让两台电脑的进程建立“连接”,就像插头插进插座。

插头与插座
插头与插座

通过socket,我们的程序可以与远程电脑通信,就像风扇通过插座获得电力。

连接建立
风扇与电力系统建立连接

Socket的使用场景
#

假设你想从电脑A的某个进程发送数据到电脑B的某个进程。你需要选择传输方式:

  • TCP:可靠传输,确保数据到达,适合需要高可靠性的场景。
  • UDP:不可靠传输,速度快但可能丢包,适合实时性要求高的场景。

初学者通常先学TCP。我们以TCP为例,看看socket怎么用:

int sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

这行代码创建一个TCP socket,返回一个文件描述符sock_fd,类似于socket的“身份证号”。

  • 服务端:用bind()绑定IP和端口,listen()监听连接,accept()接受客户端请求。
  • 客户端:用connect()向服务端发起连接,触发TCP三次握手。

三次握手
TCP三次握手

连接建立后,客户端用send()发送数据,服务端用recv()接收数据,反之亦然。这就是socket最常见的用法。

Socket的设计原理
#

Socket到底是怎么实现的?假设我们要从头设计一个网络传输功能,会怎么做?

网络传输的核心是发送接收数据,类似文件的。但有两个问题需要解决:

  1. 区分通信双方:用IP地址定位电脑,用端口定位进程。
  2. 支持多种协议:如TCP(可靠)、UDP(不可靠)或ICMP(ping命令)。

为此,Linux内核定义了一个核心数据结构:sock

Sock结构
#

sock是网络传输的基础结构,包含发送和接收数据的缓冲区。为了支持不同协议,Linux设计了以下结构:

  • inet_sock:用于网络传输的sock,增加了IP、端口、TTL等字段。
  • inet_connection_sock:面向连接的sock,增加了TCP特有的字段,如accept队列、数据分片大小等。
  • tcp_sock:专为TCP协议设计的sock,包含滑动窗口、拥塞控制等功能。
  • udp_sock:专为UDP协议设计的sock。

sock继承结构
sock继承关系

这些结构通过“继承”复用公共功能。不同协议的sock与网卡硬件对接,实现网络通信。

Socket层
#

网络传输功能在内核中实现,需要高权限操作硬件。为让用户程序也能使用,Linux将sock封装成文件,每个sock对应一个文件描述符sock_fd。用户通过socket()创建sock时,内核同时创建一个文件,用户用send()recv()等接口操作这个文件,实际驱动内核的sock完成网络通信。

sock与文件
通过文件找到sock

Socket本质是一个接口层,介于用户程序和内核之间,封装了复杂的网络功能,提供了简单接口,如send()recv()bind()等。

socket接口层
socket接口层

这就像前后端分离:内核是“后端”,提供网络传输的API;用户程序是“前端”,调用这些API实现功能。

Socket如何实现网络通信
#

以TCP为例,网络通信分为建立连接数据传输两阶段。

建立连接
#

客户端调用connect(sock_fd, "ip:port"),通过sock_fd找到内核sock,发起TCP三次握手。

TCP三次握手
TCP三次握手

服务端调用listen()创建半连接队列(未完成三次握手的连接)和全连接队列(完成握手的连接)。调用accept()从全连接队列取一个连接。

连接队列
半连接和全连接队列

数据传输
#

sock结构包含发送缓冲区接收缓冲区(本质是链表)。

  • 发送数据:调用send(),数据放入发送缓冲区,内核决定何时发送。
  • 接收数据:数据到达内核后存入接收缓冲区,等待程序调用recv()取出。

缓冲区
发送和接收缓冲区

如果recv()时没有数据,进程会进入等待队列并休眠。数据到达时,内核唤醒等待队列中的进程处理数据。

等待队列
等待队列

多进程监听问题:多个进程监听同一sock_fd时,数据到来会触发惊群效应(Linux 2.6前唤醒所有进程,2.6后只唤醒一个)。

惊群效应
惊群效应

区分客户端:服务端用四元组(源IP、源端口、目的IP、目的端口)生成hash key,存入hash表,区分多个客户端。

四元组
四元组映射

C语言如何实现“继承”
#

Linux内核用C语言实现,C没有继承特性。Linux通过结构体嵌套模拟继承:

struct tcp_sock {
    struct inet_connection_sock inet_conn; // 父结构放首位
    // 其他字段
};

struct inet_connection_sock {
    struct inet_sock icsk_inet; // 父结构放首位
    // 其他字段
};

通过内存地址强转,将父结构转为子结构,实现类似继承的效果。

struct tcp_sock *tcp_sk(const struct sock *sk) {
    return (struct tcp_sock *)sk;
}

内存布局
内存布局

总结
#

  • Socket:中文“套接字”,可理解为一套用于连接的数字(sock_fd)。
  • Sock:内核中的网络传输结构,分为sockinet_socktcp_sock等,支持不同协议。
  • Socket层:用户空间和内核之间的接口层,封装sock为文件,用户通过sock_fd操作内核网络功能。
  • 通信过程:通过三次握手建立连接,用发送/接收缓冲区传输数据,用四元组区分客户端。
  • 继承实现:C语言通过结构体嵌套和内存地址转换模拟继承。

Socket让网络编程变得简单,就像插座让电器使用电力一样方便!

相关文章

C语言函数指针
C 函数指针
20个Docker容器常用脚本命令
Docker Script
45个高效Linux命令组合
Linux Command