前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解TCP/IP协议的实现之socket(基于linux1.2.13)

深入理解TCP/IP协议的实现之socket(基于linux1.2.13)

作者头像
theanarkh
发布2020-03-12 00:26:24
7540
发布2020-03-12 00:26:24
举报
文章被收录于专栏:原创分享原创分享

socket大家都知道是用于网络通信的,也知道他是ip和端口的组合。但是很多同学可能不是很清楚socket的原理和实现。下面我们深入理解一下socket到底是什么。 我们回忆一下socket编程的步骤,不管是客户端还是服务端,第一个调的函数都是socket。我们就从这个函数的实现开始,看看一个socket到底是什么。

代码语言:javascript
复制
// 新建一个socket结构体,并且创建一个下层的sock结构体,互相关联
static int sock_socket(int family, int type, int protocol)
{
    int i, fd;
    struct socket *sock;
    struct proto_ops *ops;

    // 找到对应的协议族,比如unix域、ipv4
    for (i = 0; i < NPROTO; ++i) 
    {   // 从props数组中找到family协议对应的操作函数集,props由系统初始化时sock_register进行操作
        if (pops[i] == NULL) continue;
        if (pops[i]->family == family) 
            break;
    }

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

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

    // 分配一个新的socket结构体
    if (!(sock = sock_alloc())) 
    {
        ...
    }
    // 设置类型和操作函数集
    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. 根据传的协议类型,找到对应的函数集,因为不同的协议族他的底层操作是不一样的。 2. 分配一个socket结构体。定义如下。我们大概了解一下字段就行。

代码语言:javascript
复制
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节点
    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结构体,初始化inode结构体的socket结构体
    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数加一
    sockets_in_use++;
    // 返回新的socket结构体,他挂载在inode中
    return sock;
}

sock_alloc首先分配了一个inode,inode节点里有一个socket结构体,然后初始化socket结构体的一些字段,并把他的地址返回。

3. 这时候我们拿到一个socket结构体。接着调create函数(省略了部分代码)。

代码语言:javascript
复制
// 创建一个sock结构体,和socket结构体互相关联
static int inet_create(struct socket *sock, int protocol)
{
    struct sock *sk;
    struct proto *prot;
    int err;
    // 分配一个sock结构体
    sk = (struct sock *) kmalloc(sizeof(*sk), GFP_KERNEL);
    switch(sock->type) 
    {
        case SOCK_STREAM:
            protocol = IPPROTO_TCP;
            // 函数集
            prot = &tcp_prot;
            break;

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

    }
    // sock结构体的socket字段指向上层的socket结构体
    sk->socket = sock;
    // 省略一堆对sock结构体的初始化代码
}

我们发现创建一个socket的时候,申请了一个socket结构体,同时也申请了一个sock结构体。为什么需要两个结构体,并且这两个结构体关联在一起呢?这要说到网络协议的复杂性,而这个设计就是linux对这个复杂性的解决方案。我们回头看看socket函数的参数。

代码语言:javascript
复制
socket(int family, int type, int protocol)

family是协议簇,比如unix域、ipv4、ipv6,type是在第一个参数的基础上的子分类。比如ipv4下有tcp、udp、raw、packet。protocol对tcp、udp没用,对raw、packet的话是标记上层协议类型。这好比一棵树一样,从根节点开始,有很多分支。socket结构体是整个网络协议实现的最上层结构,是第一层抽象。根据协议簇的不同,有不同的实现函数,在同一协议簇下,也有不同的子分类,比如ipv4下有tcp、udp等。不同子类具体的逻辑也不一样。即数据结构和算法都不一样。所以socket结构体有一个data字段,他是自定义的,对于ipv4的实现,他是指向一个sock结构体,对于unix域的实现,unix_proto_data结构体。这就解决了不同协议簇(family)不同实现的问题。那对于同一协议簇下的不同子类型,又如何实现呢?比如ipv4下的tcp、udp。linux给出的方案是在sock结构体中定义一个字段,根据子类型type的值,指向不同的底层协议函数集。

在这里插入图片描述
在这里插入图片描述

在申请完sock结构体并且和socket结构体互相关联后。这时候我们拿到了一个inode,一个socket结构体,一个sock结构体。然后根据inode拿到一个file和fd文件描述符。最后返回fd给用户。内容结构图如下。

在这里插入图片描述
在这里插入图片描述

这就是socket函数返回后的内存结构体。后续我们调用bind,listen等等函数,传入fd,系统就会根据上面图的指向,一直找到tcp函数集,执行对应的函数,对于udp也是一样,不同是tcp函数集变成udp函数集。这一篇我们先介绍socket函数的逻辑,下面继续分析socket编程系列函数的实现。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档