客户端-服务器编程模型
每个网络应用都是基于客户端-服务器模型的。采用这个模型,一个应用是由一个服务器进程和一个或多个客户端进程组成。
客户端-服务器模型中的基本操作是事务(transaction)。一个客户端-服务器事物由以下四步组成。
- 当一个客户端需要服务时,他就向服务器发送一个请求,发起一个事物。例如,当Web浏览器需要一个文件时,它就发送一个请求给Web服务器。
- 服务器收到请求后,解释它,并以适当的方式操作它的资源。例如,当Web服务器收到浏览器发出的请求后,它就读取一个磁盘文件。
- 服务器给客户端发送一个响应,并等待下一个请求。例如,Web服务器将文件发送回客户端。
- 客户端收到响应并处理它。例如,当Web浏览器收到来自服务器的一页后,就在屏幕上显示此页。
认识到客户端和服务器是进程,而不是常提到的机器或者主机,这是很重要的。
网络
客户端和服务器通常运行在不同的主机上,并通过计算机网络的硬件和软件资源来通信。
对主机而言,网络是一种I/O设备,是数据源和数据接收方。
全球IP因特网
因特网的客户端和服务器混合使用套接字接口函数和Unix I/O函数来进行通信。通常将套接字函数实现为系统调用,这些系统调用会陷入内核,并调用各种内核模式的TCP/IP函数。
- 主机集合被映射为一组32位的IP地址。
- 这组IP地址被映射为一组称为 因特网域名(Internet domain name) 的标识符。
- 因特网主机上的进程能够通过 连接(connection) 和任何其他因特网主机上的进程通信。
IP地址
IP地址存放在IP地址结构中1
2
3struct in_addr{
uint32_t s_addr; /*Address in network byte order (big-endian)
}
主机字节顺序与网络字节顺序互换
因为因特网主机可以有不同的主机字节顺序,TCP/IP为任意整数数据项定义了统一的网络字节顺序(大端字节顺序)。
1 |
|
inet_pton函数将点分十进制字符串转换为二进制的网络字节顺序的IP地址,inet_ntop函数将一个二进制的网络字节序的IP地址转换为点分十进制。
1 |
|
因特网连接
一个套接字是连接的一个端点。每个套接字都有相应的套接字地址,是由一个因特网地址和一个16位的整数端口组成的,用“地址:端口”来表示。
一个连接是由它两端的套接字地址唯一确定的。这对套接字地址叫做套接字对(socket pair)
套接字的地址结构
从Linux内核的角度看,一个套接字就是通信的一个端点。从Linux程序的角度来看,套接字就是一个有相应描述符的打开文件。
1 | struct sockaddr_in { |
我们可以通过下图理解各个函数。
socket函数
客户端和服务器使用socket函数来创建一个套接字描述符(socket descriptor)。
1 |
|
如果想要使套接字称为连接的一个端点,就用如下硬编码的参数来调用socke函数:clientfd = Socket(AF_INMET, SOCKET_STREAM, 0);
socket返回的clientfd描述符仅是部分打开的,还不能读写。如何完成打开套接字的工作,取决于我们是客户端还是服务器。
connect函数
客户端通过调用connect函数来建立和服务器的连接。
1 |
|
connect函数试图与套接字地址为addr的服务器建立一个因特网连接,其中addrlen是sizeof(sockaddr_in)。connect函数会阻塞,一直到成功连接或是发生错误。
bind函数
剩下的套接字函数————bind、listen和accept,服务器用它们和客户端建立连接。
1 |
|
bind函数告诉内核将addr中的服务器套接字地址和套接字描述符sockfd联系起来。
listen函数
客户端时发起连接请求的主动实体。服务器是等待来自客户端的连接请求的被动实体。默认情况下,内核会认为socket函数创建的描述符对应于主动套接字(active socket),默认情况下,它存在于一个连接的客户端。服务器调用listen函数告诉内核,描述符是被服务器使用的,而不是客户端使用的。
accept函数
服务器通过调用accept函数等待来自客户端的连接请求。
1 |
|
accept函数等待来自客户端的连接请求到达侦听描述符listenfd,然后在addr中填写客户端的套接字地址,并返回一个**已连接描述符(connected descriptor),这个描述符可被来利用Unix I/O函数与客户端通信。