《unix环境高级编程》--- 网络IPC:套接字

时间:2021-11-11 22:19:54

打印主机和服务信息
如果由多个协议为指定的主机提供相应的服务,程序性会打印出超过一条的信息。本例中,仅打印IPv4上的协议的地址信息,可在hint中设置ai_family打印限制在AF_INET协议族。

#include "apue.h"
#include <netdb.h>
#include <arpa/inet.h>
#if defined(BSD) || defined(MACOS)
#include <sys/socket.h>
#include <netinet.h>
#endif

void print_family(struct addrinfo *aip)
{
    printf(" family: ");
    switch(aip->ai_family)
    {
    case AF_INET:
        printf("inet");
        break;
    case AF_INET6:
        printf("inet6");
    case AF_UNIX:
        printf("unix");
    case AF_UNSPEC:
        printf("unspecified");
    default:
        printf("unknown");
    }
}

void print_type(struct addrinfo *aip)
{
    printf(" type: ");
    switch(aip->ai_socktype)
    {
    case SOCK_STREAM:
        printf("stream");
        break;
    case SOCK_DGRAM:
        printf("datagram");
        break;
    case SOCK_SEQPACKET:
        printf("seqpacket");
        break;
    case SOCK_RAW:
        printf("raw");
        break;
    default:
        printf("unknown (%d)", aip->ai_socktype);
    }
}

void print_protocol(struct addrinfo *aip)
{
    printf(" protocol: ");
    switch(aip->ai_protocol)
    {
    case 0:
        printf("default");
        break;
    case IPPROTO_TCP:
        printf("TCP");
        break;
    case IPPROTO_UDP:
        printf("UDP");
        break;
    case IPPROTO_RAW:
        printf("raw");
        break;
    default:
        printf("unkown (%d)", aip->ai_protocol);
    }
}

void print_flags(struct addrinfo *aip)
{
    printf("flags:");
    if(aip->ai_flags == 0)
    {
        printf(" 0");
    }
    else
    {
        if(aip->ai_flags & AI_PASSIVE)   /* 套接字地址用于监听绑定 */
            printf(" passive"); 
        if(aip->ai_flags & AI_CANONNAME) /* 需要一个规范名(而不是别名)*/
            printf(" canon");
        if(aip->ai_flags & AI_NUMERICHOST)  /* 以数字格式返回主机地址 */
            printf(" numhost");
    #if defined(AI_NUMERICSERV)
        if(aip->ai_flags & AI_NUMERICSERV)  /* 以端口号返回服务 */
            printf(" numserv");
    #endif
    #if defined(AI_V4MAPPED)
        if(aip->ai_flags & AI_V4MAPPED)  /* 如果没有找到IPv6地址,则返回映射到IPv6格式的IPv4地址 */
            printf(" v4mapped");
    #endif
    #if defined(AI_ALL)
        if(aip->ai_flags & AI_ALL) /* 查询配置的地址类型(IPv4或IPv6)*/
            printf(" all");
    #endif
    }
}

int main(int artc, char *argv[])
{
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;
    struct sockaddr_in *sinp;
    const char *addr;
    int err;
    char abuf[INET_ADDRSTRLEN];

    if(artc != 3)
        err_quit("usage: %s nodename service", argv[0]);

    /* int getaddrinfo(const char *restrict host, const char *restrict service, const struct addrinfo *restrict hint, struct addrinfo **restrict res); 将一个主机名和服务器名映射到一个地址 返回一个addrinfo链表,可用freeaddrinfo释放一个或多个这样的结构。 返回:成功--0,出错--错误码 host:主机名 service:服务器名 如果仅提供一个名字,另外一个必须是空指针。 主机名可以是节点名或点分十进制表示的主机地址。 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_cannonname; canonical name of host struct addrinfo *ai_next; next in list ... }; 根据某些规则,可以提供一个可选的hint来选择地址。hint是一个用于过滤地址的模板, 仅使用ai_family、ai_flags、ai_protocol和ai_socktype字段。剩余的整数字段必须设 为零,并且指针字段为空。 struct sockaddr { unsigned char sa_len; total length sa_family_t sa_family; address family char sa_data[4]; variable-length address }; */
    hint.ai_flags = AI_CANONNAME;  /* 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;
    if((err = getaddrinfo(argv[1], argv[2], &hint, &ailist)) != 0)
        err_quit("getaddrinfo error: %s", gai_strerror(err));
    for(aip = ailist; aip != NULL; aip=aip->ai_next)
    {
        print_flags(aip);
        print_family(aip);
        print_type(aip);
        print_protocol(aip);
        printf("\n\thost %s", aip->ai_canonname? aip->ai_canonname:"-");
        if(aip->ai_family == AF_INET)
        {
            /* 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]; filler }; */
            sinp = (struct sockaddr_in *)aip->ai_addr;

            /* const char *inet_ntop(int domain, const void *restrict addr, char *restrict str, socklen_t size); 将网络字节序的二进制地址转换成文本字符串格式。 返回值:若成功则返回地址字符串指针,若出错则返回NULL domain:仅支持两个值,AF_INET和AF_INET6 str:保存文本字符串的缓冲区 size:str的大小 INET_ADDRSTRLEN:定义了足够大的空间存储表示IPv4地址的文本字符串 INET6_ADDRSTRLEN:定义了足够大的空间存储表示IPv6地址的文本字符串 */
            addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf, INET_ADDRSTRLEN);

            printf(" address: %s", addr? addr : "unknown");
            printf(" port: %d", ntohs(sinp->sin_port));
        }
        printf("\n");
    }
    exit(0);
}

《unix环境高级编程》--- 网络IPC:套接字

支持重试的连接
使指数补偿算法用处理瞬时connect错误。如果调用connect失败,进程休眠一段时间后再尝试,每循环一次增加每次尝试的延迟,直到最大延迟为2分钟。

#include "apue.h"
#include <sys/socket.h>

#define MAXSLEEP 128

int connect_retry(int sockfd, const struct sockaddr *addr, socklen_t alen)
{
    int nsec;

    /* Try to connect with exponential backoff */
    for(nsec = 1; nsec <= MAXSLEEP; nsec <<= 1)
    {
        /* int connect(int sockfd, const struct sockaddr *addr, socklen_t len); 返回值:成功返回0,出错返回-1 addr:想与之通信的服务器地址。 如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址。 */
        if(connect(sockfd, addr, alen) == 0)
        {
            /* Connection accepted */
            return (0);
        }
        /* Delay before trying again */
        if(nsec <= MAXSLEEP / 2)
            sleep(nsec);
    }
    return (-1);
}

服务器初始化套接字端点

#include "apue.h"
#include <errno.h>
#include <sys/socket.h>

int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen)
{
    int fd;
    int err = 0;

    /*
        int socket(int domain, int type, int protocol);
        创建一个套接字。
        返回值:成功返回套接字描述符,出错返回-1。
        domain:地址族,以AF_开头
        type:套接字类型
        protocol:通常为0,表示按给定的域和套接字类型选择默认协议。
              当同一域和套记字支持多个协议时,可用protocol参数选择一个特定的协议。
    */
    if((fd = socket(addr->sa_family, type, 0)) < 0)
        return (-1);

    /*
        int bind(int sockfd, const struct sockadddr *addr, socklen_t len);
        将地址绑定到一个套接字
        返回值:成功返回0,出错返回-1
        对于因特网域,如果直到IP地址为INADDR_ANY,套接字端点可被绑定到所有的系统网络接口。
        意味着可以收到这个系统所安装的所有网卡的数据包。如果调用connect或listen,但没有绑定
        地址到一个套接字,系统会选一个地址并将其绑定到套接字。
    */
    if(bind(fd, addr, alen) < 0)
    {
        err = errno;
        goto errout;
    }
    if(type == SOCK_STREAM || type == SOCK_SEQPACKET)
    {
        /*
            int listen(int sockfd, int backlog);
            宣告可以接受连接请求
            返回值:成功返回0,出错返回-1
            backlog:表示该进程所要入队的连接请求数量。
        */
        if(listen(fd, qlen) < 0)
        {
            err = errno;
            goto errout;
        }
    }
    return (fd);

errout:
    close(fd);
    errno = err;
    return (-1);
}

面向连接的客户端、服务器
该例子的完整过程见:https://www.cnblogs.com/leealways87/archive/2012/10/03/2710652.html

1、用于获取服务器uptime的客户端命令。
连接服务器,读取服务器发送的字符串并打印到标志输出。一次recv不一定读取整个字符串,需多次调用。
如果服务器支持多重网络接口或多重网络协议,getaddrinfo会返回不只一个候选地址。轮流尝试每个地址,
当找到一个允许连接到服务的地址时便可停止。

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>

#define MAXADDRLEN 256
#define BUFLEN 128

extern int connect_retry(int, const struct sockaddr *, socklen_t);

void print_uptime(int sockfd)
{
    int n;
    char buf[BUFLEN];

    while((n = recv(sockfd, buf, BUFLEN, 0)) > 0)
        write(STDOUT_FILENO, buf, n);
    if(n < 0)
        err_sys("recv error");
}

int main(int argc, char *argv[])
{
    struct addrinfo *ailist, *aip;  
    struct addrinfo hint;
    int sockfd, err;

    if(argc != 2)
        err_quit("usage: ruptime hostname");
    hint.ai_flags = 0;
    hint.ai_family = 0;
    hint.ai_socktype = SOCK_STREAM;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
        err_quit("getaddrinfo error: %s", gai_strerror(err));
    for(aip = ailist; aip != NULL; aip = aip->ai_next)
    {
        if((sockfd = socket(aip->ai_family, SOCK_STREAM, 0)) < 0)
            err = errno;
        if(connect_retry(sockfd, aip->ai_addr, aip->ai_addrlen) < 0)
            err = errno;
        else
        {
            print_uptime(sockfd);
            exit(0);
        }
    }

    fprintf(stderr, "can't connect to %s: %s\n", argv[1], strerror(err));
    exit(1);
}

2、提供系统uptime的服务器程序
为找到地址,服务器程序需要获得其允许时的主机名。通过调用gethostname,服务器程序获得主机名,并查看远程uptime服务地址,可能有多个地址返回,仅简单选择第一个来建立被动套接字端点。使用initserver初始化套接字端点,并等待到来的连接请求。

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <sys/socket.h>

#define BUFLEN 128
#define QLEN 10

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

extern int initserver(int, struct sockaddr *, socklen_t, int);

void serve(int sockfd)
{
    int clfd;
    FILE *fp;
    char buf[BUFLEN];

    for(;;)
    {
        /* int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len); 获得连接请求并建立连接 返回值:成功返回套接字描述符,出错返回-1。 如果不关心客户端标识,可将addr和len设置为NULL。 accept会在addr填充客户端地址并跟新len为改地址大小。 如果没有连接请求等待处理,accept会阻塞直到一个请求到来。 如果sockfd处于非阻塞模式,accept会返回-1并将errno设置为EAGAIN或EWOULDBLOCK */
        clfd = accept(sockfd, NULL, NULL);
        if(clfd < 0)
        {
            /*printf("ruptimed: accept error: %s", strerror(errno));*/
            syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
            exit(1);
        }
        if((fp = popen("/usr/bin/uptime", "r")) == NULL)
        {
            sprintf(buf, "error: %s\n", strerror(errno));
            /* ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags); 发送数据。 返回值:成功返回发送的字节数,出错返回-1。 成功返回不必然表示连接另一端的进程接收数据,仅保证数据已成功发送到网络上。 */
            send(clfd, buf, strlen(buf), 0);
        }
        else
        {
            while(fgets(buf, BUFLEN, fp) != NULL)
                send(clfd, buf, strlen(buf), 0);
            pclose(fp);
        }
        close(clfd);
    }
}

int main(int argc, char *argv[])
{
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;
    int sockfd, err, n;
    char *host;

    if(argc != 1)
        err_quit("usage: ruptimed");
 #ifdef _SC_HOST_NAME_MAX
    n = sysconf(_SC_HOST_NAME_MAX);
    if(n < 0)  /* best guess */
 #endif
    n = HOST_NAME_MAX;
    host = malloc(n);
    if(host == NULL)
        err_sys("malloc error");
    if(gethostname(host, n) < 0)
        err_sys("gethostname error");
        daemonize("ruptimed"); 
    hint.ai_flags = AI_CANONNAME;
    hint.ai_family = 0;
    hint.ai_socktype = SOCK_STREAM;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0)
    {
        /* printf(" ruptimed: getaddrinfo error: %s\n", gai_strerror(err)); */
        syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err));
        exit(1);
    }
    for(aip = ailist; aip != NULL; aip = aip->ai_next)
    {
        if((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN)) >= 0)
        {
            serve(sockfd);
            exit(0);
        }
    }
    exit(1);
}

查看守护进程:ps ajx

《unix环境高级编程》--- 网络IPC:套接字

《unix环境高级编程》--- 网络IPC:套接字

用于显示命令直接写到套接字的服务器程序
以前的方式采用popen允许uptime命令,并从连接到命令标准输出的管道读取输出,现在用fork创建一个子进程,并用dup2使子进程的STDIN_FILENO的副本打开到/dev/null,STDOUT_FILENO和STDERR_FILENO打开到套接字端点。当执行uptime时,命令将结果写到标准输出,该标准输出连接到套接字,所以数据被送到ruptime客户端命令。

父进程可以安全地关闭连接到客户端的文件描述符,因为子进程仍旧打开着。父进程等待子进程处理完毕,所以子进程不会变成僵死进程。因运行uptime花费时间不会太长,父进程在接受下一个连接请求前,可等待子进程退出。但这种策略不适合子进程运行时间较长的情况。

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/wait.h>

#define QLEN 10

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

extern int initserver(int, struct sockaddr *, socklen_t, int);

void serve(int sockfd)
{
    int clfd, status;
    pid_t pid;

    for(;;)
    {
        clfd = accept(sockfd, NULL, NULL);
        if(clfd < 0)
        {
            syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
            exit(1);
        }
        if((pid = fork()) < 0)
        {
            syslog(LOG_ERR, "ruptimed: fork error: %s", strerror(errno));
            exit(1);
        }
        else if(pid == 0)  /* child */
        {
            /* The parent called daemonize, so STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO are already open to /dev/null. Thus, the call to close doesn't need to be protected by checks that clfd isn't already equal to one of these values. */
            if(dup2(clfd, STDOUT_FILENO) != STDOUT_FILENO || 
               dup2(clfd, STDERR_FILENO) != STDERR_FILENO)
            {
                syslog(LOG_ERR, "ruptimed: unexpected error");
                exit(1);
            }
            close(clfd);
            execl("/usr/bin/uptime", "uptime", (char *)0);
            syslog(LOG_ERR, "ruptimed: unexpected return from exec: %s", strerror(errno));
        }
        else  /* parent */
        {
            close(clfd);
            waitpid(pid, &status, 0);
        }
    }
}

int main(int argc, char *argv[])
{
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;
    int sockfd, err, n;
    char *host;

    if(argc != 1)
        err_quit("usage: ruptimed");
 #ifdef _SC_HOST_NAME_MAX 
    n = sysconf(_SC_HOST_NAME_MAX);
    if(n < 0)  /* best guess */
 #endif
    n = HOST_NAME_MAX;
    host = malloc(n);
    if(host == NULL)
        err_sys("malloc error");
    if(gethostname(host, n) < 0)
        err_sys("gethostname error");
    daemonize("ruptimed-fd");
    hint.ai_flags = AI_CANONNAME;
    hint.ai_family = 0;
    hint.ai_socktype = SOCK_STREAM;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0)
    {
        syslog(LOG_ERR, "ruptimed: getaddinfo error: %s", gai_strerror(err));
        exit(1);
    }
    for(aip = ailist; aip != NULL;  aip = aip->ai_next)
    {
        if((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN)) >= 0)    
        {
            serve(sockfd);
            exit(0);
        }
    }
    exit(1);
}

无连接的客户端、服务器
1、采用数据包服务的客户端命令
用alarm函数避免recvfrom时无限阻塞。
对于基于数据包的协议,需要一种方法来通知服务器需要它提供服务。本例中,只是简单地给服务器发送1字节的消息。
服务器接收后从包中得到地址,并使用这个地址发送响应消息。如果服务器提供多个服务,可使用这个请求消息指示所需要的服务,但如果服务只做一件使其,则1字节的消息内容无关紧要。
如果服务器不在运行状态,客户端调用recvfrom会无限期阻塞。对于面向连接的例子,如果服务器不运行,connect调用会失败。为避免无限期阻塞,调用recvfrom前设置警告时钟。

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>

#define BUFLEN 128
#define TIMEOUT 20

void sigalrm(int signo)
{

}

void print_uptime(int sockfd, struct addrinfo *aip)
{
    int n;  
    char buf[BUFLEN];

    buf[0] = 0;
    /* ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *destaddr, socklen_t destlen); 对于面向连接的套接字,目标地址可忽略,因目标地址蕴含在连接中。 */
    if(sendto(sockfd, buf, 1, 0, aip->ai_addr, aip->ai_addrlen) < 0)
        err_sys("Sendto error");
    alarm(TIMEOUT);
    /* ssize_t receform(int sockfd, void *restrict buf, size_t len, int flags, struct sockaddr *restrict addr, socklen_t *restrict addrlen); 如果addr非空,将包含所获得的数据发送者的套接字端点的地址。 addrlen为指向一个包含addr所指的套接字缓冲区字节大小的整数。 返回时,该整数设置为地址的实际字节大小。 */
    if((n = recvfrom(sockfd, buf, BUFLEN, 0, NULL, NULL)) < 0)
    {
        if(errno != EINTR)
            alarm(0);
        err_sys("recv error");
    }
    alarm(0);
    write(STDOUT_FILENO, buf, n);
}

int main(int argc, char *argv[])
{
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;
    int sockfd, err;
    struct sigaction sa;

    if(argc != 2)
        err_quit("usage: ruptime hostname");
    sa.sa_handler = sigalrm;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    if(sigaction(SIGALRM, &sa, NULL) < 0)
        err_sys("sigaction error");
    hint.ai_flags = 0;
    hint.ai_family = 0;
    hint.ai_socktype = SOCK_DGRAM;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
        err_quit("getaddrinfo error: %s", gai_strerror(err));

    for(aip = ailist; aip != NULL; aip = aip->ai_next)
    {
        if((sockfd = socket(aip->ai_family, SOCK_DGRAM, 0)) < 0)
            err = errno;
        else
        {
            print_uptime(sockfd, aip);
            exit(0);
        }
    }

    fprintf(stderr, "can't contact %s: %s\n", argv[1], strerror(err));
    exit(1);
}

2、基于数据报提供系统uptime的服务器程序
在recvfrom中阻塞等待服务请求。当一个请求到达时,保存请求者地址并使用popen运行uptime命令。采用sendto将输出发送到客户端,其目标地址设为刚才的请求者地址。

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <sys/socket.h>

#define BUFLEN 128
#define MAXADDRLEN 256
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

extern int initserver(int, struct sockaddr *, socklen_t, int);

void serve(int sockfd)
{
    int n;
    socklen_t alen;
    FILE *fp;
    char buf[BUFLEN];
    char abuf[MAXADDRLEN];

    for(;;)
    {
        alen = MAXADDRLEN;
        if((n = recvfrom(sockfd, buf, BUFLEN, 0, (struct sockaddr *)abuf, &alen)) < 0)
        {
            syslog(LOG_ERR, "ruptimed: recvfrom error: %s", strerror(errno));
            exit(1);
        }
        if((fp = popen("/usr/bin/uptime", "r")) == NULL)
        {
            sprintf(buf, "error: %s\n", strerror(errno));
            sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)abuf, alen);
        }
        else
        {
            if(fgets(buf, BUFLEN, fp) != NULL)
                sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)abuf, alen);
            pclose(fp);
        }
    }
}

int main(int argc, char *argv[])
{
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;
    int sockfd, err, n;
    char *host;

    if(argc != 1)
        err_quit("usage: ruptimed");
 #ifdef _SC_HOST_NAME_MAX
    n = sysconf(_SC_HOST_NAME_MAX);
    if(n < 0)  /* best guess */
 #endif
    n = HOST_NAME_MAX;
    host = malloc(n);
    if(host == NULL);
    {
        err_sys("malloc error");
    }
    if(gethostname(host, n) < 0)
        err_sys("gethostname error");
    printf("%s\n", host);
    daemonize("ruptimed-dg");
    hint.ai_flags = AI_CANONNAME;
    hint.ai_family = 0;
    hint.ai_socktype = SOCK_DGRAM;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0)
    {
        syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err));
        exit(1);
    }
    for(aip = ailist; aip != NULL; aip = aip->ai_next)
    {
        if((sockfd = initserver(SOCK_DGRAM, aip->ai_addr, aip->ai_addrlen, 0)) >= 0)
        {
            serve(sockfd);
            exit(0);
        }
    }
    free(host);
    exit(1);
}

采用地址复用初始化套接字端点
通常TCP的实现不允许绑定同一个地址,可通过套接字选项SO_REUSEADDR超越这个限制。

#include "apue.h"
#include <errno.h>
#include <sys/socket.h>

int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen)
{
    int fd, err;    
    int reuse = 1;

    if((fd = socket(addr->sa_family, type, 0)) < 0)
        return (-1);

    /*
       int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
       设置套接字选项。
       level:协议,如果是通用的套接字层选项,设置成SOL_SOCKET,否则设置成选项的协议号,
                  如对于TCP选项,为IPPROTO_TCP,对于IP选项,为IPPROTO_IP。
       val:根据选项的不同指向一个数据结构或一个整数。一些选项是on/off开关。
       len:指定了val指向的对象的大小。

       SO_REUSEADDR:如果*val非零,重用bind中的地址
    */
    if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) < 0)
    {
        err = errno;
        goto errout;
    }
    if(bind(fd, addr, alen) < 0)
    {
        err = errno;
        goto errout;
    }
    if(type == SOCK_STREAM || type == SOCK_SEQPACKET)
    {
        if(listen(fd, qlen) < 0)
        {
            err = errno;
            goto errout;
        }
    }
 errout:
    close(fd);
    errno = err;
    return (-1);
}

带外数据
带外数据是一些通信协议所支持的可选特征,允许更高优先级的数据比普通数据优先传输。即使传输队列已有数据,带外数据先传输。
TCP支持带外数据,但UDP不支持。TCP仅支持一个字节的紧急数据,但允许紧急数据在普通数据传递机制数据流之外传输。

非阻塞和异步I/O
通常,recv没有数据可用时会阻塞等待。通用,套接字输出队列没有足够的空间发送消息时,send会阻塞。在套接字非阻塞模式下,这些函数不会阻塞而是失败,设置errno为EWOULDBLOCK或EAGAIN,这是,可用poll或select来判断何时能接收或传输数据。
在基于套接字的异步I/O中,当能够从套接字中读取数据,或者套接字写队列中的空间变得可用时,可安排发送信号SIGIO。通过如下2个步骤实现:
1、建立套接字拥有者关系,信号可以被传送到合适的进程。
step1:在fcntl使用F_SETOWN命令,建立套接字所有权
step2:在ioctl中使用FIOSETOWN命令
step3:在ioctl中使用SIOCSPGRP命令
2、通知套接字当I/O操作不会阻塞时发信号告知。
step1:在fcntl中使用F_SETFL命令并启用文件标志O_ASYNC
step2:在ioctl中使用FIOASYNC,启用异步信号

启用异步套接字I/O:

int setasync(int sockfd)
{
    int n;

    if(fcntl(sockfd, F_SETOWN, getpid()) < 0)
        return (-1);
    n = 1;
    if(ioctl(sockfd, FIOASYNC, &n) < 0)
        return (-1);
    return (0);
}