1 socket函数
int socket(int family, int type, intprotocol);
其中family是协议族,type是套接字类型,protocol为某个协议组合,设为0的话是取family和type组合的系统默认值。
常用的family值有:AF_INET(IPV4)、AF_INET6(IPV6)。
常用的type值有:SOCK_STREAM(TCP|SCTP)、SOCK_DGRAM(UDP)、SOCK_SEQPACKET(SCTP)。
2 connect函数
客户端调用connect前不必非得调用bind。如果是TCP套接字,connect会在三路握手建立成功或出错时才返回,出错返回可能是:
1. TCP客户没收到SYN分节,返回超时错误。
2. 服务器端的响应为RST,表明该服务器没有进程在监听此端口。产生RST的三个条件:SYN到达的端口没有监听进程;TCP想取消一个已有连接;TCP接到不存在的连接上的分节。
3. 客户发出的SYN被路由认为不可到达。客户内核会重发,若超时后未成功则返回错误。
connect导致套接字状态从CLOSED转移到SYN_SENT,若成功再转移到ESTABLISHED,若失败则该套接字不再可用,必须关闭。这样的套接字不可再connect,需要close后重新调用socket获取。
3 bind函数
bind是将本地协议地址绑定到套接字上。绑定时未指定的IP地址和端口会由内核选择。
TCP客户通常不绑定,在connect时,内核选择源IP地址。TCP服务器如果没有绑定IP地址,内核将在建立连接时把客户发送的SYN的目的IP设为服务器的源IP地址。
IPV4的通配地址为INADDR_ANY,IPV6的通配地址为IN6ADDR_ANY_INIT。
没有指定bind的端口号时,需要用getsockname来返回协议地址,再获取端口号。
进程捆绑非通配IP到套接字上的常见例子是同一主机上提供多个Web服务器,每个服务器的副本捆绑不同的IP。另一种方法是单个服务器进程绑定通配地址,当客户连接到达时,用getsockname获取客户的目的IP,再处理相应的服务请求。
4 listen函数
仅由TCP服务器调用:
1. socket创建的套接字为主动套接字,listen时会转换成被动套接字,从CLOSED转换为LISTEN。
2. 第二个参数backlog规定了内核应该为相应套接字排队的最大连接个数。
内核要为监听套接字维护两个队列:
1. 未完成连接队列,客户的SYN已到达服务器,未完成三路握手,处于SYN_RCVD状态。
2. 已完成连接队列,完成握手的连接,处于ESTABLISHED状态,等待被accept。
不要把backlog设为0。
5 accept函数
服务器用accept从已完成连接队列头返回下一个已完成连接。
用clientaddr的sin_addr和sin_port字段查看客户的IP和端口。
6 fork和exec函数
进程在调用exec前打开的描述符通常跨exec继续保持打开,但可以用fcntl设置FD_CLOEXEC禁止掉。
7 并发服务器
多进程的并发服务器,监听进程在fork后要关掉connfd,子进程在fork后要关掉listenfd,否则描述符的引用计数不为0,在close时不会真正关闭。
8 close函数
close后的套接字描述符不能再使用,但TCP将尝试发送已排队的任何数据,完毕后进行正常的终止序列。
9 getsockname和getpeername函数
前者返回套接字的本地端的地址结构,后者返回外地端的地址结构。只知道connfd的时候,获取客户身份的唯一途径就是调用getpeername。
exec后的程序获取connfd的方法:
1. 将描述符转为字符串,作为命令行参数传给新程序。
2. 约定在调用exec前,总把某个特定描述符设置为connfd,如将0、1、2设置为connfd。