1 Socket API简介
1.1 最初设计
最初设计是基于BSD UNIX-Berkley,面向TCP/IP协议栈的接口。经过后续的发展,已经可以面向多个协议栈。Socket API也是事实上的工业标准,绝大多数的操作系统都支持,主流的操作系统如Windows和Linux都是支持的。Windows和Linux操作系统下Socket API的功能是有重叠的,基本编程思路不变,只需改动极小量的代码即可在相应操作系统编译、链接、运行。
1.2 形象比喻
首先,Socket API是Internet网络应用最典型的API接口,使用的通信模型为客户/服务器模型(C/S),这类模型描述了应用进程间通信的抽象机制。
我们可以将socket形象比喻成插头与插座,现在你想要给自己的手机充电,需要什么东西才能通过插座给手机充电呢?那就是适配手机的充电线和充电插头,在此我们把它们看成一个整体,叫做充电工具。现在你只需要将充电工具的USB插口一端与手机连接,另一端插头插到插座上,手机就能够进行充电,我们无须了解插座后面的电路构造,只要将插头插入插座就能使用充电功能,这就是API的现实体现。
1.3 通信定位
在一台主机上可能运行多个进程,比如现在打开的简书Web应用或者QQ,假设突然有个套接字要过来和你的进程通信,那现在主机上那么多进程,它应该和哪个进程通信?换句话说,它应该如何找到指定的进程进行通信?再形象点,插头要插哪个插座上?我们要如何准确定位服务器端的套接字呢?
其实很简单,在某一特定网络中,一台主机对应唯一的IP地址,服务器其实也是网络上的一台主机,也对应一个IP地址,这其实就能找到主机的地址。以五层模型为参考,主机间应用层的交互实际上是通过底层传输层协议来进行交互,操作系统需要提供端口来给进程,即端口号。
对外(主机与主机间)来说,套接字通过IP地址+端口号来标识通信端点,这样的标识是唯一的。对内(主机本身)来说,操作系统使用套接字描述符(socket descriptor)来管理套接字,大小一般是小整数。由此可见,套接字对内对外管理是不一样的。
- 需要标识通信端点(对外):IP地址+Port端口号→套接字的端点地址
(1)IP地址标识互联网中的机器地址。
(2)Port端口号为16位整数,标识某一具体机器的进程,绑定套接字。 - 操作系统/进程管理套接字(对内):使用套接字描述符(socket descriptor)。
2 socket抽象
2.1 socket创建
- 在Unix、Linux操作系统中,将socket看作文件。
- 当应用进程创建套接字时,操作系统分配一个数据结构存储该套接字相关信息。
- 返回套接字描述符。
2.2 socket管理
每个进程管理一个socket描述符表,表中的入口都有一个指针,指向所存储的数据结构。
| socket描述符表 | socket数据结构 |
|---|---|
| [0] | struct sockaddr_in * |
| [1] | 其他数据结构 |
| [2] | 其他数据结构 |
| ... | 其他数据结构 |
作为套接字最重要的信息就是套接字的地址信息,使用前需要指定本地的端点地址和远程的端点地址。使用TCP/IP协议簇的网络应用程序声明端点地址变量时,通过结构sockaddr_in来描述地址信息,使用前需要include <netinet/in.h>。
struct sockaddr_in{
u_char sin_len; // 地址长度
u_char sin_family; // 地址族(TCP/IP:AF_INET) 面向各种不同的协议栈,主要是TCP
u_short sin_port; // 端口号
struct in_addr sin_addr; // IP地址
char sin_zero[8]; // 未用(置0) 填充0以保持与sockaddr结构的长度相同
}
不同地址族(协议栈)的端点地址是不一样的,TCP/IP协议簇的端点地址为IP+Port,通常指定其sin_family数据成员为AF_INET符号常量,表示本身为TCP/IP协议簇。