zoukankan      html  css  js  c++  java
  • unix socket,stream vs 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;
        }

  • 相关阅读:
    互联网实习笔记之shell笔记
    互联网实习笔记之30天总结
    记被论文排版虐的一天
    大论文排版技巧
    Matlab2014a使用VS2015混合编译
    腾讯云服务器创建swap空间
    使用vs2015开发linux:Ubuntu程序
    使用VS2015远程GDB调试
    nullptr、NULL、null和0
    《将博客搬至CSDN》
  • 原文地址:https://www.cnblogs.com/tsecer/p/10487408.html
Copyright © 2011-2022 走看看