端口号标识了一个主机上进行通信的不同的应用程序
TCP/IP 协议中,使用 五元组 (5-tuple) 来唯一标识一条网络通信。这个五元组包含以下五个信息元素:
可以通过netstat -nltp
查看:
在Linux系统中,可通过vim /etc/services
来查看知名端口号:
可以。以TCP为例,可以创建多个listen套接字,用的是不同的端口号。一个服务器可以创建两个端口号,一个进行发送数据,另一个进行发送控制命令。
原则上不可以。需要保证端口号与服务之间的唯一性。
进程在Linux内核中实际上是一个struct task_struct
,这就是描述进程的一个结构体。操作系统内部维护了一张哈希表,哈希表对应的key
对应端口号,value
对应进程PCB的地址。在进行bind
绑定的时候是将进程PCB地址与哈希表的key
端口号进程绑定,换言之,所谓的绑定就是将PCB地址和端口号构建在哈希表中。底层收到数据,读取到目的端口号就可以找到对应的进程,就可以将数据交给这个进程。因此一个端口号只能被一个进程绑定,需要保持key
值唯一。
UDP报头一定是一个结构体
16 位 UDP 长度, 表示整个数据报(UDP 首部+UDP 数据)的最大长度
应用层交给 UDP 多长的报文, UDP 原样发送, 既不会拆分, 也不会合并
sendto
会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作;UDP不需要可靠性保证,不需要丢包重传,只需要添加报头,UDP的报头很简单只有8个字节,添加报头后直接发送,因此不需要放在发送缓冲区保存起来。
虽然UDP不需要保证可靠性,但是起码需要保证报文不会大面积丢失,因此提供一个接收缓冲区,当上层正在读取UDP报文,操作系统可以继续接收UDP数据,这样也会在一定程度提高效率。接收缓冲区一旦写满了,这样再接收到的数据就会丢失。
UDP 的 socket 既能读, 也能写, 这个概念叫做 全双工
UDP 协议首部中有一个 16 位的最大长度. 也就是说一个 UDP 能传输的数据最大长度是 64K(包含 UDP 首部)。如果我们需要传输的数据超过 64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装。
UDP报头实际上也是一个结构体,它的具体内容如下:
struct udphdr {
__be16 source; // 源端口号
__be16 dest; // 目标端口号
__be16 len; // UDP 数据长度(包括头部和数据部分)
__be16 check; // 校验和(UDP 校验和)
};
当我们需要拿到对应成员的数据时,使用二进制格式进行序列化即可,struct udphdr* h
指向对应的成员。
在接收方或者发送方,通信双方的操作系统内会同时存在很多报文,要么向上交付,要么向下交付,UDP报文会接受很多数据。在进行通信时,需要对报文进行管理,先描述,再组织。
描述报文的结构体为struct sk_buff
,内部有数据包的头信息、数据、缓冲区等。将应用层数据拷贝到缓冲区实际上是将应用层数据拷贝到缓冲区,此时有了数据。
struct sk_buff {
struct sk_buff *next; // 指向下一个 sk_buff(链表结构)
struct sk_buff *prev; // 指向上一个 sk_buff(链表结构)
struct net_device *dev; // 网络设备(接口),即接收数据包的网卡
unsigned int len; // 数据包的长度
unsigned int data_len; // 有效数据的长度(不包括协议头)
unsigned char *data; // 指向数据区的指针(即数据包的负载部分)
unsigned char *head; // 数据包的开始地址(头部)
unsigned char *tail; // 数据包的尾部(通常指向空闲区域的结束)
unsigned char *end; // 数据包的结束地址(缓冲区末尾)
unsigned int truesize; // sk_buff 的实际大小(包括头部、数据区和尾部)
struct sk_buff *next_free; // 用于内核的 sk_buff 内存池中的链表
struct sock *sk; // 套接字结构体(用于连接和数据传输)
unsigned int protocol; // 数据包的协议类型(如 IPv4、IPv6、ARP 等)
__be16 transport_header; // 运输层协议头部位置(如 UDP、TCP)
__be16 network_header; // 网络层协议头部位置(如 IP)
__be16 mac_header; // 链路层协议头部位置(如以太网)
// 更多字段和标志用于特定功能(例如 QoS、优先级、标记等)
};
然后添加UDP报头:
struct sk_buff bufffer;
(struct udphdr*) buffer->head-=sizeof(struct udphdr);
buffer->head->source=12345;
buffer->head->dest=8888;
buffer->head->len=100;
buffer->head->check=Check();
后续需要再添加报文时,head
就继续往前移动即可。