Socket本身有“插座”的意思,在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区(队列)形成的伪文件。既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。与管道类似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。
在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。
套接字通信原理如下图所示:
在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区。
(一)基本的socket接口函数
socket()函数
socket函数类似于open,用来打开一个网络连接,如果成功则返回一个网络文件描述符(int类型),之后我们操作这个网络连接都通过这个网络文件描述符。
(1)函数原型
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
(2)参数
①domain: 指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
- AF_INET IPv4因特网域
- AF_INET6 IPv6因特网域
- AF_UNIX UNIX域
- AF_ROUTE 路由套接字
- AF_KEY 密钥套接字
- AF_UNSPEC 未指定
- 协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
②type参数指定socket的类型:
- SOCK_STREAM:流式套接字提供可靠的、面向连接的通信流;它使用TCP 协议,从而保证了数据传输的正确性和顺序性。
- SOCK_DGRAM :数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP。
- SOCK_RAW:允许程序使用低层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
③protocol
- 通常赋值”0“。
- 0 选择type类型对应的默认协议
- IPPROTO_TCP TCP传输协议
- IPPROTO_UDP UDP传输协议
- IPPROTO_SCTP SCTP传输协议
- IPPROTO_TIPC TIPC传输协议
- 注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
(3)返回值
- 成功,返回一个整型socket描述符,可以在后面的调用中使用它。socket描述符是一个整数,但它指向内部数据结构(socket结构)的指针,它指向描述符表入口。调用socket函数时,socket执行体将建立一个socket结构,实际上建立一个socket意味着为一个socket数据结构分配存储空间。socket执行体为你管理描述符表。该空间还有许多成员,并不会在初始化时被填充,可调用其它函数进行填充完善。 失败,返回-1。
- 两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。socket数据结构中包含这五种信息。
bind()函数
bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。(用于绑定IP
地址和端口号到socketfd
)
端口号:实质就是一个数字编号,用来在我们一台主机中(主机的操作系统中)唯一的标识一个能上网的进程。端口号和IP地址一起会被打包到当前进程发出或者接收到的每一个数据包中。每一个数据包将来在网络上传递的时候,内部都包含了发送方和接收方的信息(就是IP地址和端口号),所以IP地址和端口号这两个往往是打包在一起不分家的。
(1)函数原型
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
(2)参数
- sockfd:即socket描述符,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
- addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同。
- addrlen对应的是地址的长度,常被设置为
sizeof(struct sockaddr)
//ipv4对应的是:
struct sockaddr
{
unisgned short as_family; // 协议族
char sa_data[14]; // IP+端口
};
同等替换:
struct sockaddr_in
{
sa_family_t sin_family; /* 协议族 */
in_port_t sin_port; /* 端口号*/
struct in_addr sin_addr; /*IP地址结构体*/
unsigned char sin_zero[8]; /* 填充 没有实际意义,只是为跟sockaddr结构在内存中对齐,这样两者才能相互转换*/
};
/*两个结构是等同的可以先互转换,第一个结构将地址和端口绑定了,第二个结构将两者分开表示*/
/* IP地址结构如下:为32位字 */
struct in_addr
{
uint32_t s_addr; /* address in network byte order */
};
//ipv6对应的是:
struct sockaddr_in6
{
sa_family_t sin6_family; /* AF_