文章目录
网络编程套接字(1)
1. 预备知识
1.1 源IP与目的IP
- 在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址。
- 源IP地址:发送主机的IP地址
- 目的IP地址:接收主机的IP地址
1.2 认识端口号
端口号(port)是传输层协议的内容。
- 端口号是一个2字节16位的整数;
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
- IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
- 一个端口号只能被一个进程占用。
1.3 理解 “端口号” 和 “进程ID”
-
A主机把数据发送给B主机是目的吗?
不是的,这只是一种手段,我们其实是要把A主机上的数据交给B主机上的某种服务。(本质是某种客户端和某种服务器) -
我们平时想看抖音短视频必须打开抖音APP,打开抖音APP就是启动抖音的客户端,本质是一个进程;同时我们能在任意时间刷短视频,是因为抖音的服务器在 7*24小时内以进程的方式在运行;所以我们所有的请求动作不是由主机A发起的,而是由主机A上的客户端进程发起的,后与主机B上的服务器进程进行通信。
-
网络通信本质:其实是进程间通信
-
通信的两个阶段:
-
先将数据通过OS,将数据发送到目标主机(手段)
- TCP/IP协议完成。IP可以标识互联网上唯一的一台主机
-
再在本机收到的数据,推送给自己上层的指定进程
- 端口号可以标识对应主机内,唯一的一个进程
-
-
IP地址 + 端口号 = 互联网中唯一的一个进程 (目前)
[源IP,源端口号;目的IP,目的端口] 可以标识互联网中唯二的两个进程,我们把这种通信方式叫做: socket通信
网络通信的本质:是通过IP地址 + 端口号构建进程的唯一性,来进行基于网络的进程间通信IP
问题:
-
端口号,为什么不用进程PID来标识进程的唯一性?
- 不是所有进程都要进行网络通信,只有部分进程进行网络通信,用PID很难区分两者(不进行网络通信和进行网络通信)
- 网络功能设计上要使用PID字段,PID是OS层面进程管理的概念,这么做意味着网络模块要增加进程管理的部分,增加了进程管理和网络管理的耦合度
-
一个端口号只能被一个进程绑定,一个进程可以关联多个端口号
理解: 在OS层面上可以维护一张哈希表,进程在绑定port时就是把PID填入哈希表中,哈希表中下标唯一,一个PID可以对应很多下标;所以在数据报到来时,OS可以根据报文的目的端口号查哈希表找到对应进程,未来把数据交给进程
1.4 源端口号和目的端口号
传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 “数据是谁发的, 要发给谁”;
1.5 认识TCP协议和UDP协议
(1) TCP
此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论TCP的一些细节问题。
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
(2) UDP
此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面再详细讨论。
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
1.6 网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏
移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定: 先发出的数据是低地址,后发出的数据是高地址。
- 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);
- 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
- 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
- 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
2. socket编程接口
2.1 socket 常见API
// 创建 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);
说明: 参数在使用时在详细解释
2.2 sockaddr结构
上面是一套网络通信的接口,sockaddr是这些接口中最重要的参数
-
sockaddr_in:是用于网络通信的套接字,
-
sockaddr_un:是用于域间通信(本地通信)的套接字,用来取代system V原始通信的方案
-
sockaddr:是一套通用的接口,既可以本地通信又可以网络通信
但是我们在使用函数的时候,传的参数是sockaddr,而不是sockaddr_in和sockaddr_un
- sockaddr_in和sockaddr_un结构体中前16比特位(前两个字节)分别代表是网络通信还是本地通信
- 我们在使用socket接口时,根据通信类型选择sockaddr_in和sockaddr_un这两种结构体,需要对结构体内的字段进行填充,传参给sockaddr时必须要进行强制类型转换
- 在socket函数的内部,对传进来的参数 sockaddr 直接去前两个字节,判断是网络通信还是本地通信, 后再强制类型转换回 sockaddr_in 和 sockaddr_un结构体
传不同的结构体,用相同的参数来接受,在函数内部自动进行区分,这是用C语言实现多态的一种做法