Android socket源碼解析(一)socket的初始化原理

前言

前四篇文章講述了Okhttp的核心原理费变,得知Okhttp是基于Socket開發(fā)的,而不是基于HttpUrlConnection開發(fā)的店煞。

其中對于客戶端來說获讳,核心有如下四個(gè)步驟:

  • 1.dns lookup 把資源地址轉(zhuǎn)化為ip地址
  • 2.socket.connect 通過socket把客戶端和服務(wù)端聯(lián)系起來
  • 3.socket.starthandshake
  • 4.socket.handshake

第五篇介紹了DNS的查詢流程。本文開始聊聊Socket中的核心原理愤诱。而socket就是所有網(wǎng)絡(luò)編程的核心云头,就算是DNS的查詢實(shí)現(xiàn)也是通過socket為基礎(chǔ)實(shí)現(xiàn)。

注意接下來涉及的內(nèi)核源碼是3.1.8淫半。

正文

Socket是什么溃槐?在計(jì)算機(jī)術(shù)語中都叫套接字。這種翻譯比較拗口且難以理解科吭。我在網(wǎng)上看到一個(gè)比較有趣的解釋昏滴,socket的在日常用法的語義為插槽。如果把語義延展開來砌溺,可以看成是兩個(gè)服務(wù)器之間的用于通信的插槽影涉,一旦通過connect鏈接起來就把兩個(gè)服務(wù)器之間的通信通道通過socket插槽鏈接起來了。

1.客戶端使用

來看看Java中的用法(這里我們只關(guān)注客戶端tcp傳輸?shù)倪壿嫞┕娣ィ话闶褂萌缦拢?/p>

  • 1.聲明一個(gè)Socket對象
Socket socket = new Socket(host, port);

也可以直接想Okhttp中一樣直接使用默認(rèn)的構(gòu)造函數(shù)

Socket socket = new Socket();
  • 2.如果之前沒有設(shè)置過地址蟹倾,那么此時(shí)就需要connect 鏈接對端的地址
socket.connect(address, connectTimeout)
    1. 獲取到socket的讀寫流,往socket中寫入數(shù)據(jù)猖闪,或者讀取數(shù)據(jù)
socket.getOutputStream().write(message.getBytes("UTF-8"));
InputStream inputStream = socket.getInputStream();
len = inputStream.read(bytes)

實(shí)際上客戶端的用法十分簡單鲜棠。

對于服務(wù)端又是怎么使用的呢?

2.服務(wù)端使用

  • 1.構(gòu)建一個(gè)ServerSocket 對象后培慌,調(diào)用accept方法生成一個(gè)Socket對象

    ServerSocket server = new ServerSocket(port);
   
    Socket socket = server.accept();
    1. 通過獲取讀取流和寫入流進(jìn)行對客戶端socket的發(fā)送的數(shù)據(jù)的讀取以及應(yīng)答豁陆。

    InputStream inputStream = socket.getInputStream();
    byte[] bytes = new byte[1024];
    int len;
    StringBuilder sb = new StringBuilder();

    while ((len = inputStream.read(bytes)) != -1) {
      sb.append(new String(bytes, 0, len, "UTF-8"));
    }

    OutputStream outputStream = socket.getOutputStream();
    outputStream.write("...".getBytes("UTF-8"));

當(dāng)理解了整個(gè)使用后,我們依次來看看socket在底層中都做了什么吵护?

正文

我們先從客戶端Socket實(shí)例化盒音,到服務(wù)端Socket的初始化并監(jiān)聽開始考察表鳍。

1.客戶端Socket實(shí)例化

    public Socket() {
        setImpl();
    }

    void setImpl() {
        if (factory != null) {
            impl = factory.createSocketImpl();
            checkOldImpl();
        } else {
            // SocketImpl!
            impl = new SocksSocketImpl();
        }
        if (impl != null)
            impl.setSocket(this);
    }

Socket所有的事情都會交給這個(gè)SocksSocketImpl完成。

2.SocksSocketImpl 構(gòu)造函數(shù)與初始化

先來看看SocksSocketImpl 的UML繼承結(jié)構(gòu)

SocksSocketImpl.png

這個(gè)結(jié)構(gòu)圖中祥诽,SocketOptions定義了接口敞亮譬圣,抽象類SocketImpl 定義了connect等核心的抽象方法,PlainSocketImpl則定義了一個(gè)構(gòu)造函數(shù)雄坪,這一個(gè)特殊的構(gòu)造函數(shù)厘熟,這個(gè)構(gòu)造函數(shù)為PlainSocketImpl創(chuàng)建了一個(gè)FileDescriptor文件描述符對象。

SocksSocketImpl無參構(gòu)造函數(shù)并不會做任何事情:


    SocksSocketImpl() {
        // Nothing needed
    }

而setSocket中维哈,調(diào)用的是SocketImpl中方法:

    void setSocket(Socket soc) {
        this.socket = soc;
    }

存儲當(dāng)前的socket對象

3.Socket connect 鏈接到客戶端

上文中绳姨,當(dāng)Okhttp客戶端需要鏈接到某個(gè)地址,就會嘗試著從域名中解析出地址和端口阔挠,然后通過這兩個(gè)數(shù)據(jù)生成InetSocketAddress對象飘庄。

  open fun connectSocket(socket: Socket, address: InetSocketAddress, connectTimeout: Int) {
    socket.connect(address, connectTimeout)
  }
   public void connect(SocketAddress endpoint, int timeout) throws IOException {


        InetSocketAddress epoint = (InetSocketAddress) endpoint;
        InetAddress addr = epoint.getAddress ();
        int port = epoint.getPort();
        checkAddress(addr, "connect");

        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            if (epoint.isUnresolved())
                security.checkConnect(epoint.getHostName(), port);
            else
                security.checkConnect(addr.getHostAddress(), port);
        }
        if (!created)
            createImpl(true);
        if (!oldImpl)
            impl.connect(epoint, timeout);
        else if (timeout == 0) {
            if (epoint.isUnresolved())
                impl.connect(addr.getHostName(), port);
            else
                impl.connect(addr, port);
        } else
            throw new UnsupportedOperationException("SocketImpl.connect(addr, timeout)");


        connected = true;
        bound = true;
    }
  • 1.首先通過SecurityManager 校驗(yàn)即將鏈接的服務(wù)器地址和端口號是否合,不合法則直接拋出異常

  • 2.如果沒有創(chuàng)建過购撼,則調(diào)用createImpl 創(chuàng)建一個(gè)底層的Socket對象

  • 3.如果不是使用以前的Socket的對象竭宰,也就是oldImpl為false,那么就調(diào)用SocksSocketImplconnect進(jìn)行鏈接份招。

3.1.Socket createImpl 創(chuàng)建一個(gè)底層的Socket對象

     void createImpl(boolean stream) throws SocketException {
        if (impl == null)
            setImpl();
        try {
            impl.create(stream);
            created = true;
        } catch (IOException e) {
            throw new SocketException(e.getMessage());
        }
    }

核心還是SocksSocketImplcreate 方法。

3.2.AbstractPlainSocketImpl create

文件:/libcore/ojluni/src/main/java/java/net/AbstractPlainSocketImpl.java

    protected synchronized void create(boolean stream) throws IOException {
        this.stream = stream;
        if (!stream) {
            ResourceManager.beforeUdpCreate();

            try {
                socketCreate(false);
            } catch (IOException ioe) {
...
            }
        } else {
            socketCreate(true);
        }
        if (socket != null)
            socket.setCreated();
        if (serverSocket != null)
            serverSocket.setCreated();

        if (fd != null && fd.valid()) {
            guard.open("close");
        }
    }

核心是由PlainSocketImpl實(shí)現(xiàn)的抽象方法socketCreate完成的狞甚。

    void socketCreate(boolean isStream) throws IOException {
        fd.setInt$(IoBridge.socket(AF_INET6, isStream ? SOCK_STREAM : SOCK_DGRAM, 0).getInt$());

        if (serverSocket != null) {
            IoUtils.setBlocking(fd, false);
            IoBridge.setSocketOption(fd, SO_REUSEADDR, true);
        }
    }

通過IoBridge.socket創(chuàng)建一個(gè)socket對象后锁摔,并返回這個(gè)對象對應(yīng)的文件描述符具柄設(shè)置到PlainSocketImpl中緩存的文件描述符對象.注意這個(gè)過程寫死了AF_INET6 作為ip地址族傳入。說明必定是可以接受ipv6協(xié)議的地址數(shù)據(jù)哼审。

    public static FileDescriptor socket(int domain, int type, int protocol) throws SocketException {
        FileDescriptor fd;
        try {
            fd = Libcore.os.socket(domain, type, protocol);

            return fd;
        } catch (ErrnoException errnoException) {
            throw errnoException.rethrowAsSocketException();
        }
    }

注意這個(gè)方法是一個(gè)native方法谐腰,我們?nèi)ハ旅嫖募校?br> libcore/luni/src/main/native/libcore_io_Linux.cpp

static jobject Linux_socket(JNIEnv* env, jobject, jint domain, jint type, jint protocol) {
    if (domain == AF_PACKET) {
        protocol = htons(protocol);  // Packet sockets specify the protocol in host byte order.
    }
    int fd = throwIfMinusOne(env, "socket", TEMP_FAILURE_RETRY(socket(domain, type, protocol)));
    return fd != -1 ? jniCreateFileDescriptor(env, fd) : NULL;
}

能看到實(shí)際上就是調(diào)用了系統(tǒng)調(diào)用socket 為套接字創(chuàng)建一個(gè)對應(yīng)的文件描述符后,返回一個(gè)Java的FileDescriptor 對象返回涩盾。

4.Socket系統(tǒng)調(diào)用

文件:/net/socket.c

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
    int retval;
    struct socket *sock;
    int flags;

...

    flags = type & ~SOCK_TYPE_MASK;
    if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
        return -EINVAL;
    type &= SOCK_TYPE_MASK;

    if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
        flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

    retval = sock_create(family, type, protocol, &sock);
    if (retval < 0)
        goto out;

    retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
    if (retval < 0)
        goto out_release;

out:
    /* It may be already another descriptor 8) Not kernel problem. */
    return retval;

out_release:
    sock_release(sock);
    return retval;
}

分為2個(gè)步驟:

  • 1.sock_create 創(chuàng)建一個(gè)socket 結(jié)構(gòu)體
  • 2.sock_map_fd 把結(jié)構(gòu)體和fd具柄關(guān)聯(lián)起來

為了更加清晰的理解socket結(jié)構(gòu)體的內(nèi)容我們看看這個(gè)結(jié)構(gòu)體都包含了什么字段十气?

struct socket {
    socket_state        state;

    kmemcheck_bitfield_begin(type);
    short           type;
    kmemcheck_bitfield_end(type);

    unsigned long       flags;

    struct socket_wq __rcu  *wq;

    struct file     *file;
    struct sock     *sk;
    const struct proto_ops  *ops;
};
  • 1.socket_state 當(dāng)前socket的枚舉狀態(tài)
  • 2.type 當(dāng)前socket對應(yīng)的類型
  • 3.flag 當(dāng)前socket帶上的標(biāo)識為
  • 4.socket中對應(yīng)的等待隊(duì)列
  • 5.file socket對應(yīng)的文件描述符
  • 6.sock 結(jié)構(gòu)體是指socket中更為核心的具體操作函數(shù)以及標(biāo)志位
  • 7.proto_ops 對應(yīng)的協(xié)議操作結(jié)構(gòu)體

4.1.__sock_create 創(chuàng)建socket結(jié)構(gòu)體

int __sock_create(struct net *net, int family, int type, int protocol,
             struct socket **res, int kern)
{
    int err;
    struct socket *sock;
    const struct net_proto_family *pf;
...


    err = security_socket_create(family, type, protocol, kern);
    if (err)
        return err;


    sock = sock_alloc();
...

    sock->type = type;

...

    rcu_read_lock();
    pf = rcu_dereference(net_families[family]);
    err = -EAFNOSUPPORT;
    if (!pf)
        goto out_release;

    if (!try_module_get(pf->owner))
        goto out_release;

    /* Now protected by module ref count */
    rcu_read_unlock();

    err = pf->create(net, sock, protocol, kern);
    if (err < 0)
        goto out_module_put;


    if (!try_module_get(sock->ops->owner))
        goto out_module_busy;

    module_put(pf->owner);
    err = security_socket_post_create(sock, family, type, protocol, kern);
    if (err)
        goto out_sock_release;
    *res = sock;

    return 0;

...
}
  • 1.security_socket_create 通過SELinux進(jìn)行校驗(yàn)。SELinux本質(zhì)上就是在一個(gè)文件中寫好了每一個(gè)進(jìn)程允許做的事情春霍,當(dāng)需要讀取文件數(shù)據(jù)砸西,socket等敏感操作時(shí)候?qū)M(jìn)行一次check≈啡澹可以通過security_register 進(jìn)行注冊芹枷。

    1. 一旦校驗(yàn)通過后,則會調(diào)用sock_alloc創(chuàng)建一個(gè)sock結(jié)構(gòu)體莲趣,該并在結(jié)構(gòu)體記錄當(dāng)前type鸳慈,常見的數(shù)據(jù)類型如下:
enum sock_type {
SOCK_STREAM = 1,
SOCK_DGRAM = 2,
SOCK_RAW = 3,
...
}

SOCK_STREAM 是指面向數(shù)據(jù)流屬于TCP協(xié)議;SOCK_DGRAM面向數(shù)據(jù)報(bào)文屬于UDP協(xié)議喧伞;SOCK_RAW 是指原始ip包

    1. rcu_dereference 進(jìn)行rcu(Read-Copy-update)模式保護(hù)當(dāng)前net_families (地址族)數(shù)組中對應(yīng)引用的指針走芋。rcu實(shí)際上就是我之前說過的一種特殊的線程設(shè)計(jì)绩郎,任意讀取數(shù)據(jù),寫時(shí)候需要進(jìn)行同步的方式翁逞。當(dāng)讀取的情況比較多的時(shí)候肋杖,就可以采用這種方式。 注意net_families 是指當(dāng)前的傳遞下來的domain,也就是這是ipv4還是ipv6族熄攘。
  • 4.調(diào)用net_families中對應(yīng)族的的create方法迈着。

在這里涉及到了幾個(gè)比較核心結(jié)構(gòu)體。

4.1.1.socket 結(jié)構(gòu)體的創(chuàng)建與socket 內(nèi)核模塊的初始化

static struct socket *sock_alloc(void)
{
    struct inode *inode;
    struct socket *sock;

    inode = new_inode_pseudo(sock_mnt->mnt_sb);
    if (!inode)
        return NULL;

    sock = SOCKET_I(inode);

    kmemcheck_annotate_bitfield(sock, type);
    inode->i_ino = get_next_ino();
    inode->i_mode = S_IFSOCK | S_IRWXUGO;
    inode->i_uid = current_fsuid();
    inode->i_gid = current_fsgid();
    inode->i_op = &sockfs_inode_ops;

    this_cpu_add(sockets_in_use, 1);
    return sock;
}
  • 1.new_inode_pseudo 通過虛擬文件系統(tǒng)創(chuàng)建一個(gè)inode對象掩驱。注意這里是通過結(jié)構(gòu)體為名為sock_mntvfsmount 創(chuàng)建一個(gè)inode
    1. SOCKET_I 從inode中創(chuàng)建socket結(jié)構(gòu)體降淮。

4.1.2.socket模塊的初始化以及對應(yīng)vfsmount生成原理

關(guān)于sock_mnt 初始化,在socket內(nèi)核模塊加載時(shí)候就加載好了:

static int __init sock_init(void)
{
    int err;
    err = net_sysctl_init();
    if (err)
        goto out;

    skb_init();


    init_inodecache();

    err = register_filesystem(&sock_fs_type);
    if (err)
        goto out_fs;
    sock_mnt = kern_mount(&sock_fs_type);
...
}

這個(gè)過程實(shí)際上就是初始化好socket對應(yīng)的虛擬文件系統(tǒng)操作,把sock_fs_type注冊在虛擬文件系統(tǒng)中哲思,并通過kern_mount 調(diào)用操作結(jié)構(gòu)體的mount指針掛在在系統(tǒng)中洼畅,返回mnt 結(jié)構(gòu)體掛載對象進(jìn)行操作。

static struct file_system_type sock_fs_type = {
    .name =     "sockfs",
    .mount =    sockfs_mount,
    .kill_sb =  kill_anon_super,
};

結(jié)合第一段代碼棚赔,可以得知帝簇,實(shí)際上整個(gè)socket內(nèi)核模塊初始化調(diào)用的就是sockfs_mount

static struct dentry *sockfs_mount(struct file_system_type *fs_type,
             int flags, const char *dev_name, void *data)
{
    return mount_pseudo(fs_type, "socket:", &sockfs_ops,
        &sockfs_dentry_operations, SOCKFS_MAGIC);
}
static const struct super_operations sockfs_ops = {
    .alloc_inode    = sock_alloc_inode,
    .destroy_inode  = sock_destroy_inode,
    .statfs     = simple_statfs,
};

static const struct dentry_operations sockfs_dentry_operations = {
    .d_dname  = sockfs_dname,
};

mount_pseudo 會為當(dāng)前socket對應(yīng)的超級塊設(shè)置一套操作結(jié)構(gòu)體靠益,生成對應(yīng)的inode的時(shí)候會先調(diào)用dentry_operations 生成一個(gè)個(gè)detry(你可以看成文件路徑),接著調(diào)用sockfs_opsalloc_inode 生成inode丧肴。

4.1.3.sock_alloc_inode 生成對應(yīng)的inode
static struct inode *sock_alloc_inode(struct super_block *sb)
{
    struct socket_alloc *ei;
    struct socket_wq *wq;

    ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);
    if (!ei)
        return NULL;
    wq = kmalloc(sizeof(*wq), GFP_KERNEL);
    if (!wq) {
        kmem_cache_free(sock_inode_cachep, ei);
        return NULL;
    }
    init_waitqueue_head(&wq->wait);
    wq->fasync_list = NULL;
    RCU_INIT_POINTER(ei->socket.wq, wq);

    ei->socket.state = SS_UNCONNECTED;
    ei->socket.flags = 0;
    ei->socket.ops = NULL;
    ei->socket.sk = NULL;
    ei->socket.file = NULL;

    return &ei->vfs_inode;
}

這個(gè)過程實(shí)際上從快速緩存中生成一個(gè)socket_alloc 對象,這個(gè)對象中持有inode結(jié)構(gòu)體胧后。并初始化對應(yīng)的那個(gè)等待隊(duì)列芋浮。

static inline struct socket *SOCKET_I(struct inode *inode)
{
    return &container_of(inode, struct socket_alloc, vfs_inode)->socket;
}

當(dāng)需要獲取對應(yīng)的socket結(jié)構(gòu)體時(shí)候,就會通過inode反過來查找socket_alloc中的socket.

struct socket_alloc {
    struct socket socket;
    struct inode vfs_inode;
};

4.1.4.Linux內(nèi)核網(wǎng)絡(luò)模塊初始化 net_families 地址族的注冊

知道socket結(jié)構(gòu)體是如何生成的壳快。再來關(guān)注net_families是什么時(shí)候注冊的纸巷。地址族是什么?顧名思義就是指地址類別眶痰,比如ipv4瘤旨,ipv6等地址類型。

我們看看文件:
/net/ipv4/af_inet.c

對應(yīng)ipv4 內(nèi)核模塊的注冊代碼:

static int __init inet_init(void)
{
    struct inet_protosw *q;
    struct list_head *r;
    int rc = -EINVAL;

    rc = proto_register(&tcp_prot, 1);
    if (rc)
        goto out;

    rc = proto_register(&udp_prot, 1);
    if (rc)
        goto out_unregister_tcp_proto;

    rc = proto_register(&raw_prot, 1);
    if (rc)
        goto out_unregister_udp_proto;

    rc = proto_register(&ping_prot, 1);
    if (rc)
        goto out_unregister_raw_proto;

    /*
     *  Tell SOCKET that we are alive...
     */

    (void)sock_register(&inet_family_ops);

#ifdef CONFIG_SYSCTL
    ip_static_sysctl_init();
#endif

    /*
     *  Add all the base protocols.
     */

    if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
        pr_crit("%s: Cannot add ICMP protocol\n", __func__);
    if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
        pr_crit("%s: Cannot add UDP protocol\n", __func__);
    if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
        pr_crit("%s: Cannot add TCP protocol\n", __func__);


    for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
        INIT_LIST_HEAD(r);

    for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
        inet_register_protosw(q);


    arp_init();


    ip_init();

    tcp_v4_init();


    tcp_init();

    udp_init();


    udplite4_register();

    ping_init();


...


    ipfrag_init();

    dev_add_pack(&ip_packet_type);

    rc = 0;
...
}

fs_initcall(inet_init);

這一段其實(shí)就是Linux內(nèi)核對網(wǎng)絡(luò)模塊的初始化:

    1. proto_register 將tcp_prot(tcp協(xié)議),udp_prot(udp協(xié)議),raw_prot(原始ip包),ping_prot( ping 協(xié)議) 結(jié)構(gòu)體注冊到全局變量proto_list竖伯。

簡單的看看tcp_prot 結(jié)構(gòu)體內(nèi)容:

struct proto tcp_prot = {
    .name           = "TCP",
    .owner          = THIS_MODULE,
    .close          = tcp_close,
    .connect        = tcp_v4_connect,
    .disconnect     = tcp_disconnect,
    .accept         = inet_csk_accept,
    .ioctl          = tcp_ioctl,
    .init           = tcp_v4_init_sock,
    .destroy        = tcp_v4_destroy_sock,
    .shutdown       = tcp_shutdown,
    .setsockopt     = tcp_setsockopt,
    .getsockopt     = tcp_getsockopt,
    .recvmsg        = tcp_recvmsg,
    .sendmsg        = tcp_sendmsg,
    .sendpage       = tcp_sendpage,
    .backlog_rcv        = tcp_v4_do_rcv,
    .release_cb     = tcp_release_cb,
    .hash           = inet_hash,
    .unhash         = inet_unhash,
    .get_port       = inet_csk_get_port,
    .enter_memory_pressure  = tcp_enter_memory_pressure,
    .stream_memory_free = tcp_stream_memory_free,
    .sockets_allocated  = &tcp_sockets_allocated,
    .orphan_count       = &tcp_orphan_count,
    .memory_allocated   = &tcp_memory_allocated,
    .memory_pressure    = &tcp_memory_pressure,
    .sysctl_mem     = sysctl_tcp_mem,
    .sysctl_wmem        = sysctl_tcp_wmem,
    .sysctl_rmem        = sysctl_tcp_rmem,
    .max_header     = MAX_TCP_HEADER,
    .obj_size       = sizeof(struct tcp_sock),
    .slab_flags     = SLAB_DESTROY_BY_RCU,
    .twsk_prot      = &tcp_timewait_sock_ops,
    .rsk_prot       = &tcp_request_sock_ops,
    .h.hashinfo     = &tcp_hashinfo,
    .no_autobind        = true,
...
};

在這個(gè)結(jié)構(gòu)體定義了tcp協(xié)議的面向網(wǎng)絡(luò)鏈接的操作符

  • 2.sock_register初始化socket模塊以及協(xié)議族存哲,從嚴(yán)格意義來說在proto_register注冊過程中已經(jīng)完成了常用協(xié)議的注冊。

  • 3.inet_add_protocolicmp_protocol,udp_protocol,tcp_protocol 等回調(diào)協(xié)議添加到inet_protos 鏈表中黔夭。

int inet_add_protocol(const struct net_protocol *prot, unsigned char protocol)
{
...
    return !cmpxchg((const struct net_protocol **)&inet_protos[protocol],
            NULL, prot) ? 0 : -1;
}

我們調(diào)tcp協(xié)議對應(yīng)的tcp_protocol結(jié)構(gòu)體看看里面定義了什么操作函數(shù):

static const struct net_protocol tcp_protocol = {
    .early_demux    =   tcp_v4_early_demux,
    .handler    =   tcp_v4_rcv,
    .err_handler    =   tcp_v4_err,
    .no_policy  =   1,
    .netns_ok   =   1,
    .icmp_strict_tag_validation = 1,
};

主要函數(shù)handler 所對應(yīng)的tcp_v4_rcv方法宏胯。這個(gè)方法決定了tcp協(xié)議數(shù)據(jù)從另一端到來后的操作。

    1. 遍歷inetsw_array 數(shù)組本姥,把每個(gè)元素通過inet_register_protosw方法注冊到inetsw數(shù)組中肩袍。

來看看inetsw_array 內(nèi)容:

static struct inet_protosw inetsw_array[] =
{
    {
        .type =       SOCK_STREAM,
        .protocol =   IPPROTO_TCP,
        .prot =       &tcp_prot,
        .ops =        &inet_stream_ops,
        .flags =      INET_PROTOSW_PERMANENT |
                  INET_PROTOSW_ICSK,
    },

    {
        .type =       SOCK_DGRAM,
        .protocol =   IPPROTO_UDP,
        .prot =       &udp_prot,
        .ops =        &inet_dgram_ops,
        .flags =      INET_PROTOSW_PERMANENT,
       },

       {
        .type =       SOCK_DGRAM,
        .protocol =   IPPROTO_ICMP,
        .prot =       &ping_prot,
        .ops =        &inet_dgram_ops,
        .flags =      INET_PROTOSW_REUSE,
       },

       {
           .type =       SOCK_RAW,
           .protocol =   IPPROTO_IP,    /* wild card */
           .prot =       &raw_prot,
           .ops =        &inet_sockraw_ops,
           .flags =      INET_PROTOSW_REUSE,
       }
};

這個(gè)數(shù)組決定了tcp層協(xié)議的操作符,協(xié)議類型婚惫,以及協(xié)議面向外部的模塊處理方法氛赐。

舉一個(gè)tcp的例子:

  • 類型 為SOCK_STREAM 代表是面向數(shù)據(jù)流
  • protocol 為 IPPROTO_TCP 代表當(dāng)前協(xié)議是tcp協(xié)議
  • prot 為tcp_prot 就是tcp協(xié)議的特殊操作方法
  • ops 為inet_stream_ops 是指當(dāng)前數(shù)據(jù)流對應(yīng)的文件描述符中復(fù)寫的操作是什么
  • flags 是指當(dāng)前的協(xié)議狀態(tài)魂爪。INET_PROTOSW_PERMANENT 是指永久不變的協(xié)議,INET_PROTOSW_REUSE 是指該協(xié)議會復(fù)用端口艰管。
const struct proto_ops inet_stream_ops = {
    .family        = PF_INET,
    .owner         = THIS_MODULE,
    .release       = inet_release,
    .bind          = inet_bind,
    .connect       = inet_stream_connect,
    .socketpair    = sock_no_socketpair,
    .accept        = inet_accept,
    .getname       = inet_getname,
    .poll          = tcp_poll,
    .ioctl         = inet_ioctl,
    .listen        = inet_listen,
    .shutdown      = inet_shutdown,
    .setsockopt    = sock_common_setsockopt,
    .getsockopt    = sock_common_getsockopt,
    .sendmsg       = inet_sendmsg,
    .recvmsg       = inet_recvmsg,
    .mmap          = sock_no_mmap,
    .sendpage      = inet_sendpage,
    .splice_read       = tcp_splice_read,
#ifdef CONFIG_COMPAT
...
#endif
};

從這個(gè)結(jié)構(gòu)體中滓侍,我們就能大致猜到整個(gè)tcp的設(shè)計(jì)框架了。必定是先找到socket文件描述符中對應(yīng)的協(xié)議文件描述符inet_stream_ops,接著找到tcp_prot結(jié)構(gòu)體進(jìn)行進(jìn)一步的處理牲芋。

    1. arp_init 對數(shù)據(jù)鏈路層的arp協(xié)議相關(guān)的鄰居表進(jìn)行初始化撩笆。
    1. ip_init 對網(wǎng)絡(luò)層的ip模塊的初始化,在這里初始化ip對應(yīng)的route_table 內(nèi)存缸浦。
    1. tcp_v4_init 與 tcp_init 可以看作一個(gè)整體都是初始化tcp模塊夕冲。tcp_init 初始化了inet_hashinfo結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體實(shí)際上就是用于管理分配給socket端口裂逐。該結(jié)構(gòu)體會通過一個(gè)哈希表對端口進(jìn)行一次管理歹鱼。
    1. tcp_init 方法則是初始化請求需要的內(nèi)存,以及inet_hashinfo中的hash表卜高, 并計(jì)算之后每一個(gè)請求和接受的臨時(shí)緩沖區(qū)計(jì)算好大小閾值弥姻。發(fā)送緩沖區(qū)為16kb,接受緩沖區(qū)大小約為85kb掺涛。
  • 9.udp_init 初始化了UDP需要的內(nèi)存閾值庭敦,udplite4_register 則是注冊一個(gè)全新的UDLITE協(xié)議到內(nèi)核中。在這個(gè)過程就能看到內(nèi)核添加一個(gè)自定義協(xié)議的原始三步驟: proto_register ,inet_add_protocol,inet_register_protosw
void __init udplite4_register(void)
{
    udp_table_init(&udplite_table, "UDP-Lite");
    if (proto_register(&udplite_prot, 1))
        goto out_register_err;

    if (inet_add_protocol(&udplite_protocol, IPPROTO_UDPLITE) < 0)
        goto out_unregister_proto;

    inet_register_protosw(&udplite4_protosw);

....
}
    1. ping_init 對Ping協(xié)議進(jìn)行初始化
    1. ipfrag_init 初始化inet_frags結(jié)構(gòu)體薪缆。該結(jié)構(gòu)體實(shí)際上負(fù)責(zé)了整個(gè)數(shù)據(jù)流臨時(shí)緩沖區(qū)的分片螺捐。

我們把重心放在sock_register中。

4.1.5.sock_register 注冊創(chuàng)建地址族結(jié)構(gòu)體
int sock_register(const struct net_proto_family *ops)
{
    int err;

    if (ops->family >= NPROTO) {
        return -ENOBUFS;
    }

    spin_lock(&net_family_lock);
    if (rcu_dereference_protected(net_families[ops->family],
                      lockdep_is_held(&net_family_lock)))
        err = -EEXIST;
    else {
        rcu_assign_pointer(net_families[ops->family], ops);
        err = 0;
    }
    spin_unlock(&net_family_lock);

    return err;
}

能看到是做了一個(gè)rcu的鎖進(jìn)行保護(hù)后矮燎,把net_proto_family注冊到net_families 數(shù)組中。

來看看注冊的對象赔癌,family的字段為PF_INET诞外,能通過net_families尋找下標(biāo)為PF_INET 找到inet_family_ops.這樣就注冊到socket模塊中。

static const struct net_proto_family inet_family_ops = {
    .family = PF_INET,
    .create = inet_create,
    .owner  = THIS_MODULE,
};

在socket進(jìn)行初始化的時(shí)候灾票,調(diào)用了net_families 中對應(yīng)family下的net_proto_family峡谊,設(shè)置的是AF_INET6的參數(shù)。說明是進(jìn)入了對應(yīng)的ipv6的內(nèi)核模塊進(jìn)行創(chuàng)建刊苍,我們來看看ipv6內(nèi)核模塊加載的核心方法既们。

這里注意,如果你去看源碼你會發(fā)現(xiàn)在Java源碼中對應(yīng)AF_INET6 是一個(gè)placeholder方法返回的默認(rèn)數(shù)值是0.實(shí)際上這個(gè)過程是JVM加載的時(shí)候設(shè)置進(jìn)去的正什。而設(shè)置的數(shù)據(jù)可以打印AF_INET6出來 也是對應(yīng)上內(nèi)核AF_INET6一樣的數(shù)值10

4.1.6.ipv6 ip地址族內(nèi)核模塊初始化

static int __init inet6_init(void)
{
...
    err = sock_register(&inet6_family_ops);
...
...
}
module_init(inet6_init);

注冊這個(gè)結(jié)構(gòu)體:

static const struct net_proto_family inet6_family_ops = {
    .family = PF_INET6,
    .create = inet6_create,
    .owner  = THIS_MODULE,
};

說明此時(shí)注冊在socket模塊中net_families 對應(yīng)下標(biāo)是PF_INET6的創(chuàng)建協(xié)議族方法啥纸。

我們回到最初的__sock_create方法創(chuàng)建socket結(jié)構(gòu)體的下面這段話

 err = pf->create(net, sock, protocol, kern);

就能明白,實(shí)際上就是調(diào)用了inet6_create方法婴氮。

4.1.7.inet6_create 創(chuàng)建ipv6的協(xié)議族

文件:/net/ipv6/af_inet6.c

注意此時(shí)傳下來的參數(shù)是socket結(jié)構(gòu)體斯棒,socket->typeSOCK_STREAM(此時(shí)我們討論的是TCP)

static int inet6_create(struct net *net, struct socket *sock, int protocol,
            int kern)
{
    struct inet_sock *inet;
    struct ipv6_pinfo *np;
    struct sock *sk;
    struct inet_protosw *answer;
    struct proto *answer_prot;
    unsigned char answer_flags;
    int try_loading_module = 0;
    int err;

...
    err = -ESOCKTNOSUPPORT;
    rcu_read_lock();
    list_for_each_entry_rcu(answer, &inetsw6[sock->type], list) {

        err = 0;
        /* Check the non-wild match. */
        if (protocol == answer->protocol) {
            if (protocol != IPPROTO_IP)
                break;
        } else {
            /* Check for the two wild cases. */
            if (IPPROTO_IP == protocol) {
                protocol = answer->protocol;
                break;
            }
            if (IPPROTO_IP == answer->protocol)
                break;
        }
        err = -EPROTONOSUPPORT;
    }

    if (err) {
...
    }

    err = -EPERM;
    if (sock->type == SOCK_RAW && !kern && !capable(CAP_NET_RAW))
        goto out_rcu_unlock;

    sock->ops = answer->ops;
    answer_prot = answer->prot;
    answer_flags = answer->flags;
    rcu_read_unlock();

    WARN_ON(answer_prot->slab == NULL);

    err = -ENOBUFS;
    sk = sk_alloc(net, PF_INET6, GFP_KERNEL, answer_prot);
    if (sk == NULL)
        goto out;

    sock_init_data(sock, sk);

    err = 0;
    if (INET_PROTOSW_REUSE & answer_flags)
        sk->sk_reuse = SK_CAN_REUSE;

    inet = inet_sk(sk);
    inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;

    if (SOCK_RAW == sock->type) {
        inet->inet_num = protocol;
        if (IPPROTO_RAW == protocol)
            inet->hdrincl = 1;
    }

    sk->sk_destruct     = inet_sock_destruct;
    sk->sk_family       = PF_INET6;
    sk->sk_protocol     = protocol;

    sk->sk_backlog_rcv  = answer->prot->backlog_rcv;

    inet_sk(sk)->pinet6 = np = inet6_sk_generic(sk);
    np->hop_limit   = -1;
    np->mcast_hops  = IPV6_DEFAULT_MCASTHOPS;
    np->mc_loop = 1;
    np->pmtudisc    = IPV6_PMTUDISC_WANT;
    sk->sk_ipv6only = net->ipv6.sysctl.bindv6only;

...

    if (inet->inet_num) {

        inet->inet_sport = htons(inet->inet_num);
        sk->sk_prot->hash(sk);
    }
    if (sk->sk_prot->init) {
        err = sk->sk_prot->init(sk);
        if (err) {
            sk_common_release(sk);
            goto out;
        }
    }

...
}
  • 1.遍歷保存在inetsw6的協(xié)議列表中對應(yīng)type的協(xié)議結(jié)構(gòu)體,賦值到answer中盾致。對應(yīng)在ipv6中的tcp協(xié)議結(jié)構(gòu)體是如下:
static struct inet_protosw tcpv6_protosw = {
    .type       =   SOCK_STREAM,
    .protocol   =   IPPROTO_TCP,
    .prot       =   &tcpv6_prot,
    .ops        =   &inet6_stream_ops`,
    .flags      =   INET_PROTOSW_PERMANENT |
                INET_PROTOSW_ICSK,
};

此時(shí)socket結(jié)構(gòu)體中的ops就被替換成了inet6_stream_ops,如果是v4,則是替換成inet_stream_ops.

  • 2.通過sk_alloc方法創(chuàng)建sock結(jié)構(gòu)體荣暮,并且把tcpv6_prot賦值給sock結(jié)構(gòu)體.并記錄當(dāng)前的協(xié)議類型庭惜,sock_init_data則初始化sock的的關(guān)鍵操作。
void sock_init_data(struct socket *sock, struct sock *sk)
{
    skb_queue_head_init(&sk->sk_receive_queue);
    skb_queue_head_init(&sk->sk_write_queue);
    skb_queue_head_init(&sk->sk_error_queue);

    sk->sk_send_head    =   NULL;

    init_timer(&sk->sk_timer);

    sk->sk_allocation   =   GFP_KERNEL;
    sk->sk_rcvbuf       =   sysctl_rmem_default;
    sk->sk_sndbuf       =   sysctl_wmem_default;
    sk->sk_state        =   TCP_CLOSE;
    sk_set_socket(sk, sock);

    sock_set_flag(sk, SOCK_ZAPPED);

    if (sock) {
        sk->sk_type =   sock->type;
        sk->sk_wq   =   sock->wq;
        sock->sk    =   sk;
    } else
        sk->sk_wq   =   NULL;

    spin_lock_init(&sk->sk_dst_lock);
    rwlock_init(&sk->sk_callback_lock);
    lockdep_set_class_and_name(&sk->sk_callback_lock,
            af_callback_keys + sk->sk_family,
            af_family_clock_key_strings[sk->sk_family]);

    sk->sk_state_change =   sock_def_wakeup;
    sk->sk_data_ready   =   sock_def_readable;
    sk->sk_write_space  =   sock_def_write_space;
    sk->sk_error_report =   sock_def_error_report;
    sk->sk_destruct     =   sock_def_destruct;

    sk->sk_frag.page    =   NULL;
    sk->sk_frag.offset  =   0;
    sk->sk_peek_off     =   -1;

    sk->sk_peer_pid     =   NULL;
    sk->sk_peer_cred    =   NULL;
    sk->sk_write_pending    =   0;
    sk->sk_rcvlowat     =   1;
    sk->sk_rcvtimeo     =   MAX_SCHEDULE_TIMEOUT;
    sk->sk_sndtimeo     =   MAX_SCHEDULE_TIMEOUT;

    sk->sk_stamp = ktime_set(-1L, 0);

#ifdef CONFIG_NET_RX_BUSY_POLL
    sk->sk_napi_id      =   0;
    sk->sk_ll_usec      =   sysctl_net_busy_read;
#endif

    sk->sk_max_pacing_rate = ~0U;
    sk->sk_pacing_rate = ~0U;
    /*
     * Before updating sk_refcnt, we must commit prior changes to memory
     * (Documentation/RCU/rculist_nulls.txt for details)
     */
    smp_wmb();
    atomic_set(&sk->sk_refcnt, 1);
    atomic_set(&sk->sk_drops, 0);
}
EXPORT_SYMBOL(sock_init_data);
struct proto tcpv6_prot = {
    .name           = "TCPv6",
    .owner          = THIS_MODULE,
    .close          = tcp_close,
    .connect        = tcp_v6_connect,
    .disconnect     = tcp_disconnect,
    .accept         = inet_csk_accept,
    .ioctl          = tcp_ioctl,
    .init           = tcp_v6_init_sock,
    .destroy        = tcp_v6_destroy_sock,
    .shutdown       = tcp_shutdown,
    .setsockopt     = tcp_setsockopt,
    .getsockopt     = tcp_getsockopt,
    .recvmsg        = tcp_recvmsg,
    .sendmsg        = tcp_sendmsg,
    .sendpage       = tcp_sendpage,
    .backlog_rcv        = tcp_v6_do_rcv,
    .release_cb     = tcp_release_cb,
    .hash           = tcp_v6_hash,
    .unhash         = inet_unhash,
    .get_port       = inet_csk_get_port,
    .enter_memory_pressure  = tcp_enter_memory_pressure,
    .stream_memory_free = tcp_stream_memory_free,
    .sockets_allocated  = &tcp_sockets_allocated,
    .memory_allocated   = &tcp_memory_allocated,
    .memory_pressure    = &tcp_memory_pressure,
    .orphan_count       = &tcp_orphan_count,
    .sysctl_mem     = sysctl_tcp_mem,
    .sysctl_wmem        = sysctl_tcp_wmem,
    .sysctl_rmem        = sysctl_tcp_rmem,
    .max_header     = MAX_TCP_HEADER,
    .obj_size       = sizeof(struct tcp6_sock),
    .slab_flags     = SLAB_DESTROY_BY_RCU,
    .twsk_prot      = &tcp6_timewait_sock_ops,
    .rsk_prot       = &tcp6_request_sock_ops,
    .h.hashinfo     = &tcp_hashinfo,
    .no_autobind        = true,
...
};
  • 3.調(diào)用了sk_prot的init方法穗酥。其實(shí)對應(yīng)就是tcpv6_prot的init方法护赊。
4.1.8.sk_alloc 創(chuàng)建sock結(jié)構(gòu)體
struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
              struct proto *prot)
{
    struct sock *sk;

    sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
    if (sk) {
        sk->sk_family = family;
        sk->sk_prot = sk->sk_prot_creator = prot;
    }

    return sk;
}
EXPORT_SYMBOL(sk_alloc);

這里值得注意的是sock結(jié)構(gòu)體真正進(jìn)行初始化的是通過 sk_prot_alloc,當(dāng)初始化結(jié)束后砾跃,才對sock結(jié)構(gòu)體中的數(shù)據(jù)進(jìn)行賦值骏啰。注意在這里sock結(jié)構(gòu)體把對應(yīng)協(xié)議proto結(jié)構(gòu)體保存在sk_prot字段中。

static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority,
        int family)
{
    struct sock *sk;
    struct kmem_cache *slab;

    slab = prot->slab;
    if (slab != NULL) {
...
    } else
        sk = kmalloc(prot->obj_size, priority);
...

    return sk;
...
}

能看到是通過kmalloc從高速緩沖區(qū)中初始化一段結(jié)構(gòu)體大小為obj_size的內(nèi)存蜓席。這個(gè)大小是什么呢器一?

實(shí)際上并非是一致暴露在我們眼中sock結(jié)構(gòu)體而是tcp6_sock

.obj_size       = sizeof(struct tcp6_sock),

只是這個(gè)結(jié)構(gòu)體擁有了sock結(jié)構(gòu)體所有的字段,且內(nèi)存結(jié)構(gòu)一致而直接轉(zhuǎn)化成sock結(jié)構(gòu)體.

文件:/include/linux/ipv6.h

struct tcp6_sock {
    struct tcp_sock   tcp;
    /* ipv6_pinfo has to be the last member of tcp6_sock, see inet6_sk_generic */
    struct ipv6_pinfo inet6;
};

能看到這個(gè)tcp6_sock結(jié)構(gòu)體包含了tcp_sock 真正包含sock結(jié)構(gòu)體和一個(gè)ipv6_pinfo ipv6地址內(nèi)容的結(jié)構(gòu)體.

讓我們繼續(xù)深挖tcp_sock的內(nèi)存結(jié)構(gòu):
文件:/include/linux/tcp.h

struct tcp_sock {
    /* inet_connection_sock has to be the first member of tcp_sock */
    struct inet_connection_sock inet_conn;
...
};

但是還不夠清晰厨内,繼續(xù)深挖祈秕,因?yàn)榭梢灾苯訜o縫強(qiáng)轉(zhuǎn)sock結(jié)構(gòu)體必定包含這個(gè)結(jié)構(gòu)體在inet_connection_sock中。

文件:/include/net/inet_connection_sock.h

struct inet_connection_sock {
    /* inet_sock has to be the first member! */
    struct inet_sock      icsk_inet;
...
};

文件:/include/net/inet_sock.h

struct inet_sock {
    /* sk and pinet6 has to be the first two members of inet_sock */
    struct sock     sk;
...
};

終于看到了雏胃,sock結(jié)構(gòu)體在這里请毛。實(shí)際上這個(gè)思想想不想我們java的繼承呢?只是內(nèi)存結(jié)構(gòu)上有一定要求而已瞭亮。

sock內(nèi)存結(jié)構(gòu).png
4.1.9.tcpv6_prot init

文件:/net/ipv6/tcp_ipv6.c

static int tcp_v6_init_sock(struct sock *sk)
{
    struct inet_connection_sock *icsk = inet_csk(sk);

    tcp_init_sock(sk);

    icsk->icsk_af_ops = &ipv6_specific;


    return 0;
}
  • 1.inet_csk 其實(shí)就是相當(dāng)于把sock強(qiáng)轉(zhuǎn)成inet_connection_sock方仿。因?yàn)?code>inet_connection_sock結(jié)構(gòu)體內(nèi)存結(jié)構(gòu)前半段和sock一致,因此可以無縫轉(zhuǎn)化统翩。

  • 2.tcp_init_sock 初始化inet_connection_sock結(jié)構(gòu)體

文件:/net/ipv4/tcp.c

void tcp_init_sock(struct sock *sk)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);

    __skb_queue_head_init(&tp->out_of_order_queue);
    tcp_init_xmit_timers(sk);
    tcp_prequeue_init(tp);
    INIT_LIST_HEAD(&tp->tsq_node);

    icsk->icsk_rto = TCP_TIMEOUT_INIT;
    tp->mdev_us = jiffies_to_usecs(TCP_TIMEOUT_INIT);

...

    icsk->icsk_sync_mss = tcp_sync_mss;

    sk->sk_sndbuf = sysctl_tcp_wmem[1];
    sk->sk_rcvbuf = sysctl_tcp_rmem[1];
...
}

在這里轉(zhuǎn)化sock結(jié)構(gòu)體為tcp_sockinet_connection_sock.

tcp_sock 設(shè)置sk_buf的緩存隊(duì)列仙蚜,設(shè)置tcp一次允許超時(shí)的時(shí)間為1000個(gè)時(shí)間鐘;初始化tcp_sockout_of_order_queue隊(duì)列厂汗,初始化sk_buff_head scok預(yù)緩沖數(shù)據(jù)包隊(duì)列

sock 結(jié)構(gòu)體設(shè)置定時(shí)器,設(shè)置之前計(jì)算好的接受和發(fā)送緩沖區(qū)的大小委粉。

4.2.sock_map_fd 把socket結(jié)構(gòu)體和文件描述符關(guān)聯(lián)起來

文件:/net/socket.c

static int sock_map_fd(struct socket *sock, int flags)
{
    struct file *newfile;
    int fd = get_unused_fd_flags(flags);
    if (unlikely(fd < 0))
        return fd;

    newfile = sock_alloc_file(sock, flags, NULL);
    if (likely(!IS_ERR(newfile))) {
        fd_install(fd, newfile);
        return fd;
    }

    put_unused_fd(fd);
    return PTR_ERR(newfile);
}

核心就是調(diào)用了sock_alloc_file方法,為socket結(jié)構(gòu)體創(chuàng)建一個(gè)file結(jié)構(gòu)體娶桦。

在看socket對應(yīng)的文件描述符的生成之前贾节,我們需要補(bǔ)充如下的知識點(diǎn):

    err = register_filesystem(&sock_fs_type);
    if (err)
        goto out_fs;
    sock_mnt = kern_mount(&sock_fs_type);
static struct file_system_type sock_fs_type = {
    .name =     "sockfs",
    .mount =    sockfs_mount,
    .kill_sb =  kill_anon_super,
};

從這兩個(gè)代碼段,可以得知衷畦。socket模塊在初始化時(shí)候會加載一個(gè)結(jié)構(gòu)體為file_system_type栗涂。這個(gè)結(jié)構(gòu)體是用于注冊VFS 也就是虛擬文件系統(tǒng)的類型結(jié)構(gòu)體。

這里的含義是祈争,注冊并掛載了sockfs虛擬文件系統(tǒng)斤程。而在下面的sock_alloc_file 所創(chuàng)建的socket文件描述符就是創(chuàng)建在這個(gè)虛擬文件系統(tǒng)中。

struct file *sock_alloc_file(struct socket *sock, int flags, const char *dname)
{
    struct qstr name = { .name = "" };
    struct path path;
    struct file *file;

    if (dname) {
        name.name = dname;
        name.len = strlen(name.name);
    } else if (sock->sk) {
        name.name = sock->sk->sk_prot_creator->name;
        name.len = strlen(name.name);
    }
    path.dentry = d_alloc_pseudo(sock_mnt->mnt_sb, &name);
...
    path.mnt = mntget(sock_mnt);

    d_instantiate(path.dentry, SOCK_INODE(sock));
    SOCK_INODE(sock)->i_fop = &socket_file_ops;

    file = alloc_file(&path, FMODE_READ | FMODE_WRITE,
          &socket_file_ops);
    if (unlikely(IS_ERR(file))) {
        /* drop dentry, keep inode */
        ihold(path.dentry->d_inode);
        path_put(&path);
        return file;
    }

    sock->file = file;
    file->f_flags = O_RDWR | (flags & O_NONBLOCK);
    file->private_data = sock;
    return file;
}

注意這里面sk->sk_prot_creator是指inet6_create中調(diào)用的sk_alloc菩混,在此時(shí)就是指tcpv6_prot.

注意在socket內(nèi)存文件申請時(shí)候暖释,使用socket文件系統(tǒng)中袭厂,對應(yīng)mount掛載對象生成對應(yīng)的內(nèi)存文件路徑。

核心源碼如下球匕;

static char *sockfs_dname(struct dentry *dentry, char *buffer, int buflen)
{
    return dynamic_dname(dentry, buffer, buflen, "socket:[%lu]",
                dentry->d_inode->i_ino);
}

文件名為socket:[inode號]纹磺。

當(dāng)生成了socket對應(yīng)的file內(nèi)存文件后,就會保存到socket 結(jié)構(gòu)體中亮曹。

在Linux內(nèi)核初始化時(shí)候橄杨,會初始化Socket內(nèi)核模塊對應(yīng)的自定義文件系統(tǒng)(file_system_type ) sock_fs_type 結(jié)構(gòu)體。

static struct file_system_type sock_fs_type = {
    .name =     "sockfs",
    .mount =    sockfs_mount,
    .kill_sb =  kill_anon_super,
};

該結(jié)構(gòu)體定義了掛載函數(shù)照卦,文件系統(tǒng)名式矫,刪除數(shù)據(jù)時(shí)候?qū)Τ墘K的清理操作

static const struct super_operations sockfs_ops = {
    .alloc_inode    = sock_alloc_inode,
    .destroy_inode  = sock_destroy_inode,
    .statfs     = simple_statfs,
};

而這種文件系統(tǒng)實(shí)際上設(shè)置的就是對超級塊的操作。當(dāng)掛載成功后役耕,又注冊了如下的信息:

static const struct super_operations sockfs_ops = {
    .alloc_inode    = sock_alloc_inode,
    .destroy_inode  = sock_destroy_inode,
    .statfs     = simple_statfs,
};

/*
 * sockfs_dname() is called from d_path().
 */
static char *sockfs_dname(struct dentry *dentry, char *buffer, int buflen)
{
    return dynamic_dname(dentry, buffer, buflen, "socket:[%lu]",
                dentry->d_inode->i_ino);
}

static const struct dentry_operations sockfs_dentry_operations = {
    .d_dname  = sockfs_dname,
};

static struct dentry *sockfs_mount(struct file_system_type *fs_type,
             int flags, const char *dev_name, void *data)
{
    return mount_pseudo(fs_type, "socket:", &sockfs_ops,
        &sockfs_dentry_operations, SOCKFS_MAGIC);
}

此時(shí)就定義了文件目錄對象操作dentry_operations 這里面決定了目錄名為對應(yīng)socket:[inode]號采转,并且設(shè)置了sockfs_ops 申請inode的操作對象。

小結(jié)

到這里就聊完了客戶端對應(yīng)的socket是如何通過socket系統(tǒng)調(diào)用初始化的瞬痘。能看到在創(chuàng)建socket的時(shí)候故慈,內(nèi)核創(chuàng)建的結(jié)構(gòu)體嵌套層級十分多,在這里先中斷源碼解析流程框全,進(jìn)行一次總結(jié)察绷。

在Linux內(nèi)核中,無法直接訪問socket所對應(yīng)的文件描述符津辩,因?yàn)閷?yīng)socket的file_operation 是禁止的拆撼。只能通過socket系統(tǒng)調(diào)用訪問socket對應(yīng)的socket描述符。

在內(nèi)核啟動(dòng)初期喘沿,會啟動(dòng)socket內(nèi)核模塊闸度,并掛載socket內(nèi)核模塊對應(yīng)的文件系統(tǒng)。并初始化默認(rèn)的協(xié)議集蚜印。

當(dāng)Java調(diào)用了Socket.connect 方法后筋岛。一個(gè)socket對象才會開始通過SocksSocketImpl創(chuàng)建 socket對象。

而這個(gè)方法本質(zhì)上調(diào)用還是socket系統(tǒng)調(diào)用晒哄。將會創(chuàng)建一個(gè)復(fù)雜且龐大的結(jié)構(gòu)體。

不過我們需要記住一點(diǎn)肪获,無論怎么變都不可能脫離七層網(wǎng)絡(luò)協(xié)議寝凌。socket系統(tǒng)調(diào)用是對傳輸層網(wǎng)絡(luò)層孝赫,數(shù)據(jù)鏈路層,物理層的封裝较木。那么相對的,生成出來的socket結(jié)構(gòu)體也是根據(jù)這一層層的設(shè)計(jì)青柄,進(jìn)行封裝的伐债。

暴露在最外層的是socket結(jié)構(gòu)體,將會根據(jù)設(shè)置的socket類型预侯,從而找到對應(yīng)的地址族以及協(xié)議類型。

在這個(gè)過程中socket結(jié)構(gòu)體存在如下幾個(gè)核心結(jié)構(gòu)體:

  • 1.sock socket 持有通用的核心操作以及核心字段
  • 2.inet6_stream_ops 則是不同協(xié)議下不同的操作行為

因此整個(gè)關(guān)系如下圖:

socket結(jié)構(gòu)體.png

在整個(gè)socket通信過程峰锁,暴露向外的如上層應(yīng)用層萎馅,或者說是面向開發(fā)者來說是socket結(jié)構(gòu)體。對于內(nèi)核來說就是sock結(jié)構(gòu)體虹蒋。值得注意的是糜芳,這兩者之間是互相持有的.sock 通過sk_socket 找到socketsocket通過sk找到sock.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末魄衅,一起剝皮案震驚了整個(gè)濱河市峭竣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晃虫,老刑警劉巖皆撩,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異哲银,居然都是意外死亡扛吞,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門盘榨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喻粹,“玉大人,你說我怎么就攤上這事草巡∈匚兀” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵山憨,是天一觀的道長查乒。 經(jīng)常有香客問我,道長郁竟,這世上最難降的妖魔是什么玛迄? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮棚亩,結(jié)果婚禮上蓖议,老公的妹妹穿的比我還像新娘。我一直安慰自己讥蟆,他們只是感情好勒虾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瘸彤,像睡著了一般修然。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天愕宋,我揣著相機(jī)與錄音玻靡,去河邊找鬼。 笑死中贝,一個(gè)胖子當(dāng)著我的面吹牛囤捻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雄妥,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼最蕾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了老厌?” 一聲冷哼從身側(cè)響起瘟则,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎枝秤,沒想到半個(gè)月后醋拧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淀弹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年丹壕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片薇溃。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡菌赖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沐序,到底是詐尸還是另有隱情琉用,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布策幼,位于F島的核電站邑时,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏特姐。R本人自食惡果不足惜晶丘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唐含。 院中可真熱鬧浅浮,春花似錦、人聲如沸捷枯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铜靶。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間争剿,已是汗流浹背已艰。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚕苇,地道東北人哩掺。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像涩笤,于是被迫代替她去往敵國和親嚼吞。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容