第11章:网络编程
网络编程
11.1 客户端服务器编程模型(Client - server )
一个应用是由一个服务器和一个或多个客户端组成的,这个模型的基本操作是事务(transaction),一个客户端服务器事务通常需要以下四步:

我们需要认识到客户端和服务器是进程而不是主机
11.2 网络
客户端和服务器通常运行在不同主机上,通过计算机网络的硬件和软件来进行通信。
对于主机而言网络又是一种I/O设备,一个插到I/O总线拓展槽的适配器提供了到网络的物理接口。

在物理意义上,最底层是LAN(Local Area Network)局域网,现在最流行的局域网技术是以太网(Ethernet),一个以太网段包括一些电缆和一个集线器。集线器不加分辨地将从一个端口上收到的每一个位都复制到其他所有端口上。一台主机可以发送一段位(frame帧),每个帧包括头部位(用来标识此帧的源和目的地),后面就是有效载荷。
用一些电缆和网桥就可以把多个以太网段连接成较大的局域网。
在层次的更高级别中,多个不兼容的局域网可以通过路由器的特殊计算器连接起来,这就被称为WAN(wide - area network)

互联网络至关重要的特性是,他能由采用完全不同和不兼容的技术的各种局域网和广域网组成。但是怎么做到不兼容的网络之间发送数据呢?
解决办法是TCP/IP协议,它运行在每台主机和路由器上,消除了不同网络之间的差异
这种协议必须提供两种基本能力:
1.命名机制:必须唯一为主机标识地址
2.传送机制:通过定义一种把数据位捆扎成不连续的片(称为包),从而消除了这种差异。一个包由包头和有效载荷构成,包头包括包的大小和dst src的地址。

fh11指向路由器,fh2指向lan2的主机b
11.3 全球IP因特网
下图展示了一个因特网客户端服务端应用程序的基本硬件的软件架构

从程序员的角度,我们可以把因特网看做一个世界范围的主机集合,满足以下特性:
1:主机集合被映射为一组32位的ip地址
2: ip地址被映射为一组称为因特网域名的标识符
3:因特网主机上的进程可以通过connection 和任何其他因特网主机上的进程通信
11.3.1 IP地址
IP地址就是一个32位的无符号整数,网络程序把IP地址存放在下面的IP地址结构中
struct in_addr{ uint32_t s_addr; };
TCP/IP定义了统一的网络字节顺序(大端法)Unix提供了下面这样的函数用于在网络和主机字节顺序间实现转换。
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
IP地址通常是以一种称为点分十进制表示法来表示的,每个字节由他的十进制值表示,例如128.2.194.242 表示地址0x8002c2f2
应用程序使用inet_pton 和 inet_ntop 来实现IP地址和点分十进制串之间的转换
#include<arpa/inet.h> int inet_pton(AF_INET,const char *src, const void *dst);成功返回1,src为非法返回0,出错返回-1 const char *inet_ntop(AF_INET,const void *src , const char *src);成功返回指向点分十进制字符串的指针,出错报NULL
11.3.2 因特网域名
一旦一个组织拥有了耳机域名就可以随便创建人和新的域名了。
11.3.3 因特网连接
套接字是通信的基石,是支持TCP/IP协议的路通信的基本操作单元。每个套接字是有一个因特网地址和16位整数端口组成,表示为“地址:端口”
当客户端发起一个请求的时候,客户端套接字端口号是由内核随机分配的,称为临时端口。但是服务器的端口通常是某个知名端口,是和服务相对应的。如WEB服务器通常使用端口80.
一个连接是由它两端的套接字地址唯一确定的,这叫做套接字对(socket pair)

11.4 套接字接口
套接字接口是一组函数,

11.4.1 套接字地址结构
从Linux内核考虑,一个套接字就是一个连接的端口。而从linux程序考虑,一个套接字就是一个有相应描述符的打开文件。
struct sockaddr_in{ uint16_t sin_family; //protocal family (always AF_INET) uint16_t sin_port; // port number struct in_addr sin_addr; unsighed char sin_zero[8]; }; //generic socket address structure struct sockaddr{ uint16_t sin_family; char sa_data[14]; };
11.4.2 socket函数
服务器和客户端使用socket函数来创建一个套接字描述符.成功返回描述符,失败返回-1
int socket(int domain , int type , int protocol);
11.4.3 connet函数
客户端通过connect函数来建立与服务器的连接
int connect(int clientfd , const struct sockaddr *addr , socklen_t addrlen);
addrlen = sizeof(sockaddr_in)
11.4.4 bind函数
剩下的套接字函数:bind listen 和accept 服务器用它们来与客户端建立联系
bind函数告诉内核把addr中的服务器套接字地址和套接字描述符sockfd联系起来
t bind(int sockfd , const struct sockaddr *addr,socklen_t addrlen);
11.4.5 listen函数
客户端是发起连接请求的实体,默认情况下内核会认为socket函数创建的描述符对应于主动套接字,存在于一个连接的客户端。服务器调用Listen函数高速内核,描述符属于服务器。
int listen(int sockfd , int backlog); // 通常我们把backlog设置为一个较大的数如1024
11.4.6 accept函数
int accept(int listenfd , struct sockaddr *addr, int *addrlen);
accpt 函数等待来自客户端的连接请求到达监听标识符,然后在addr中填写客户端的套接字地址。返回一个已连接描述符
11.4.7 主机和服务的转换
struct addrinfo{ int ai_flags; int ai_family; int ai_socktype; int ai_protocol; char *ai_canonname; size_t ad_addrlen; struct sockaddr *ai_addr; struct addrinfo *ai_next; };
先介绍addrinfo的结构

ai_flags 是一个位掩码,可以把各种值用|连接起来,
常用的有 AI_ADDRCONFIG 在使用连接的时候推荐使用,只有当本地主机被配置为IPv4,返回IPv4。
AI_NUMERICSERV 强制service为端口号
ai_family 是代表用的ipv4还是ipv6 是socket的第一个参数 AF_INT 表示ipv4 AF_INT6 表示 ipv6
ai_socktype 设置为SOCK_STREAM 限制每个地址最多只有一个addrinfo结构
下面介绍getaddrinfo
int getaddrinfo(const char *host , const char *service , const struct addrinfo *hints , struct addrinfo **result)
当getaddrinfo创建输出列表中的addrinfo结构时候,会填写除了ai_flags以外的所有字段。
这个函数很好的一个方面是,他的addrinfo结构中的字段是不透明的,它们可以直接传递给套接字接口的函数
hints 是一个addrinfo结构,只能设置下列字段:ai)family , ai_socktype , ai_protocol ai_flags. 其他字段必须设置为0 , 实际上 我们用memset将整个结构清零。
下面介绍getnameinfo
int getnameinfo(const struct sockaddr *sa , socklen_t salen , char *host , size_t hostlen,\ char *service , size_t servlen , int flags)
成功返回0 , 如果错误则为非0的错误代码 , 错误代码交给gai_strerror(错误代码)
flags 也是位掩码,下面是两个有用的值
NI_NUMERICHOST 使该函数返回一个数字地址字符串
NI_NUMERICSERV 使该函数返回简单的端口号
gai_strerror 与Freeaddrinfo()
gai_strerror(代码代号)返回一个错误代码的解释字符串
freeaddrinfo释放空间

浙公网安备 33010602011771号