zoukankan      html  css  js  c++  java
  • UNP总结 Chapter 15~17 Unix域协议、非阻塞式I/O、ioctl操作

    一、Unix域协议

    Unix域协议并不是一个实际的协议族,它只是在同一台主机上进行客户-服务器通信时,使用与在不同主机上的客户和服务器间通信时相同的API(套接口或XTI)的一种方法

    当客户和服务器在同一台主机上时,Unix域协议是IPC通信方式的一种替代品。

    Unix域提供了两种类型的套接口:字节流套接口(与TCP类似)和数据报套接口(与UDP类似)。

    1.Unix域套接口地址结构

    struct sockaddr_un {
      sa_family_t sun_family;     /* AF_LOCAL */
      char        sun_path[104];  /* null-terminated pathname */
    };

    存放sun_path数组中的路径名必须以空字符结尾

    2.socketpair函数

    socketpair函数建立一对相互连接的套接口,这个函数支队Unix域套接口使用。

    #include <sys/socket.h>
     
    int socketpair(int family, int type, int protocol, int sockfd[2]);
    //返回: 成功返回0,出错返回-1

    family必须为AF_LOCAL,protocol必须为0,type可以是SOCK_STREAM或SOCK_DGRAM,新创建的两个套接口描述字作为sockfd[0]和sockfd[1]返回

    创建的两个套接口是没有名字的,即没有涉及隐式bind。

    指定type参数为SOCK_STREAM调用socketpair所得到的结果称为流管道(stream pipe),这和一般的Unix管道(由pipe函数生成)类似,但流管道是全双工的,即两个描述字都是可读写的

    3.描述符传递

    一般传递描述符的方法:

    • 在fork调用后,子进程共享父进程的所有打开的描述字
    • 在调用exec时所有描述字仍保持打开

    第一个例子中进程打开一个描述字,调用fork,然后父进程关闭描述字,让子进程处理这个描述字。这样将一个打开的描述字从父进程传递到子进程。

    两个进程之间传递描述符涉及的步骤:

    1).创建一个字节流的或数据报的Unix域套接口

    2).进程可以用任何返回描述字的Unix函数打开一个描述字:譬如open, pipe, mkfifo, socket或accept。

    3).发送进程建立一个msghdr结构,其中包含要传递的描述字。

    4).接收进程调用recvmsg在来自步骤1的Unix域套接字上接收这个描述符,传递描述字不是传递描述字的编号,而是在接收进程中创建一个新的描述字,指向内核的文件表中与发送进程发送的描述字相同的项。

    4.程序实例

    #include     "unp.h"
    
    int
    my_open(const char *pathname, int mode)
    {
        int     fd, sockfd[2], status;
        pid_t   childpid;
        char    c, argsockfd[10], argmode[10];
    
        Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);
    
        if ( (childpid = Fork()) == 0) { /* child process */
            Close(sockfd[0]);
            snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[1]);
            snprintf(argmode, sizeof(argmode), "%d", mode);
            execl("./openfile", "openfile", argsockfd, pathname, argmode,
                  (char *) NULL);
             err_sys("execl error");
        }
    
        /* parent process - wait for the child to terminate */
        Close(sockfd[1]);           /* close the end we don't use */
    
        Waitpid(childpid, &status, 0);
        if (WIFEXITED(status) == 0)
            err_quit("child did not terminate");
        if ( (status = WEXITSTATUS(status)) == 0)
            Read_fd(sockfd[0], &c, 1, &fd);
        else {
            errno = status;         /* set errno value from child's status */
            fd = -1;
        }
        Close(sockfd[0]);
        return (fd);
    }

    二、非阻塞式I/O

    1.概述

    1).输入操作: read, readv, recv, recvfrom和recvmsg函数。

    2).输出操作: write, writev, send, sendto和sendmsg函数。

    3).接收外来连接: accept函数

    4).初始化外出的连接: 用于TCP的connect函数

    2.非阻塞读和写

    我们维护两个缓冲区: to容纳从标准输入到服务器去的数据,fr容纳自服务器到标准输出来的数据

    这里程序仅给出包含非阻塞的前半部分:

    #include     "unp.h"
    
    void
    str_cli(FILE *fp, int sockfd)
    {
        int     maxfdp1, val, stdineof;
        ssize_t n, nwritten;
        fd_set  rset, wset;
        char    to[MAXLINE], fr[MAXLINE];
        char    *toiptr, *tooptr, *friptr, *froptr;
    
        val = Fcntl(sockfd, F_GETFL, 0);
        Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);
    
        val = Fcntl(STDIN_FILENO, F_GETFL, 0);
        Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);
    
        val = Fcntl(STDOUT_FILENO, F_GETFL, 0);
        Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);
    
        toiptr = tooptr = to;       /* initialize buffer pointers */
        friptr = froptr = fr;
        stdineof = 0;
    
        maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
        for ( ; ; ) {
            FD_ZERO(&rset);
            FD_ZERO(&wset);
            if (stdineof == 0 && toiptr < &to[MAXLINE])
                FD_SET(STDIN_FILENO, &rset);     /* read from stdin */
            if (friptr < &fr[MAXLINE])
                FD_SET(sockfd, &rset);  /* read from socket */
            if (tooptr != toiptr)
                FD_SET(sockfd, &wset);  /* data to write to socket */
            if (froptr != friptr)
                FD_SET(STDOUT_FILENO, &wset);   /* data to write to stdout */
             Select(maxfdp1, &rset, &wset, NULL, NULL);

    3.非阻塞connect

    非阻塞的connect有三种用途:

    1). 我们可以在三路握手同时做一些其他的处理。完成一个connect要花一个往返时间完成,而且可以是在任何地方,从几个毫秒的局域网到几百毫秒或几秒的广域网。

    2). 可以用这种技术同时建立多个连接。这在Web浏览器中很普遍

    3). 由于我们用select等待连接的完成,因此可以给select设置一个时间限制,从而缩短connect的超时时间。

    非阻塞connect虽然听似简单,却有一些必须处理的细节

    1).即使套接口是非阻塞的,如果连接的服务器在同一台主机上,那么在调用connect建立连接时,连接通常会立即建立成功.我们必须处理这种情况;

    2).源自Berkeley的实现(和Posix.1g)有两条与select和非阻塞IO相关的规则:

    • 当连接建立成功时,套接字描述符变成可写;
    • 当连接出错时,套接子描述符变成既可读又可写;

    注意:当一个套接口出错时,它会被select调用标记为既可读又可写;

    程序实例:

    #include     "unp.h"
    
    int
    connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
    {
        int     flags, n, error;
        socklen_t len;
        fd_set rset, wset;
        struct timeval tval;
    
        flags = Fcntl(sockfd, F_GETFL, 0);
        Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    
        error = 0;
        if ( (n = connect(sockfd, saptr, salen)) < 0)
            if (errno != EINPROGRESS)
                return (-1);
    
        /* Do whatever we want while the connect is taking place. */
    
        if (n == 0)
            goto done;               /* connect completed immediately */
    
        FD_ZERO(&rset);
        FD_SET(sockfd, &rset);
        wset = rset;
        tval.tv_sec = nsec;
        tval.tv_usec = 0;
    
        if ( (n = Select(sockfd + 1, &rset, &wset, NULL,
                        nsec ? &tval : NULL)) == 0) {
            close(sockfd);          /* timeout */
            errno = ETIMEDOUT;
            return (-1);
        }
    
        if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
            len = sizeof(error);
            if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
                return (-1);     /* Solaris pending error */
        } else
            err_quit("select error: sockfd not set");
    
      done:
        Fcntl(sockfd, F_SETFL, flags);  /* restore file status flags */
    
        if (error) {
           close(sockfd);           /* just in case */
           errno = error;
           return (-1);
        }
        return (0);
    }

    4.非阻塞accept

    阻塞模式下,服务器会一直阻塞在accept调用上,知道其他某个客户建立一个连接为止,但是在此期间,服务器单纯阻塞在accept调用上,无法处理任何其他已就绪的描述符

    非阻塞accept模式下解决办法

    1).当使用select获悉某个监听套接字上何时有已完成连接准备被accept时候,总是把这个监听套接字设置为非阻塞

    2).在后续的accept调用忽略以下错误:EWOULDBLOCK(客户终止连接时)、ECONNABORTED(客户终止连接时)、EPROTO(客户终止连接时)和EINTR(如果有信号被捕获)

    三、ioctl操作

    网络程序中ioctl常用于在程序启动时获得主机上所有接口的信息:接口的地址,接口是否支持广播,是否支持多播,等等。

    #include <unistd.h>
     
    int ioctl(int fd, int request, ... /* void *arg */ );
    //返回:若成功0,出错-1

    其中第三个参数总是一个指针,但指针的类型依赖于request参数,我们可以把和网络相关的请求(request)划分为6类:

    • 套接口操作
    • 文件操作
    • 接口操作
    • ARP高速缓存操作
    • 路由表操作
    • 流系统

    用法详见UNP

  • 相关阅读:
    IAR EWARM PRINTF/SCANF FORMATTER
    Windows Self Signed Driver
    Remove a Driver Package from the Driver Store
    CMSIS-DAP调试器
    CHM文件无法查看内容解决办法
    HRESULT 0x80131515 解决方法
    dmalloc 原文 翻译整理
    Linux错误代码
    Windows 错误代码
    调码王版本历史
  • 原文地址:https://www.cnblogs.com/biyeymyhjob/p/2625849.html
Copyright © 2011-2022 走看看