linux c套接字编程(4) - Internet domain socket

2018-06-25 23:33:16

Intemet domain 流 socket 是基于 TCP 之上的,它们提供了可靠的双向字节流通信信道。
Internet domain 数据报 socket 是基于 UDP 之上的。 UDP socket 与之在 UNIX domain 中的对应实体类似,但需要注意下列差别。

1.UNIX domain 数据报 socket 是可靠的,但 UDP socket 则是不可靠的 ― 数据报可能会丢失、重复或到达的顺序与它们被发送的顺序不同。

2.在一个 UNIX domain 数据报 socket 上发送数据会在接收 socket 的数据队列为满时阻塞。与之不同的是,使用 UDP 时如果进入的数据报会使接收者的队列溢出,那么数据报就会默默地被丢弃。 


网络字节序

IP 地址和端口号是整数值。在将这些值在网络中传递时碰到的一个问题是不同的硬件结构会以不同的顺序来存储一个多字节整数的字节。从图 59 一 1 中可以看出,存储整数时先存储(即在最小内存地址处)最高有效位的被称为大端,那些先存储最低有效位的被称为小端。

小端架构中最值得关注的是x86。其他大多数架构都是大端的。在特定主机上使用的字节序被称为主机字节序。

由于端口号和 IP 地址必须在网络中的所有主机之间传递并且需要被它们所理解,因此必须要使用一个标准的字节序。这种字节序被称为网络字节序,它是大端的。

在后面将会介绍各种用于将主机名 (如www.kernel.org) 和 服务名 (如http) 转换成对应的数字形式的函数。这些函数一般会返回用网络字节序表示的整数,并且可以直接将这些整数复制进一个 socket地址结构的相关字段中。

有时候可能会直接使用IP地址和端口号的整数常量形式,如可能会选择将端口号硬编码进程序中,或者将端口号作为一个命令行参数传递给程序,或者在指定一个IPv4地址时使用诸如 INADDR_ANY 和 INADDR_LOOPBACK之类的常量。这些值在C中是按照主机的规则来表示的, 因此它们是主机字节序的,在将它们存储进 socket地址结构中之前需要将这些值转换成网络字节序。

htons()、 htonl()、 ntohs() 以及 ntohl() 函数被定义(通常为宏)用来在主机和网络字节序之 间转换整数。

#include <arpa/inet.h>

uint16_t htons(uint16_t host_uint16);

//Returns host_uint16 converted to network byte order

uint32_t htonl(uint32_t host_uint32);

//Returns host_uint32 converted to network byte order

uint16_t ntohs(uint16_t net_uint16);

//Returns net_uint16 converted to host byte order

uint32_t ntohl(uint32_t net_uint32);

//Returns net_uint32 converted to host byte order

一般来说,只需要在主机字节序与网络字节序不同时才会用到以上几个函数,但是为了可移植性,最好都用上。

自定义 readLine() 函数

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

    if (n <= 0 || buffer == NULL) {
        errno = EINVAL;
        return -1;
    }

    buf = buffer;                       /* No pointer arithmetic on "void *" */

    totRead = 0;
    for (;;) {
        numRead = read(fd, &ch, 1);

        if (numRead == -1) {
            if (errno == EINTR)         /* Interrupted --> restart read() */
                continue;
            else
                return -1;              /* Some other error */

        } else if (numRead == 0) {      /* EOF */
            if (totRead == 0)           /* No bytes read; return 0 */
                return 0;
            else                        /* Some bytes read; add '\0' */
                break;

        } else {                        /* 'numRead' must be 1 if we get here */
            if (totRead < n - 1) {      /* Discard > (n - 1) bytes */
                totRead++;
                *buf++ = ch;
            }

            if (ch == '\n')
                break;
        }
    }

    *buf = '\0';
    return totRead;
}

如果在遇到换行符之前读取的字节数大于或等于 n-1 ,那么 readLine() 函数会丢弃多余的字节(包括换行符)。如果在前面的 n-1 字节中读取了换行符,那么在返回的字符串中就会包含这个换行符。(因此可以通过检查在返回的 buffer中结尾 null 字节前是否是一个换行符来确定是否有字节被丢弃了。)采用这种方法之后,将输入以行为单位进行处理的应用程序协议就不会将一个很长的行处理成多行了。

当然,这可能会破坏协议,因为两端的应用程序不再同步了。另一种做法是让 readLine() 只读取足够的字节数来填充提供的缓冲器,而将到下一行新行为止的剩余字竹留给下一个 readLine() 调用。在这种情况下, readLine() 的调用者需要处理读取部分行的情况。


IPv4 socket 地址

#include <netinet/in.h>

struct in_addr {         /* IPv4 4-byte address */
    in_addr_t s_addr;     /* Unsigned 32-bit integer */
};
struct sockaddr_in {     /* IPv4 socket address */
    sa_family_t sin_family;     /* Address family (AF_INET) */
    in_port_t sin_port;     /* Port number */
    struct in_addr sin_addr;     /* IPv4 address */
    unsigned char __pad[X];     /* Pad to size of 'sockaddr'
                            structure (16 bytes) */
};

在前面文章曾讲过普通的 sockaddr 结构中有一个字段来标识 socket domain,该字段对应于 sockaddr_in结构中的 sin_ family 字段,其值总为 AF_INET。 sin_port和 sin_addr字段是端口号和IP地址,它们都是网络字节序的。 In_port_t 和 in_addr_t 数据类型是无符号整型,其长度分别为16位和32位。

IPv6 socket 地址

IPv6 地址时128位而不是32位。

struct in6_addr {         /* IPv6 address structure */
    uint8_t s6_addr[16];  /* 16 bytes == 128 bits */
};

struct sockaddr_in6 {         /* IPv6 socket address */
    sa_family_t sin6_family;  /* Address family (AF_INET6) */
    in_port_t sin6_port;         /* Port number */
    uint32_t sin6_flowinfo;     /* IPv6 flow information */
    struct in6_addr sin6_addr;  /* IPv6 address */
    uint32_t sin6_scope_id;     /* Scope ID (new in kernel 2.4) */
};

sin_family 字段会被设置为 AF_INET6。sin6_port 和 sin6_addr 字段分别是端口号和IP地址。(uint8_t 数据类型被用来定义 in6_addr 结构中字节的类型,它是一个8位的无符号整型。)剩余的字段sin6_flowinfo 和 sin6_scope_id 则超出了本文的范围,在本书给出所有例子中都会将它们设置为0。 sockaddr_in6结构中的所有字段都是以网络字节序存储的。由于ipv6暂时使用不多,以后再深入讲解。


inet_pton()、inet_ntop()

这两个函数允许在IPv4 和 IPv6地址的二进制形式和点分十进制表示法或十六进制字符串表示法之间进行转换。

#include <arpa/inet.h>

int inet_pton(int domain, const char *src_str, void *addrptr);

//Returns 1 on successful conversion, 0 if src_str is not in
//presentation format, or –1 on error

const char *inet_ntop(int domain, const void *addrptr, char *dst_str, size_t len);

//Returns pointer to dst_str on success, or NULL on error

函数名中 p 表示展现(presentation),n 表示网络 network。展示形式是人类可读的字符串。

204.11.189.116 (IPv4 点分十进制地址)。
::1 (IPv6 冒号分隔的十六进制地址)。
::FFFF:204.152.189.116 (IPv4 映射的 IPv6 地址)。

inet_pton() 函数将 src_str 中包含的展现字符串转换成网络字节序的二进制IP地址。 domain 参数应该被指定为 AF_INET 或 AF_INET6。转换得到的地址会被放在 addrptr 指向的结构中, 它应该根据在 domain参数中指定的值指向一个 in_addr 或 in6_addr结构。

inet_ntop() 函数执行逆向转换。同样, domain应该被指定为 AF_INET 或 AF_INET6, addrptr应该指向一个待转换的 in_addr 或 in6_addr 结构。得到的以 null 结尾的字符串会被放置在 dst_str 指向的缓冲器中。len参数必须被指定为这个缓冲器的大小。 inet_ntop() 在成功时会返回 dst_str。如果len的值太小了,那么 inet_ntop() 会返回NULL并将errno 设置成 ENOSPC。

要正确计算 dst_str指向的缓冲区的大小可以使用在 <netinet/in.h> 中定义的两个常量。这些常量标识出了IPV4 和 IPv6 地址的展现字符串的最大长度(包括结尾的null字节)。

#define INET_ADDRSTRLEN 16 /* Maximum IPv4 dotted-decimal string */
#define INET6_ADDRSTRLEN 46 /* Maximum IPv6 hexadecimal string */

域名解析和 /etc/services 文件

当一个程序调用 getaddrinfo() 来解析(即获取IP地址)一个域名时, getaddrinfo() 会使用一组库函数( resolver库)来与本地的DNS服务器通信。如果这个服务器无法提供 所需的信息,那么它就会与位于层级中的其他DNS服务器进行通信以便获取信息。 有时候,这个解析过程可能会花费很多时间,DNS服务器采用了缓存技术来避免在查询常见域名时所发生的不必要的通信。 

众所周知的端口号是由 IANA 集中注册的,其中每个端口号都有一个对应的服务名。端口号和服务名会记录在文件 /etc/services 中。getaddrinfo() 和 getnameinfo() 函数会使用这个文件中的信息在服务名和端口号之间进行转换。


getaddrinfo() 函数

#include <sys/socket.h>
#include <netdb.h>

struct addrinfo {
    int ai_flags;     /* Input flags (AI_* constants) */
    int ai_family;     /* Address family */
    int ai_socktype;  /* Type: SOCK_STREAM, SOCK_DGRAM */
    int ai_protocol;   /* Socket protocol */
    size_t ai_addrlen;  /* Size of structure pointed to by ai_addr */
    char *ai_canonname; /* Canonical name of host */
    struct sockaddr *ai_addr;  /* Pointer to socket address structure */
    struct addrinfo *ai_next;  /* Next structure in linked list */
};

int getaddrinfo(const char *host, const char *service,
                const struct addrinfo *hints, struct addrinfo **result);

//Returns 0 on success, or nonzero on error

成功时返回0,发生错误时返回非零值。 getaddrinfo() 以host、 service以及 hints参数作为输入,其中host参数包含一个主机名或一个以 IPv4 点分十进制标记或 IPv6 十六进制字符串标记的数值地址字符串。 service参数包含一个服务名或一个十进制端口号。 hints参数指向一个 addrinfo结构,该结构规定了选择通过 result返回的 socket地址结构的标准。稍后会介绍有关 hints参数的更多细节。

getaddrinfo() 会动态地分配一个包含 addrinfo 结构的链表并将 result 指向这个列表的表头。 每个 addrinfo 结构包含一个指向与host和 service对应的 socket地址结构的指针。

result参数返回一个结构列表而不是单个结构,因为与在host、 service以及 hints中指定的标准 对应的主机和服务组合可能有多个。如查询拥有多个网络接口的主机时可能会返回多个地址结构。
此外,如果将 hints. ai_socktype指定为0,那么就可能会返回两个结构:一个用于 SOCK_DGRAM socket,另一个用于 SOCK_STREAM socket —— 前提是给定的 service 同时对TCP和UDP可用。 

通过 result 返回的 addrinfo 结构的字段描述了关联 socket 地址结构的属性。 ai_family 字段会被设置成 AF_INET 或 AF_INET6,表示该 socket地址结构的类型。 ai_socktype 字段会被设置成 SOCK_STREAM或 SOCK_DGRAM,表示这个地址结构是用于TCP服务还是用于UDP 服务。ai_protocol字段会返回与地址族和 socket类型匹配的协议值。( ai_family、 ai_socktype 以及 ai_protocol三个字段为调用 socket创建该地址上的 socket时所需的参数提供了取值。) ai_addrlen 字段给出了 ai_addr 指向的 socket 地址结构的大小(字节数) 。in_addr字段指向 socket 地址结构(IPv4时是一个 in_addr结构,IPv6时是一个in6_addr结构)。 ai_flags 字段未用(它用于 hints参数)。 ai_canonname字段仅由第一个 addrinfo 结构使用并且其前提是像下面所描述的那样在 hints.ai_flags 中使用了 AI_CANONNAME 字段。 与 gethostbyname() 一样, getaddrinfo() 可能需要向一台DNS服务器发送一个请求,并且这个请求可能会花费一段时间。

hints 参数

hins参数为如何选择 getaddrinfo返回的 socket 地址结构指定了更多的标准。当用作 hints 参数时只能设置 addrinfo 结构的 ai_flags、 ai_family、ai_socktype 以及 ai_protocol字段,其他字段未用到,并将应该根据具体情况将其初始化为0 或 NULL。

hints.ai_family 字段选择了返回的 socket地址结构的域,其取值可以是 AF_INET 或 AF_INET6(或其他一些AF*常量,只要实现支持它们)。如果需要获取所有种类 socket地址结构,那么可以将这个字段的值指定为 AF_UNSPEC。

hints. ai_socktype 字段指定了使用返回的 socket地址结构的 socket类型。如果将这个字段 指定为 SOCK_DGRAM,那么查询将会在UDP服务上执行,对应的 socket 地址结构会通过 result返回。如果指定了 SOCK_STREAM,那么将会执行一个TCP服务查询。如果将 hints. ai_socktype指定为0,那么任意类型的 socket都是可接受的。

hints.ai_protocol字段为返回的地址结构选择了 socket协议。在本文中,这个字段的值总 是会被设置为0,表示调用者接受任何协议。

hints.ai_flags字段是一个位掩码,它会改变 getaddrinfo() 的行为。这个字段的取值是下列值中的零个或多个取OR得来的。

AI_ADDRCONFIG 在本地系统上至少配置了一个 IPv4 地址时返回IPv4地址,在本地系统上至少配置了一个 IPv6 地址时返回IPv6地址,不是回环地址。

AI_ALL 参见下面AI_V4MAPPED。

AI_CANONNAME 如果host不为NULL,那么返回一个指向以 null 结尾的字符串,该字符串包含了主机的规范名。这个指针会在通过 result 返回的第一个 addrinfo 结构中的 al_canonname 字段指向的缓冲区中返回。

AI_NUMERICHOST 强制将 host 解释成一个数值地址字符串。这个常量用于在不必要解析名字时防止进行名字解析,因为名字解析可能会花费较长的时间。

AI_NUMERICSERV 将 service解释成一个数值端口号。这个标记用于防止调用任意的名字解析服务,因为当 service为一个数值字符串时这种调用是没有必要的。

Al_PASSIVE 返回一个适合进行被动式打开(即一个监听 socket)的 socket地址结构。在这种情况下, host应该是 NULL,通过 result 返回的 socket 地址结构的IP地址部分将会包含一个通配 IP 地址 (即 INADDR_ANY 或 IN6ADDR_ANY_INIT)。如果没有设置这个标记,那么通过 result返回的地址结构将能用于 connect()和 sendto()。如果host为NULL,那么返回的 socket 地址结构中的 IP地址将会被设置成回环 IP 地址(根据所处的域,其值为 INADDR_LOOPBACK 或 IN6ADDR_LOOPBACK_INIT) 。

Al_V4MAPPED 如果在 hints的 ai_family 字段中指定了 AF_INET6,那么在没有找到匹配的IPv6地址时应 该在 result 返回IPv4映射的IPv6地址结构。如果同时指定了 AI_ALL 和 AIV4_MAPPED,那么在 result 中会同时返回 IPv6 和 IPv4 地址,其中IPv4地址会被返回成 IPV4映射的IPv6地址结构。 

正如前面介绍 AI_PASSIVE 时指出的那样,host可以被指定为NULL。此外,还可以将 service 指定为NULL,在这种情况下,返回的地址结构中的端口号会被设置为0(即只关心将 主机名解析成地址)。然而无法将host 和 service同时指定为NULL。

如果无需在 hints中指定上述的选取标准,那么可以将 hints指定为NULL,在这种情况下会将 ai_socktype和 ai_protocol 假设为0,将 ai_flags 假设为(AI_Ⅴ4MAPPED | AI_ADDRCONFIG),将 ai_family假设为 AF_UNSPEC。( glibc实现有意与SUSv3背道而驰, 它声称如果 hints为NULL,那么会将 ai_flags假设为0。)

释放 addrinfo 列表:freeaddrinfo()

getaddrinfo() 函数会动态地为 result 引用的所有结构分配内存,使用这个函数可以方便的一步释放内存。

#include <sys/socket.h>
#include <netdb.h>

void freeaddrinfo(struct addrinfo *result);

错误诊断:gai_strerror()

下表为 getaddrinfo() 和 getnameinfo() 返回的错误码

错误常量 描述 
 EAI_ADDRFAMILY 在 hints.ai_family 中不存在host地址
 EAI_AGAIN 名字解析过程发生临时错误
 EAI_BADFLAGS 在hints.ai_flags 指定了无效的标记
 EAI_FAIL 访问名字服务器是发生故障
 EAI_FAMILY 不支持hints.ai_family 指定的地址族
 EAI_MEMORY 内存分配故障
 EAI_NODATA 没有与host关联的地址
 EAI_NONAME 未知的host或service,或两者都为NULL或指定了
AI_NUMERICSERV 同时 service没有指向一个数字
 EAI_OVERFLOW 参数缓冲区溢出
 EAI_SERVICE hints.ar_socktype 不支持指定的 service
 EAI_SOCKTYPE 不支持指定的 hints.ai_socktype
 EAI_SYSTEM 通过 errno 返回的系统错误

gai_strerror() 函数会返回描述该错误的字符串。

#include <netdb.h>

const char *gai_strerror(int errcode);

//Returns pointer to string containing error message


getnameinfo()函数

getnameinfo() 函数是 getaddrinfo()的逆函数。给定一个 socket 地址结构(IPv4或IPv6), 它会返回一个包含对应的主机和服务名的字符串或者在无法解析名字时返回一个等价的数值。

#include <sys/socket.h>
#include <netdb.h>

int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host,
                size_t hostlen, char *service, size_t servlen, int flags);

//Returns 0 on success, or nonzero on error

addr参数是一个指向待转换的 socket 地址结构的指针,该结构的长度是由 addrlen指定的 通常,addr和 addrlen 的值是从 accept()、 recvfrom()、 getsockname() 或 getpeername()调用中获得的。

得到的主机和服务名是以 null 结尾的字符串,它们会被存储在 host 和 service 指向的缓冲区中。调用者必须要为这些缓冲区分配空间并将它们的大小传入 hostlen 和 servlen。

<netdb.h> 头文件定义了两个常量来辅助计算这些缓冲区的大小。 NI_MAXHOST 指出了返回的主机名字符串的最大字节数,其取值为1025。 NI_MAXSERV 指出了返回的服务名字符串的最大字节数,其取值为32。(从 glibc2.8起,必须要定义 _BSD_SOURCE、 _SVID_SOURCE 或 _GNU_SOURCE中的其中一个特性文本宏才能获取 NI_MAXHOST 和 NI_MAXSERV的定义)。

如果不想获取主机名,那么可以将host指定为NULL并且将 hostlen指定为0。同样地, 如果不需要服务名,那么可以将 service指定为NULL并且将 servlen指定为0。但是 host 和 service 中至少有一个必须为非NULL值(并且对应的长度参数必须为非零)。 

最后一个参数 flags 是一个位掩码,它控制着 getnameinfo() 的行为,其取值为下面这些常量取OR。

NI_DGRAM 在默认情况下, getnameinfo() 返回与流 socket(即TCP)服务对应的名字。通常,这是无关紧要的,因为正如599节中指出的那样,与TCP和UDP端口对应的服务名通常是相同的,但在一些名字不同的场景中, NI_DGRAM标记会强制返回数据报 socket(即UDP) 服务的名字。

NI_NAMEREQD 在默认情况下,如果无法解析主机名,那么在host中会返回一个数值地址字符串。如果指定了 NI_NAMEREQD,那么就会返回一个错误( EAI NONAME)。

NI_NOFQDN 在默认情况下会返回主机的完全限定域名。指定 NI_NOFQDN 标记会导致当主机位于局域网中时只返回名字的第一部分(即主机名)。

NI_NUMERICHOST 强制在host中返回一个数值地址字符串。这个标记在需要避免可能耗时较长的DNS服务器调用时是比较有用的。

NI_NUMERICSERV 强制在 service中返回一个十进制端口号字符串。这个标记在知道端口号没有对应的服务名时—如果它是一个由内核分配给 socket 的临时端口号以及需要避免不必要的搜索 etc/services的低效性时是比较有用的。 

getnameinfo()在成功时会返回0,发生错误时会返回上表非零错误码。

例子-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 <sys/un.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <netdb.h>

#define BACKLOG 50
#define PORT_NUM "50000"        /* Port number for server */

#define INT_LEN 30              /* Size of string able to hold largest
                                   integer (including terminating '\n') */
#define ADDRSTRLEN (NI_MAXHOST + NI_MAXSERV + 10)

ssize_t readLine(int fd, void *buffer, size_t n);

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

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

    if (n <= 0 || buffer == NULL) {
        errno = EINVAL;
        return -1;
    }

    buf = buffer;                       /* No pointer arithmetic on "void *" */

    totRead = 0;
    for (;;) {
        numRead = read(fd, &ch, 1);

        if (numRead == -1) {
            if (errno == EINTR)         /* Interrupted --> restart read() */
                continue;
            else
                return -1;              /* Some other error */

        } else if (numRead == 0) {      /* EOF */
            if (totRead == 0)           /* No bytes read; return 0 */
                return 0;
            else                        /* Some bytes read; add '\0' */
                break;
        } else {                        /* 'numRead' must be 1 if we get here */
            if (totRead < n - 1) {      /* Discard > (n - 1) bytes */
                totRead++;
                *buf++ = ch;
            }

            if (ch == '\n')
                break;
        }
    }

    *buf = '\0';
    return totRead;
}

int main(int argc, char *argv[]){
    uint32_t seqNum;
    char reqLenStr[INT_LEN];            /* Length of requested sequence */
    char seqNumStr[INT_LEN];            /* Start of granted sequence */
    struct sockaddr_storage claddr;
    int lfd, cfd, optval, reqLen;
    socklen_t addrlen;
    struct addrinfo hints;
    struct addrinfo *result, *rp;

    char addrStr[ADDRSTRLEN];
    char host[NI_MAXHOST];
    char service[NI_MAXSERV];

    if (argc > 1 && strcmp(argv[1], "--help") == 0){
        printf("%s [init-seq-num]\n", argv[0]);
        return 0;
    }

    seqNum = (argc > 1) ? atoi(argv[1]) : 0;

    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) errExit("signal");

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_family = AF_UNSPEC;        /* Allows IPv4 or IPv6 */
    hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
                        /* Wildcard IP address; service name is numeric */

    if (getaddrinfo(NULL, PORT_NUM, &hints, &result) != 0)
        errExit("getaddrinfo");

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

        if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1)
             errExit("setsockopt");

        if (bind(lfd, rp->ai_addr, rp->ai_addrlen) == 0)
            break;                      /* Success */
        
        //失败
        close(lfd);
    }

    if (rp == NULL)
        fatal("Could not bind socket to any address");

    if (listen(lfd, BACKLOG) == -1)
        errExit("listen");

    freeaddrinfo(result);

    for (;;) {                  /* Handle clients iteratively */

        /* Accept a client connection, obtaining client's address */

        addrlen = sizeof(struct sockaddr_storage);
        cfd = accept(lfd, (struct sockaddr *) &claddr, &addrlen);
        if (cfd == -1) {
            perror("accept");
            continue;
        }

        if (getnameinfo((struct sockaddr *) &claddr, addrlen,
                    host, NI_MAXHOST, service, NI_MAXSERV, 0) == 0)
            snprintf(addrStr, ADDRSTRLEN, "(%s, %s)", host, service);
        else
            snprintf(addrStr, ADDRSTRLEN, "(?UNKNOWN?)");
        printf("Connection from %s\n", addrStr);

        //读取客户端发送过来的数据,包括换行符
        if (readLine(cfd, reqLenStr, INT_LEN) <= 0) {
            close(cfd);
            continue;                   /* Failed read; skip request */
        }
        
        printf("%s", reqLenStr);    //打印
        
        reqLen = atoi(reqLenStr);
        if (reqLen <= 0) {              /* Watch for misbehaving clients */
            close(cfd);
            continue;                   /* Bad request; skip it */
        }

        //回复客户端
        snprintf(seqNumStr, INT_LEN, "%d\n", seqNum);
        if (write(cfd, seqNumStr, strlen(seqNumStr)) != strlen(seqNumStr))
            fprintf(stderr, "Error on write");

        seqNum += reqLen;               /* Update sequence number */

        if (close(cfd) == -1)           /* Close connection */
            perror("close");
    }
}

例子-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/un.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <netdb.h>

#define PORT_NUM "50000"        /* Port number for server */
#define INT_LEN 30              /* Size of string able to hold largest
                                   integer (including terminating '\n') */
void errExit(char *msg){
    perror(msg);
    exit(1);
}
void fatal(char *msg){
    printf(msg);
    printf("\n");
    exit(1);
}

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

    if (n <= 0 || buffer == NULL) {
        errno = EINVAL;
        return -1;
    }

    buf = buffer;                       /* No pointer arithmetic on "void *" */

    totRead = 0;
    for (;;) {
        numRead = read(fd, &ch, 1);

        if (numRead == -1) {
            if (errno == EINTR)         /* Interrupted --> restart read() */
                continue;
            else
                return -1;              /* Some other error */

        } else if (numRead == 0) {      /* EOF */
            if (totRead == 0)           /* No bytes read; return 0 */
                return 0;
            else                        /* Some bytes read; add '\0' */
                break;
        } else {                        /* 'numRead' must be 1 if we get here */
            if (totRead < n - 1) {      /* Discard > (n - 1) bytes */
                totRead++;
                *buf++ = ch;
            }

            if (ch == '\n')
                break;
        }
    }

    *buf = '\0';
    return totRead;
}

int main(int argc, char *argv[]){
    char *reqLenStr;                    /* Requested length of sequence */
    char seqNumStr[INT_LEN];            /* Start of granted sequence */
    int cfd;
    ssize_t numRead;
    struct addrinfo hints;
    struct addrinfo *result, *rp;

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

    /* Call getaddrinfo() to obtain a list of addresses that
       we can try connecting to */

    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 = SOCK_STREAM;
    hints.ai_flags = AI_NUMERICSERV;

    if (getaddrinfo(argv[1], PORT_NUM, &hints, &result) != 0)
        errExit("getaddrinfo");

    /* 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){
        cfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (cfd == -1)
            continue;                           /* On error, try next address */

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

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

        close(cfd);
    }

    if (rp == NULL)
        fatal("Could not connect socket to any address");

    freeaddrinfo(result);

    /* Send requested sequence length, with terminating newline */

    reqLenStr = (argc > 2) ? argv[2] : "1";
    if (write(cfd, reqLenStr, strlen(reqLenStr)) !=  strlen(reqLenStr))
        fatal("Partial/failed write (reqLenStr)");
    if (write(cfd, "\n", 1) != 1)
        fatal("Partial/failed write (newline)");

    //读取服务端的返回的数据,包括换行符
    numRead = readLine(cfd, seqNumStr, INT_LEN);
    if (numRead == -1)
        errExit("readLine");
    if (numRead == 0)
        fatal("Unexpected EOF from server");

    printf("Sequence number: %s", seqNumStr);   /* Includes '\n' */

    exit(EXIT_SUCCESS);                         /* Closes 'cfd' */
}
[root@izj6cfw9yi1iqoik31tqbgz c]# gcc server.c -o server
[root@izj6cfw9yi1iqoik31tqbgz c]# gcc client.c -o client
[root@izj6cfw9yi1iqoik31tqbgz c]# ./server 
Connection from (localhost, 54720)
1
Connection from (localhost, 54722)
1111
Connection from (localhost, 54724)
22
[root@izj6cfw9yi1iqoik31tqbgz c]# ./client localhost 1
Sequence number: 0
[root@izj6cfw9yi1iqoik31tqbgz c]# ./client localhost 1111
Sequence number: 1
[root@izj6cfw9yi1iqoik31tqbgz c]# ./client localhost 22
Sequence number: 1112


过时的主机和服务转换 api

#include <arpa/inet.h>

int inet_aton(const char *str, struct in_addr *addr);

//Returns 1 (true) if str is a valid dotted-decimal address, or 0 (false) on error

char *inet_ntoa(struct in_addr addr);

//Returns pointer to (statically allocated) dotted-decimal string version of addr

这两个函数将点分十进制与网络字节序的IPv4 地址相互转换。 

 

#include <netdb.h>

extern int h_errno;

struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const char *addr, socklen_t len, int type);

//Both return pointer to (statically allocated) hostent structure on success, or NULL on error

这两个函数允许主机名和ip之间相互转换。

报错的时候会设置全局变量 h_errno,这个变量与 errno 类似。下面两个函数类似于 perror() 和 strerror()()。


unix 与 Internet domain socket 比较

1.unix domain socket 的速度比 Internet domain socket 快。
2.unix domain socket 可以使用目录权限来控制对其访问。
3.unix domain socket 可以传递打开文件描述符。

总之一句话,只在本机通信的优先选择 unix domain socket,要联网的,选择Internet domain socket。


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

 

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