zoukankan      html  css  js  c++  java
  • APUE 学习笔记(十一) 网络IPC:套接字

    1. 网络IPC 

    套接字接口既可以用于计算机之间进程通信,也可以用于计算机内部进程通信
     
    套接字描述符在Unix系统中是用文件描述符实现的
     
    /* 创建一个套接字 */
    #include <sys/socket.h>
    int socket(int domain, int type, int protocol);

    protocol通常是0,表示按给定的域或套接字类型选择默认协议
    在AF_INET中,SOCK_STREAM的默认协议是 TCP
    在AF_INET中,SOCK_DGRAM的默认协议是 UDP
     
    套接字通信是双向的,可以采用 shutdown来禁止套接字上的 输入/输出
     
    #include <sys/socket.h>
    int shutdown(int sockfd, int how);
    how为SHUT_RD(关闭读端)时,无法从套接字上读取数据
    how为SHUT_WR(关闭写端)时,无法向套接字发送数据
     

    2. 套接字地址

    大端:最大字节地址对应于数字最低有效字节,阅读序
    小端:最小字节地址对应于数字最低有效字节,逆阅读序
     
    例如:0x04030201
    大端机:04==> ch[0]  01==> ch[3]
    小端机:04==> ch[3]  01==> ch[0]
    不管字节如何排序,数字最高位总是在左边,最低位总是在右边

     

    TCP/IP协议栈采用大端字节序
    TCP/IP提供4个通用函数来处理字节序转换:
    #include <arpa/inet.h>
    
    uint32_t htonl(uint32_t hostint32);   // 返回值:以网络字节序表示的32位整型数
    uint16_t htons(uint16_t hostint16);  // 返回值:以网络字节序表示的16位整型数
    uint32_t ntohl(uint32_t  netint32);   // 返回值:以主机字节序表示的32位整型数
    uint16_t ntohl(uint16_t  netint16);   // 返回值:以主机字节序表示的16位整型数
    "h"代表 host主机字节序, “n”代表net网络字节序,“l”代表32位long整型,“s”代表16位short整型
     
    通用地址结构sockaddr
    struct sockaddr {
        sa_family_t   sa_family;         /* address family */
        char          sa_data[14];     /* variable-length address */
    };
    struct sockaddr 一共为18字节(32位地址+14字节填充) 

     在IPv4 套接字地址结构 sockaddr_in (in表示internet网络):

    struct in_addr {
        in_addr_in   s_addr;           /* IPv4 address*/
    };
    
    struct sockaddr_in {
        sa_family_t       sin_family;   /* address family */
        in_port_t         sin_port;      /* port number */
        struct in_addr    sin_addr;     /* IPv4 address */
        unsigned char     sin_zero[8];
    };
    struct sockaddr_in 一共为18字节(4字节family + 16位端口号 + 32位IPv4地址 + 8字节填充)

     二进制地址格式和 点分十进制格式转换:

    #include <arpa/inet.h>
    
    const char* inet_ntop(int domain, const char* addr, char* str, socklen_t size);
    int         inet_pton(int domain, const char* str, void* str);
    参数domain可以支持 AF_INET和AF_INET6
     
    地址信息查询:
    #include <sys/socket.h>
    #include <netdb.h>
    
    int   getaddrinfo(const char* host, const char* service, const struct addrinfo* hint, struct addrinfo* res);
    void  freeaddrinfo(struct addrinfo* ai);
    struct addrinfo {
        int                       ai_flags;        /* customize behavior */
        int                       ai_family;       /* address family */
        int                       ai_socktype;     /* socket type */
        int                       ai_protocol;     /* protocol */
        socklen_t                 ai_addrlen;      /* length in bytes of address */
        struct sockaddr*          ai_addr;         /* address */
        char*                     ai_canonname;
        struct addrinfo*          ai_next;        /* next in list */
        ....
    }; 
    函数getaddrinfo 可以将 IPv4和IPv6代码统一起来,所以网络编程中套接字地址信息都必须使用此函数,便于统一和移植
     
    函数getaddrinfo 允许将一个主机名和服务器名映射到一个地址,需要提供 host主机名 或 service 服务器名,否则指针设为空
    主机名字可以是一个 节点名或者点分十进制表示的主机地址
    函数getaddrinfo返回 一个 结构体 struct addrinfo的链表,freeaddrinfo来释放这个结构体链表
    #include <sys/socket.h>
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    
    void print_family(struct addrinfo* aip)
    {
        fprintf(stdout, "family:");
        switch (aip->ai_family) {
            case AF_INET:
                fprintf(stdout, "inet");
                break;
            case AF_INET6:
                fprintf(stdout, "inet6");
                break;
            case AF_UNIX:
                fprintf(stdout, "unix");
                break;
            case AF_UNSPEC:
                fprintf(stdout, "unspecfied");
                break;
            default:
                fprintf(stdout, "unknown");
        }
    }
    
    void print_type(struct addrinfo* aip)
    {
        fprintf(stdout, "type");
        switch (aip->ai_socktype) {
            case SOCK_STREAM:
                fprintf(stdout, "stream");
                break;
            case SOCK_DGRAM:
                fprintf(stdout, "datagram");
                break;
            case SOCK_SEQPACKET:
                fprintf(stdout, "seqpacket");
                break;
            case SOCK_RAW:
               fprintf(stdout, "raw");
                break;
            default:
                fprintf(stdout, "unknown (%d)", aip->ai_socktype);
        }
    }
    
    void print_protocol(struct addrinfo* aip)
    {
        fprintf(stdout, "protocol");
        switch (aip->ai_protocol) {
            case 0:
                fprintf(stdout, "default");
                break;
            case IPPROTO_TCP:
               fprintf(stdout, "tcp");
                break;
            case IPPROTO_UDP:
                fprintf(stdout, "udp");
                break;
            case IPPROTO_RAW:
                fprintf(stdout, "raw");
                break;
            default:
                fprintf(stdout, "unknown (%d)", aip->ai_protocol);
        }
    }
    
    void print_flags(struct addrinfo* aip)
    {
        fprintf(stdout, "flags");
        if (aip->ai_flags == 0) {
           fprintf(stdout, "0");
        } else {
            if (aip->ai_flags & AI_PASSIVE) {
                fprintf(stdout, "passive");
            }
            if (aip->ai_flags & AI_CANONNAME) {
                fprintf(stdout, "canon");
            }
            if (aip->ai_flags & AI_NUMERICHOST) {
                fprintf(stdout, "numhost");
            }
        }
    }
    int main(int argc, char* argv[])
    {
        struct addrinfo* ailist = NULL;
        struct addrinfo* aip = NULL;
        struct addrinfo hint;
        struct sockaddr_in* sinp;
        const  char* addr = NULL;
        char   abuf[INET_ADDRSTRLEN];
    
        if (argc != 3) {
            fprintf(stdout, "usage:%s <hostname> <service>", argv[0]);
            return 1;
        }
    
        hint.ai_flags = AI_CANONNAME;
        hint.ai_family = 0;
        hint.ai_socktype = 0;
        hint.ai_protocol = 0;
        hint.ai_addrlen = 0;
        hint.ai_canonname = NULL;
        hint.ai_addr = NULL;
        hint.ai_next = NULL;
        int ret = getaddrinfo(argv[1], argv[2], &hint, &ailist);
        if (ret != 0) {
            fprintf(stderr, "getaddrinfo error
    ");
            return 1;
        }
    
        for (aip = ailist; aip != NULL; aip = aip->ai_next) {
            print_flags(aip);
            print_family(aip);
            print_type(aip);
            print_protocol(aip);
            fprintf(stdout, "
    	host %s", aip->ai_canonname ? aip->ai_canonname : '-');
            if (aip->ai_family == AF_INET) {
                sinp = (struct sockaddr_in*)aip->ai_addr;
                addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf, INET_ADDRSTRLEN);
                fprintf(stdout, "address %s", addr ? addr : "unknown");
                fprintf(stdout, "port %d", ntohs(sinp->sin_port));
            }
           fprintf(stdout, "
    ");
        }
        return 0;
    }

    套接字与地址绑定:

    #include <sys/socket.h>
    
    int bind(int sockfd, const struct sockaddr* addr, socklen_t len);
    如果将addr指定为 INADDR_ANY,则套接字可以接收到这个系统所安装的所有网卡的数据包
     

    3. 建立连接

    #include <sys/socket.h>
    int connect(int sockfd, const struct sockaddr* addr, socklen_t len);
    客户端调用connect函数,请求与服务器建立连接,addr为服务器地址
     
    处理瞬时connect错误:
    #include <sys/socket.h>
    #define MAXSLEEP 128
    
    int connect_retry(int sockfd, const struct sockaddr* addr, socklen_t len)
    {
        /* try to connect with exponential backoff */
        for (int nsec = 1; nsec <= MAXSLEEP; nsec << 1) {
            if (connect(sockfd, addr, len) == 0) {
                /* connection accepted */
                return 0;
            }
    
            /* delay before trying again */
            if (nsec <= MAXSLEEP / 2)
                sleep(nsec);
        }
        return -1;
    }
    这个函数使用了 指数补偿的算法,如果调用connect失败,进程就休眠一小段时间再尝试连接,每循环一次就加倍每次尝试的延迟
    #include <sys/socket.h>
    
    int listen(int sockfd, int backlog);
    服务器调用listen函数来宣告自己可以接受连接请求
    参数backlog提供了一个提示,用于表示该进程所要入队的连接请求数量,一旦队列满,系统会拒绝多余连接请求
     
    #include <sys/socket.h>
    int accept(int sockfd, struct sockaddr* addr, socklen_t* len); 
    服务器调用accept函数来获取连接请求并建立连接,函数返回 已连接描述符,已连接描述符与监听描述符不同
     
    addr和len都是客户端地址参数,如果不关心客户端标识,可以将这两个参数设为NULL,否则,在调用accept之前,必须将addr设为足够大的缓冲区来存放地址,accept调用返回时 会回填客户端的地址和地址大小
     
    服务器可以使用select或epoll来等待一个连接请求,一个等待连接的客户端请求套接字会以可读的形式出现

     4. 数据传输

    #include <sys/socket.h>
    
    ssize_t send(int sockfd, const  void* buf, size_t bytes, int flags);    // 等同于 write,套接字必须已连接
    ssize_t sendto(int sockfd, const void* buf, size_t bytes, int flags, const struct sockaddr* dstaddr, socklen_t dstlen);
    对于面向连接的套接字,使用send函数,目标地址蕴含在连接中,在此忽略
    对于面向无连接的套接字,使用sendto函数,必须指定 目标地址
     
     
    #include <sys/socket.h>
    
    ssize_t recv(int sockfd, void* buf, size_t bytes, int flags);  //类似于 read
    ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* addr, socklen_t* addrlen);
    对于面向连接的套接字,使用recv函数,目标地址蕴含在连接中,在此忽略
    对于面向无连接的套接字,使用recvfrom函数,必须指定 目标地址
     
     
    /* tcp_connect for client:
     * hostname or ip:   www.google.com or 127.0.0.1
     * service  or port: http or 9877
     */
    int tcp_connect(const char* hostname, const char* service) 
    {
        struct addrinfo hints;
        struct addrinfo* result;
        struct addrinfo* rp;
    
        memset(&hints, 0, sizeof(struct addrinfo));
        hints.ai_family   = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
    
        int res = getaddrinfo(hostname, service, &hints, &result);
        if (res != 0) {
                fprintf(stderr, "tcp_connect error for %s, %s: %s", hostname, service, gai_strerror(res));
                exit(0);
        }
    
        int sockfd;    
        for (rp = result; rp != NULL; rp = rp->ai_next) {
            sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
            if (sockfd < 0)
                continue;
            int rc = connect(sockfd, rp->ai_addr, rp->ai_addrlen);
            if (rc == 0)
                break;
            close(sockfd);
        }
        if (rp == NULL) {
            unix_error("tcp_connect error");
        }
        freeaddrinfo(result);
        return sockfd;
    }
    /* tcp_listen for server:
     * hostname or ip:   www.google.com or 127.0.0.1
     * service  or port: http or 9877
     */
    
    int tcp_listen(const char* hostname, const char* service, socklen_t* paddrlen) 
    {
        struct addrinfo hints;
        struct addrinfo* result;
        struct addrinfo* rp;
    
        memset(&hints, 0, sizeof(struct addrinfo));
        hints.ai_flags    = AI_PASSIVE;
        hints.ai_family   = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
    
        int res = getaddrinfo(hostname, service, &hints, &result);
        if (res != 0) {
            fprintf(stderr, "tcp_listen error for %s, %s: %s", hostname, service, gai_strerror(res));
            exit(0);
        }
    
        int listenfd;
        for (rp = result; rp != NULL; rp = rp->ai_next) {
            listenfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
            if (listenfd < 0)
                continue;
            int rc = bind(listenfd, rp->ai_addr, rp->ai_addrlen);
            if (rc == 0)
                break;
            Close(listenfd);
        }
        if (rp == NULL) {
            unix_error("tcp_listen error");
        }
        Listen(listenfd, LISTENQ);
        if (paddrlen) {
            *paddrlen = rp->ai_addrlen;
        }
        freeaddrinfo(result);
        return listenfd;
    }

    5. 套接字选项

    #include <sys/socket.h>
    
    int setsockopt(int sockfd, int level, int option, const void* val, socklen_t len);
    int getsockopt(int sockfd, int level, int option, void* val, socklen_t lenp);
     

    6. 带外数据

    带外数据 允许更高优先级的数据比普通数据优先传输,TCP支持带外数据,UDP不支持
    TCP仅支持一个字节的带外数据,但是允许带外数据在普通传输机制流之外传输,为了产生带外数据,需要在send函数中指定 MSG_OOB标志
    当带外数据出现在套接字读取队列时,select函数会返回一个文件描述符并且拥有一个异常状态挂起
     

    7. 非阻塞I/O

    recv函数没有数据可读时会阻塞等待,当套接字输出队列没有足够空间来发送消息时 send函数会阻塞
    如果套接字是非阻塞模式,这些情况下,这些函数不是阻塞而是失败,设置errno为EWOULDBLOCK或者EAGAIN
  • 相关阅读:
    node.js 基础篇
    node.js 开发环境搭建
    Velocity VelocityEngine 支持多种loader 乱码问题
    tomcat集群及session共享
    上海支付宝终面后等了两周,没能收到offer却来了杭州淘宝的电话面试
    支付宝hr终面,忐忑的等待结果
    mysql 数据范围总结
    rsync同步文件(多台机器同步代码...)
    linux基础命令
    路飞学城项目之前后端交互,跨域问题
  • 原文地址:https://www.cnblogs.com/wwwjieo0/p/3741177.html
Copyright © 2011-2022 走看看