TCP连接的分组交换
TIME_WAIT状态
主动执行关闭的一端主动执行这个状态,该端点停留在这个状态的持续时间为最长分节生命期(maximum segment lifetime, MSL)的两倍,有时被称为2SML。
存在的理由:
可靠地实现TCP全双工连接的终止
假设上图中最终的ACK丢失,则服务器端会重发FIN来请求客户的ACK。如果客户不维护这些信息,它将响应以一个RST,该分解被服务器解释成一个错误。如果TCP要彻底终止某个连接上两个方向的数据流(全双工关闭),那么它需要正确处理四次挥手中任何一个分节丢失的状况。主动执行关闭的一端可能不得不重发ACK,所以需要进入TIME_WAIT状态。
允许老的重复分节在网络中消逝
假设在某一IP地址的某一端口上有一个连接,该连接关闭之后过了一段时间又再次在该IP和端口上建立了另一个连接,这个连接称为前一个连接的化身。TCP必须防止来自某个连接的老的重复分组在该连接已终止后再现,从而被误解成属于同一连接的某个新的化身。因此,TCP将不给处于TIME_WAIT状态的连接发起新的化身。而且TIME_WAIT的时间为2SML,足以让这个连接的分组都被丢弃。这样,可以保证在建立一个新的连接时,之前连接的老的重复的分组都已经在网络中消逝了。
IPv4套接字地址结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct in_addr { in_addr_t s_addr; }; struct sockaddr_in { unit8_t sin_len; sa_family sin_family; in_port_t sin_port; struct in_addr sin_addr ; char sin_zero[8 ]; };
POSIX 定义的数据类型
数据类型
说明
头文件
int8_t
带符号的8位整数
<sys/types.h>
uint8_t
无符号的8位整数
<sys/types.h>
int16_t
带符号的16位整数
<sys/types.h>
uint16_t
无符号的16位整数
<sys/types.h>
int 32_t
带符号的32位整数
<sys/types.h>
uint32_t
无符号的32位整数
<sys/types.h>
sa_family_t
套接字地址结构的地址族
<sys/socket.h>
socklen_t
套接字地址结构的长度,一般为uint32_t
<sys/socket.h>
in_addr_t
IPv4地址,一般为uint32_t
<netinet/in.h>
in_port_t
TCP或UDP端口,一般为uint16_t
<netinet/in.h>
通用套接字地址结构
1 2 3 4 5 6 7 struct sockaddr { uint8_t sa_len; sa_family_t sa_family; char sa_data[14 ]; };
调用bind
等函数时,需要将特定协议的套接字地址结构的指针进行强制类型转换,例如:
1 2 3 4 5 struct sockaddr_in serv ; bind(socketfd, (struct sockaddr*) &sockaddr_in, sizeof (serv))
IPv6套接字地址结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct in6_addr { uint8_t s6_addr[16 ]; }; #define SIN6_LEN struct sockaddr_in6 { uint8_t sin6_len; sa_family_t sin6_family; in_port_t sin6_port; uint32_t sin6_flowinfo; struct in6_addr sin6_addr ; uint32_t sin6_scope_id; };
如果系统支持套接字地址结构中的长度字段,那么 SIN6_LEN常值必须定义。
新的通用套接字地址结构
1 2 3 4 5 6 7 8 9 10 11 12 struct socketaddr_storage { uint8_t ss_len; sa_family_t ss_family; }
值-结果参数
1 2 3 4 5 struct sockaddr_un cli ; socklen_t len;len = sizeof (cli); getpeername(unixfd, (struct sockaddr*)&cli, &len);
把套接字地质结构大小这个参数从一个整数改为指向某个整数变量的指针,其原因在于:当函数被调用时,结构大小时一个值,告诉内核该结构的大小,这样内核在写该结构时不会越界。当函数返回时,结构大小又是一个结果,它告诉进程内核在这个结构中存储了多少信息。这种类型的参数被称为值-结果 参数
字节排序函数
1 2 3 4 5 #include <netinet/in.h> uint16_t htons(uint16_t host16bitvalue);uint32_t htonl(uint32_t host32bitvalue); uint16_t ntohs(uint16_t net16bitvalue);uint32_t ntohl(uint32_t net16bitvalue);
字节操纵函数
1 2 3 4 5 6 #include <stirngs.h> void bzero (void *dest, size_t nbytes) ; void bcopy (const void *src, void *dest, size_t nbytes) ; void bcmp (const void *ptr1, const void *ptr2, size_t nbytes) ;
1 2 3 4 5 #include <string.h> void *memset (void *dest, int c, size_t len) ; void *memcpy (void *dest, void *src, size_t nbytes) ; int memcmp (const void *ptr1, const void *ptr2, size_t nbytes) ;
地址转换函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <arpa/inet.h> int inet_aton (const char *strptr, struct in_addr *addrptr) ; in_addr_t inet_addr(const char *strptr); char *inet_ntoa (struct in_addr inaddr) ; #define INET_ADDRSTRLEN 16 #define INET6_ADDR STRLEN 46 int inet_pton (int family, const char *strptr, void *addrptr) ; const char *inet_ntop (int family, const void *addrptr, char *strptr, size_t len) ;
基本TCP套接字编程
1 2 3 #include <sys/socket.h> int socket (int family, int type, int protocol) ;
family常值
family
说明
AF_INET
IPv4协议
AF_INET6
IPv6协议
AF_LOCAL
Unix域协议
AF_ROUTE
路由套接字
AF_KEY
密钥套接字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int connect (int sockfd, const struct sockaddr *servaddr, socklen_t addrlen) ; int bind (int sockfd, const struct sockaddr *myaddr, socklen_t addrlen) ; int lister (int sockfd, int 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 eror" ); }
backlog
内核为每个监听套接字维护两个队列:
未完成连接队列
服务器收到了SYN分节,在等待完成相应的TCP三次握手过程,套接字处于SYN_RCVD状态
已完成连接的队列
服务器完成了三次握手,这些套接字处于ESTABLISHED状态
backlog曾被定义为两个队列总和的最大值。源自Berkeley的实现给backlog增设了一个模糊音字,把它乘以1.5得到未处理队列的最大长度。
1 2 3 int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen) ; int close (int sockfd) ;
并发服务器
1 2 3 4 5 6 7 8 #include <unistd.h> pid_t fork(void ); int execl (const char *pathname, const char *arg0, ...) ;int execv (const char *pathname, char *const *argv[]) ;int execle (const char *pathnbame, const char *arg0, ... ) ;int execve (const char *pathname, char *const argv[], char *const envp[]) ;int execlp (const char *filename, const char *arg0, ...) ;int execvp (const char *filename, char *const argv[]) ;
fork只调用一次,在父进程中返回一次,在子进程中也返回一次。
可以用fork来创建进程自身的副本,也可以用fork创建一个进程的副本后调用exec函数把自身替换为新的程序
典型并发服务器程序的轮廓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pid_t pid;int listenfd, connfd;listenfd = socket(...); bind(listenfd, ...); listen(listenfd, LISTENQ); for ( ; ; ){ connfd = Accept(listenfd, ...); if ((pid = fork())== 0 ) { close(listenfd); doit(connfd); close(connfd); exit (0 ); } close(connfd); }
getsockname 和 getpeername
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <sys/socket.h> int getsockname (int sockfd, struct sockaddr *localaddr, socklen_t *addrlen) ;int getpeername (int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen) ;int sockfd_to_family (int sockfd) { struct sockaddr_storage ss ; socklen_t len; len = sizeof (ss); if (getsockname(sockfd, (struct sockaddr*)&ss, &len) < 0 ) return (-1 ); return (ss.ss_family); }