网络通信字节序 端口号 套接字
网络通信原理
概念
日常网络通信的本质就是进程间通过网络协议栈进行通信。
传输层协议(
TCP
和UDP
)的数据段中有两个端口号, 分别叫做源端口号和目的端口号。当数据从传输层传递到应用层时,为了能将有效载荷发送到指定的应用,引入了端口号的概念。
- 对于发收主机来说,端口号都能唯一标识该主机上的网络应用层的进程。
通过端口找到进程
接收数据包:传输层(TCP或UDP)接收到数据包。
检查端口号:检查数据包中的目标端口号。
查找端口表:根据目标端口号在系统的端口表或监听端口列表中查找匹配的端口。
交付数据:找到对应的端口后,将数据包交付给该端口对应的应用程序或进程。
传输层收到端口号之后,根据端口号去寻找端口号到对应的表中进程的
pcb
。
socket(套接字)
IP
地址能表示唯一的主机。port
端口号,可以标识该主机上唯一的一个进程。一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定。
文件描述符和套接字
文件描述符表:
- 每个进程都有一个文件描述符表,用于跟踪该进程打开的文件、套接字、管道等资源。每个表项对应一个文件描述符,文件描述符是一个整数(指针数组的下标),指向内核中的文件表项。
套接字在文件描述符表中的位置:
当进程创建一个套接字(使用
socket()
系统调用)时,内核会在进程的文件描述符表中为该套接字分配一个新的文件描述符。之后,进程可以通过这个文件描述符对套接字进行各种操作,比如绑定地址(
bind()
)、监听连接(listen()
)、接收数据(recv()
)等。- 在每一层使用与特定协议匹配的方法集。
因此.IP:Port可以标识全网唯一的一个进程,这两者的抽象整合,就叫做套接字
。
为什么不使用
UID
来进行标识进程?并非所有进程都要进行网络通信。
降低网络和系统之间的耦合程度。
我们的客户端如何知道服务器的端口号?
- 一般都在客户端内预先设置。
- 或者提前约定好,例如
(HTTP)
的80端口。
UDP 和 TCP
TCP (Transmission Control Protocol 传输控制协议)
- 可靠传输。
- 传输层协议。
- 有连接。
- 可靠传输。
- 面向字节流:即没有明确边界的连续字节序列。
UDP(User Datagram Protocol用户数据报协议)
- 传输层协议。
- 不建立连接。
- 不可靠传输。
- 面向数据报:即独立的、固定长度的数据包。
网络字节序
统一接受数据和发送数据双方的设备存储的大小端问题。
TCP/IP协议规定,网络数据流应采用大端字节序,即低位(低权值)高地址。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
h表示host。
n表示network
l表示32位长整数
s表示16位短整数。
解释:htonl表示将32位的长整数从主机字节序转换为网络字节序。例如将IP地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回
- 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
套接字编程种类
域间套接字,一般用于一个设备上的不同进程。前两字节地址类型一般为
AF_UNIX
原始套接字,一般不处理传输层的问题,更加偏向底层,一般用于网络工具。
网络套接字,一般用于用户间的网络通信。前两字节地址类型一般为
AF_INET
。
struct sockaddr 是通用的地址结构体,包含了所有具体地址结构体的公共部分。它通常作为参数传递给套接字函数,如 bind、connect、recvfrom、sendto 等。
struct sockaddr_in 是 struct sockaddr 的具体实现,专门用于IPv4网络编程。
struct sockaddr_un 是 struct sockaddr 的具体实现,专门用于UNIX域套接字编程。
在实际使用中,常常需要将具体的地址结构体(如 struct sockaddr_in 或 struct sockaddr_un)转换为通用的 struct sockaddr,以便传递给通用的套接字函数。—— 类似于多态。
socket常见API
函数 | 原型 | 描述 |
---|---|---|
socket |
int socket(int domain, int type, int protocol); |
创建一个新的套接字,返回一个文件描述符。 |
bind |
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
将套接字绑定到一个本地地址。 (socket API可以都用struct sockaddr * 类型表示) |
listen |
int listen(int sockfd, int backlog); |
使套接字进入被动监听状态,等待连接请求。 |
accept |
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); |
接受一个连接请求,并返回一个新的套接字文件描述符。 |
connect |
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
主动发起连接到服务器。 |
send |
ssize_t send(int sockfd, const void *buf, size_t len, int flags); |
发送数据到连接的套接字。 |
recv |
ssize_t recv(int sockfd, void *buf, size_t len, int flags); |
从连接的套接字接收数据。 |
sendto |
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); |
发送数据到指定地址的套接字。 |
recvfrom |
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); |
从指定地址的套接字接收数据。 |
close |
int close(int sockfd); |
关闭套接字。 |
shutdown |
int shutdown(int sockfd, int how); |
禁止对套接字的读写操作。 |
setsockopt |
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); |
设置套接字选项。 |
getsockopt |
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); |
获取套接字选项。 |
getaddrinfo |
int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); |
解析主机名或服务名,并获得地址信息。 |
freeaddrinfo |
void freeaddrinfo(struct addrinfo *res); |
释放由 getaddrinfo 分配的地址信息。 |
inet_pton |
int inet_pton(int af, const char *src, void *dst); |
将点分十进制的IPv4和IPv6地址转化为二进制形式。 |
inet_ntop |
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); |
将二进制形式的IPv4和IPv6地址转化为点分十进制的字符串。 |
htons |
uint16_t htons(uint16_t hostshort); |
将主机字节序转换为网络字节序(短整数)。 |
htonl |
uint32_t htonl(uint32_t hostlong); |
将主机字节序转换为网络字节序(长整数)。 |
ntohs |
uint16_t ntohs(uint16_t netshort); |
将网络字节序转换为主机字节序(短整数)。 |
ntohl |
uint32_t ntohl(uint32_t netlong); |
将网络字节序转换为主机字节序(长整数)。 |