所有的客户和服务器都从调用socket开始,它返回一个套接字描述符。客户随后调用connect,服务器则调用bind,listen和close。套接字通常使用标准的close函数关闭,也可以使用shutdown函数关闭。
大多数TCP服务器是并发的,它们为每个待处理的客户调用fork派生一个子进程。
TCP状态转换
TCP编程基本函数
socket
#include <sys/socket.h> int socket(int family, int type, int protocol); //family:协议族,如下图4-2所示 //type:套接字类型,如下图4-3所示 //protocol:协议类型常值或为0(选用给定的前两者参数构成的默认组合) //返回值:非负整数,成功;0,失败。称为套接字描述符,sockfd
connect
TCP客户用来建立与TCP服务器的连接。
调用connect函数,会触发TCP三路握手过程,仅在连接成功或出错时才返回。
#include <sys/socket.h> int connect(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen); //sockfd:套接字描述符; //struct sockaddr* :指向套接字地址结构的指针; //socklen_t:该结构的大小; //返回值:0,成功;-1,出错。
出错返回的几种情况:
- TCP客户没有收到SYN分节的响应;(分别在0,6,24秒时发送SYN分节,若75秒依旧没有响应则返回本错误)
- 若对客户的SYN分节响应的是RST,表明该服务器主机在指定的端口处没有进程在等待与之连接;
- 若客户的SYN在中间的某个路由上引发了"destination unreachable"错误,按照第一种错误处理
按照TCP状态转换图,connect函数导致当前套接字从CLOSED状态转移到SYN_SENT状态,若成功再转移到ESTABLISHED状态。
若connect失败,则必须关闭此套接字,不能对这样的套接字再次调用connect函数。
bind
bind函数把本地协议地址赋予一个套接字
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen); //返回值:0,成功;-1,出错
对于TCP,调用bind函数可以指定一个端口号或者一个IP地址,也可以都不指定:
- 指定端口为0,内核在bind被调用时选择临时端口;如果IP地址为通配地址,内核在套接字已连接时才选择一个本地IP地址。
- IPv4通配地址由常值INADDR_ANY指定(32位),一般为0;IPv6地址(128位)存放在一个结构中
- 如果让内核来为套接字选择端口,函数bind不返回该端口,需要调用getsockname返回协议地址,来得到这个临时端口值
通配地址使用方法:
//IPv4通配地址 struct sockaddr_in servaddr; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //IPv6通配地址 struct sockaddr_in6 serv; serv.sin6_addr = in6addr_any; //系统分配in6addr_any变量并将其初始化为常值IN6ADDR_ANY_INIT
listen
仅用于TCP调用,做两件事:
- 当socket函数建立套接字时,它被假设为一个主动套接字。把一个未连接的套接字转换成一个被动套接字。
- 本函数第二个参数规定了内核应该为相应套接字排队的最大连接个数
#include <sys/socket.h> int listen(int sockfd, int backlog); //返回值:0,成功;-1,出错
本函数调用发生在调用socket和bind函数之后,调用accept函数之前
内核为任何一个给定的监听套接字维护两个队列:
(1)未完成连接队列
每个SYN分节对应队列中的一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。
(2)已完成连接队列
每个已完成TCP三路握手过程的客户对应队列中的一项,这些套接字处于ESTABLISHED状态。
三路握手
RTT:round-trip time(往返时间/时延)
SYN:synchronous(建立连接/同步)
ACK:acknowledgement(确认/响应)
RST:reset(重置)
backlog的值
参数backlog曾经被规定为两个队列项总和的最大值,历史沿用的代码给出的值为5。而如今繁忙的服务器一天可能处理几百万个连接,这个偏小的值就不够了,必须指定一个大得多的backlog值。
现在的做法常常指定一个默认值,不过允许通过命令行选项或环境变量覆写该默认值。方法如下:
void Listen(int fd, int backlog) { char *ptr; if( (ptr==getenv("LISTENQ")) !=NULL ) backlog = atoi(ptr); if( listen(fd, backlog) < 0) err_sys("listen error"); }
accept
#include <sys/socket.h> int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); //参数cliaddr和addrlen返回已连接的对端进程的协议地址 //返回值:大于0,成功;-1,出错