文章目录
- 一、端口号
- 1.1 认识端口号
- 1.2 端口号 VS 进程 PID
- 二、认识 TCP 协议
- 三、认识 UDP
- 四、网络字节序列
- 五、socket 编程接口
- 5.1 常用 API
- 5.2 sockaddr 结构
- 六、结语
一、端口号
网络通信的本质是应用层软件进行数据的发送和接受,软件在启动之后,本质上就是进程,所以网络通信的本质就是进程间通信。网络协议中的下三层,主要解决的是数据安全可靠的送到远端机器。
1.1 认识端口号
-
端口号是一个2字节16位的整数。
-
端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理。
-
IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程。
-
一个端口号只能被一个进程占用。
-
我们把这种基于 IP + 端口号的通信就称作 socket。
1.2 端口号 VS 进程 PID
PID 已经可以唯一标识一个进程,为什么还有有端口号的概念?因为并不是所有进程都要进行网络通信,但是所有进程都要有 PID,其次单独设置端口号可以让系统和网络功能进行解耦。但是一个进程可以绑定多个端口号,但是一个端口号只能被一个进程绑定。
每个服务的端口号必须是众所周知的。
二、认识 TCP 协议
-
传输层协议
-
有连接
-
可靠传输(丢包重传、按需到达等,虽然可靠,但是复杂)
-
面向字节流
三、认识 UDP
- 传输层协议
- 无连接
- 不可靠传输(只要发出去了就不管了)
- 面向数据报
四、网络字节序列
在内存中的多字节数据相对于内存地址有大端和小端之分,两台进行网络通信的主机,可能发方采用大端,收方采用小端,此时就会出现发方根据大端的规则去发送数据,而收方是按照小端的规则去解释数据的,这样就会导致收方将数据解释出来的意思并不是发方的本意。因此,网络规定:
-
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出。
-
接收主机把从网络上接到的字节依次保存在接受缓冲区中,也是按照内存地址从低到高的顺序保存。
-
因此,网络数据流的地址应该这样规定**:先发出的数据是低地址,后发出的数据是高地址**。
-
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 地址转换后准备发送。 -
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回。
-
如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
五、socket 编程接口
5.1 常用 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);
socket
参数是socket
函数额返回值,本质上就是一个文件描述符。
5.2 sockaddr 结构
sockt
API 是一层抽象的网络编程接口,适用于各种底层网络协议,如 IPv4
、IPv6
,以及 UNIX Domain Socket
。然而各种网络协议的地址格式并不一样。套接字编程一共有三种:域间套接字(一个主机内部的进程之间进行通信)、原始套接字(可以跳过传输层,通常用来编写一些网络工具)、网络套接字(用户间的网络通信)。因为套接字编程有三种,所以一般的想法是,设计出三套不同的套接字编程接口,但是这里为了简化,实际上只设计了一套接字编程接口。既然只设计了一套接口,那就要求不管是哪种套接字编程,都只能调用这一种接口,那就要求参数必须要一样。struct sockaddr
就起到这样的作用,它将三种套接字编程的参数类型进行了统一。从下图可以看到,struct sockaddr_in
就是网络套接字的类型,struct sockaddr_un
就是域间套接字的类型,它们的前两字节都存的是套接字类型,所有的套接字编程接口,在内部会去检查类型,然后去执行不同的套接字编程代码。
-
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
结构体指针作为参数。
六、结语
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!