网络协议栈学习(二)socket 本质

  下面通过学习linux 1.2.13源码进一步理解socket通信机制。对该版本源码的学习主要参考《Linux内核网络栈源代码情景分析》(曹桂平 编著)。

  要理解socket的本质,就要理解当调用socket函数时,该函数到底创建了什么?返回了什么?

int  socket(int family, int type, int protocol);

  socket 函数为用户层函数,该函数对应的内核函数为sock_socket(socket.c文件),源码如下:

static int sock_socket(int family, int type, int protocol)
{
    int i, fd;
    struct socket *sock;
    struct proto_ops *ops;

    /* Locate the correct protocol family. */
    for (i = 0; i < NPROTO; ++i)
    {
        if (pops[i] == NULL) continue;
        if (pops[i]->family == family)
            break;
    }

    if (i == NPROTO)
    {
          return -EINVAL;
    }

    ops = pops[i];

/*
 *    Check that this is a type that we know how to manipulate and
 *    the protocol makes sense here. The family can still reject the
 *    protocol later.
 */

    if ((type != SOCK_STREAM && type != SOCK_DGRAM &&
        type != SOCK_SEQPACKET && type != SOCK_RAW &&
        type != SOCK_PACKET) || protocol < 0)
            return(-EINVAL);

/*
 *    Allocate the socket and allow the family to set things up. if
 *    the protocol is 0, the family is instructed to select an appropriate
 *    default.
 */

    if (!(sock = sock_alloc()))
    {
        printk("NET: sock_socket: no more sockets\n");
        return(-ENOSR);    /* Was: EAGAIN, but we are out of
                   system resources! */
    }

    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);
}

   sock_socket 函数完成如下工作:

(1)分配socket、sock结构,这两个结构在网络栈的不同层次表示一个套接字连接。

(2)分配inode、file结构用于普通文件操作。

(3)分配一个文件描述符并返回给应用程序作为以后的操作句柄。

  sock_alloc 函数用于分配一个inode节点,并返回该节点的socket指针

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

    inode = get_empty_inode();
    if (!inode)
        return NULL;

    inode->i_mode = S_IFSOCK;
    inode->i_sock = 1;
    inode->i_uid = current->uid;
    inode->i_gid = current->gid;

    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;
    sockets_in_use++;
    return sock;
}

inode的定义如下

/* include/fs.h */
struct inode {
    dev_t        i_dev;
    unsigned long    i_ino;
    umode_t        i_mode;
    nlink_t        i_nlink;
    uid_t        i_uid;
    gid_t        i_gid;
    dev_t        i_rdev;
    off_t        i_size;
    time_t        i_atime;
    time_t        i_mtime;
    time_t        i_ctime;
    unsigned long    i_blksize;
    unsigned long    i_blocks;
    unsigned long    i_version;
    struct semaphore i_sem;
    struct inode_operations * i_op;
    struct super_block * i_sb;
    struct wait_queue * i_wait;
    struct file_lock * i_flock;
    struct vm_area_struct * i_mmap;
    struct inode * i_next, * i_prev;
    struct inode * i_hash_next, * i_hash_prev;
    struct inode * i_bound_to, * i_bound_by;
    struct inode * i_mount;
    unsigned short i_count;
    unsigned short i_wcount;
    unsigned short i_flags;
    unsigned char i_lock;
    unsigned char i_dirt;
    unsigned char i_pipe;
    unsigned char i_sock;
    unsigned char i_seek;
    unsigned char i_update;
    union {
        struct pipe_inode_info pipe_i;
        struct minix_inode_info minix_i;
        struct ext_inode_info ext_i;
        struct ext2_inode_info ext2_i;
        struct hpfs_inode_info hpfs_i;
        struct msdos_inode_info msdos_i;
        struct umsdos_inode_info umsdos_i;
        struct iso_inode_info isofs_i;
        struct nfs_inode_info nfs_i;
        struct xiafs_inode_info xiafs_i;
        struct sysv_inode_info sysv_i;
        struct socket socket_i;
        void * generic_ip;
    } u;
};

  inode 结构是文件系统的一个结构体,该结构体中的成员变量u指明了该inode结构具体的文件类型,当inode是用于socket通信时,u的值就为socket_i。sock_alloc 的作用就是创建inode结构体,然后返回socket_i的地址。至于具体如何分配inode涉及到文件系统方面的知识,这里暂不讨论。

  当协议族为AF_INET时,ops->create 将调用inet_create(struct socket*sock, int protocol)函数。该函数将创建一个sock结构体并使得socket的data指针指向该sock结构体。

static int inet_create(struct socket *sock, int protocol)
{
    struct sock *sk;
    struct proto *prot;
    int err;

    sk = (struct sock *) kmalloc(sizeof(*sk), GFP_KERNEL);
    if (sk == NULL)
        return(-ENOBUFS);
    sk->num = 0;
    sk->reuse = 0;
    switch(sock->type)
    {
        case SOCK_STREAM:
        case SOCK_SEQPACKET:
            if (protocol && protocol != IPPROTO_TCP)
            {
                kfree_s((void *)sk, sizeof(*sk));
                return(-EPROTONOSUPPORT);
            }
            protocol = IPPROTO_TCP;
            sk->no_check = TCP_NO_CHECK;
            prot = &tcp_prot;
            break;

        case SOCK_DGRAM:
            if (protocol && protocol != IPPROTO_UDP)
            {
                kfree_s((void *)sk, sizeof(*sk));
                return(-EPROTONOSUPPORT);
            }
            protocol = IPPROTO_UDP;
            sk->no_check = UDP_NO_CHECK;
            prot=&udp_prot;
            break;

        case SOCK_RAW:
            if (!suser())
            {
                kfree_s((void *)sk, sizeof(*sk));
                return(-EPERM);
            }
            if (!protocol)
            {
                kfree_s((void *)sk, sizeof(*sk));
                return(-EPROTONOSUPPORT);
            }
            prot = &raw_prot;
            sk->reuse = 1;
            sk->no_check = 0;    /*
                         * Doesn‘t matter no checksum is
                         * performed anyway.
                         */
            sk->num = protocol;
            break;

        case SOCK_PACKET:
            if (!suser())
            {
                kfree_s((void *)sk, sizeof(*sk));
                return(-EPERM);
            }
            if (!protocol)
            {
                kfree_s((void *)sk, sizeof(*sk));
                return(-EPROTONOSUPPORT);
            }
            prot = &packet_prot;
            sk->reuse = 1;
            sk->no_check = 0;    /* Doesn‘t matter no checksum is
                         * performed anyway.
                         */
            sk->num = protocol;
            break;

        default:
            kfree_s((void *)sk, sizeof(*sk));
            return(-ESOCKTNOSUPPORT);
    }
    sk->socket = sock;
#ifdef CONFIG_TCP_NAGLE_OFF
    sk->nonagle = 1;
#else
    sk->nonagle = 0;
#endif
    sk->type = sock->type;
    sk->stamp.tv_sec=0;
    sk->protocol = protocol;
        ......
    sk->timer.function = &net_timer;
    skb_queue_head_init(&sk->back_log);
    sk->blog = 0;
    sock->data =(void *) sk; //socket 指向 sock
    sk->dummy_th.doff = sizeof(sk->dummy_th)/4;
       ......
    if (sk->prot->init)
    {
        err = sk->prot->init(sk);
        if (err != 0)
        {
            destroy_sock(sk);
            return(err);
        }
    }
    return(0);
}

  最后调用get_fd 返回一个文件描述符给上层应用。

/* socket.c */
static int get_fd(struct inode *inode)
{
    int fd;
    struct file *file;

    /*
     *    Find a file descriptor suitable for return to the user.
     */

    file = get_empty_filp(); // 获取一个闲置的file结构
    if (!file)
        return(-1);

    for (fd = 0; fd < NR_OPEN; ++fd)
        if (!current->files->fd[fd])
            break;
    if (fd == NR_OPEN)
    {
        file->f_count = 0;
        return(-1);
    }

    FD_CLR(fd, &current->files->close_on_exec);
        current->files->fd[fd] = file;
    file->f_op = &socket_file_ops; // socket 文件操作
    file->f_mode = 3;
    file->f_flags = O_RDWR;
    file->f_count = 1;
    file->f_inode = inode;
    if (inode)
        inode->i_count++;
    file->f_pos = 0;
    return(fd);
}

  get_fd 用于为网络套接字分配一个文件描述符,分配描述符的同时需要一个file结构,每个file结构都需要一个inode结构对应。内核维护一个file结构数据,get_empty_filp 函数即通过检查该数组,获取一个闲置的成员。f_op 字段的赋值实现了网络操作的普通文件接口。如果调用write、read函数进行操作就会调用相应的sock_read 和 sock_write 函数。

  如何根据文件描述如fd找到相应的sock?

时间: 2024-10-02 20:59:02

网络协议栈学习(二)socket 本质的相关文章

网络协议栈学习(一)socket通信实例

网络协议栈学习(一)socket通信实例 该实例摘自<linux网络编程>(宋敬彬,孙海滨等著). 例子分为服务器端和客户端,客户端连接服务器后从标准输入读取输入的字符串,发送给服务器:服务器接收到字符串后,发送给服务器:服务器接收到字符串后统计字符串的长度,然后将该值传给客户端:客户端将接收到的信息打印到标准输出. 一.服务器端代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #in

Linux内核--网络协议栈深入分析(二)--sk_buff的操作函数

本文分析基于Linux Kernel 3.2.1 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7972647 更多请查看网络栈分析专栏http://blog.csdn.net/column/details/linux-kernel-net.html 作者:闫明 1.alloc_skb()函数 该函数的作用是在上层协议要发送数据包的时候或网络设备准备接收数据包的时候会调用alloc_skb()函数分配sk_buff结构体,需要释

linux网络编程学习笔记之二 -----错误异常处理和各种碎碎(更新中)

errno 在unix系统中对大部分系统调用非正常返回时,通常返回值为-1,并设置全局变量errno(errno.h),如socket(), bind(), accept(), listen().erron存放一个正整数来保存上次出错的错误值. 对线程而言,每个线程都有专用的errno变量,不必考虑同步问题. strerror converts to English (Note: use strerror_r for thread safety) perror is simplified str

[转帖]脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?

脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么? http://www.52im.net/thread-1732-1-1.html 1.引言 本文接上篇<脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手>,继续脑残式的网络编程知识学习 ^_^. 套接字socket是大多数程序员都非常熟悉的概念,它是计算机网络编程的基础,TCP/UDP收发消息都靠它.我们熟悉的web服务器底层依赖它,我们用到的MySQL关系数据库.Redis内存数据库底层依赖它.我们用微信和别

Linux程序设计学习笔记----Socket网络编程基础之TCP/IP协议簇

转载请注明出处: ,谢谢! 内容提要 本节主要学习网络通信基础,主要涉及的内容是: TCP/IP协议簇基础:两个模型 IPv4协议基础:IP地址分类与表示,子网掩码等 IP地址转换:点分十进制\二进制 TCP/IP协议簇基础 OSI模型 我们知道计算机网络之中,有各种各样的设备,那么如何实现这些设备的通信呢? 显然是通过标准的通讯协议,但是,整个网络连接的过程相当复杂,包括硬件.软件数据封包与应用程序的互相链接等等,如果想要写一支将联网全部功能都串连在一块的程序,那么当某个小环节出现问题时,整只

转 网络编程学习笔记一:Socket编程

网络编程学习笔记一:Socket编程 “一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. ——有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时,浏览器的进程怎么与web服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠socket?那什么是socket?socket的类型有哪些?还有socket的基本函数,这些都是本文想介绍的.本文的主要内容如下:

ECC协议栈学习总结

            ECC协议栈学习总结 目录 1 前言    3 2  ECC通讯原理... 3 2.1   ECC通讯模型... 3 2.2   ECC通讯实现... 5 2.2.1 物理层实现..................................................................................................... .5 2.2.2 数据链路层实现...............................

网络编程学习小结

几种网络编程方式: ISAPI.CGI.WinInet.Winsock 它们之间的差别: 1)  ISAPI主要是开发基于浏览器client与server端程序.效率比CGI方式高,并且也扩展了CGI没有的一些功能.(基于TCP/IP模型中的应用层) 2)  CGI主要是开发基于浏览器client与server端程序.(基于TCP/IP模型中的应用层) 3)  WinInet主要是开发client程序.(基于TCP/IP模型中的应用层) 4)  Winsock主要是基于socket来开发clie

Photon服务器引擎(二)socket/TCP/UDP基础及Unity聊天室的实现

Photon服务器引擎(二)socket/TCP/UDP基础及Unity聊天室的实现 我们平时说的最多的socket是什么呢,实际上socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API). 通过Socket,我们才能使用TCP/IP协议.实际上,Socket跟TCP/IP协议没有必然的联系.Socket编程接口在设计的时候,就希望也能适应其他的网络协议.所以说,Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,