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服务器则是迭代的。



 



posted on 2011-12-19 19:38  s7vens  阅读(489)  评论(0编辑  收藏  举报