linux c套接字编程(5) - 高级特性

2018-06-27 21:13:39

部分读部分写

如果没有足够的缓冲区空间来传输所有请求的字节,并且满足了如下几条的其中一条时, 可能会出现部分写的现象。

1.在 write() 调用传输了部分请求的字节后被信号处理例程中断。

2.套接字工作在非阻塞模式下( O_NONBLOCK),可能当前只能传输一部分请求的字节。

3.在部分请求的字节已经完成传输后出现了一个异步错误。对于这里的异步错误,我们指的是应用程序使用的套接字API调用中出现了一个异步错误。异步错误是可能会发生的,比如,由于TCP连接出现问题,可能就会使对端的应用程序崩溃。

在所有上述情况中,假设缓冲区空间至少能传输1字节数据, write调用成功,并返回传输到输出缓冲区中的字节数。如果出现了部分读现象,有些时候需要重新调用系统调用来完成全部数据的传输。

#include <unistd.h>
#include <errno.h>
#include <sys/types.h>

ssize_t readn(int fd, void *buffer, size_t n){
    ssize_t numRead;                    /* # of bytes fetched by last read() */
    size_t totRead;                     /* Total # of bytes read so far */
    char *buf;

    buf = buffer;                       /* No pointer arithmetic on "void *" */
    for (totRead = 0; totRead < n; ) {
        numRead = read(fd, buf, n - totRead);

        if (numRead == 0)               /* EOF */
            return totRead;             /* May be 0 if this is first read() */
        if (numRead == -1) {
            if (errno == EINTR)
                continue;               /* Interrupted --> restart read() */
            else
                return -1;              /* Some other error */
        }
        totRead += numRead;
        buf += numRead;
    }
    return totRead;                     /* Must be 'n' bytes if we get here */
}

ssize_t writen(int fd, const void *buffer, size_t n){
    ssize_t numWritten;                 /* # of bytes written by last write() */
    size_t totWritten;                  /* Total # of bytes written so far */
    const char *buf;

    buf = buffer;                       /* No pointer arithmetic on "void *" */
    for (totWritten = 0; totWritten < n; ) {
        numWritten = write(fd, buf, n - totWritten);

        if (numWritten <= 0) {
            if (numWritten == -1 && errno == EINTR)
                continue;               /* Interrupted --> restart write() */
            else
                return -1;              /* Some other error */
        }
        totWritten += numWritten;
        buf += numWritten;
    }
    return totWritten;                  /* Must be 'n' bytes if we get here */
}


shutdown 系统调用

close() 会将双方的通信道关闭,而shutdown() 可以只关闭一端。

#include <sys/socket.h>

int shutdown(int sockfd, int how);

//Returns 0 on success, or –1 on error

系统调用 shutdown() 可以根据参数 how 的值选择关闭套接字通道的一端还是两端。参数 how 的值可以指定为如下几种。 

SHUT_RD 关闭连接的读端。之后的读操作将返回文件结尾(0)。数据仍然可以写入到套接字上。在UNIX域流式套接字上执行了 SHUT_RD操作后,对端应用程序将接收到一个 SIGPIPE 信号,如果继续尝试在对端套接字上做写操作的话将产生 EPIPE 错误。SHUT_RD对于TCP套接字来说没有什么意义。

SHUT_WR 关闭连接的写端。一旦对端的应用程序已经将所有剩余的数据读取完毕,它就会检测到文件结尾。后续对本地套接字的写操作将产生 SIGPIPE信号以及 EPIPE错误。而由对端写入的数据仍然可以在套接字上读取。换句话说,这个操作允许我们在仍然能读取对端发回给我们的数据时,通过文件结尾来通知对端应用程序本地的写端已经关闭了。 SHUT_WR操作在sh 和rsh中都有用到。在 shutdown中最常用到的操作就是 SHUT_WR,有时候也被称为半关闭套接字。

SHUT_RDWR 将连接的读端和写端都关闭。这等同于先执行 SHUT_RD,跟着再执行一次 SHUT_WR 操作。

除了参数how的语义之外, shutdown() 同 close() 之间的另一个重要区别是:无论该套接字上是否还关联有其他的文件描述符, shutdown都会关闭套接字通道。例如, 假设 sockfd指向一个已连接的流式套接字,如果执行下列调用,那么连接依然会保持打开状态 我们仍然可以通过文件描述符fd2在该连接上做IO操作。

fd2 = dup(sockfd);
close(sockfd);

但是,如果我们执行如下的调用,那么该连接的双向通道都会关闭,通过 fd2 也无法再执行IO操作了。 

fd2= dup(sockfd);
shutdown(sockfd, SHUT_RDWR);

如果套接字文件描述符在 fork() 时被复制,那么此时也会出现相似的场景。如果在 fork() 调用之后,一个进程在描述符的副本上执行一次 SHUT_RDWR操作,那么其他的进程就无法再在这个文件描述符上执行I/O操作了。 

需要注意的是, shutdown并不会关闭文件描述符,就算参数how指定为 SHUT_RDWR时也是如此。要关闭文件描述符,我们必须另外调用 close()。

例子-server.c

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <syslog.h>
#include <sys/wait.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define SERVICE "echo"          /* Name of TCP service */
#define BUF_SIZE 4096
typedef enum { FALSE, TRUE } Boolean;

void errExit(char *msg){
    perror(msg);
    exit(1);
}
void fatal(char *msg){
    printf(msg);
    printf("\n");
    exit(1);
}

static void grimReaper(int sig){
    int savedErrno;             /* Save 'errno' in case changed here */

    savedErrno = errno;
    while (waitpid(-1, NULL, WNOHANG) > 0)
        continue;
    errno = savedErrno;
}

/* Handle a client request: copy socket input back to socket */

static void
handleRequest(int cfd)
{
    char buf[BUF_SIZE];
    ssize_t numRead;

    while ((numRead = read(cfd, buf, BUF_SIZE)) > 0) {
        if (write(cfd, buf, numRead) != numRead) {
            syslog(LOG_ERR, "write() failed: %s", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }

    if (numRead == -1) {
        syslog(LOG_ERR, "Error from read(): %s", strerror(errno));
        exit(EXIT_FAILURE);
    }
}

static int              /* Public interfaces: inetBind() and inetListen() */
inetPassiveSocket(const char *service, int type, socklen_t *addrlen,
                  Boolean doListen, int backlog)
{
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int sfd, optval, s;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;
    hints.ai_socktype = type;
    hints.ai_family = AF_UNSPEC;        /* Allows IPv4 or IPv6 */
    hints.ai_flags = AI_PASSIVE;        /* Use wildcard IP address */

    s = getaddrinfo(NULL, service, &hints, &result);
    if (s != 0)
        return -1;

    /* Walk through returned list until we find an address structure
       that can be used to successfully create and bind a socket */

    optval = 1;
    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sfd == -1)
            continue;                   /* On error, try next address */

        if (doListen) {
            if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optval,
                    sizeof(optval)) == -1) {
                close(sfd);
                freeaddrinfo(result);
                return -1;
            }
        }

        if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
            break;                      /* Success */

        /* bind() failed: close this socket and try next address */

        close(sfd);
    }

    if (rp != NULL && doListen) {
        if (listen(sfd, backlog) == -1) {
            freeaddrinfo(result);
            return -1;
        }
    }

    if (rp != NULL && addrlen != NULL)
        *addrlen = rp->ai_addrlen;      /* Return address structure size */

    freeaddrinfo(result);

    return (rp == NULL) ? -1 : sfd;
}

int
inetListen(const char *service, int backlog, socklen_t *addrlen)
{
    return inetPassiveSocket(service, SOCK_STREAM, addrlen, TRUE, backlog);
}

int main(int argc, char *argv[]){
    int lfd, cfd;               /* Listening and connected sockets */
    struct sigaction sa;

    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sa.sa_handler = grimReaper;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        syslog(LOG_ERR, "Error from sigaction(): %s", strerror(errno));
        exit(EXIT_FAILURE);
    }

    lfd = inetListen(SERVICE, 10, NULL);
    if (lfd == -1) {
        syslog(LOG_ERR, "Could not create server socket (%s)", strerror(errno));
        exit(EXIT_FAILURE);
    }

    for (;;) {
        cfd = accept(lfd, NULL, NULL);  /* Wait for connection */
        if (cfd == -1) {
            syslog(LOG_ERR, "Failure in accept(): %s", strerror(errno));
            exit(EXIT_FAILURE);
        }

        /* Handle each client request in a new child process */

        switch (fork()) {
        case -1:
            syslog(LOG_ERR, "Can't create child (%s)", strerror(errno));
            close(cfd);                 /* Give up on this client */
            break;                      /* May be temporary; try next client */

        case 0:                         /* Child */
            close(lfd);                 /* Unneeded copy of listening socket */
            handleRequest(cfd);
            _exit(EXIT_SUCCESS);

        default:                        /* Parent */
            close(cfd);                 /* Unneeded copy of connected socket */
            break;                      /* Loop to accept next connection */
        }
    }
}

例子-client.c

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/file.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define BUF_SIZE 100

void errExit(char *msg){
    perror(msg);
    exit(1);
}
void fatal(char *msg){
    printf(msg);
    printf("\n");
    exit(1);
}

int inetConnect(const char *host, const char *service, int type){
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int sfd, s;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;
    hints.ai_family = AF_UNSPEC;        /* Allows IPv4 or IPv6 */
    hints.ai_socktype = type;

    s = getaddrinfo(host, service, &hints, &result);
    if (s != 0) {
        errno = ENOSYS;
        return -1;
    }

    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sfd == -1)
            continue;                   /* On error, try next address */

        if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
            break;                      /* Success */

        close(sfd);
    }

    freeaddrinfo(result);

    return (rp == NULL) ? -1 : sfd;
}

int main(int argc, char *argv[]){
    int sfd;
    ssize_t numRead;
    char buf[BUF_SIZE];

    if (argc != 2 || strcmp(argv[1], "--help") == 0){
        printf("%s host\n", argv[0]);
        return 1;
    }

    sfd = inetConnect(argv[1], "echo", SOCK_STREAM);
    if (sfd == -1)
        errExit("inetConnect");

    switch (fork()) {
    case -1:
        errExit("fork");

    case 0:             /* 子进程读取服务端响应输出到标准输出 */
        for (;;) {
            numRead = read(sfd, buf, BUF_SIZE);
            if (numRead <= 0)                   /* Exit on EOF or error */
                break;
            printf("%.*s", (int) numRead, buf);
        }
        exit(EXIT_SUCCESS);

    default:            /* 父进程读取标准输入的值发送给服务端 */
        for (;;) {
            numRead = read(STDIN_FILENO, buf, BUF_SIZE);
            if (numRead <= 0)                   /* Exit loop on EOF or error */
                break;
            if (write(sfd, buf, numRead) != numRead)
                fatal("write() failed");
        }

        if (shutdown(sfd, SHUT_WR) == -1)
            errExit("shutdown");
        exit(EXIT_SUCCESS);
    }
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./server
[root@izj6cfw9yi1iqoik31tqbgz c]# cat tmp.txt 
www.freecls.com
沧浪水
[root@izj6cfw9yi1iqoik31tqbgz c]# ./client localhost < tmp.txt 
[root@izj6cfw9yi1iqoik31tqbgz c]# www.freecls.com
沧浪水


专用于套接字的 IO 系统调用:recv() 和 send()

#include <sys/socket.h>

ssize_t recv(int sockfd, void *buffer, size_t length, int flags);

//Returns number of bytes received, 0 on EOF, or –1 on error

ssize_t send(int sockfd, const void *buffer, size_t length, int flags);
//Returns number of bytes sent, or –1 on error

recv() 和 send() 的返回值以及前3个参数同 read() 和 write() 一样。最后一个参数flags是个位掩码,用来修改 IO 操作的行为。

对于 recv() 来说,该参数可以为下列值相或的结果。

MSG_DONTWAIT 让 recv() 以非阻塞方式执行。如果没有数据可用,那么 recv() 不会阻塞而是立刻返回,伴随的错误码为 EAGAIN。我们可以通过 fontI() 把套接字设为非阻塞模式(O_NONBLOCK) 从而达到相同的效果。区别在于 MSG_DONTWAIT允许我们在每次调用中控制非阻塞行为。

MSG_OOB 在套接字上接收带外数据。我们将在后面简要描述这个特性。

MSG_PEEK 从套接字缓冲区中获取一份请求字节的副本,但不会将请求的字节从缓冲区中实际移除。这份数据稍后可以由其他的recv() 或 read()调用重新读取。

MSG_WAITALL 通常, recv调用返回的字节数比请求的字节数(由 length参数指定)要少,而那些字节实际上还在套接字中。指定了 MSG_WAITALL 标记后将导致系统调用阻塞,直到成功接收到 length 个字节。但是,就算指定了这个标记,当出现如下情况时,该调用返回的字节数可能还是会少于请求的字节。这些情况是:

(a)捕获到一个信号;
(b)流式套接字的对端终止了连接;
(c)遇到了带外数据字节;
(d)从数据报套接字接收到的消息长度小于 length个字节;
(e)套接字上出现了错误。
( MSG_WAITALL标记可以取代我们]在上面给出的 read() 函数,区别在于我们实现的 read() 函数在被信号处理例程中断后会重新得到调用。) 

对于 send() 来说,flags 参数可以为下列值相或的结果。

MSG_DONTWAIT 让 send() 以非阻塞方式执行。如果数据不能立刻传输(因为套接字发送缓冲区已满),那 么该调用不会阻塞,而是调用失败,伴随的错误码为 EAGAIN。和 recv() 一样,可以通过对套接字设定 O_NONBLOCK 标记来实现同样的效果。

MSG_MORE 在TCP套接字上,这个标记实现的效果同套接字选项 TCP_CORK(见下面)完成的功能相同。区别在于该标记可以在每次调用中对数据进行处理,而TCP_CORK 设置了就算在子进程中继承了了标记还在。从 Linux2.6版以来,这个标记也可以用于数据报套接字,但所代表的意义有所不同。在连续的 send() 或 sendto() 调用中 传输的数据,如果指定了 MSG_MORE 标记,那么数据会打包成一个单独的数据报。仅当下一 次调用中没有指定该标记时数据才会传输出去。( Linux也提供了类似的 UDP_CORK套接字 选项,这将导致在连续的 send() 或 sendto()调用中传输的数据会累积成一个单独的数据报,当 取消 UDP_CORK选项时才会将其发送出去。) MSG_MORE 标记对UNIX域套接字没有任何效果。

MSG_NOSIGNAL 当在已连接的流式套接字上发送数据时,如果连接的另一端已经关闭了,指定该标记后将 不会产生 SIGPIPE 信号。相反, send() 调用会失败,伴随的错误码为 EPIPE。这和忽略 SIGPIPE 信号所得到的行为相同。区别在于该标记可以在每次调用中控制信号发送的行为。

MSG_OOB 在流式套接字上发送带外数据。


sendfile() 系统调用

像Web服务器和文件服务器这样的应用程序常常需要将磁盘上的文件内容不做修改地通过(已连接)套接字传输出去。一种方法是通过循环按照如下方式处理。 

while((n = read(diskfilefd, buf, BUZ SIZE)) > 0)
    write(sockfd, buf, n)

对于许多应用程序来说,这样的循环是完全可接受的。但是,如果我们需要通过套接字频繁地传输大文件的话,这种技术就显得很不高效。为了传输文件,我们必须使用两个系统调用(可能需要在循环中多次调用):一个用来将文件内容从内核缓冲区 cache中拷贝到用户空间,另一个用来将用户空间缓冲区拷贝回内核空间,以此才能通过套接字进行传输。下图左侧展示了这种场景。

如果应用程序在发起传输之前根本不对文件内容做任何处理的话, 那么这种两步式的处理就是一种浪费。系统调用 sendfile() 被设计为用来消除这种低效性。如图右侧所示,当应用程序调用 sendfile() 时,文件内容会直接传送到套接字上,而不会经过用户空间。这种技术被称为零拷贝传输( zero-copy transfer)。

#include <sys/sendfile.h>

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

//Returns number of bytes transferred, or –1 on error

系统调用 sendfile() 在代表输入文件的描述符 in_fd 和代表输出文件的描述符 out_fd 之间传送文件内容(字节)。描述符 out_fd必须指向一个套接字。参数 in_fd 指向的文件必须是可以进行 mmap 操作的。在实践中,这通常表示一个普通文件。这些局限多少限制了 sendfile 的使用。我们可以使用 sendfile() 将数据从文件传递到套接字上,但反过来就不行。 另外,我们也不能通过 sendfile在两个套接字之间直接传送数据。

如果参数 offset 不是 NULL,它应该指向一个off_t 值,该值指定了起始文件的偏移量 意即从 in_fd 指向的文件的这个位置开始,可以传输字节。这是一个传入传出参数(又叫值一 结果参数)。在返回的值中,它包含从in_fd 传输过来的紧靠着最后一个字节的下一个字节的偏移量。在这里, serdfile() 不会更改infd的文件偏移量。 

如果参数 offset 指定为 NULL 的话,那么从 in_fd 传输的字节就从当前的文件偏移量处开始,且在传输时会更新文件偏移量以反映出已传输的字节数。

参数 count 指定了请求传输的字节数。如果在 count个字节完成传输前就遇到了文件结尾符,那么只有文件结尾符之前的那些字节能传输。调用成功后, sendfile 会返回实际传输的字节数。


TCP_CORK 套接字选项

要进一步提高TCP应用使用 sendfile() 时的性能,采用 Linux专有的套接字选项 TCP_CORK 常常会很有帮助。例如,Web服务器传送页面给浏览器,作为对请求的响应。Web服务器的响 应由两部分组成:HTTP首部,也许会通过write来输出;页面数据,可以通过 sendfile() 来输出。在这种场景下,通常会传输2个 TCP 报文段:HTTP首部在第一个(非常小)报文段中, 而页面数据在第二个报文段中发送。这对网络带宽的利用率是不够高效的。可能还会在发送和接收TCP报文时做些不必要的工作,因为在许多情况下HTTP首部和页面数据都比较小,足以容纳在一个单独的TCP报文段中。套接字选项 TCP_CORK 正是被设计为用来解决这种低效性。

当在TCP套接字上启用了 TCP_CORK 选项后,之后所有的输出都会缓冲到一个单独的 TCP 报文段中,直到满足以下条件为止:

1.已达到报文段的大小上限。
2.取消了 TCP_CORK选项。
3.套接字被关闭。
4.当启用 TCP_CORK后,从写入第一个字节开始已经经历了200毫秒。(如果应用程序 忘记取消 TCP_CORK选项,那么超时时间可确保被缓冲的数据能得以传输。)

int optval;

optval = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, &optval, sizeof(optval));
write(sockfd, ...);    /* Write HTTP headers */
sendfile(sockfd, ...); /* Send page data */

//发送
optval = 0
setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, &optval, sizeof(optval));


获取套接字地址

getsockname() 和 getpeername() 这两个系统调用分别返回本地套接字地址以及对端套接字地址。

#include <sys/socket.h>

int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

//Both return 0 on success, or –1 on error

对于这两个系统调用, sockfd 表示指向套接字的文件描述符,而 addr 是一个指向 sockaddr 结构体的指针,该结构体包含着套接字的地址。这个结构体的大小和类型取决于套接字域 addrlen 是一个保存结果值的参数。在执行调用之前, addrlen应该被初始化为addr所指向的缓冲区空间的大小。调用返回后, addrlen中包含实际写入到这个缓冲区中的字节数。

getsockname() 可以返回套接字地址族,以及套接字所绑定到的地址。如果套接字绑定到了另一个程序(比如 inetd(8) ),且套接字文件描述符在经过 exec() 调用后仍然得到保留,那么此时 getsockname() 就能派上用场了。 

当隐式绑定到一个 Internet域套接字上时,如果我们想获取内核分配给套接字的临时端口号,那么调用 getsockname()也是有用的。内核会在出现如下情况时执行一个隐式绑定。 

1.已经在TCP套接字上执行了 connect() 或 listen() 调用,但之前还没有通过 bind()绑定到 一个地址上。
2.当在UDP套接字上首次调用 sento() 时,该套接字之前还没有绑定到地址上。
3.调用 bind() 时将端口号( sin_port)指定为0。这种情况下 bind() 会为套接字指定一个 IP地址,但内核会选择一个临时的端口号。

系统调用 getpeername() 返回流式套接字连接中对端套接字的地址。如果服务器想找出发出连接的客户端地址,这个调用就特别有用,主要用于TCP套接字上。

对端套接字的地址信息也可以在执行 accept() 时获取,但是如果服务器进程是由另一个程序调用的,而 accept() 是由该程序(比如 inetd)所执行,那么服务器进程可以继承套接字文件描述符,但由 accept() 返回的地址信息就不存在了。 

例子

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <syslog.h>
#include <sys/wait.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

typedef enum { FALSE, TRUE } Boolean;

#define IS_ADDR_STR_LEN 4096

void errExit(char *msg){
    perror(msg);
    exit(1);
}
void fatal(char *msg){
    printf(msg);
    printf("\n");
    exit(1);
}

char * inetAddressStr(const struct sockaddr *addr, socklen_t addrlen,
               char *addrStr, int addrStrLen){
    char host[NI_MAXHOST], service[NI_MAXSERV];

    if (getnameinfo(addr, addrlen, host, NI_MAXHOST,
                    service, NI_MAXSERV, NI_NUMERICSERV) == 0)
        snprintf(addrStr, addrStrLen, "(%s, %s)", host, service);
    else
        snprintf(addrStr, addrStrLen, "(?UNKNOWN?)");

    return addrStr;
}

static int              /* Public interfaces: inetBind() and inetListen() */
inetPassiveSocket(const char *service, int type, socklen_t *addrlen,
                  Boolean doListen, int backlog)
{
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int sfd, optval, s;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;
    hints.ai_socktype = type;
    hints.ai_family = AF_UNSPEC;        /* Allows IPv4 or IPv6 */
    hints.ai_flags = AI_PASSIVE;        /* Use wildcard IP address */

    s = getaddrinfo(NULL, service, &hints, &result);
    if (s != 0)
        return -1;

    /* Walk through returned list until we find an address structure
       that can be used to successfully create and bind a socket */

    optval = 1;
    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sfd == -1)
            continue;                   /* On error, try next address */

        if (doListen) {
            if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optval,
                    sizeof(optval)) == -1) {
                close(sfd);
                freeaddrinfo(result);
                return -1;
            }
        }

        if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
            break;                      /* Success */

        /* bind() failed: close this socket and try next address */

        close(sfd);
    }

    if (rp != NULL && doListen) {
        if (listen(sfd, backlog) == -1) {
            freeaddrinfo(result);
            return -1;
        }
    }

    if (rp != NULL && addrlen != NULL)
        *addrlen = rp->ai_addrlen;      /* Return address structure size */

    freeaddrinfo(result);

    return (rp == NULL) ? -1 : sfd;
}

int
inetListen(const char *service, int backlog, socklen_t *addrlen)
{
    return inetPassiveSocket(service, SOCK_STREAM, addrlen, TRUE, backlog);
}

int inetConnect(const char *host, const char *service, int type){
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int sfd, s;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;
    hints.ai_family = AF_UNSPEC;        /* Allows IPv4 or IPv6 */
    hints.ai_socktype = type;

    s = getaddrinfo(host, service, &hints, &result);
    if (s != 0) {
        errno = ENOSYS;
        return -1;
    }

    /* Walk through returned list until we find an address structure
       that can be used to successfully connect a socket */

    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sfd == -1)
            continue;                   /* On error, try next address */

        if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
            break;                      /* Success */

        /* Connect failed: close this socket and try next address */

        close(sfd);
    }

    freeaddrinfo(result);

    return (rp == NULL) ? -1 : sfd;
}

int main(int argc, char *argv[]){
    int listenFd, acceptFd, connFd;
    socklen_t len;                      /* Size of socket address buffer */
    void *addr;                         /* Buffer for socket address */
    char addrStr[IS_ADDR_STR_LEN];

    if (argc != 2 || strcmp(argv[1], "--help") == 0){
        printf("%s service\n", argv[0]);
        return 0;
    }
        

    listenFd = inetListen(argv[1], 5, &len);
    if (listenFd == -1)
        errExit("inetListen");

    connFd = inetConnect(NULL, argv[1], SOCK_STREAM);
    if (connFd == -1)
        errExit("inetConnect");

    acceptFd = accept(listenFd, NULL, NULL);
    if (acceptFd == -1)
        errExit("accept");

    addr = malloc(len);
    if (addr == NULL)
        errExit("malloc");

    if (getsockname(connFd, addr, &len) == -1)
        errExit("getsockname");
    printf("getsockname(connFd):   %s\n",
            inetAddressStr(addr, len, addrStr, IS_ADDR_STR_LEN));
    if (getsockname(acceptFd, addr, &len) == -1)
        errExit("getsockname");
    printf("getsockname(acceptFd): %s\n",
            inetAddressStr(addr, len, addrStr, IS_ADDR_STR_LEN));

    if (getpeername(connFd, addr, &len) == -1)
        errExit("getpeername");
    printf("getpeername(connFd):   %s\n",
            inetAddressStr(addr, len, addrStr, IS_ADDR_STR_LEN));
    if (getpeername(acceptFd, addr, &len) == -1)
        errExit("getpeername");
    printf("getpeername(acceptFd): %s\n",
            inetAddressStr(addr, len, addrStr, IS_ADDR_STR_LEN));

    sleep(30);                          /* Give us time to run netstat(8) */
    exit(EXIT_SUCCESS);
}


备注
1.编译器版本gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/64 

 

©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。