什么是套接口?socket、套接口、套接口描述符之间的关系
UNP 的译者在 p6 中的译注中提到:socket 一词应该译成“套接口”,理由如下:首先,作为网络编程 API 之一的套接口(sockets.注意这种用法总是采用复数形式,例如 sockets API、sockets library)跟 XTI 一样,是应用层到传输层或其他协议层的访问接口。其次,具体使用的套接口时与 unix 管道的某一端类似的东西,我们既可以往这个“口”写数据,也可以从这个“口”读取数据。最后,套接口函数使用套接口描述字访问具体的套接口,如果把套接口描述字的简称 socketfd 译成“套接字”倒比较合适。从这个意义上来讲,一个套接口可以对应多个套接字,因为 unix 的描述字既可以复制,也可以继承,反过来,一个套接字对应且只对应一个套接口。
如何创建套接口?
unix下通过 socket 函数创建套接口,该函数原型是:
int socket(int family, int type, int protocol)。
其中,family 参数指明协议族,它是 AF_INET(IPv4 协议)、AF_INET6(IPv6)协议、AF_LOCAL()、AF_ROUTE(路由套接口)、AF_KEY(密钥套接口)中的一种。
其中, type 指明套接口类型,它是SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)、SOCK_SEQPACKET(有序分组套接口)、SOCK_RAW(原始套接口)中的一个。
其中,protocol 是 IPPROTO_TCP( TCP 协议)、IPPROTO_UDP( UDP 协议)、IPPROTO_SCTP(SCTP协议)中的一种。
如果创建成功返回套接口描述符,简称套接字
套接口用来做什么?
在客户端:通过 connet() 用来与服务器建立连接。connet() 函数原型如下:
int connec(int sockfd, const struct socktaddr *servaddr, socklen_t addrlen);
其中,sockfd 就是套接口描述符(用来描述套接口)。
其中,servaddr 是套接口地址结构,这个结构用来指定 connect() 要连接的服务器的 IP 和 端口号。
其中,addrlen 时套接口地址结构的大小。
由于 connect() 函数要在客户端与服务器端建立连接,所以在调用 connect() 的时候会激发三次握手:1.主动请求连接方向被动请求连接方发送一个 SYN 分节,此时主动方处于 SYN_SENT 状态,如果被动方收到 SYN 分节,并返回一个 ACK 和 一个自己的 SYN,此时被动方处于SYN_RCVD 状态。调用 connect() 的主动方接受到被动方回应的 ACK 后,connect() 返回 0(表示成功),否则回返回 -1(出错)。connect() 成功返回后,主动方处于 ESTABLISHED 状态,表示连接已经建立。并向被动方发送一个 ACK(用来回应被动方发送的 SYN 分节)。
在服务器端:在服务器端稍微复杂一点点,要比客户端多两个步骤:
1、用 bind() 函数将一个本地协议地址赋予一个套接口。对于网际地址时 32 为的 IPV4地址或 128 位的 IPV6 地址与 16 位的 TCP 或 UDP 端口号的组合。bind() 函数原型如下:
int bind(int sockfd,const struct sockaddr *myaddr ,socklen_t addrlen);
bind() 函数的原型和 connect() 一样,但作用还是有差别的。connect() 是将 sockfd 与套接口地址结构对应的服务器建立连接,而 bind() 是将 sockfd 与本地套接口地址结构进行绑定。
2、调用 listen() 函数将套接口变成监听套接口,listen() 原型如下:
int listen(int sockfd, int backlog);
其作用有两个:a.将一个未连接的套接口转换成一个被动套接口,只是内核应该接受指向该套接口的的连接请求。并使套接口从 closed 状态转换到 LISTEN 状态。
b.listen() 的第二个参数规定了内核应该为相应套接口排队的最大连接个数。
3、调用 accept() 函数在监听套接口(由listen()转化的套接口描述符)接受新连接的到达,如果没有新连接到达那么进程就投入睡眠(假设套接口为缺省的阻塞方式)。上面我说过,客户端是通过 connect() 来建立连接的,在客户端收到服务器端的 ACK时,connect() 函数返回,并且发送一个 ACK,当服务器端接受到这个 ACK 时,此时三次握手完成。连接建立完成,新连接到达,此时,服务器端的监听套接口转换成 ESTABLISHED 状态,accept() 成功返回。
accept() 函数原型如下:int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
其中, sockfd 时监听套接口,cliaddr 是已连接的对端进程(客户)的协议地址。addrlen 是值-结果参数,是内存存在该套接口地址结构(cliaddr)的确切字节数。
accept() 返回非负描述符,我们称其为已连接套接口描述符。一定要区别监听套接口描述符和已连接套接口描述符,一般来说服务器在其生命周期内只创建一个监听套接口描述符,而内核为每个由服务器进程接受的客户连接创建一个已连接套接口。
什么是套接口?