网络IPC:套接字之建立连接

如果处理的是面向连接的网络服务(SOCK_STREAM或SOCK_SEQPACKET),在开始交换数据以前,需要在请求服务的进程套接字(客户端)和提供服务的进程套接字(服务器)之间建立一个连接。客户端可以用connect建立一个连接。

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
返回值:若成功则返回0,出错则返回-1

在connect中所指定的地址是想与之通信的服务器地址。如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址。

当连接一个服务器时,出于一些原因,连接可能失败。要连接的机器必须开启并且正在运行,服务器必须绑定到一个想与之连接的地址,并且在服务器的等待连接队列中应有足够的空间。因此,应用程序必须能够处理connect返回的错误,这些错误可能由一些瞬时变化条件引起。

实例

程序清单16-2显示了一种如何处理瞬时connect错误的方法。这在一个负载很重的服务器上很有可能发生。

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

#define MAXSLEEP 128

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

    /*
    * Try to connect with exponential backoff.
    */
    for(nsec = 1; nsec <= MAXSLEEP; nsec <<= 1)
    {
        if(connect(sockfd, addr, alen) == 0)
        {
            /*
            * Connection accepted. 
            */
            return(0);
        }
        
        /*
        * Delay before trying again.
        */
        if(nsec <= MAXSEELP/2)
            sleep(nsec);
    }
    return(-1);
}

这个函数使用了名为指数补偿(exponential backoff)的算法。如果调用connect失败,进程就休眠一小段时间后再尝试,每循环一次增加每次尝试的延迟,直到最大延迟为2分钟。

如果套接字描述符处于非阻塞模式下,那么在连接不能马上建立时,connect将会返回-1,并且将errno设为特殊的错误码EINPROGRESS。应用程序可以使用poll或select来判断文件描述符何时可写。如果可写,连接完成。

函数connect还可以用于无连接的网络服务(SOCK_DGRAM)。这看起来有点矛盾,实际上却是一个不错的选择。如果在SOCK_DGRAM套接字上调用connect,所有发送报文的目标地址设为connect调用中所指定的地址,这样每次传送报文时就不需要再提供地址。另外,仅能接收来自指定地址的报文。

服务器调用listen来宣告可以接受连接请求。

#include <sys/socket.h>
int listen(int sockfd, int backlog);
返回值:若成功则返回0,出错则返回-1

参数backlog提供了一个提示,用于表示该进程所要入队的连接请求数量。其实际值由系统决定,但上限由<sys/socket.h>中SOMAXCONN指定。

一旦队列满,系统会拒绝多余连接请求,所以backlog的值应该基于服务器期望负载和接受连接请求与启动服务的处理能力来选择。

一旦服务器调用了listen,套接字就能接收连接请求。使用函数accept获得连接请求并建立连接。

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
返回值:若成功则返回文件(套接字)描述符,出错则返回-1

函数accept所返回的文件描述符是套接字描述符,该描述符连接到调用connect的客户端。这个新的套接字描述符和原始套接字(sockfd)具有相同的套接字类型和地址族。传给accept的原始套接字没有关联到这个连接,而是继续保持可用状态并接受其他连接请求。

如果不关心客户端标识,可以将参数addr和len设为NULL;否则,在调用accept之前,应将参数addr设为足够大的缓冲区来存放地址,并且将len设为指向代表这个缓冲区大小的整数的指针。返回时,accept会在缓冲区填充客户端的地址并且更新指针len所指向的整数为该地址的大小。

如果没有连接请求等待处理,accept会阻塞直到一个请求到来。如果sockfd处于非阻塞模式,accept会返回-1并将errno设置为EAGAIN或EWOULDBLOCK。

如果服务器调用accept并且当前没有连接请求,服务器会阻塞直到一个请求到来。另外,服务器可以使用poll或select来等待一个请求的到来。在这种情况下,一个带等待处理的连接请求套接字会以可读的方式出现。

实例

程序清单16-3显示了一个服务器进程用以分配和初始化套接字的函数。

程序清单16-3 服务器初始化套接字端点 

#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;
    
    if((fd = socket(addr->sa_family, type, 0)) < 0)
        return(-1);
    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;    
        }
    }
    return(fd);

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

本篇博文内容摘自《UNIX环境高级编程》(第2版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/