学习目标:
- 了解网络的基本架构
- 了解常见的网络协议
- 学会编写web服务器
网络架构
每个网络应用都是基于客户端-服务器
模型。对于一个主机而言,网络只是一种I/O设备,作为数据源和数据接收方。
根据网络的应用范围和架构层级:
- LAN-Local Area Network
- 以太网(Ethernet)
- WAN-Wide Area Network
- High speed point-to-point phone lines
最底层Ethernet segment
几个host连接到hub上,通常范围是一个房间或一个楼层
- 每个以太网适配器都有一个全球唯一的48位地址(也就是MAC地址)
- 不同主机间传送数据的称为帧,由header和有效载荷组成
桥接以太网bridge Ethernet
通常是一层楼的范围,Bridge知道从某端口可达的主机,并有选择在端口间复制数据。
下一层-internet
互联网络重要的特性是:由采用完全不同和不兼容技术的各种局域网和广域网组成,关键在于协议软件
。
网络协议
这种协议控制主机和路由器协同工作来实现数据传输。在不同的 LAN 和 WAN 中传输数据,就要守规矩,这个规矩就是协议。协议负责做的事情有:
- 命名机制
- 定义 host address 格式
- 每个主机和路由器至少有一个internet address
- 传送机制
- 标准传输单元-package(把数据位捆扎成不连续的片)
- 包由包头和有效载荷组成
- 包头:包的大小、源主机和目的主机的地址
- 有效载荷:源主机发出的数据位
在网络协议下,具体的数据传输如下图所示,PH = Internet pachage header, FH = LAN frame header.
TCP/IP 实际上是一个协议族。
- IP 协议提供基本的命名方法和递送机制,这种递送机制能够从一台因特网主机往其他主机发送包,也叫做数据报(datagram)。
- TCP 是一个构建在 IP 之上的复杂协议,提供子进程间可靠的全双工(双向的)连接。
Internet 是 internet 最著名的例子。主要基于 TCP/IP 协议族:
- IP(Internet Protocal)
- Provides basic naming scheme and unreliable delivery capability of packages(datagrams) from host-to-host
- UDP(Unreliable Datagram Protocal)
- Use IP to provide unreliable datagram delivery from process-to-process(进程间)
- TCP(Transmission Control Protocal)
- Uses IP to provide reliable byte streams from process-to-process over connection
Accessed via a mix of Unix file I/O and functions from sockets interface
- 主机集合被映射为一组32位的IP地址
- IP地址被映射到域名
- 不同主机之间通信,可以通过 connection 来交换数据
Internet域名
- 因特网定义了域名集合和IP地址集合之间的映射。
- 这个映射是通过分布世界范围内的数据库DNS(Domain Name System,域名系统)来维护的。
- DNS数据库是由数以百万计的
主机条目结构(host entry structure)
组成的。
因特网应用通过gethostname
和gethostbyaddr
函数,从DNS数据库检索任意的主机条目。
Internet连接
客户端和服务器通过连接(connection)来发送字节流,特点是:
- 点对点:连接一对进程
- 全双工:数据可以同时双向流动
- 可靠:字节的发送循序和收到的一致
Socket 则可以被认为是 connection 的 endpoint,socket 地址是一个IPaddress:port
对。
Port是一个16位的整数,用来标识不同的进程,利用不同的端口来连接不同的服务:
- Ephemeral port(临时端口):Assigned automatically by client kernel when client makes a connection request
- Well-known port:Associated with some service provided by a server
- echo server:7/echo
- ssh server:22/ssh
- email server:25/smtp
- web servers:80/http
套接字接口
套接字接口(socket interface)是一组函数,它们和Unix I/O函数结合起来,用以创建应用。
从Unix内核的角度来看,一个套接字就是通信的一个端点;从Unix程序的角度来看,套接字就是一个有相应描述符的打开文件。
connect、bind和accept函数要求一个指向与协议相关的套接字地址结构的指针。
简单服务器实现
架构总览
根据上面的流程图,总共有5个步骤:
- 开启服务器(open_listenfd函数)
getaddrinfo
:设置服务器的相关信息socket
:创建socket descriptor, 也就是后来用来读写的file descriptorint socket(int domain, int type, int protocol);
- 例如
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
AF_INET
表示在用32位IPv4地址SOCK_STREAM
表示这个 socket 将是 connection 的 endpoint- 前面这种写法是协议相关的,建议使用
getaddrinfo
生成的参数来进行配置,这样就与协议无关
bind
:请求kernel把socket address和socket desctiptor绑定- int bind(int sockfd, SA *addr, socklen_t addrlen);
- The process can read bytes that arrive on the connection whose endpoint is
addr
by reading from descriptorsockfd
- Similarly,writes to sockfd are transferred along connection whose endpoint is
addr
- 最好用
getaddrinfo
生成的参数作为addr
和addrlen
listen
: 默认来说,我们从socket函数中得到的descriptor默认是active socket(也就是客户端的连接),调用listen函数告诉kernel这个socket是被服务器使用的int listen(int sockfd, int backlog);
- 把
sockfd
从active socket转换成listening socket,用来接收客户端的请求 backlog
的数值表示kernel在接收多少个请求之后(队列缓存起来)开始拒绝请求
accept
:调用accept函数,开始等待客户端请求int accept(int listenfd, SA *addr, int *addrlen);
- 等待绑定到
listenfd
的连接接收到请求,然后把客户端的socket address写入到addr
,大小写入到addrlen
- 返回一个connected descriptor用来进行信息传输(类似Unix I/O)
- 开启客户端(open_clientfd 函数,设定访问地址,尝试连接)
getaddrinfo
:设置客户端的相关信息socket
:创建socket descriptor,也就是之后用来读写的file descriptorconnect
:客户端调用connect来建立和服务器的连接int connect(int clientfd, SA *addr, socklen_t addrlen);
- 尝试与在socket address
addr
的服务器建立连接 - 如果成功
clientfd
可以进行读写 - connection由socket对描述
(x:y, addr.sin_addr:addr.sin_port)
x
是客户端地址,y
是客户端临时端口,后面两个是服务器的地址和端口- 最好用
getaddrinfo
生成的参数作为addr
和addrlen
- 交换数据(主要是一个流程循环,客户端向服务器写入,就是发送请求;服务器向客户端写入,就是发送响应)
- [Client]
rio_writen
:写入数据,相当于向服务器发送请求 - [Client]
rio_readlineb
:读取数据,相当于从服务器接收响应 - [Server]
rio_readlineb
:读取数据,相当于从客户端接收请求 - [Server]
rio_writen
:写入数据,相当于向客户端发送响应
- [Client]
- 关闭客户端(主要是
close
)- [Client]close:关闭连接
- 断开客户端
- [Server]
rio_readlined
:收到客户端发来的关闭连接请求 - [Server]
close
:关闭与客户端的连接
- [Server]
Client:open_clientfd
将socket
和connect
函数包装成一个叫做open_clientfd
的辅助函数。open_clientfd
函数和运行在主机hostname
上的服务器建立一个连接,并在知名端口port
上监听连接请求。
在这里用文字描述一下流程:在创建了套接字描述符(第7行)后,我们检索服务器的DNS主机条目
,并拷贝主机条目中的第一个IP地址到服务器的套接字地址结构
(10~14行)。在用按照网络字节顺序
的服务器的知名端口号
初始化套接字地址结构(15行)之后,我们发起了一个到服务器的连接请求
(18行)。当connect
函数返回时,我们返回套接字描述符
给客户端,客户端就可以立即开始用Unix I/O
和服务器通信了。
Server:open_listenfd
|
|