IP地址是在IP协议中,用来标识网络中不同主机的地址,它有两个版本,一个是IPv4
,一个是IPv6
,IPv4
是一个32位4字节的数字,通常使用点分十进制的字符串表示IP地址,例如192.168.1.1
,IPv6
是一个108位16字节的数字,一般来说,现在在世界上使用规模最大的就是IPv4
,但是因为4字节能表示的数字只有不到43亿,随着时代的进步,入网设备从电脑和手机逐渐变成了除了它们以外其他的例如空调、洗衣机、冰箱、智能音箱、智能门锁、电视等等,每一个智能设备都要有一个自己的IP地址,所以我们国家对于IPv6
的布置是战略性的,我国是世界上对于IPv6普及最广的国家,当然我们现在包括本文后面一般提到IP协议一般都是IPv4
源IP地址和目的IP地址共同构成了网络通信的基础寻址体系,它们确保了数据在复杂的网络环境中能够准确、高效地传输,在数据发送前,发送设备会在 IP 数据包的头部封装好源 IP 地址和目的 IP 地址,然后数据在网络中传输时,路由器等网络设备会根据这些地址信息进行路由选择和数据转发,直到数据到达目的设备,整个网络通信过程就是围绕着源 IP 地址和目的 IP 地址进行的一系列数据封装、传输、路由和交付的操作
IP地址用于不同网络之间寻址,MAC地址用于同局域网传输
端口号是传输层协议的内容
端口号是一个16位2字节的数字,用来标识一个进程并操作系统当前的这个数据要交给哪个进程处理
IP地址+端口号能够标识网络上的某一台主机的某一进程
一个端口号只能被一个进程占用,但一个进程可以绑定多个端口号
这里我们有问题了,主播主播,为什么端口号和进程pid都能标识一个进程,那他们分出来干啥呢?
其实,端口号与进程pid
,前者用来表示网络内容,后者用来表示系统内容,将网络和系统的内容进行解耦合,在一方出现问题的时候不影响另一方
在网络通信中,源端口号和目的端口号与源 IP 地址、目的 IP 地址以及传输层协议(TCP 或 UDP)共同构成了完整的通信标识,IP地址能表示唯一的主机,端口号能标识该主机上的唯一一个进程,所以他们结合就可以找到我们指定的进程,当应用程序要发送数据时,会在传输层将数据封装成数据包,并在数据包中添加源端口号、目的端口号、源 IP 地址和目的 IP 地址等信息,在数据传输过程中,网络设备会根据这些信息进行数据的路由和转发,确保数据能够从源应用程序或进程准确地传输到目标应用程序或进程
在C语言的学习过程中,我们认识到内存(包括磁盘文件)当中的多字节数据相对于内存地址有大小端之分,实际上,网络上也有大小端之分,大端就是数据的高位字节存于低地址,低位字节存于高地址,小端就是数据的低位字节存于低地址,高位字节存于高地址
对于大端(小端)来说,对于 32 位整数0x12345678
,高位字节0x12
存于内存的低(高)地址,接着依次是0x34
、0x56
、0x78
存于更高(低)地址
网络字节序规定了在网络上传输数据时,统一采用大端序,如果主机是小端存储,在收到数据后,可以通过一些函数将大端数据转化成小端,下面是C语言的函数
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);//将一个32位无符号整数从主机字节序转换为网络字节序
uint16_t htons(uint16_t hostshort);//将一个 16 位的无符号整数从主机字节序转换为网络字节序
uint32_t ntohl(uint32_t netlong);//将一个 32 位的无符号整数从网络字节序转换为主机字节序
uint16_t ntohs(uint16_t netshort);//将一个 16 位的无符号整数从网络字节序转换为主机字节序
当我们进行网络之间通信的时候,比如我们在微信上发送一个hello world,首先我们要打开微信,然后输入发出,然后另一个用户在微信上收到hello world,这个过程实际上,将中间很多过程都忽略掉,就是两个进程在进行进程间通信,当我们以这个视角看待网络通信的时候,那么网络通信的过程也就成了一种介质,类似于管道或者共享内存一样的通信信道
在 TCP/IP 四层模型中,每一层都包含了多种不同的协议,这些协议各自承担着不同的功能,共同协作以实现网络通信,下面这些内容需要的看一下,这里主要是指出每层都有多种协议,所以在数据解析的过程中,都是通过报头里面的源xx号和目标xx号起到的作用,精确找到要找到哪个协议解析
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
返回值:成功返回套接字描述符sockfd
,失败返回-1
domain
:指定通信域
通信域 | 意义 |
---|---|
AF_INET | IPv4网络协议 |
AF_INET6 | IPv6网络协议 |
AF_UNIX 或 AF_LOCAL | 用于本地通信,可在同一台机器的不同进程间进行高效数据传输 |
type
:指定套接字类型
套接字类型 | 意义 |
---|---|
SOCK_STREAM | 流式套接字,基于TCP实现 |
SOCK_DGRAM | 数据报套接字,基于UDP实现 |
SOCK_RAW | 原始套接字,允许程序直接访问底层网络协议 |
protocol
:指定了使用的具体协议,通常将其设为 0,这样系统会根据 domain
和 type
自动选择合适的协议
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:成功返回0,失败返回-1
sockfd
:要进行绑定的套接字描述符
addr
:一个指向 struct sockaddr
类型的指针,它存储了要绑定的地址信息,具体的结构在下个话题中说
addrlen
:这是 addr
所指向结构体的长度,通常使用 sizeof
运算符来获取,这里位输入型参数
#include <sys/socket.h>
int listen(int sockfd, int backlog);
返回值:成功返回0,失败返回-1
sockfd
:要监听的套接字描述符
backlog
:指定请求连接队列的最大长度,当有多个客户端同时发起连接请求时,服务器无法立即处理所有请求,这些未处理的连接请求会被放入一个队列中等待处理,backlog
就是这个队列的最大长度,如果队列已满,新的连接请求将被拒绝
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
返回值:成功返回一个新的套接字描述符,这个新的套接字专门用于与发起连接请求的客户端进行数据传输,原来的 sockfd
仍然用于继续监听其他客户端的连接请求
sockfd
:这是由 socket
函数创建、bind
函数绑定地址和端口,并且通过 listen
函数设置为监听状态的套接字描述符
addr
:一个指向 struct sockaddr
类型的指针,它存储了发起连接请求的客户端的地址信息
addrlen
:这是一个指向 socklen_t 类型的指针,用于传入 addr 所指向结构体的长度,并且在函数返回时,该指针指向的值会被更新为实际存储的客户端地址信息的长度,这里是输入输出型参数
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:成功返回0,失败返回-1
sockfd
:要连接的套接字描述符
addr
:存储了要连接的服务器的地址信息
addrlen
:表示 addr
所指向的地址结构体的长度,通常使用 sizeof
运算符来获取,输入型参数
sockaddr
的结构如下图左一所示,实际上,我们在函数中用到的结构体是右边两个而不是左一,在sockaddr
接受传来的参数时,它会通过判断前面16位2字节的地址类型是AF_INET
还是AF_UNIX
,然后在使用时通过强制转化转换成sockaddr_in
或 sockaddr_un
,前者用于网络间通信,后者用于域间通信即进程间通信