套接字函数
socket函数
创建一个套接字用于通信:
#include <sys/socket.h>
int socket(int family, int type, int protocol); //成功返回非负的描述符,出错返回-1
/*
参数:
family:指定通信协议族(protocol family),常用取值AF_INET(IPv4)
type:指定socket类型, 流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW
protocol:协议类型,常用取值0, 使用默认协议
*/
bind函数
把一个本地协议地址赋予一个套接字,对网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位TCP或UDP端口的组合。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind第二个参数是
const的,所以无法返回所选的值。因此,如果内核分配临时端口(指定端口为0则内核在bind调用时选择一个临时端口),那么
为了获取临时端口的值只能使用getsockname来返回协议地址。如果指定通配IP,那么内核将在
套接字已连接(TCP)或
已在套接字上发出数据报时(UDP)才选择一个本地IP。
bind可以指定一个端口号或一个IP也可以两个都不指定:
(1)TCP客户或服务器未曾绑定一个端口,当调用connect或listen时,内核为响应的套接字选择临时端口。这对于TCP客户是正常的,但对于TCP服务器却很罕见,因为TCP服务器是通过众所周知的端口来连接的。
(2)当绑定IP:对客户端来说就指定了源IP地址;对服务器来说就它就只接收从这个IP地址发起的连接。所以通常服务器不绑定IP。当客户连接套接字时,内核根据所用外出网络接口选择源IP。服务器没绑定IP的话,内核会把客户发来的SYN的目的地址作为服务器的源IP。
connect函数
TCP客户与TCP服务器建立连接。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*
参数:
sockfd:未连接套接字
addr:要连接的套接字地址
addrlen:第二个参数addr长度
*/
客户调用connect前不必非要调用bind,因为如果需要,内核会确定源IP并选择临时端口。
如果是TCP套接字,调用connect会激发三次握手,而且仅连接建立成功或出错才返回,其中错误有以下几种:
(1)客户没收到SYN分节的响应,返回ETIMEDOUT错误。会发几次SYN,仍未收到响应会返回该错误。
(2)若对客户SYN响应是RST(复位),表明在我们连接的端口上没有进程在等待连接(例如服务器没运行)。这是错误,客户收到RST立即返回ECONNREFUSED错误。
(3)若客户发出的SYN在中间某路由上引发“destination unreachable”(目的地不可达)ICMP错误,这是软错误。客户内核保存该信息,继续隔一段时间就发SYN,若仍没响应,则保存ICMP错误作为EHOSTUNREACH或ENETUNREACH错误返回给进程。
按照TCP状态转换图(参考《unix网络编程》(3)TCP连接的建立和终止),connect导致当前套接字由CLOSED状态转移到SYN_SENT状态,若成功则转移到ESTABLISHED状态。
若connect失败则该套接字不可用,必须关闭,不能对它再次调用connect,必须close描述符然后重新调用socket。
listen函数
该函数仅由TCP服务器调用,完成两件事:
(1)listen函数应该用在调用socket和bind函数之后, 并且用在调用accept之前。用于将一个套接字从一个主动套接字(socket建立的是主动套接字)转变成为被动套接字,指示内核应该接受指向该套接字的连接请求。
(2)第二个参数指定内核应该为相应套接字排队的最大连接个数。
int listen(int sockfd, int backlog);
/*
backlog说明:对于给定的监听套接口,内核要维护两个队列: 1、已由客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程(SYN_RCVD状态)
2、已完成连接的队列(ESTABLISHED状态)但是两个队列长度之和不能超过backlog
*/
按照TCP状态转换图(参考
《unix网络编程》(3)TCP连接的建立和终止
),调用listen导致套接字从CLOSED转换到LISTEN。
accept函数
该函数由TCP服务器调用。用于从已经完成连接队列对头返回下一个已完成连接;若已完成队列空,则进程睡眠(如果套接字为默认的阻塞方式)。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/*
参数:
sockfd:服务器套接字
addr:将返回对等方的套接字地址, 不关心客户身份的话, 可以设置为NULL
addrlen:返回对等方的套接字地址长度, 不关心的话可以设置成为NULL, 否则一定要初始化
*/
注意:accept函数称其第一个参数为
监听套接字描述符(由socket创建,用于bind和listen);称其返回值为
已连接套接字描述符。
区分两个套接字:
服务器通常只有一个监听套接字,在该服务器生命周期内一直存在。内核为每个服务器接受的客户连接创建一个已连接套接字(三次握手完成);当服务器完成对客户的服务时,相应的已连接套接字被关闭。