概述
本节讲的是编写TCP 客户/服务器所需要的基本套接字函数和简单的并发服务器,它是在大量的用户连接到同一服务器时用于提供并发性的一种UNIX技术,每个用户连接都会使他派生(fork)出一个新进程。
基本流程如下:
- 服务器启动,等待连接
- 某个客户启动,向服务器发送请求
- 服务器处理该请求(fork 一个进程与其连接),并给客户发回一个响应
- 客户关闭连接的客户端,给服务器发回一个EOF(文件结束)通知
- 服务器关闭连接的服务端,等待下次连接
socket 函数
网络I/O所必要的函数,用于指定期望的通信协议
#include <sys/socket.h>
int socket (int family, int type, int protocol);
//返回:若成功则返回套接字描述符sockfd,失败则返回-1
其中,family是某个常量,指定协议域,例如AF_INET4 和 AF_INET6等;type参数指明套接字类型:字节流套接字SOCK_STREAM,数据报套接字SOCK_DGRAM等;protocal参数为协议类型字符串常值。
connect 函数
TCP 用 connect函数来进行用户和TCP 服务器的连接。
#include <sys/socket.h>
int connect (int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
///返回:成功则返回0,失败则返回1
sockfd 是socket 函数返回的套接字描述符,第二个、第三个参数是 指向套接字接口结构的指针和该结构的长度。
注:套接字接口结构必须包括服务器的IP地址和端口号;TCP套接字在调用connect的函数时候会触发TCP三次握手。
connect 函数会导致套接字从closed状态转换到syn_sent状态,如果连接建立成功就被转移到established 状态,如果失败则转移到closed状态。
bind 函数
bind函数把本地协议地址赋予一个套接字,协议地址为IP地址和端口号的组合。
#include <sys/socket.h>
int bind (int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
///返回:成功则返回0,失败则返回1
同上,sockfd 是socket 函数返回的套接字描述符,第二个、第三个参数是 指向特定于协议的地址结构的指针和该结构的长度。
listen 函数
listen 函数仅由TCP服务器调用,功能如下:
- 当socket 函数创建一个套接字时,它被假设成一个主动套接字,也就是说,它是一个将调用connect 发起连接的客户套接字。而listen 函数把该套接字转换成一个被动套接字,指示内核接受指向该套接字的连接请求。 即该套接字状态从CLOSED转换成LISTEN状态。
- 本函数的第二个参数规定了内核应该为相应套接字排队的最大连接个数。
#include <sys/socket.h>
int bind (int sockfd, int backlog);
///返回:成功则返回0,失败则返回1
为了认识backlog 参数,我们需要了解内核给任何一个监听套接字维护两个队列
- 未完成连接队列:客户发送到SYN到服务器,服务器还在等待进行TCP三次握手环节,这些套接字处于SYN_RCVD状态
- 已完成连接队列:已完成TCP三次握手环节的每个客户对应其中一项,套接字处于ESTABLISHED状态。
两个队列之和不超过backlog,注不要把backlog设置为0,否则不同实现意义不同。
accept 函数
accept 函数由TCP服务器调用,用于从已完成队列队头返回下一个已完成连接。如果已完成连接为空,进程开始休眠。
#include <sys/socket.h>
int accept (int sockfd, const struct sockaddr *cliaddr, socklen_t addrlen);
//返回:若成功则返回非负描述符,失败则返回-1
fork 和 exec 函数
fork 是Unix 系统中派生新进程的唯一方法(可能有变体)。
#include <unistd.h>
pid_t fork (void);
//返回:在子进程中为0,父进程为子进程ID,出错则返回0
fork有两个典型用法:
- 一个进程创建一个自身的副本,这样每个副本都可以在另一个副本执行其他任务的同时处理各自的某个操作,常用于网络服务器
- 一个进程想要执行另一个程序。该进程先调用fork创建一个自身的副本,然后在副本中调用exec中把自身替换成新的程序。
exec能把当前进程映像转换成新的程序文件,称调用exec的程序称为调用进程,新执行的程序叫做新程序。
并发服务器
并发服务器最简单的编写方法就是fork子进程来服务每个用户,即connect之后会fork一个子进程来与客户进行连接。