【Linux 內核網絡協議棧源碼剖析】sendto 函數剖析

原文點擊打開鏈接



前面介紹的函數基本上都是TCP協議的,如listen,connect,accept 等函數,這都是爲可靠傳輸協議TCP定製的。對於另一個不可靠udp協議(通信系統其可靠性交由上層應用層負責),則主要由兩個函數完成,sendto 和 recvfrom 函數。這裏先介紹 sendto 函數。

說明:sendto 和 recvfrom 函數不限於udp協議,這裏只是udp協議當中是採用這兩個函數實現的,所以就放在udp協議中介紹。

對於 udp 協議的介紹和編程實現請參考下文:UDP 客戶/服務器簡單 Socket 程序

簡要介紹下UDP數據報格式,相比TCP數據報格式,實在是簡潔不少。

                                  

上面的各個字段含義一目瞭然(上面是16是表示該字段佔16bit,udp頭部佔8字節),其中長度指的是此 UDP 數據報的長度(包括 UDP 數據報頭部和 “數據” 部分)。

一、應用層——sendto 函數

#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
	           const struct sockaddr *to, socklen_t *addrlen);
//若成功則返回寫的字節數,出錯則返回-1
/*參數解析
前面三個參數分別表示:套接字描述符,指向寫出緩衝區的指針和寫字節數。
to:指向一個含有數據報接收者的協議地址(如IP地址和端口號)的套接字地址結構,其大小由addrlen參數指定
*/

該函數的作用是:向指定端口發送給定地址中的指定大小數據(如客戶端sockfd,向 to 指定的遠端套接字發送buff 緩衝區內nbytes 個字節數據)

二、BSD Socket層——sock_sendto 函數

/*
 *	Send a datagram to a given address. We move the address into kernel
 *	space and check the user space data area is readable before invoking
 *	the protocol.
 */
//發送數據給指定的遠端地址,主要用於UDP協議
//前面三個參數分別表示套接口描述字、指向緩衝區的指針和讀寫字節數
//addr指向一個含有數據包接收者的協議地址(含ip地址和端口號)的套接口地址結構
//其大小由addr_len參數指定
//該函數的作用就是向指定地址的遠端發送數據包:將buff緩衝區中len大小的數據發送給addr指定的遠端套接字
static int sock_sendto(int fd, void * buff, int len, unsigned flags,
	   struct sockaddr *addr, int addr_len)
{
	struct socket *sock;
	struct file *file;
	char address[MAX_SOCK_ADDR];
	int err;
	//參數有效性檢查
	if (fd < 0 || fd >= NR_OPEN || ((file = current->files->fd[fd]) == NULL))
		return(-EBADF);
	//找到給定文件描述符對應的socket結構
	if (!(sock = sockfd_lookup(fd, NULL)))
		return(-ENOTSOCK);

	if(len<0)
		return -EINVAL;
	//檢查權限,buff中len個字節區域是否可讀
	err=verify_area(VERIFY_READ,buff,len);
	if(err)
	  	return err;
  	//從addr拷貝addr_len大小的數據到address
	if((err=move_addr_to_kernel(addr,addr_len,address))<0)
	  	return err;
    //調用下層函數sendto,inet域爲inet_sendto函數
	return(sock->ops->sendto(sock, buff, len, (file->f_flags & O_NONBLOCK),
		flags, (struct sockaddr *)address, addr_len));
}
三、INET Socket層——inet_sendto 函數

 //INET socket層
static int inet_sendto(struct socket *sock, void *ubuf, int size, int noblock, 
	    unsigned flags, struct sockaddr *sin, int addr_len)
{
    //得到socket對應的sock結構
	struct sock *sk = (struct sock *) sock->data;
	//判斷該套接字的有效性,是否處於關閉狀態(半關閉)
	if (sk->shutdown & SEND_SHUTDOWN) 
	{
		send_sig(SIGPIPE, current, 1);
		return(-EPIPE);
	}
	if (sk->prot->sendto == NULL) 
		return(-EOPNOTSUPP);
	if(sk->err)
		return inet_error(sk);
	/* We may need to bind the socket. */
	//自動綁定一個本地端口號
	if(inet_autobind(sk)!=0)
		return -EAGAIN;
	//調用下層傳輸層函數udp_sendto函數
	return(sk->prot->sendto(sk, (unsigned char *) ubuf, size, noblock, flags, 
			   (struct sockaddr_in *)sin, addr_len));
}

四、傳輸層

udp_sento 函數

static int udp_sendto(struct sock *sk, unsigned char *from, int len, int noblock,
	   unsigned flags, struct sockaddr_in *usin, int addr_len)
{
	struct sockaddr_in sin;
	int tmp;

	/* 
	 *	Check the flags. We support no flags for UDP sending
	 */
	 //udp除了MSG_DONTROUTE外,不支持任何其他標誌位
	if (flags&~MSG_DONTROUTE) 
	  	return(-EINVAL);
	/*
	 *	Get and verify the address. 
	 */
	//對遠端地址的合法性檢查,由於不涉及網絡數據傳送,所以無法驗證這個地址存在性
	
	if (usin) 
	{
	//如果明確指定遠端地址,就直接檢查該地址的有效性
		if (addr_len < sizeof(sin)) //大小
			return(-EINVAL);
		memcpy(&sin,usin,sizeof(sin));
		if (sin.sin_family && sin.sin_family != AF_INET) //本地地址有效性
			return(-EINVAL);
		if (sin.sin_port == 0) //端口號有效性
			return(-EINVAL);
	} 
	else 
	{
	//如果沒有明確指定遠端地址,則檢查之前是否調用了connect函數進行了地址綁定
		if (sk->state != TCP_ESTABLISHED) 
			return(-EINVAL);
		//如果進行了綁定,則將遠端地址設置爲這個綁定的地址
		sin.sin_family = AF_INET;
		sin.sin_port = sk->dummy_th.dest;
		sin.sin_addr.s_addr = sk->daddr;
  	}
  
  	/*
  	 *	BSD socket semantics. You must set SO_BROADCAST to permit
  	 *	broadcasting of data.
  	 */
  	//處理尚未指定本地地址的情況
  	if(sin.sin_addr.s_addr==INADDR_ANY)
  		sin.sin_addr.s_addr=ip_my_addr();

	//處理廣播的情況
  	if(!sk->broadcast && ip_chk_addr(sin.sin_addr.s_addr)==IS_BROADCAST)
	    	return -EACCES;			/* Must turn broadcast on first */

	sk->inuse = 1;//加鎖

	/* Send the packet. */
	//轉調用udp_send函數
	tmp = udp_send(sk, &sin, from, len, flags);

	/* The datagram has been sent off.  Release the socket. */
	//數據包以發送,釋放該套接字,前面介紹到這個函數的兩個功能
	//取決於sk_dead字段是否設置
	release_sock(sk);
	return(tmp);
}

udp_send 函數
 //根據被調用出清楚參數情況
static int udp_send(struct sock *sk, struct sockaddr_in *sin,
	 unsigned char *from, int len, int rt)
{
	struct sk_buff *skb;
	struct device *dev;
	struct udphdr *uh;
	unsigned char *buff;
	unsigned long saddr;
	int size, tmp;
	int ttl;
  
	/* 
	 *	Allocate an sk_buff copy of the packet.
	 */
	//計算所需要分配的封裝數據的緩衝區大小 
	size = sk->prot->max_header + len;
	//分配指定大小的sk_buff 結構用於封裝數據
	skb = sock_alloc_send_skb(sk, size, 0, &tmp);


	if (skb == NULL) 
		return tmp;

	skb->sk       = NULL;	/* to avoid changing sk->saddr */
	skb->free     = 1;//發送完後數據包立即釋放,udp不提供超時重傳
	skb->localroute = sk->localroute|(rt&MSG_DONTROUTE);//指定路由類型

	/*
	 *	Now build the IP and MAC header. 
	 */
	 
	buff = skb->data;//udp首部和有效負載
	saddr = sk->saddr;//本地地址
	dev = NULL;
	ttl = sk->ip_ttl;
#ifdef CONFIG_IP_MULTICAST
	//如果目的地址是多播,則設置TTL值爲1,表示侷限於本地網絡,不可跨越路由器

	if (MULTICAST(sin->sin_addr.s_addr))
		ttl = sk->ip_mc_ttl;
#endif
	//創建MAC首部和IP首部
	tmp = sk->prot->build_header(skb, saddr, sin->sin_addr.s_addr,
			&dev, IPPROTO_UDP, sk->opt, skb->mem_len,sk->ip_tos,ttl);

	skb->sk=sk;//關聯	/* So memory is freed correctly */
	
	/*
	 *	Unable to put a header on the packet.
	 */
	 		    
	if (tmp < 0 ) //創建失敗
	{
		sk->prot->wfree(sk, skb->mem_addr, skb->mem_len);
		return(tmp);
  	}
  	
	buff += tmp;//定位到udp首部位置
	saddr = skb->saddr; /*dev->pa_addr;*/
	//數據報sk_buff中掛載的數據部分長度:下面註釋,len是有效數據負載長度
	skb->len = tmp + sizeof(struct udphdr) + len;	/* len + UDP + IP + MAC */
	skb->dev = dev;//網絡接口設備
	
	/*
	 *	Fill in the UDP header. 
	 */
	//udp首部字段的初始化
	uh = (struct udphdr *) buff;
	uh->len = htons(len + sizeof(struct udphdr));//長度字段
	uh->source = sk->dummy_th.source;//源端端口,sk中tcp首部字段
	uh->dest = sin->sin_port;//目的端口
	buff = (unsigned char *) (uh + 1);//定位到數據部分
	//MAC header | IP Header | UDP Header | Data
	//uh本身已經指向了udp首地址,uh+1,表示後移一個udp首部大小位置,定位到了數據負載

	/*
	 *	Copy the user data. 
	 */
	//從from拷貝len大小的數據到buff,即把應用層中待發送的緩衝區的數據拷貝到數據包的數據負載中
	//然後通過數據包整體打包發送出去。
	//就好比貨物搭上了貨輪開往目的地,爲啥不是火車呢,因爲火車線路已經固定好了,只能這麼走。
	memcpy_fromfs(buff, from, len);

  	/*
  	 *	Set up the UDP checksum. 
  	 */
  	//同tcp,這裏進行udp校驗和檢查 
	udp_send_check(uh, saddr, sin->sin_addr.s_addr, skb->len - tmp, sk);

	/* 
	 *	Send the datagram to the interface. 
	 */
	 
	udp_statistics.UdpOutDatagrams++;
	//調用ip_queue_xmit函數將數據包發往網絡層模塊處理。以下處理就和TCP協議一樣了,二者的差異只在於傳輸層
	//該函數以及更下層數據傳送前面已經介紹,
	sk->prot->queue_xmit(sk, dev, skb, 1);
	return(len);
}

關於ip_queue_xmit 函數的介紹以及更下層的數據傳送,參見博文:【Linux 內核網絡協議棧源碼剖析】數據包發送

可以看出,udp是一種無連接傳輸層協議,不像tcp那樣需要服務器監聽,也不必等待客戶端與服務器建立連接後才能通信,效率優於tcp協議,但udp則不能保證數據傳輸的可靠性。
udp 的數據傳輸,實現並不像tcp那樣要建立一條數據傳輸通道,而是直接創建套接字後,直接傳送數據到給定的遠端(提供遠端地址),數據傳送過程無超時重傳和序列號校驗工作,適用於數據傳輸的連續性比數據的完整性更重要的場合,允許數據在傳輸過程中有部分丟失,如IP電話、流媒體通信等。




發佈了22 篇原創文章 · 獲贊 4 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章