Chinaunix首页 | 论坛 | 博客
  • 博客访问: 766603
  • 博文数量: 247
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 501
  • 用 户 组: 普通用户
  • 注册时间: 2013-07-12 21:53
个人简介

系统未建立

文章分类

全部博文(247)

文章存档

2021年(1)

2020年(3)

2019年(5)

2018年(3)

2017年(44)

2016年(75)

2015年(52)

2014年(63)

2013年(1)

我的朋友

分类: LINUX

2016-05-23 12:02:57

         本文的主要内容有:1. Linux为什么能在进程间传递SOCKET文件描述?让读者知其然,更知其所以然。2. 怎么样传输和传输中应该注意哪些问题,为什么。3. 结合上篇《初探SSL编程》讲讲如果是SSLsocket该怎么解决问题。

         其实,不管是Linux还是windows都会有自己的进程空间,虽然Linux的子进程会自动继承父进程已经打开的文件描述符,但是在实际应用中,可能需要在fork子进程之后再传递打开的文件描述符。(我的下一篇文章就会分析为什么在我们的产品中不能通过每一个socket连接之后fork一个进程来进行通信)。今天我们就讲讲怎样在fork子进程之后再传递打开的socket文件描述符,最常见的就是accept函返回的SOCKET FD.(注意其他文件描述也是可以的)

         首先,我们要先弄清楚为什么能传递?,众所周知,SOCKET描述也是文件描述符,只不过该文件描述所申请的文件是在网络文件系统的空间申请的,下面来看看调用下面这个Linux系统函数过程中,怎样分配SOCKET描述符和文件的,就弄清楚为什么能传递这个问题了。

         int server_fd = socket(AF_INET, SOCK_STREAM, 0 );

这个函数会调用系统内核中的sys_socket(),而在这个函数中在初始化struct socket struct sock之后,就会调用sock_map_fd函数,来分配socket所用的文件描述符和文件空间。下面看看这个函数:

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

}

首先是从文件系统中分配一个文件描述符fd,至于怎么分配文件描述符不是本文重点,具体请看我的博客《Linux位图算法》。分配好文件描述符之后,就要调用sock_alloc_file到网络空间分配文件,这个函数较多, 我们只看关键代码两行就够了:

sock->file = file;

file->private_data = sock;

看到这里,我们就会有这么一个逻辑,原来对于没有socket文件描述符都打开了一个文件,系统在使用socket接口进行操作的时候,都要通过这个文件来获取socket结构,并对其相应的操作,那么我们只要有文件描述符,就可以中file结构中的private_data字段获取socket结构。而文件系统是个进程所共有的,所以在Linux中同意主机上的socket文件描述符是可以传递。第一个问题解决。

其次,第二个问题,怎样传输,这里只告诉传输方法和平时应用的时候注意哪些,为什么等,而不涉及内核是怎样支持其传递,因涉及的内容太多。(后面我的Linux网络通信源码分析系列都会讲清楚,敬请期待。),

第一步: 首先在父子进程间建立SOCKET Unix域连接作为消息传递的通道,这样父子进程就可以通信了,

第二步,假设accept函数返回的SOCKET描述符是client_fd,调用dup_fd =dup(client_fd) ;然后将dup_fd通过sendmsg函数发送子进程,关闭client_fd,close(client_fd);为什么需要这么做,而不是直接就把client_fd发送过去?1在发往子进程网络的过程,描述符依然还是可以接收数据,这时候父进程select捕捉到了该事件,应该读取数据,而导致子进程捕捉不到该事件,而没法读到数据。导致数据漏读。如果是先dup一个出来把原来的关闭,等到dup_fd到了子进程之后,即可相应该事件。

第三步,子进程通过recvmsg接收到dup_fd之后,也得调用 server_fd= dup(dup_fd),然后关闭dup_fd,close(dup_fd);为什么?同样是在传输中接收到数据,这样dup_fd没法捕捉到。dup之后就会获取响应事件。

接下来看看sendmsgrecvmsg函数,以及需要注意的地方:

#include

#include

int sendmsg(int s, const struct msghdr *msg, unsigned int flags);

int recvmsg(int s, struct msghdr *msg, unsigned int flags);

 

struct msghdr {

    void        *msg_name;

    socklen_t    msg_namelen;

    struct iovec  *msg_iov;

    size_t       msg_iovlen;

    void        *msg_control;

    size_t       msg_controllen;

    int          msg_flags;

};

 

struct iovec {

     ptr_t iov_base; /* Starting address */

     size_t iov_len; /* Length in bytes */

};

 

struct cmsghdr {

    socklen_t cmsg_len;

    int       cmsg_level;

    int       cmsg_type;

    /* u_char     cmsg_data[]; */

};

咋一看, 这文件描述符放哪儿呢?看看如下伪代码:  

struct iovec iov[1];

    // 有些系统使用的是旧的msg_accrights域来传递描述符,Linux下是新的msg_control字段

#ifdef HAVE_MSGHDR_MSG_CONTROL

union{

    struct cmsghdr cm;

        char control[CMSG_SPACE(sizeof(int))];

    }control_un;

    // 设置辅助缓冲区和长度

    msg.msg_control = control_un.control;

    msg.msg_controllen = sizeof(control_un.control);

    // 只需要一组附属数据就够了,直接通过CMSG_FIRSTHDR取得

    struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg);

    // 设置必要的字段,数据和长度

    cmptr->cmsg_len = CMSG_LEN(sizeof(int)); // fd类型是int,设置长度

    cmptr->cmsg_level = SOL_SOCKET;

cmptr->cmsg_type = SCM_RIGHTS;  // 指明发送的是描述符

 

//数据缓冲区,至少一个1个字节

iov[0].iov_base = ptr;

iov[0].iov_len = nbytes;

msg.msg_iov = iov;

msg.msg_iovlen = 1;

*((int*)CMSG_DATA(cmptr)) = dup_fd; // dup出来的fd写入辅助数据中

至于子进程代码,这里就不多说了,各位可以根据父进程代码,自己去写就OK了,看看需要注意的地方:

  1. msghdr msg_control 缓冲区必须与 cmghdr 结构对齐,可以看到我们使用了现使用了一个 union 结构来保证这一点。

  2. 数据缓冲区中至少要有一个字节的数据。

最后我们用一张时序图来表示SSL套接口的传输,看看我们是怎么解决问题的。

阅读(1078) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~