溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何深入理解TCP/IP協(xié)議的socket實現(xiàn)

發(fā)布時間:2021-11-23 22:08:15 來源:億速云 閱讀:194 作者:柒染 欄目:大數(shù)據(jù)

這篇文章給大家介紹如何深入理解TCP/IP協(xié)議的socket實現(xiàn),內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

socket大家都知道是用于網(wǎng)絡(luò)通信的,也知道他是ip和端口的組合。但是很多同學(xué)可能不是很清楚socket的原理和實現(xiàn)。下面我們深入理解一下socket到底是什么。
    我們回憶一下socket編程的步驟,不管是客戶端還是服務(wù)端,第一個調(diào)的函數(shù)都是socket。我們就從這個函數(shù)的實現(xiàn)開始,看看一個socket到底是什么。

// 新建一個socket結(jié)構(gòu)體,并且創(chuàng)建一個下層的sock結(jié)構(gòu)體,互相關(guān)聯(lián)
static int sock_socket(int family, int type, int protocol)
{
    int i, fd;
    struct socket *sock;
    struct proto_ops *ops;

    // 找到對應(yīng)的協(xié)議族,比如unix域、ipv4
    for (i = 0; i < NPROTO; ++i) 
    {   // 從props數(shù)組中找到family協(xié)議對應(yīng)的操作函數(shù)集,props由系統(tǒng)初始化時sock_register進(jìn)行操作
        if (pops[i] == NULL) continue;
        if (pops[i]->family == family) 
            break;
    }

    if (i == NPROTO) 
    {
          return -EINVAL;
    }
    // 函數(shù)集
    ops = pops[i];

    // 檢查一下類型
    if ((type != SOCK_STREAM && type != SOCK_DGRAM &&
        type != SOCK_SEQPACKET && type != SOCK_RAW &&
        type != SOCK_PACKET) || protocol < 0)
            return(-EINVAL);

    // 分配一個新的socket結(jié)構(gòu)體
    if (!(sock = sock_alloc())) 
    {
        ...
    }
    // 設(shè)置類型和操作函數(shù)集
    sock->type = type;
    sock->ops = ops;
    if ((i = sock->ops->create(sock, protocol)) < 0) 
    {
        sock_release(sock);
        return(i);
    }
    // 返回一個新的文件描述符
    if ((fd = get_fd(SOCK_INODE(sock))) < 0) 
    {
        sock_release(sock);
        return(-EINVAL);
    }

    return(fd);
}
 

我們從上到下,逐步分析這個過程。
1 根據(jù)傳的協(xié)議類型,找到對應(yīng)的函數(shù)集,因為不同的協(xié)議族他的底層操作是不一樣的。
2 分配一個socket結(jié)構(gòu)體。定義如下。我們大概了解一下字段就行。

struct socket {
  short            type;       /* SOCK_STREAM, ...     */
  socket_state        state;
  long            flags;
  struct proto_ops    *ops;   
  // 這個字段要記一下    
  void            *data;      
  struct socket        *conn;      
  struct socket        *iconn;     
  struct socket        *next;
  struct wait_queue    **wait;     
  struct inode        *inode;
  struct fasync_struct  *fasync_list;    
};

struct socket *sock_alloc(void)
{
    struct inode * inode;
    struct socket * sock;
    // 獲取一個可用的inode節(jié)點
    inode = get_empty_inode();
    if (!inode)
        return NULL;
    // 初始化某些字段
    inode->i_mode = S_IFSOCK;
    inode->i_sock = 1;// socket文件
    inode->i_uid = current->uid;
    inode->i_gid = current->gid;
    // 指向inode的socket結(jié)構(gòu)體,初始化inode結(jié)構(gòu)體的socket結(jié)構(gòu)體
    sock = &inode->u.socket_i;
    sock->state = SS_UNCONNECTED;
    sock->flags = 0;
    sock->ops = NULL;
    sock->data = NULL;
    sock->conn = NULL;
    sock->iconn = NULL;
    sock->next = NULL;
    sock->wait = &inode->i_wait;
    // 互相引用
    sock->inode = inode;        /* "backlink": we could use pointer arithmetic instead */
    sock->fasync_list = NULL;
    // socket數(shù)加一
    sockets_in_use++;
    // 返回新的socket結(jié)構(gòu)體,他掛載在inode中
    return sock;
}
 

sock_alloc首先分配了一個inode,inode節(jié)點里有一個socket結(jié)構(gòu)體,然后初始化socket結(jié)構(gòu)體的一些字段,并把他的地址返回。

3 這時候我們拿到一個socket結(jié)構(gòu)體。接著調(diào)create函數(shù)(省略了部分代碼)。

// 創(chuàng)建一個sock結(jié)構(gòu)體,和socket結(jié)構(gòu)體互相關(guān)聯(lián)
static int inet_create(struct socket *sock, int protocol)
{
    struct sock *sk;
    struct proto *prot;
    int err;
    // 分配一個sock結(jié)構(gòu)體
    sk = (struct sock *) kmalloc(sizeof(*sk), GFP_KERNEL);
    switch(sock->type) 
    {
        case SOCK_STREAM:
            protocol = IPPROTO_TCP;
            // 函數(shù)集
            prot = &tcp_prot;
            break;

        case SOCK_DGRAM:
            protocol = IPPROTO_UDP;
            prot=&udp_prot;
            break;

    }
    // sock結(jié)構(gòu)體的socket字段指向上層的socket結(jié)構(gòu)體
    sk->socket = sock;
    // 省略一堆對sock結(jié)構(gòu)體的初始化代碼
}
 

我們發(fā)現(xiàn)創(chuàng)建一個socket的時候,申請了一個socket結(jié)構(gòu)體,同時也申請了一個sock結(jié)構(gòu)體。為什么需要兩個結(jié)構(gòu)體,并且這兩個結(jié)構(gòu)體關(guān)聯(lián)在一起呢?這要說到網(wǎng)絡(luò)協(xié)議的復(fù)雜性,而這個設(shè)計就是linux對這個復(fù)雜性的解決方案。我們回頭看看socket函數(shù)的參數(shù)。

socket(int family, int type, int protocol)
 

family是協(xié)議簇,比如unix域、ipv4、ipv6,type是在第一個參數(shù)的基礎(chǔ)上的子分類。比如ipv4下有tcp、udp、raw、packet。protocol對tcp、udp沒用,對raw、packet的話是標(biāo)記上層協(xié)議類型。這好比一棵樹一樣,從根節(jié)點開始,有很多分支。socket結(jié)構(gòu)體是整個網(wǎng)絡(luò)協(xié)議實現(xiàn)的最上層結(jié)構(gòu),是第一層抽象。根據(jù)協(xié)議簇的不同,有不同的實現(xiàn)函數(shù),在同一協(xié)議簇下,也有不同的子分類,比如ipv4下有tcp、udp等。不同子類具體的邏輯也不一樣。即數(shù)據(jù)結(jié)構(gòu)和算法都不一樣。所以socket結(jié)構(gòu)體有一個data字段,他是自定義的,對于ipv4的實現(xiàn),他是指向一個sock結(jié)構(gòu)體,對于unix域的實現(xiàn),unix_proto_data結(jié)構(gòu)體。這就解決了不同協(xié)議簇(family)不同實現(xiàn)的問題。那對于同一協(xié)議簇下的不同子類型,又如何實現(xiàn)呢?比如ipv4下的tcp、udp。linux給出的方案是在sock結(jié)構(gòu)體中定義一個字段,根據(jù)子類型type的值,指向不同的底層協(xié)議函數(shù)集。

如何深入理解TCP/IP協(xié)議的socket實現(xiàn)  
在這里插入圖片描述

在申請完sock結(jié)構(gòu)體并且和socket結(jié)構(gòu)體互相關(guān)聯(lián)后。這時候我們拿到了一個inode,一個socket結(jié)構(gòu)體,一個sock結(jié)構(gòu)體。然后根據(jù)inode拿到一個file和fd文件描述符。最后返回fd給用戶。內(nèi)容結(jié)構(gòu)圖如下。  
 
如何深入理解TCP/IP協(xié)議的socket實現(xiàn)  
在這里插入圖片描述

這就是socket函數(shù)返回后的內(nèi)存結(jié)構(gòu)體。后續(xù)我們調(diào)用bind,listen等等函數(shù),傳入fd,系統(tǒng)就會根據(jù)上面圖的指向,一直找到tcp函數(shù)集,執(zhí)行對應(yīng)的函數(shù),對于udp也是一樣,不同是tcp函數(shù)集變成udp函數(shù)集。

關(guān)于如何深入理解TCP/IP協(xié)議的socket實現(xiàn)就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI