UNP Chapter 4 - 基本TCP套接口编程
4.1. 概述
Unix在同时有大量的客户连接到同一服务器上时提供并发性,每个客户连接都迫使服务器为他派生(fork)一个新进程。
这里只考虑用fork的每客户单进程模型(one-process-per-client model),在讨论线程时,将考虑另外一种模型,及每客户单线程模型(one-thread-per-client model)。
4.2. socket函数
为了执行网络I/O, 一个进程必须做的第一件事情就是调用socket函数, 指定期望的通信协议类型(使用IPv4的TCP, 使用IPv6的UDP, Unix域字节流协议等)。
#include <sys/socket.h>
int socket(int family, int type, int protocol); // 返回: 非负描述符-成功, -1 -出错
family指明协议族,它可以是如下
AF_INET IPv4协议
AF_INET6 IPv6协议
AF_LOCAL Unix域协议
AF_ROUTE 路由套接口
AF_KEY 密钥套接口
type指明套接口类型,它可以是如下
SOCK_STREAM 字节流套接口
SOCK_DGRAM 数据报套接口
SOCK_RAW 原始套接口
protocol参数一般设置为0,除非用在原始套接口上
并非所有的套接口family和type的组合都是有效的,有效组合如下
AF_INET AF_INET6 AF_LOCAL AF_ROUTE AF_KEY
SOCK_STREAM TCP TCP Yes
SOCK_DGRAM UDP UDP Yes
SOCK_RAW IPv4 IPv6 Yes Yes
socket函数在成功时返回一个小的非负整数值,它与文件描述字类似,称为套接口描述字(socket descriptor)简称套接字(sockfd)。
AF_xxx 与 PF_xxx
AF_前缀代表地址族(address family),PF_前缀代表协议族(protocol family)。
4.3. connect函数
TCP客户用connect函数来建立一个与TCP服务器的连接
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr * servaddr, socklen_t addrlen); // 返回: 0-成功, -1 -出错
4.4. bind函数
函数bind给套接口分配一个本地协议地址
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen); // 返回: 0-成功, -1 -出错
A process can bind a specific IP address to its socket. the IP address must belong to an interface on the host.
对于IPv4来说,通配地址由常值INADDR_ANY来指定,其值一般为0,它通知内核选择IP地址,例如
struct sockaddr_in servaddr;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* wildcard */
对于IPv6如下, 系统分配变量in6addr_any并将其初始化为常值IN6ADDR_ANY_INIT
struct sockaddr_in6 serv;
serv.sin6_addr = in6addr_any; /* wildcard */
4.5. listen函数
#include <sys/socket.h>
int listen(int sockfd, int backlog); // 返回: 0-成功 -1 -出错
函数listen仅被TCP服务器调用,它做两件事情
1. 将函数socket创建一个套接口时,它被假设为一个主动套接口,也就是说,它是一个将调用connect发起连接的客户套接口,函数listen将未连接的套接口转换成被动套接口,指示内核应该接受指向此套接口的连接请求。
2. 函数的第二个参数规定了内核为此套机口排队的最大连接个数。
一般来说,bind函数应该在调用函数socket和bind之后,调用函数accept之前调用。
4.6. accept函数
函数accept由TCP服务器调用,从已完成连接队列头返回下一个已完成连接,若已完成连接队列为空,则进程睡眠(假定套接口为缺省的阻塞方式)
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr * cliaddr, socklen_t * addrlen); // 返回: 非负描述字-OK, -1 -出错
参数cliaddr和addrlen用来返回连接对方进程(客户)的协议地址,addrlen是value-result参数。调用前,我们将由 * addrlen所指的整数值置为由cliaddr所指的套接口地址结构的长度,返回时,此整数值即为由内核存在此套接口地址结构内的准确字节数。如果函数accept执行成功,则返回值是由内核自动生成的一个全新套接字,代表与客户的TCP连接。当讨论函数accept时,常把它的第一个参数称为监听套接口(listening socket)描述字(有函数socket生成的描述字,用作函数bind和listen的第一个参数),把它的返回值称为已连接套接口(connected socket)描述字。
将这两个套接口区分开是很重要的,一个给定的服务器常常是只生成一个监听套接口且一直存在,直到该服务器关闭,内核为每个被接受的客户连接创建了一个已连接套接口(也就是说内核已为它完成TCP三路握手过程),当服务器完成某客户的服务时,关闭已连接套接口。
4.7. fork和exec函数
函数fork是Unix中派生新进程的唯一方法
#include <unistd.h>
pid_t fork(void); // 返回: 在子进程中为0,在父进程中为子进程ID,-1 -出错
函数fork调用一次返回两次。在调用进程(称为父进程),它返回一次,返回值是新派生进程(称为子进程)的进程ID号,在子进程它还返回一次,返回值为0。因此,可通过返回值来判断当前进程是子进程还是父进程。
以文件形式存储在磁盘上的可执行程序被Unix执行的唯一方法是: 由一个现有进程调用六个exec函数中的一个。exec用新程序代替当前进程映像,且此新程序一般都从main函数开始执行,进程ID并不改变。我们一般将调用exec的进程称为调用进程(calling process),而将新执行的程序称为新程序(new program)。
#include <unistd.h>
int execl(const char * pathname, const char * arg0, ... /* (char * )0 */);
int execv(const char * pathname, char * const argv[]);
int execle(const char * pathname, const char * arg0, ... /* (char * )0, char * const envp[] */);
int execve(const char * pathname, char * const argv[], char * const envp[]);
int execlp(const char * filename, const char * arg0, ... /* (char *) 0 */);
int execvp(const char * filename, char * const argv[]);
这些函数只有在出错时才返回调用者,否则,控制权传递到新程序的开始,通常是传递到函数main。
只有execve是内核中的系统调用,其他五个函数都是调用execve的库函数。
4.8. Concurrent servers 并发服务器
4.9. close函数
#include <unistd.h>
int close(int sockfd); // 返回: 0 -OK, -1 -出错
4.10. getsockname和getpeername函数
getsockname返回与套接口关联的本地协议地址,getpeername返回与套机口关联的远程协议地址。
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr * localaddr, socklen_t * addrlen); // 0-OK, -1 -出错
int getpeername(int sockfd, struct sockaddr * peeraddr, socklen_t * addrlen); // 0-OK, -1 -出错
两个函数最后一个参数是value-result参数,都装填由指针localaddr或者peeraddr所指的套接口地址结构。
4.11. 小结
所有的客户和服务器都从调用socket开始,返回一个套接口描述字。然后,客户调用connect,服务器调用bind,listen,accept。套接口一般由标准的close函数关闭,当然也可用函数shutdown来关闭。多数TCP服务器是与调用fork来处理每个客户连接的服务器并发执行的。多数UDP服务器则是迭代的。