1. 数据传输的目的
前一篇文章中我们讲解了网络传输的流程,那么网络传输的目的是什么呢?难道我们只是将数据从一台主机传输到另一台主机吗?
当然不是的!因为数据是给人用的。比如:聊天是人在聊天,下载是人在下载,浏览网页是人在浏览? 但是人是怎么看到聊天信息的呢?怎么执行下载任务呢?怎么浏览网页信息呢?是通过启动的 qq,迅雷,浏览器。 而启动的 qq,迅雷,浏览器都是进程。换句话说,进程是人在系统中的代表,只要把数据给进程就相当于人就拿到了数据。 所以数据传输到主机是手段,将数据传达给主机的进程才是目的。 但是系统中,同时会存在非常多的进程,当数据到达目标主机之后,怎么转发给目标进程?这就要在网络的背景下,在系统中标识主机的唯一性。
2. 认识端口号
端口号(port)是传输层协议的内容。
端口号是一个 2 字节 16 位的整数,用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理。IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程。因此一个端口号只能被一个进程占用,但是一个进程可以绑定多个端口号。
我们有这样的问题:那为什么不使用PID来标识进程的唯一性呢?而是使用端口号来标识?
这是为了避免网络与系统的耦合度过高,参考LWP和线程的关系。
端口号范围划分:
• 0 - 1023: 知名端口号,HTTP、 FTP、SSH 等这些广为使用的应用层协议,他们的端口号都是固定的。
•1024 - 65535: 操作系统动态分配的端口号,客户端程序的端口号就是由操作系统从这个范围分配的。
3. 认识Socket编程
现在我们知道,IP 地址用来标识互联网中唯一的一台主机,port 用来标识该主机上唯一的一个网络进程,那么IP+Port 就能表示互联网中唯一的一个进程。在通信的时候,本质是两个互联网进程代表人来进行通信,{srcIp, srcPort,dstIp,dstPort} 这样的 4 元组就能标识互联网中唯二的两个进程。所以网络通信的本质就是进程间通信。我们把 ip+port 叫做套接字 socket。
4. 传输层的典型代表
如果我们了解了系统,也了解了网络协议栈,我们就会清楚,传输层是属于内核的,那么我们要通过网络协议栈进行通信,必定调用的是传输层提供的系统调用来进行的网络通信。
下图是对TCP协议和UDP协议的简略介绍:
5. 网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。那么如何定义网络数据流的地址呢?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存。因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。
TCP/IP 协议规定:网络数据流应采用大端字节序,即低地址高字节。不管这台主机是大端机还是小端机,都会按照这个 TCP/IP 规定的网络字节序来发送/接收数据。如果当前发送主机是小端,就需要先将数据转成大端,否则就忽略, 直接发送即可。
为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
#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);
6. Socket编程接口
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
//参数:
// domain:需要指定域/协议簇
// type:类型
// protocol:协议,一般置0即可
//返回值:
// 成功返回socket文件描述符,失败则返回-1// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
//参数:
// socket:需要绑定的socket文件描述符
// address:绑定的结构
// address_len:绑定结构的大小
Socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如 IPv4、IPv6。以及后面要讲的 UNIX Domain Socket。然而,各种网络协议的地址格式并不相同。
IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中,IPv4 地址用 sockaddr_in 结构体表示,包括 16 位地址类型,16 位端口号和32 位 IP 地址。IPv4、IPv6 地址类型分别定义为常数 AF_INET、AF_INET6。这样,只要取得某种 sockaddr 结构体的首地址,不需要知道具体是哪种类型的 sockaddr 结构体,就可以根据地址类型字段确定结构体中的内容。socket API 可以都用 struct sockaddr * 类型表示,在使用的时候需要强制转化成 sockaddr_in。这样的好处是程序的通用性可以接收 IPv4、IPv6以及 UNIX Domain Socket 各种类型的 sockaddr 结构体指针做为参数。