现在的网络应用随处可见,无法想象生活离开了网络会变得怎样,最常见的就是通过浏览器上网,在地址栏输入 URL 敲击回车,然后浏览器就呈现出相应的页面。虽然现在的网络应用五花八门,但是它们都是基于相同的编程模型,依赖相同的编程接口。
每个网络应用都是基于客户端-服务端编程模型的,采用这个模型,一个应用是由一个服务器进程和一个或多个客户端进程组成。由服务器管理着某种资源,通过操作这些资源来为客户端提供某种服务。需要注意的是,客户端和服务端是进程,而不是我们常提到的机器或主机。
在系统级 I/O中已经提到过,网络也仅仅是一种文件而已。一个插到 I/O 总线扩展槽的网络适配器提供了网络的物理接口。从网络上接收到的数据从适配器经过 I/O 和内存总线复制到内存;当然数据也同样可以从内存复制到网络。
互联网络由大大小小的局域网和广域网组成,这些局域网和广域网可能采用了完全不同和不兼容的技术,互联网络需要解决的问题是把这些不兼容的网络连接起来。解决办法就是一层运行在每台机器和路由器上的协议软件,这种解决方案随处可见,比如 java 之所以夸平台是因为不同平台上的 JVM 做了适配。
这个协议软件控制路由器和主机如何协同工作来实现数据传输,在命名上定义一种统一的主机地址格式消除差异;在数据传输上通过定义一种把数据位捆绑成不连续的片(包)来消除差异,一个包由包头和有效载荷组成,包头包括包的大小以及源主机和目的主机的地址,有效载荷包括从源主机发出的数据位。
全球 IP 因特网是最著名最成功的互联网络实现,每台因特网主机都运行实现了 TCP/IP 协议的软件。一个 IP 地址就是一个 32 位无符号整数,如下是 IP 网络程序存放的 IP 地址结构,它总是以大端法(网络字节顺序)存放。
struct in_addr {
uint32_t s_addr;
}
复制代码
因特网客户端和服务器相互通信使用的是 IP 地址,但是这对于人们而言太不友好了,所以因特网定义了一组更加人性化的域名,并且定义了域名集合和 IP 地址集合之间的映射。在 1988 年之前,这个映射都是通过一个叫做HOSTS.TXT
的文本文件来手工维护的,此后改为通过 DNS(域名系统)来维护。
客户端和服务端通过在连接
上发送和接收字节流来通信,这个连接是点对点的,并且是全双工的。一个套接字
就是连接的一个端点,每个套接字都有相应的套接字地址,由一个因特网地址和一个 16 位整数端口组成。客户端套接字地址中的端口是内核自动随机分配的,称之为临时端口;而服务端通常是很著名的端口,比如 Web 服务器用 80 端口,电子邮件服务器用 25 端口,并且每个知名端口都有其相应的服务名。下面是套接字地址的结构。
// _in 是 internet 的缩写
struct scoket_in {
uint16_t sin_family; // 协议族
uint16_t sin_port; // 端口号
struct sin_addr; // IP 地址
unsigned char sin_zero[8]; // sizeof(struct sockaddr)
}
struct socketaddr {
uint16_t sa_family;
char sa_data[14];
}
复制代码
内核提供了编写网络应用所需要的函数,基本看函数名称就知道它的功能了,下面展示了一小部分函数。
int socket(int domain, int type, int protocol);
int connect(int clientfd, const struct sockadd *addr,
socklen_t addrlen);
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int listenfd, struct sockaddr *addr,
int *addrlen);
int getaddrinfo(const char *host, const char *service,
const struct addrinfo *hints,
struct addrinfo **result);
int getnameinfo(const struct sockaddr *sa,
socklen_t salen, char *host, size_t hostlen,
char *service, size_t servlen, int flags);
............
复制代码
像用于转换二进制和字符串表示的getaddrinfo()
、getnameinfo()
函数是可重入的。通过内核提供的这些函数就可以编写出自己的 web 服务器。
可重入函数主要用于多任务系统中,简单讲就是可中断函数,在这个函数执行的任何阶段打断它,操作系统去调用另一段代码,再返回控制时不会出现什么错误。因为它除了使用自己栈上的变量外不依赖于任何环境。注意可重入函数不一定是线程安全的。
最后是原书作者自己实现的一个 TINY Wed 服务器,用 C 实现的,在 linux 环境下编译运行即可访问,下图是这个服务器运行的效果,访问链接http://127.0.0.1:8000/cgi-bin/adder?3&7
就可以得到运算结果。整个程序代码也不长,我准备好好看看再抄一遍,源码可访问链接。