• 从unix类套接口看stream与dgram区别及抽象套接口


    一、unix套接字
    这 种形式的套接字和通常的计算机间通讯不同,它是用来进行计算机内部进程间通讯的一种方式。大家比较经常接触到的进程间通讯方式可能是管道(无名和命名)、 消息队列、共享内存等,可能对这个使用的比较少。那么我们可以想象一下这种通讯方式和前几种通讯方式相比,它的特殊之处在哪里?
    作为一个套接字, 不管它是用文件的形式呈现,还是以socket的形式呈现,它总归具有socket的基因,不然它就不放在内核的net文件夹下了。一个socket的特 征就是一个client/server的模式,而server的一个重要特点就是在某一个特定的地址只能有一个侦听服务者,而客户端是未知的任意多。想一 下消息队列和命名管道,它的接收者和发送者都是不确定的多对多关系。
    那么可能有些同学会觉得,只要大家约定好,侦听的只有一个就好了,没必要专门折腾一个新的套接口吧。事实上很多事情的确是必须要使用制度保证的,就像我们原来假设如果大家遵守道德就可以避免社会丑恶一样,骚年,你太天真了。
    另 一方面,一个最为合适模型能够准确的反映出一个真实的世界,同样可以减少对于工程理解的问题。如果可能存在多对多的模型,那么有些人可能就会猜测这个地方 是不是故意为了实现之后的多对多,合适的才是最好的。考虑一下常用的桌面系统,里面很多的功能都应该是一个人来提供集中式服务的,仅此一家,绝无分店。例 如对于整个桌面的裁剪,例如系统的剪切板。随便打开一个gnome桌面系统,大家应该可以看到很多的unix类型的套接字。
    二、绑定同一个端口时何处出错返回
    事 实上之前的很多描述我几乎查看资料,只是信口开河,但是我不是一个随便的人,所以我还是要验证一下,google一下unix socket找一个例子,验证了一个套接口只能有一个bind操作的实施,这没有什么,写个程序测试一下就可以完成,你甚至不用写程序,在网上下载一个源 代码编译运行一下即可。但是这都不是我们想要的,真正想要的不仅是猜得到开始,猜得到结局,好药猜得到过程。那么这里的问题就是,对于bind系统系统调 用,到达内核之后是从哪里错误返回的呢?
    同样是看了一下代码,我的猜测是从 unix_bind---->>>vfs_mknod--->>>ext2_mknod--->>>ext2_add_nondir--->>>ext2_add_link
                err = -EEXIST;
                if (ext2_match (namelen, name, de))
                    goto out_unlock;
    我 猜测是从这里返回的。同样,作为一个严谨的人,我还是要验证一下,因为今天看了似懂非懂的看了一部电影叫做《蝴蝶效应》,内容并不重要,重要的是此时此刻 我想表达的意思是:一个错误可能会引发更多的错误。老实说,测试例子并不是我从头写的,同样是拷贝下来直接编译测试的,由于这种代码到网上随便一搜就有, 所以我就不在这里再列一份,增加互联网的负担了:
    (gdb) s
    may_create (nd=0x0, child=0xcfe2246c, dir=0xcfbccb8c) at fs/namei.c:1427
    1427        if (child->d_inode)
    (gdb) n
    1428            return -EEXIST;
    (gdb) bt
    #0  may_create (nd=0x0, child=0xcfe2246c, dir=0xcfbccb8c) at fs/namei.c:1428
    #1  vfs_mknod (nd=0x0, child=0xcfe2246c, dir=0xcfbccb8c) at fs/namei.c:1841
    #2  0xc079c43e in unix_bind (sock=0xcf2d1980, uaddr=0xcff99ecc, addr_len=13)
        at net/unix/af_unix.c:811
    #3  0xc06d6119 in sys_bind (fd=3, umyaddr=0xbfcf9f0a, addrlen=12) at net/socket.c:1302
    #4  0xc06d7504 in sys_socketcall (call=2, args=0xbfcf9ef0) at net/socket.c:1992
    #5  0xc0107a84 in ?? ()
    #6  0x00000002 in ?? ()
    #7  0xbfcf9ef0 in ?? ()
    #8  0x00000000 in ?? ()
    (gdb) 
    可见虽然猜对了结局,但是没有猜对过程,它的结果是在中间一个不起眼的地方返回的,而根本没有达到底层的文件系统,也就是在通用的vfs文件系统层就完成了这个判断和检测。
    三、stream和dgram的区别
    1、发送不同
    事实上我也没有看出有什么比较明显的不同的地方,最为明显的地方是stream发送的时候必须是已经执行过connect操作而dgram则没有这个检测。
    另一个就是在超时等待的时间上,两者在发送skb的申请上都进行了判断,那就是一个socket不可能占用系统中所有的skb(也就是内存)资源,所以如果这个接收的另一端一直没有接收,那么这个地方就需要在申请skb的地方阻塞。
    但是对于stream来说,如果skb申请完成,那么就不存在超时的问题,这个消息一定是会到达对方的。相反,对于dgram来说,即使申请到了skb,在发送的时候它同样会存在一个超时检测。下面是dgram的代码
    if (unix_peer(other) != sk &&
            (skb_queue_len(&other->sk_receive_queue) >
             other->sk_max_ack_backlog
    )) {
            if (!timeo) {
                err = -EAGAIN;
                goto out_unlock;
            }

            timeo = unix_wait_for_peer(other, timeo);

            err = sock_intr_errno(timeo);
            if (signal_pending(current))
                goto out_free;

            goto restart;
        }

        skb_queue_tail(&other->sk_receive_queue, skb);
    不过大家也不用大惊小怪,因为stream其实也有这个检测,只是它的检测并不是在发送的时候完成检测,而是在connect的地方完成的检测:
    static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr,
                       int addr_len, int flags)
    ……

        if (skb_queue_len(&other->sk_receive_queue) >
            other->sk_max_ack_backlog
    ) {
            err = -EAGAIN;
            if (!timeo)
                goto out_unlock;

            timeo = unix_wait_for_peer(other, timeo);
    大 家可能比较好奇,这里的timeo的默认值是多少呢?默认是一个比较大的值,也就是最大的正整数,如果以秒为单位,在这么长的时间内,太阳可能已经爆炸了 (好像记得太阳的寿命是50亿年?)unix_create1--->>>sock_init_data
    #define LONG_MAX    ((long)(~0UL>>1))
    #define    MAX_SCHEDULE_TIMEOUT    LONG_MAX

        sk->sk_rcvtimeo        =    MAX_SCHEDULE_TIMEOUT;
        sk->sk_sndtimeo        =    MAX_SCHEDULE_TIMEOUT;

    2、接收不同
    这个是一个比较有意思的不同,所以大家要注意。
    static int unix_dgram_recvmsg(struct kiocb *iocb, struct socket *sock,
                      struct msghdr *msg, size_t size,
                      int flags)
        if (size > skb->len)
            size = skb->len;
        else if (size < skb->len)
            msg->msg_flags |= MSG_TRUNC;

        err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, size);
    对应地,字节流的处理过程为
    static int unix_stream_recvmsg(struct kiocb *iocb, struct socket *sock,
                       struct msghdr *msg, size_t size,
                       int flags)

            skb = skb_dequeue(&sk->sk_receive_queue);
    ……
            chunk = min_t(unsigned int, skb->len, size);
            copied += chunk;
            size -= chunk;
            /* Mark read part of skb as used */
            if (!(flags & MSG_PEEK))
            {
    ……

                /* put the skb back if we didn't use it up.. */
                if (skb->len)
                {
                    skb_queue_head(&sk->sk_receive_queue, skb);
                    break;
                }

                kfree_skb(skb);
    }
    这个明显的区别就是:对于dgram来说,如果此次调用给出的接收空间小于报文大小,那么多出来的部分会从系统中丢弃,所以说是比较败家的;而字节流的操作则比较节俭,需要取多少就取多少,剩余的部分再次放回缓冲区,等待下一次读取
    但是这里大家要和IP层的报文分段(frag)区分开来,dgram格式的报文同样会在ip层分段,如果说报文比较大的话。
    四、抽象套接字
    下面这些套接字的特点就是都是以@开始的,显然它们是有故事的。
    [tsecer@Harry strorint]$ cat /proc/net/unix | grep @
    ed5c3600: 00000002 00000000 00010000 0001 01 12469 @/tmp/.ICE-unix/1498
    ee898400: 00000002 00000000 00010000 0001 01  9473 @/var/run/hald/dbus-c73WZHqTNc
    f650f400: 00000002 00000000 00000000 0002 01  5039 @/com/ubuntu/upstart
    f64a8400: 00000002 00000000 00010000 0001 01 11135 @/tmp/.X11-unix/X0
    ee85fe00: 00000002 00000000 00010000 0001 01  9450 @/var/run/hald/dbus-Q80GTMdfVP
    f6a79000: 00000002 00000000 00000000 0002 01  5855 @/org/kernel/udev/udevd
    ed513000: 00000002 00000000 00010000 0001 01 11360 @/tmp/gdm-session-kwySxIoQ
    ee8afc00: 00000002 00000000 00000000 0002 01  9518 @/org/freedesktop/hal/udev_event
    ed500c00: 00000002 00000000 00010000 0001 01 11227 @/tmp/gdm-greeter-dgvIxciO
    ed51a600: 00000002 00000000 00010000 0001 01 12359 @/tmp/dbus-nwC7rTEfI5
    dc5afe00: 00000003 00000000 00000000 0001 03 455530 @/tmp/.X11-unix/X0
    1、如何创建
    创 建的方法也比较简单,就是在设置套接字地址的时候开始的第一个字符是字符串结束的''标志。所有的C程序员都知道这是一个字符串结束的标志,所以放在 一个路径的开始是非常的不厚道的。但是这里通过另外辅助的结构来表示它之后还有一个字符串,那就是通过name_len来表示,如果说name_len大 于零而路径的第一个字符为空(或者path是一个空字符串),那么这个0之后的路径才是真正的路径。
    2、为什么有这种套接字
    问 题在于很多时候不同的用户是可以被chroot的,这个最为常见的就是FTP的客户端,一个用户可能会设置不同的根目录,这样在这个用户看文件系统就有一 个坐井观天的感觉,它看到的文件系统和整个系统中的文件系统并不相同。例如"/tmp/xxxx",因为它看到的是系统中一个子目录,所以这些用户通过这 样的路径就无法找到约定好的侦听套接口。
    为了解决这个问题,就引入了抽象套接口(abstract socket)的概念,也就是内核并不会真正的到路径中指定的地方(不同的文件系统)来创建文件,而是把路径作为唯一的一个字符串标志来进行hash,这 样绕过文件系统而使用最为原始的字符串作为套接口的唯一标示,这样不同的用户及时被切换了根目录,只要它们使用的字符串相同,那么就可以找到这些套接口。
    3、相关处理代码展示
        if (!sunaddr->sun_path[0]) {抽象套接口处理,只是根据名字(字符串比较)。
            err = -EADDRINUSE;
            if (__unix_find_socket_byname(sunaddr, addr_len,
                              sk->sk_type, hash)) {
                unix_release_addr(addr);
                goto out_unlock;
            }

            list = &unix_socket_table[addr->hash];
        } else {//非抽象,直接使用文件系统的inode,之前的文件系统路径查找一个完成
            list = &unix_socket_table[dentry->d_inode->i_ino & (UNIX_HASH_SIZE-1)];
            u->dentry = nd.dentry;
            u->mnt    = nd.mnt;
        }

  • 相关阅读:
    楼宇及工业自动化总线协议介绍
    PROFIBUS,PROFINET,Ethernet三者的区别
    转:OSAL多任务资源分配机制
    Zigbee系列 学习笔记二(工程文件分析)
    Zigbee系列 学习笔记一(协议栈)
    STC12C5A60S2单片机 PWM调试
    单片机 IO口配置模式介绍
    串口通信校验方式
    STC12C5A60S2单片机 串口调试
    本地Nexus 3.3.2 启动
  • 原文地址:https://www.cnblogs.com/tsecer/p/10487401.html
Copyright © 2020-2023  润新知