IP与端口号
IP 地址:定位"主机",不是最终目的
-
IP 地址用于标识网络中唯一的一台主机
-
在网络层,IP 只关心:数据该被路由到哪一台机器
数据传输到主机不是目的,而是手段
因为:
聊天是 人在聊天
下载是 人在下载
浏览网页是 人在浏览
但计算机中:
人不能直接接收网络数据
进程是人在操作系统中的代表
例如:
人的行为 对应进程 聊天 QQ 进程 下载 迅雷进程 上网 浏览器进程 真正需要数据的是进程,而不是主机本身
一台主机上:可以同时运行 成百上千个进程 ,当数据到达主机后:系统必须知道把数据交给哪一个进程 。这就引出了 端口号(Port)。
端口号(Port):定位"进程"
端口号是传输层(TCP / UDP)的概念
是一个 16 位整数(2 字节)
用来告诉操作系统:这份数据应该交给哪个网络进程处理
IP + Port 的意义:IP 标识主机,Port 标识该主机上的网络进程 。 网络通信的本质:跨网络的进程间通信。
端口号与进程的关系 :一个端口号只能被一个进程占用 ;一个进程可以绑定多个端口号。
端口号范围划分:
| 范围 | 含义 |
|---|---|
| 0 -- 1023 | 知名端口(HTTP=80,SSH=22,FTP=21) |
| 1024 -- 65535 | 临时 / 动态端口(通常由 OS 分配给客户端) |
端口号 vs 进程 ID(PID)
为什么不用 PID 做网络通信标识?
虽然:PID 在系统中是唯一的,可以标识一个进程
但问题是:PID 属于 操作系统内部概念 ,每次进程启动,PID 都会变化,如果网络层依赖 PID:会造成系统与网络强耦合。
所以系统管理归系统,网络通信归网络
因此:所有进程都有 PID,只有需要网络通信的进程才有端口号
端口号是 为网络通信专门设计的抽象
源端口号 & 目的端口号
在 TCP / UDP 报文中:源端口号(Source Port);目的端口号(Destination Port)
它们用于描述:数据是谁发的?发给谁?
配合 IP:srcIP : srcPort → dstIP : dstPort
Socket:网络通信的统一抽象
Socket = IP + Port
是对"网络进程"的抽象表示。
在网络中,一次通信可以由以下 四元组 唯一确定:{ srcIP, srcPort, dstIP, dstPort }
这表示:互联网上两个进程之间的一次通信关系
网络通信的本质
- IP 地址用来标识互联网中唯一的一台主机, port 用来标识该主机上唯一的一个网络进程,IP+Port 就能表示互联网中唯一的一个进程
- 所以,通信的时候,本质是两个互联网进程代表人来进行通信, {srcIp,srcPort, dstIp, dstPort}这样的 4 元组就能标识互联网中唯二的两个进程
- 所以,网络通信的本质,也是进程间通信,我们把 ip+port 叫做套接字 socket
IP 用来定位主机,端口用来定位主机上的网络进程,Socket(IP+Port)是网络通信的基本抽象,本质上网络通信就是跨网络的进程间通信。
传输层
如果我们了解了系统, 也了解了网络协议栈, 我们就会清楚, 传输层是属于内核的, 那么我们要通过网络协议栈进行通信, 必定调用的是传输层提供的系统调用, 来进行的网络通信
网络字节序
内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个 TCP/IP 规定的网络字节序来发送/接收数据;如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
但后面是有了规定:网络中通信,必须大端! 为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
• 这些函数名很好记,h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数。
• 例如 htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将 IP 地址转换后准备发送。
• 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
• 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
socketAPI
cppC / / 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器) int socket(int domain, int type, int protocol); // 绑定端口号 (TCP/UDP, 服务器) int bind(int socket, const struct sockaddr *address, socklen_t address_len); // 开始监听 socket (TCP, 服务器) int listen(int socket, int backlog); // 接收请求 (TCP, 服务器) int accept(int socket, struct sockaddr* address, socklen_t* address_len); // 建立连接 (TCP, 客户端) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);sockaddr 结构
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 结构体指针做为参数;(这就是C语言版本的多态,头部结构一致,因此可以接收不同的结构体)
sockaddr 结构
cppstruct sockaddr { sa_family_t sa_family; // 地址族(AF_INET / AF_INET6) char sa_data[14]; };sockaddr_in 结构 ipv4
cppstruct sockaddr_in { sa_family_t sin_family; // AF_INET in_port_t sin_port; // 端口 struct in_addr sin_addr; // IPv4 地址 unsigned char sin_zero[8]; };sockaddr_in6 ipv6
cppstruct sockaddr_in6 { sa_family_t sin6_family; // AF_INET6 in_port_t sin6_port; uint32_t sin6_flowinfo; struct in6_addr sin6_addr; uint32_t sin6_scope_id; };虽然 socket api 的接口是 sockaddr, 但是我们真正在基于 IPv4 编程时, 使用的数据结
构是 sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP 地址
in_addr 结构
in_addr 用来表示一个 IPv4 的 IP 地址. 其实就是一个 32 位的整数;






