【Linux 核心網路協議棧原始碼剖析】bind 函式剖析
阿新 • • 發佈:2019-02-10
socket 函式並沒有為套接字繫結本地地址和埠號,對於伺服器端則必須顯性繫結地址和埠號。bind 函式主要是伺服器端使用,把一個本地協議地址賦予套接字。
1、應用層——bind 函式
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
/*sockfd是由socket函式返回的套介面描述字,第二個引數是一個指向特定於協議的地址結構的指標,第三個引數是該地址結構的長度*/
bind 函式的功能則是將socket 套接字繫結指定的地址。
2、BSD Socket 層——sock_bind 函式
/* * Bind a name to a socket. Nothing much to do here since it's * the protocol's responsibility to handle the local address. * * We move the socket address to kernel space before we call * the protocol layer (having also checked the address is ok). */ //bind函式對應的BSD層函式,用於繫結一個本地地址,伺服器端 //umyaddr表示需要繫結的地址結構,addrlen表示改地址結構的長度 //這裡的fd,即為套接字描述符 static int sock_bind(int fd, struct sockaddr *umyaddr, int addrlen) { struct socket *sock; int i; char address[MAX_SOCK_ADDR]; int err; //套接字引數有效性檢查 if (fd < 0 || fd >= NR_OPEN || current->files->fd[fd] == NULL) return(-EBADF); //獲取fd對應的socket結構 if (!(sock = sockfd_lookup(fd, NULL))) return(-ENOTSOCK); //將地址從使用者緩衝區複製到核心緩衝區,umyaddr->address if((err=move_addr_to_kernel(umyaddr,addrlen,address))<0) return err; //轉呼叫bind指向的函式,下層函式(inet_bind) if ((i = sock->ops->bind(sock, (struct sockaddr *)address, addrlen)) < 0) { return(i); } return(0); }
sock_bind 函式主要就是將使用者緩衝區的地址結構複製到核心緩衝區,然後轉呼叫下一層的bind函式。
該函式內部的一個使用者空間與核心資料空間資料拷貝的函式
3、INET Socket 層——inet_bind 函式//從uaddr拷貝ulen大小的資料到kaddr,實現地址使用者空間到核心地址空間的資料拷貝 static int move_addr_to_kernel(void *uaddr, int ulen, void *kaddr) { int err; if(ulen<0||ulen>MAX_SOCK_ADDR) return -EINVAL; if(ulen==0) return 0; //檢查使用者空間的指標所指的指定大小儲存塊是否可讀 if((err=verify_area(VERIFY_READ,uaddr,ulen))<0) return err; memcpy_fromfs(kaddr,uaddr,ulen);//實質是memcpy函式 return 0; }
/* this needs to be changed to disallow
the rebinding of sockets. What error
should it return? */
//完成本地地址繫結,本地地址繫結包括IP地址和埠號兩個部分
static int inet_bind(struct socket *sock, struct sockaddr *uaddr,int addr_len)
{
struct sockaddr_in *addr=(struct sockaddr_in *)uaddr;
struct sock *sk=(struct sock *)sock->data, *sk2;
unsigned short snum = 0 /* Stoopid compiler.. this IS ok */;
int chk_addr_ret;
/* check this error. */
//在進行地址繫結時,該套接字應該處於關閉狀態
if (sk->state != TCP_CLOSE)
return(-EIO);
//地址長度欄位校驗
if(addr_len<sizeof(struct sockaddr_in))
return -EINVAL;
//非原始套接字型別,繫結前,沒有埠號,則繫結埠號
if(sock->type != SOCK_RAW)
{
if (sk->num != 0)//從inet_create函式可以看出,非原始套接字型別,埠號是初始化為0的
return(-EINVAL);
snum = ntohs(addr->sin_port);//將地址結構中的埠號轉為主機位元組順序
/*
* We can't just leave the socket bound wherever it is, it might
* be bound to a privileged port. However, since there seems to
* be a bug here, we will leave it if the port is not privileged.
*/
//如果埠號為0,則自動分配一個
if (snum == 0)
{
snum = get_new_socknum(sk->prot, 0);//得到一個新的埠號
}
//埠號有效性檢驗,1024以上,超級使用者許可權
if (snum < PROT_SOCK && !suser())
return(-EACCES);
}
//下面則進行ip地址繫結
//檢查地址是否是一個本地介面地址
chk_addr_ret = ip_chk_addr(addr->sin_addr.s_addr);
//如果指定的地址不是本地地址,並且也不是一個多播地址,則錯誤返回
if (addr->sin_addr.s_addr != 0 && chk_addr_ret != IS_MYADDR && chk_addr_ret != IS_MULTICAST)
return(-EADDRNOTAVAIL); /* Source address MUST be ours! */
//如果沒有指定地址,則系統自動分配一個本地地址
if (chk_addr_ret || addr->sin_addr.s_addr == 0)
sk->saddr = addr->sin_addr.s_addr;//本地地址繫結
if(sock->type != SOCK_RAW)
{
/* Make sure we are allowed to bind here. */
cli();
//for迴圈主要是檢查檢查有無衝突的埠號以及本地地址,有衝突,但不允許地址複用,肯定錯誤退出
//成功跳出for迴圈時,已經定位到了雜湊表sock_array指定索引的連結串列的末端
for(sk2 = sk->prot->sock_array[snum & (SOCK_ARRAY_SIZE -1)];
sk2 != NULL; sk2 = sk2->next)
{
/* should be below! */
if (sk2->num != snum) //沒有重複,繼續搜尋下一個
continue;//除非有重複,否則後面的程式碼將不會被執行
if (!sk->reuse)//埠號重複,如果沒有設定地址複用標誌,退出
{
sti();
return(-EADDRINUSE);
}
if (sk2->num != snum)
continue; /* more than one */
if (sk2->saddr != sk->saddr) //地址和埠一個意思
continue; /* socket per slot ! -FB */
//如果狀態是LISTEN表明該套接字是一個服務端,服務端不可使用地址複用選項
if (!sk2->reuse || sk2->state==TCP_LISTEN)
{
sti();
return(-EADDRINUSE);
}
}
sti();
remove_sock(sk);//將sk sock結構從其之前的表中刪除,inet_create中 put_sock,這裡remove_sock
put_sock(snum, sk);//然後根據新分配的埠號插入到新的表中。可以得知系統在維護許多這樣的表
sk->dummy_th.source = ntohs(sk->num);//tcp首部,源埠號繫結
sk->daddr = 0;//sock結構所代表套接字的遠端地址
sk->dummy_th.dest = 0;//tcp首部,目的埠號
}
return(0);
}
inet_bind 函式即為bind函式的最底層實現,該函式實現了本地地址和埠號的繫結,其中還針對上層傳過來的地址結構進行校驗,檢查是否衝突可用。需要清楚的是 sock_array陣列,這其實是一個鏈式雜湊表,裡面儲存的就是各個埠號的sock結構,陣列大小小於埠號,所以採用鏈式雜湊表儲存。
bind 函式的各層分工很明顯,主要就是inet_bind函數了,在註釋裡說的很明確了,bind 是繫結本地地址,它不負責對端地址,一般用於伺服器端,客戶端是系統指定的。
一般是伺服器端呼叫這個函式,到了這一步,伺服器端套接字綁定了本地地址資訊(ip地址和埠號),但是不知道對端(客戶端)的地址資訊。