listen 函數僅供服務器端調用,把一個未連接的套接字轉換爲一個被動套接字,指示內核應接受指向該套接字的連接請求。
1、應用層——listen 函數
#include <sys/socket.h>
int listen(int sockfd, int backlog);
/*sockfd是bind之後的套接口描述字,第二個參數規定了內核應該爲相應套接口排隊的最大連接個數*/
2、BSD Socket 層——sock_listen 函數
/*
* Perform a listen. Basically, we allow the protocol to do anything
* necessary for a listen, and if that works, we mark the socket as
* ready for listening.
*/
//服務器端監聽客戶端的連接請求
//fd表示bind後的套接字文件描述符,backlog表示排隊的最大連接個數
//listen函數把一個未連接的套接字轉換爲一個被動套接字,
//指示內核應接受該套接字的連接請求
static int sock_listen(int fd, int backlog)
{
struct socket *sock;
if (fd < 0 || fd >= NR_OPEN || current->files->fd[fd] == NULL)
return(-EBADF);
//給定文件描述符返回socket結構以及file結構指針,這裏file參數爲NULL,則表明對這個不感興趣
if (!(sock = sockfd_lookup(fd, NULL)))
return(-ENOTSOCK);
//前提是沒有建立連接,該套接字已經是連接狀態了,自然不需要
if (sock->state != SS_UNCONNECTED)
{
return(-EINVAL);
}
//調用底層實現函數(inet_listen)
if (sock->ops && sock->ops->listen)
sock->ops->listen(sock, backlog);
sock->flags |= SO_ACCEPTCON;//設置標識字段,表示正在監聽
return(0);
}
3、INET Socket 層——inet_listen 函數
/*
* Move a socket into listening state.
*/
//sock_listen的下層調用函數
//這個函數主要是對sock結構中state字段的設置。listen函數到這層完成處理
static int inet_listen(struct socket *sock, int backlog)
{
//獲取sock數據結構
struct sock *sk = (struct sock *) sock->data;
//如果sock的端口號爲0(未綁定任何端口號),則自動綁定一個本地端口號(新的未使用的最小的端口號)
//如果事先已經綁定了一個端口號,那麼這個代碼將不會執行
if(inet_autobind(sk)!=0)
return -EAGAIN;
/* We might as well re use these. */
/*
* note that the backlog is "unsigned char", so truncate it
* somewhere. We might as well truncate it to what everybody
* else does..
*/
//等待的最大數.內核限制最大連接數是5
if ((unsigned) backlog > 5)
backlog = 5;
sk->max_ack_backlog = backlog;//緩存的最大未應答數據包個數
if (sk->state != TCP_LISTEN)//如果不是listen狀態,則置位listen狀態
{
sk->ack_backlog = 0;//緩存的未應答數據包個數清0
sk->state = TCP_LISTEN;
}
return(0);
}
可以看出 inet_listen 函數主要就是設置 sock 的狀態爲TCP_LISTEN。tcp的三次握手以及四次揮手就是基於這樣的一些狀態。
函數內部有調用 inet_autobind 函數,該函數是爲沒有綁定端口的sock結構自動綁定一個端口號(系統可用的最小端口號)
/*
* Automatically bind an unbound socket.
*/
//自動綁定一個本地端口號,一般用於客戶端,實際應用層編程時,對於客戶端我們並沒有
//特別去綁定某個端口號,而是由系統自動綁定
static int inet_autobind(struct sock *sk)
{
/* We may need to bind the socket. */
if (sk->num == 0)
{
//獲取一個新的未使用的端口號
sk->num = get_new_socknum(sk->prot, 0);
if (sk->num == 0)
return(-EAGAIN);
put_sock(sk->num, sk);//加入到sock_array哈希表中
//將一個無符號短整型數從網絡字節順序轉換爲主機字節順序。大小端問題
sk->dummy_th.source = ntohs(sk->num);//TCP首部中的source字段表示本地端口號
}
return 0;
}
listen 函數把一個未連接的套接字轉換爲一個被動套接字,指示內核應接受指向該套接字的連接請求,其內部實現歸根到底就是設置 sock 結構的狀態,設置其爲 TCP_LISTEN。功能很簡單。這個函數也是服務器端調用,其套接字的地址信息狀態和bind函數執行之後是一樣的,只綁定了本地地址信息,不知道對端的地址信息。