可以将Socket理解为电话,地址理解为电话号码
客户端和服务端核心逻辑
网络编程中,客户端和服务端的核心逻辑如下:
- 服务器初始化
- 初始化Socket
- bind监听地址和端口
- listen将原先的Socket转换为服务端的Socket
- 服务端阻塞在accept上,等待客户端的请求
- 客户端初始化
- 初始化Socket
- connect向服务端的地址和端口发起请求,进行TCP三次握手,建立连接
- 数据传输过程
- 客户端向操作系统内核发起write字节流写操作
- 内核协议栈将字节流通过网络设备传到服务器端
- 服务器端从内核得到信息,将字节流从内核读入进程中,并开始业务逻辑的处理
- 完成之后,以同样的逻辑写给客户端
- 关闭
- 客户端执行Close操作,给服务端发FIN包
- 服务端收到之后被动执行关闭,处理半关闭状态
- 服务端执行Close,整个链路关闭
关闭和半关闭
- 半关闭的状态,发起 close 请求的一方在没有收到对方 FIN 包之前都认为连接是正常的
- 全关闭的状态,双方都感知连接已经关闭。
连接之后,数据的传输就不再是单向的,而是双向的,这是TCP的显著特征
客户端的connect、服务端的accept、read/write操作等都是Socket完成的,Socket是用来建立连接,传输数据的唯一路径。
Socket是什么
可以将TCP的网络交互和数据传输想象成打电话,整个类比如下:
- Socket 类比为电话机
- bind 类比为电信公司开户,绑定自家电话号码
- listen类比听到铃声
- accept类比被叫的一方拿起电话开始应答
- connect 类比拨号
- write 类比拨打电话的人说话
- read 类比接听电话的人听的过程
- 拨打电话的人挂掉电话,类比为客户端close
- 接听电话的人也挂掉电话,类比为服务端close
在电话交流过程中,电话是我们可以和外面通信的设备,对应到网络编程的世界里, Socket 也是我们可以和外界进行网络通信的途径。
Socket(套接字) 地址格式
通用套接字地址格式
/* POSIX.1g 规范规定了地址族为 2 字节的值. */
typedef unsigned short int sa_family_t;
/* 描述通用套接字地址 */
struct sockaddr{
sa_family_t sa_family; /* 地址族. 16-bit*/
char sa_data[14]; /* 具体的地址值 112-bit */
};
在这个结构体里,第一个字段是地址族,它表示使用什么样的方式对地址进行解释和保存,比电话簿里的手机格式,或者是固话格式
- AF_LOCAL:表示的是本地地址,对应的是 Unix 套接字,这种情况一般用于本地 Socket 通信,很多情况下也可以写成 AF_UNIX、AF_FILE;
- AF_INET:因特网使用的 IPv4 地址;
- AF_INET6:因特网使用的 IPv6 地址。
IPV4套接字地址格式
/* IPV4 套接字地址,32bit 值. */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
/* 描述 IPV4 的套接字地址格式 */
struct sockaddr_in
{
sa_family_t sin_family; /* 16-bit */
in_port_t sin_port; /* 端口口 16-bit*/
struct in_addr sin_addr; /* Internet address. 32-bit */
/* 这里仅仅用作占位符,不做实际用处 */
unsigned char sin_zero[8];
};
- 16-bit 的 sin_family 字段,和 sockaddr 一样,对于 IPv4 来说这个值就是 AF_INET。
- 端口号,我们可以看到端口号最多是 16-bit,也就是说最大支持 2 的 16 次方,所以支持寻址的端口号最多就是 65535。关于所谓保留端口就是大家约定俗成的,已经被对应服务广为使用的端口,比如 ftp 的 21 端口,ssh 的 22 端口,http 的 80 端口等。一般而言,大于 5000 的端口可以作为我们自己应用程序的端口使用。
- 实际的 IPv4 地址是一个 32-bit 的字段,可以想象最多支持的地址数就是 2 的 32 次方,大约是 42 亿,目前这个数字渐渐显得不太够用了,于是就有了 IPv6
IPV6套接字地址格式
struct sockaddr_in6
{
sa_family_t sin6_family; /* 16-bit */
in_port_t sin6_port; /* 传输端口号 # 16-bit */
uint32_t sin6_flowinfo; /* IPv6 流控信息 32-bit*/
struct in6_addr sin6_addr; /* IPv6 地址 128-bit */
uint32_t sin6_scope_id; /* IPv6 域 ID 32-bit */
};
整个结构体长度是 28 个字节,其中流控信息和域 IP 先不用管,这两个字段,一个在 glibc 的官网上根本没出现,另一个是当前未使用的字段。
这里的地址族显然应该是 AF_INET6,端口同 IPv4 地址一样
地址从 32 位升级到 128 位,这个数字就大到恐怖了,完全解决了寻址数字不够的问题。
本地套接字地址格式
struct sockaddr_un {
unsigned short sun_family; /* 固定为 AF_LOCAL */
char sun_path[108]; /* 路径名 */
};