在IP数据报头部中,有两个IP地址,分别叫做源IP地址,和目的IP地址。
思考:我们光有IP地址就可以完成通信了嘛?
IP地址能够标识互联网中的唯一的一台主机,想象一下发qq消息的例子,有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出,这个数据要给哪个程序进行解析。
两台主机间通信并不是使用机器通信,而是使用机器上的程序进行通信,程序也就是进程,所以需要一个标识来标记主机中的进程,也就是这里所讲的端口号,端口号(port)是传输层协议的内容,能够标识主机中唯一的一个进程。
传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述 “数据是谁发的,要发给谁”。
IP地址能够标识互联网中的唯一的一台主机,端口号能够标识主机中唯一的一个进程。也就是说{IP地址,port},就能够标识网络中唯一的一个进程,使用网络进行通信的本质就是使用{IP地址,port}进行通信的,它的名字叫做网络套接字。
我们之前在学习系统编程的时候,学习了 PID 用来表示唯一一个进程;此处我们的端口号也是唯一表示一个进程。那么这两者之间是怎样的关系?
PID是用来标识主机上的进程的,port是用来标识主机上用来进行网络通信的进程的。由于主机上并不是所有进程都需要进行网络通信的,并且我们希望其他模块(进程模块)与网络模块进行解耦,所以就有了端口号的概念。
一个端口号通常与一个进程进行绑定,一个进程也可以绑定多个端口号,但是一个端口号不能被多个进程绑定。
此处我们先对TCP(Transmission Control Protocol 传输控制协议)和UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识,后面我们再详细讨论TCP和UDP的一些细节问题。大家不要被两个协议特点所迷惑,这两个协议只有不同,没有好坏。
TCP协议的特点
UDP协议的特点
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。那么如何定义网络数据流的地址呢?
// 头文件
#include
#include
#include
#include
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
功能:创建一个新的套接字。
参数:
返回值:成功时返回套接字文件描述符,失败时返回-1并设置errno。
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
功能:将套接字与特定的IP地址和端口号绑定。
参数:
返回值:成功时返回0,失败时返回-1并设置errno。
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
功能:使套接字进入监听状态,准备接受连接请求。
参数:
返回值:成功时返回0,失败时返回-1并设置errno。
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
功能:接受一个连接请求。
参数:
返回值:成功时返回新的套接字文件描述符用于与客户端通信,失败时返回-1并设置errno。
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:主动与服务器建立连接。
参数:
返回值:成功时返回0,失败时返回-1并设置errno。
// 头文件
#include
uint32_t htonl(uint32_t hostlong);
功能:将32位无符号整数从主机字节序转换为网络字节序。
参数:
返回值:返回转换后的以网络字节序表示的 32 位无符号整数。
uint32_t ntohl(uint32_t netlong);
功能:将32位无符号整数从网络字节序转换回主机字节序。
参数:
返回值:返回转换后的以主机字节序表示的 32 位无符号整数。
uint16_t htons(uint16_t hostshort);
功能:将16位无符号整数从主机字节序转换为网络字节序。
参数:
返回值:返回转换后的以网络字节序表示的 16 位无符号整数。
uint16_t ntohs(uint16_t netshort);
功能:将16位无符号整数从网络字节序转换回主机字节序。
参数:
返回值:返回转换后的以主机字节序表示的 16 位无符号整数。
#include
#include
#include
int inet_aton(const char *cp, struct in_addr *inp);
功能:将点分十进制的IPv4地址字符串转换为网络字节序的二进制形式。
参数:
返回值:
in_addr_t inet_addr(const char *cp);
功能:将点分十进制字符串形式的 IP 地址转换为 32 位二进制整数形式(网络字节序)。
参数:
返回值:如果转换成功,返回转换后的 32 位二进制整数;如果转换失败,返回 INADDR_NONE(通常为 0xffffffff)。不过需要注意的是,INADDR_NONE 也是一个有效的 IP 地址(255.255.255.255),这可能会导致一些混淆,因此更推荐使用 inet_aton 函数。
in_addr_t inet_network(const char *cp);
功能:将点分十进制字符串形式的 IP 地址转换为 32 位二进制整数形式(主机字节序),并且通常用于提取网络号。
参数:
返回值:如果转换成功,返回转换后的 32 位二进制整数;如果转换失败,返回 -1。
char *inet_ntoa(struct in_addr in);
功能:将网络字节序的IPv4地址(struct in_addr)转换回点分十进制的字符串形式。
参数:
返回值:返回一个指向静态分配的、以null结尾的字符串的指针,该字符串包含转换后的点分十进制IPv4地址。注意,返回的字符串指针指向一个静态分配的区域,因此每次调用 inet_ntoa 时,上次调用返回的字符串可能会被覆盖。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
功能:从指定的套接字接收数据报,并可选地获取发送方的地址信息。
参数:
返回值:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
功能:用于向指定的地址发送数据报。
参数:
返回值:
网络编程中,socket分为很多种类:
由于设计者想用同一套接口就解决上面应用场景,所以就设计出来sockaddr_in结构体类型,通过下图我们可以看到sockaddr_in结构体与sockaddr_un结构体都有一个地址类型,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!