UDP 默认是无连接的,因此每次发送数据时都需要指定目的 IP 和端口。每次从 socket 中接受数据的时候可以从数据报中得到源 IP 地址和端口号。

UDP 也可以连接到一个固定的地址和端口,这样,写到此 socket 上的数据都被发往此目的地。而且只有目的地址和端口的数据报才会被传给此进程。

UDP 输出

Diagram

如果 UDP 发送时指定了目的地址,则 socket 通过 ip_pcbconnect 临时连接到目的地址,并在函数结束时断开连接。如果 socket 已经是连接套接字,则返回 EISCONN 错误。

临时连接套接字的开销很大,大约占到每个 UDP 传输 1/3 的时间。
udp_usrreq.c
int
udp_output(inp, m, addr, control)
	register struct inpcb *inp;
	register struct mbuf *m;
	struct mbuf *addr, *control;
{
	register struct udpiphdr *ui;
	register int len = m->m_pkthdr.len;
	struct in_addr laddr;
	int s, error = 0;

	if (control)
		m_freem(control);		/* XXX */

	if (addr) {
		laddr = inp->inp_laddr;
		if (inp->inp_faddr.s_addr != INADDR_ANY) {
			error = EISCONN;
			goto release;
		}
		/*
		 * Must block input while temporarily connected.
		 */
		s = splnet();
		error = in_pcbconnect(inp, addr);
		if (error) {
			splx(s);
			goto release;
		}
	} else {
		if (inp->inp_faddr.s_addr == INADDR_ANY) { (1)
			error = ENOTCONN;
			goto release;
		}
	}
	/*
	 * Calculate data length and get a mbuf
	 * for UDP and IP headers.
	 */
	M_PREPEND(m, sizeof(struct udpiphdr), M_DONTWAIT); (2)
	if (m == 0) {
		error = ENOBUFS;
		goto release;
	}

	/*
	 * Fill in mbuf with extended UDP header
	 * and addresses and length put into network format.
	 */
	ui = mtod(m, struct udpiphdr *);
	ui->ui_next = ui->ui_prev = 0;
	ui->ui_x1 = 0;
	ui->ui_pr = IPPROTO_UDP;
	ui->ui_len = htons((u_short)len + sizeof (struct udphdr));
	ui->ui_src = inp->inp_laddr;
	ui->ui_dst = inp->inp_faddr;
	ui->ui_sport = inp->inp_lport;
	ui->ui_dport = inp->inp_fport;
	ui->ui_ulen = ui->ui_len;

	/*
	 * Stuff checksum and output datagram.
	 */
	ui->ui_sum = 0;
	if (udpcksum) {
	    if ((ui->ui_sum = in_cksum(m, sizeof (struct udpiphdr) + len)) == 0)
		ui->ui_sum = 0xffff;
	}
	((struct ip *)ui)->ip_len = sizeof (struct udpiphdr) + len;
	((struct ip *)ui)->ip_ttl = inp->inp_ip.ip_ttl;	/* XXX */
	((struct ip *)ui)->ip_tos = inp->inp_ip.ip_tos;	/* XXX */
	udpstat.udps_opackets++;
	error = ip_output(m, inp->inp_options, &inp->inp_route,
	    inp->inp_socket->so_options & (SO_DONTROUTE | SO_BROADCAST),
	    inp->inp_moptions);

	if (addr) {
		in_pcbdisconnect(inp);
		inp->inp_laddr = laddr;
		splx(s);
	}
	return (error);

release:
	m_freem(m);
	return (error);
}
1如果没有指定目的地址,且套接字不是连接套接字。返回 ENOCONN。
2为 IP 和 UDP 首部分配空间。需要指定标志位 M_DONTWAIT,否则如果 socket 是临时连接的,则 IP 处理会被阻塞。所以 M_PREPEND 也会被阻塞。

连接的 socket 和多目的主机

如果客户端是连接套接字,而服务器不是。在客户端请求地址和服务器本身的 IP 不一致时会导致客户端忽略来自服务器的响应,并发出 ICMP 目的不可达的错误。这个错误的根本原因是服务器使用自己的 IP 地址填充数据包而不是使用客户端的目的地址填充。

如果 TCP 没有明确绑定一个 IP 地址,那么就会把客户端的目的地址当作自己的源地址。

Last moify: 2022-12-04 15:11:33
Build time:2025-07-18 09:41:42
Powered By asphinx