目录
在前面所介绍的几种进程间通信(信号、管道)中所通信的进程都需要在一台计算机上,而socket域进行的通信方式,不仅可以在一台计算机,而且可以是通过网络所连接的不同计算机,这种方式称为 命名socket或者UNIX域socket,这也是一种进程间通信的方式,UNIX域数据报服务是可靠的,不会传递出错也不会丢失信息。其提供两类套接字: 字节流套接字(stream)和数据报套接字(datagram)。
socket起源于UNIX,在UNIX下一切皆文件,而socket是一种“打开——读/写——关闭”的模式,服务器端和客户端各自维护一个文件,在建立连接后,通过对文件描述符的操作可以向自己文件写入内容供对方读取或者读取对方内容,将文件描述符关闭意味着通讯结束。
在介绍socket编程前首先我们要了解下 TCP/IP协议集:
1、TCP/IP协议集
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机间如何连入因特网及数据在主机间传输的标准。
TCP/IP协议不单单是指TCP和IP的协议而是指整个因特网上TCP/IP协议族。不同于ISO的七层网络模型,TCP/IP协议参考模型是将TCP/IP系列协议抽象到四个层中:如下图所示:
我们需要了解socket在TCP/IP协议参考模型中对应那一层,能够更好地了解到其特点:
通过上图我们可了解到socket对应在模型中的应用层和传输层之间的一个抽象层,他把两层之间的复杂操作抽象为几个简单的接口,从而实现进程间在网络上的通信。
2、socket套接字
用户认为两台主机间的信息传递只是建立在应用程序上,但从计网的角度而言,实际上在TCP连接中是靠套接字来传递信息。
对于TCP而言,用主机的IP地址加上应用程序对应的端口号作为TCP连接的端点,这种端点叫做socket套接字或插口,用(IP地址:端口号)来表示,区分不同应用程序进程间的网络通信和连接,主要通过三个参数:通信的目的IP、使用传输层的协议(TCP或UDP)和使用的端口号。
3、socket流程
从上述流程图可知:
- 服务器先根据地址类型(ipv4、ipv6),socket类型和选择协议创建socket(文件描述符)。
- 服务器端绑定客户端的IP地址及端口号。
- 服务器端监听端口号,等待客户端发送连接请求。服务器端socket此时未打开。
- 客户端创建socket后打开,根据服务器的ip地址和端口号尝试连接服务器socket。
- 服务器socket接收到客户端socket请求,直到客户端返回连接信息后,服务器端socket进入阻塞状态,即accept等待客户端发送信息后返回。
- 客户端连接成功后向服务器发送连接状态信息。
- 服务器accept返回连接成功。
- 客户端写入数据后服务器端进行读取信息。
- 客户端关闭套接字,服务器端也关闭。
4、socket编程API
首先根据服务器端的从上至下的顺序来介绍:
4.1、socket()创建描述符
#include <sys/socket.h> //头文件
int socket(int domain, int type, int protocol); //原型
//成功时返回非负数
socket函数对应于普通文件的打开操作,普通文件的打开操作返回一个文件描述符,而socket()
为了创建一个socket描述符,唯一标识一个socket,后续通过对其创建为socket描述符进行读写操作,从上述流程可看出客户端或者服务器端都需要socket()来创建socket描述符。
参数介绍:
- domain :协议域,又可称为协议族(family)。
常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等(AF是address family简称)。协议族决定了socket的地址类型,通常AF_INET(IPv4)、AF_INET6(IPv6). - type:指定socket类型。
常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。主要SOCK_STREAM(TCP协议)、SOCK_DGRAM(UDP协议)。 - protocol:指定协议。
常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。 当protocol为0时,会自动选择type对应的默认协议,一般为 0。
4.2、bind()绑定描述符
当我们使用socket()
来创建socket描述符时,返回的描述符信息存在于协议族中(address family, AF_XXX)空间中,没有一个具体的地址,因此我们需要借助bind()
函数将地址族中特定的地址赋给socket,服务器启动时会绑定一个特有地址(ip地址+端口号),这样客户端可通过该地址访问服务器,而客户端就可以由系统内分配一个端口和其自身的ip地址组合(connect()
时分配)。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数介绍:
- sockfd:sockfd描述字,通过
socket()
创建的唯一标识。 - addrlen:对应的地址长度。
- addr:是一个const struct sockaddr *类型的指针,指向要绑定给sockfd的协议地址,该地址结构会根据socket创建时的地址协议族的不同而不同,但最后会统一强转赋值给sockaddr类型的指针传给内核:
通用套接字sockaddr结构定义:
struct sockaddr {
unsigned short sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
ipv4对应的是sockaddr_in类型定义:
struct in_addr {
uint32_t s_addr;
};
struct sockaddr_in {
unsigned short sin_family;