Linux系统学习笔记:套接字


Yeolar   2012-05-18 14:22   


Linux系统学习笔记


上一篇总结了Linux中的一些经典的进程间通信的机制,本篇总结使用套接字的进程间通信的方法。套接字的优势在于它采用同样的接口来处理计算机内和不同计算机间的通信,通常它用于网络进程间通信,在计算机内,UNIX域套接字可以作为全双工管道的实现。



目录



套接字接口

套接字接口是一组用来结合UNIX I/O函数进行进程间通信的函数,大多数系统上都实现了它,包括各种UNIX变种、Windows和Mac系统。




套接字接口



套接字描述符

套接字是通信端点的抽象,使用套接字描述符访问套接字,Linux用文件描述符实现套接字描述符,很多处理文件描述符的函数也可以用于套接字描述符。

使用 socket

1 #include <sys/socket.h>
2 
3 /* 创建套接字
4  * @return      成功返回文件描述符,出错返回-1 */
5 int socket(int domain, int type, int protocol);

参数说明:


domain

确定通信的特性,包括地址格式,每个域都有自己的地址格式。POSIX.1指定的域包括:

  • AF_INET
  • AF_INET6
  • AF_UNIX
  • AF_UNSPEC


type

确定套接字的类型。POSIX.1定义的套接字类型有:

  • SOCK_SEQPACKET
  • SOCK_STREAM
  • SOCK_DGRAM
  • SOCK_RAW


protocol domain 和  type 给定的情况下如果有多个协议,可以用 protocol

在 AF_INET 通信域, SOCK_SEQPACKET 套接字类型的默认协议是SCTP, SOCK_STREAM 套接字类型的默认协议是TCP,SOCK_DGRAM

面向连接的协议通信可比作打电话。在交换数据前,要求在本地套接字和远程套接字之间建立一个逻辑连接,即连接是端到端的通信信道。会话中不包含地址信息,它隐含在连接中。

SOCK_STREAM 套接字提供字节流服务,从套接字读取数据时可能需要多次函数调用。 SOCK_SEQPACKET

数据报提供了无连接服务,它是一种自含报文,发送数据报可比作寄邮件。可以发送很多数据报,但不保证它们的顺序,而且可能会丢失,数据报包含接收地址。

SOCK_RAW 套接字提供了一个数据报接口用于直接访问网络层(IP层),使用它时需要自己构造协议首部。创建SOCK_RAW

尽管套接字描述符是文件描述符,担不是所有使用文件描述符的函数都能处理套接字描述符,下面是有关函数的支持情况:

close

释放套接字

dup dup2

正常复制

fcntl

F_DUPFD 、 F_GETFD 、 F_GETFL 、 F_GETOWN 、 F_SETFD 、 F_SETFL 、F_SETOWN

fstat

stat

ioctl

支持部分命令,依赖于底层设备驱动

poll

正常使用

read readv

recv

select

正常使用

write writev

send

close

可以用 shutdown

1 #include <sys/socket.h>
2 
3 /* 关闭套接字上的输入/输出
4  * @return      成功返回0,出错返回-1 */
5 int shutdown(int sockfd, int how);

how

  • SHUT_RD
  • SHUT_WR
  • SHUT_RDWR



寻址

进程标识确定目标通信进程,它有两部分:计算机的网络地址确定计算机,服务确定计算机上的特定进程。



字节序

CPU有大端字节序和小端字节序两种字节表示顺序。为使不同的计算机可以正常交换信息,网络协议指定了字节序。

TCP/IP协议栈采用大端字节序。可以用下面的函数处理主机字节序和网络字节序之间的转换。

1 #include <arpa/inet.h>
 2 
 3 /* 将主机字节序的32位整型数转换为网络字节序 */
 4 uint32_t htonl(uint32_t hostlong);
 5 /* 将主机字节序的16位整型数转换为网络字节序 */
 6 uint16_t htons(uint16_t hostshort);
 7 /* 将网络字节序的32位整型数转换为主机字节序 */
 8 uint32_t ntohl(uint32_t netlong);
 9 /* 将网络字节序的16位整型数转换为主机字节序 */
10 uint16_t ntohs(uint16_t netshort);



地址格式

地址标识特定通信域中的套接字端点,地址格式和特定通信域相关。为兼容不同格式的地址,地址被强制转换为通用的地址结构 sockaddr

1 struct sockaddr {
2     sa_family_t sa_family;      /* 地址类型 */
3     char        sa_data[14];    /* 变长地址 */
4 };

因特网地址定义在 <netinet/in.h>

AF_INET

1 struct in_addr {
 2     in_addr_t       s_addr;         /* IPv4地址 */
 3 };
 4 
 5 struct sockaddr_in {
 6     sa_family_t     sin_family;     /* 地址类型 */
 7     in_port_t       sin_port;       /* 端口号 */
 8     struct in_addr  sin_addr;       /* IPv4地址 */
 9     unsigned char   sin_zero[8];    /* 0填充 */
10 };

AF_INET6

1 struct in6_addr {
 2     uint8_t         s6_addr[16];    /* IPv6地址 */
 3 };
 4 
 5 struct sockaddr_in6 {
 6     sa_family_t     sin6_family;    /* 地址类型 */
 7     in_port_t       sin6_port;      /* 端口号 */
 8     uint32_t        sin6_flowinfo;  /* 传输类和流信息 */
 9     struct in6_addr sin6_addr;      /* IPv6地址 */
10     uint32_t        sin6_scope_id;  /* 作用域的接口集 */
11 };

in_port_t 定义为 uint16_t , in_addr_t 定义为 uint32_t

可以用 inet_ntop 和 inet_pton 函数对IPv4和IPv6地址作二进制和点分十进制字符串之间的转换。类似的还有inet_addr 和 inet_ntoa

1 #include <arpa/inet.h>
2 
3 /* 将网络字节序的二进制地址转换为字符串格式
4  * @return      成功返回地址字符串指针,出错返回NULL */
5 const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
6 /* 将字符串格式转换为网络字节序的二进制地址
7  * @return      成功返回1,格式无效返回0,出错返回-1 */
8 int inet_pton(int af, const char *src, void *dst);

af 只支持 AF_INET 和 AF_INET6

size 指定字符串缓冲区 dst 的大小,可用 INET_ADDRSTRLEN 和 INET6_ADDRSTRLEN



地址查询

使用 gethostent

1 #include <netdb.h>
 2 
 3 /* 获取文件的下一个hostent结构
 4  * @return      成功返回指向hostent结构的指针,出错返回NULL */
 5 struct hostent *gethostent(void);
 6 /* gethostent的可重入版本,自设缓冲 */
 7 int gethostent_r(struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop);
 8 /* 回到文件开头 */
 9 void sethostent(int stayopen);
10 /* 关闭文件 */
11 void endhostent(void);
12 
13 /* 通过主机名或地址查询hostent结构
14  * @return      成功返回指向hostent结构的指针,出错返回NULL */
15 struct hostent *gethostbyname(const char *name);
16 #include <sys/socket.h>       /* for AF_INET */
17 struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);

结构 hostent

1 struct hostent {
2     char  *h_name;            /* 主机名 */
3     char **h_aliases;         /* 主机别名列表 */
4     int    h_addrtype;        /* 主机地址类型 */
5     int    h_length;          /* 地址长度 */
6     char **h_addr_list;       /* 地址列表 */
7 };

其中的地址采用网络字节序。

gethostbyname 和 gethostbyaddr

使用 getnetent

1 #include <netdb.h>
2 
3 struct netent *getnetent(void);
4 void setnetent(int stayopen);
5 void endnetent(void);
6 
7 struct netent *getnetbyname(const char *name);
8 struct netent *getnetbyaddr(uint32_t net, int type);

结构 netent

1 struct netent {
2     char      *n_name;     /* 网络名 */
3     char     **n_aliases;  /* 网络别名列表 */
4     int        n_addrtype; /* 网络地址类型 */
5     uint32_t   n_net;      /* 网络号 */
6 };

网络号按网络字节序返回。地址类型为 AF_XX

使用 getprotoent

1 #include <netdb.h>
2 
3 struct protoent *getprotoent(void);
4 void setprotoent(int stayopen);
5 void endprotoent(void);
6 
7 struct protoent *getprotobyname(const char *name);
8 struct protoent *getprotobynumber(int proto);

结构 protoent

1 struct protoent {
2     char  *p_name;       /* 协议名 */
3     char **p_aliases;    /* 协议别名列表 */
4     int    p_proto;      /* 协议号 */
5 };

服务由地址的端口号部分表示,每种服务由一个唯一和熟知的端口号提供。使用 getservent

1 #include <netdb.h>
2 
3 struct servent *getservent(void);
4 void setservent(int stayopen);
5 void endservent(void);
6 
7 struct servent *getservbyname(const char *name, const char *proto);
8 struct servent *getservbyport(int port, const char *proto);

结构 servent

1 struct servent {
2     char  *s_name;       /* 服务名 */
3     char **s_aliases;    /* 服务别名列表 */
4     int    s_port;       /* 端口号 */
5     char  *s_proto;      /* 使用的协议 */
6 };

getaddrinfo 函数可以将一个主机名字和服务名字映射到一个地址, getnameinfo 的作用相反,用它们替换旧函数gethostbyname 和 gethostbyaddr 。 freeaddrinfo 用来释放 addrinfo

1 #include <sys/types.h>
 2 #include <sys/socket.h>
 3 #include <netdb.h>
 4 
 5 /* 获取地址信息
 6  * @return      成功返回0,出错返回非0错误码 */
 7 int getaddrinfo(const char *node, const char *service,
 8                 const struct addrinfo *hints, struct addrinfo **res);
 9 /* 释放一个或多个addrinfo结构的链表 */
10 void freeaddrinfo(struct addrinfo *res);
11 /* 将错误码转换为错误信息 */
12 const char *gai_strerror(int errcode);
13 
14 /* 获取主机名或/和服务名
15  * @return      成功返回0,出错返回非0值 */
16 int getnameinfo(const struct sockaddr *sa, socklen_t salen,
17                 char *host, size_t hostlen,
18                 char *serv, size_t servlen, int flags);

需要提供主机名或/和服务名,主机名可以是节点名或点分十进制字符串表示的主机地址。

结构 addrinfo

1 struct addrinfo {
 2     int              ai_flags;      /* 自定义行为 */
 3     int              ai_family;     /* 地址类型 */
 4     int              ai_socktype;   /* 套接字类型 */
 5     int              ai_protocol;   /* 协议 */
 6     size_t           ai_addrlen;    /* 地址长度 */
 7     struct sockaddr *ai_addr;       /* 地址 */
 8     char            *ai_canonname;  /* 主机规范名 */
 9     struct addrinfo *ai_next;       /* 链表中的下一个结构 */
10 };

可以用 hints 来过滤得到的结构,它只使用 ai_flags 、 ai_family 、 ai_socktype 、 ai_protocol 字段,其他字段必须设为0或 NULL

ai_flags

  • AI_ADDRCONFIG
  • AI_ALL :查找IPv4和IPv6地址,仅用于 AI_V4MAPPED
  • AI_CANONNAME
  • AI_NUMERICHOST
  • AI_NUMERICSERV
  • AI_PASSIVE
  • AI_V4MAPPED

flags

  • NI_DGRAM
  • NI_NAMEREQD
  • NI_NOFQDN
  • NI_NUMERICHOST
  • NI_NUMERICSERV



绑定地址

对于服务器,需要给接收客户端请求的套接字绑定一个众所周知的地址。客户端则让系统选择一个默认地址即可。

可以用 bind

1 #include <sys/socket.h>
2 
3 /* 将地址绑定到套接字
4  * @return      成功返回0,出错返回-1 */
5 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

对于地址,要求:

  • 地址必须有效,不能指定其他机器的地址。
  • 地址必须和套接字的地址族所支持的格式匹配。
  • 如果不是超级用户,端口号不能小于1024。
  • 一般只有套接字端点能够和地址绑定。

对于因特网域,如果指定IP地址为 INADDR_ANY

getsockname

1 #include <sys/socket.h>
2 
3 /* 获取绑定到套接字的地址
4  * @return      成功返回0,出错返回-1 */
5 int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

addrlen 指定 addr

套接字已经和对方连接时,可以用 getpeername

1 #include <sys/socket.h>
2 
3 /* 获取绑定到套接字的地址
4  * @return      成功返回0,出错返回-1 */
5 int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);



建立连接

面向连接的网络服务在交换数据前需要首先在请求服务的套接字(客户端)和提供服务的套接字(服务器)之间建立连接。可以用 connect

1 #include <sys/socket.h>
2 
3 /* 建立连接
4  * @return      成功返回0,出错返回-1 */
5 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

addr 为服务器的地址。如果 sockfd

由于服务器的负载变化等问题, connect

1 #include <unistd.h>
 2 #include <sys/socket.h>
 3 
 4 #define MAXSLEEP 128
 5 
 6 int connect_retry(int sockfd, const struct sockaddr *addr, socklen_t alen)
 7 {
 8     int nsec;
 9 
10     /* Try to connect with exponential backoff. */
11     for (nsec = 1; nsec <= MAXSLEEP; nsec <<= 1) {
12         if (connect(sockfd, addr, alen) == 0) {
13             /* Connection accepted. */
14             return(0);
15         }
16         /* Delay before trying again. */
17         if (nsec <= MAXSLEEP/2)
18             sleep(nsec);
19     }
20     return(-1);
21 }

connect 也可用于无连接网络服务。所有发送报文的目标地址被设为 addr ,并且只能接收来自 addr

服务器使用 listen

1 #include <sys/socket.h>
2 
3 /* 说明可以接受连接
4  * @return      成功返回0,出错返回-1 */
5 int listen(int sockfd, int backlog);

backlog 建议连接请求的队列大小,实际值由系统决定,上限为 SOMAXCONN

之后用 accept

1 #include <sys/socket.h>
2 
3 /* 获得连接请求,建立连接
4  * @return      成功返回文件描述符,出错返回-1 */
5 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept 会将客户端地址设置到 addr 指向的缓冲区并更新 addrlen 指向的值,若不关心客户端可将它们设为 NULL

函数返回连接到调用 connect 的客户端的套接字描述符,它和 sockfd 有相同的套接字类型和地址族, sockfd

如果没有连接请求, accept 会阻塞直到有请求到来,如果 sockfd 为非阻塞模式,则返回-1,并设 errno 为 EAGAIN或 EWOULDBLOCK (两者等价)。服务器也可以用 poll 或 select



数据传输

首先,因为套接字端点用文件描述符表示,所以可以用 read 和 write 函数来交换数据,这会带来兼容性上的好处。但如果想获得更多的功能,需要使用 send 和 recv

send

1 #include <sys/types.h>
 2 #include <sys/socket.h>
 3 
 4 /* 发送数据
 5  * @return      成功返回发送的字节数,出错返回-1 */
 6 ssize_t send(int sockfd, const void *buf, size_t len, int flags);
 7 ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
 8         const struct sockaddr *dest_addr, socklen_t addrlen);
 9 ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

send 函数类似 write ,但它支持额外的参数 flags

  • MSG_DONTROUTE
  • MSG_DONTWAIT :允许非阻塞操作,等价于用 O_NONBLOCK
  • MSG_EOR
  • MSG_OOB

send 成功返回只代表数据已经正确发送到网络上,不表示连接另一端的进程接收数据。对于支持为报文设限的协议,如果报文大小超过协议支持的最大值, send 失败并设 errno 为 EMSGSIZE 。对于字节流协议, send

sendto 和 send 的区别是它支持在无连接的套接字上指定目标地址。无连接的套接字如果在调用 connect 时没有设置目标地址,则不能用 send

sendmsg 类似于 writev ,可以指定多重缓冲区来传输数据。结构 msghdr

1 struct msghdr {
 2     void         *msg_name;       /* 可选地址 */
 3     socklen_t     msg_namelen;    /* 地址大小 */
 4     struct iovec *msg_iov;        /* I/O缓冲区数组 */
 5     size_t        msg_iovlen;     /* 数组中元素数 */
 6     void         *msg_control;    /* 辅助数据 */
 7     size_t        msg_controllen; /* 辅助数据大小 */
 8     int           msg_flags;      /* 接收到的数据的标志 */
 9 };

相应地, recv

1 #include <sys/types.h>
 2 #include <sys/socket.h>
 3 
 4 /* 接收数据
 5  * @return      成功返回接收的消息字节数,无可用消息或对方已按序结束返回0,出错返回-1 */
 6 ssize_t recv(int sockfd, void *buf, size_t len, int flags);
 7 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
 8         struct sockaddr *src_addr, socklen_t *addrlen);
 9 ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

recv 函数类似 read ,但同样它支持额外的参数 flags

  • MSG_WAITALL :等待直到所有数据可用,只对 SOCK_STREAM
  • MSG_TRUNC
  • MSG_PEEK
  • MSG_OOB

SOCK_DGRAM 和 SOCK_SEQPACKET 套接字类型一次读取就返回整个报文,所以 MSG_WAITALL

如果发送者用 shutdown 结束传输,或网络协议支持顺序关闭且发送端已关闭,则数据接收完之后 recv

recvfrom

recvmsg 类似于 readv ,可以将接收到的数据放入多个缓冲区,也可用于想接收辅助数据的情况。 msghdr 结构前面已经说明,从 recvmsg 返回时会设置 msg_flags

  • MSG_DONTWAIT : recvmsg
  • MSG_TRUNC
  • MSG_CTRUNC
  • MSG_EOR
  • MSG_OOB



套接字选项

有三种类型的套接字选项:

  1. 通用选项,适用于所有套接字类型。
  2. 特定套接字的选项,会依赖于下层协议。
  3. 特定协议的选项。

用 getsockopt 和 setsockopt

1 #include <sys/socket.h>
2 
3 /* 获取和设置套接字选项
4  * @return      成功返回0,出错返回-1 */
5 int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
6 int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

参数说明:


level SOL_SOCKET ,否则设为控制该选项的协议号。对TCP选项设为  IPPROTO_TCP ,对UDP选项设为  IPPROTO_UDP ,对IP选项设为  IPPROTO_IP optname

指定套接字选项。通用套接字选项包括:

选项

optval

说明

SO_ACCEPTCONN

int

getsockopt

SO_BROADCAST

int

*optval

SO_DEBUG

int

*optval

SO_DONTROUTE

int

*optval

SO_ERROR

int

getsockopt

SO_KEEPALIVE

int

*optval

SO_LINGER

struct linger

当有未发送消息并且套接字关闭时,延迟时间

SO_OOBINLINE

int

*optval

SO_RCVBUF

int

接收缓冲区的字节大小

SO_RCVLOWAT

int

接收调用中返回的数据最小字节数

SO_RCVTIMEO

struct timeval

套接字接收调用的超时值

SO_REUSEADDR

int

*optval 非0,重用 bind

SO_SNDBUF

int

发送缓冲区的字节大小

SO_SNDLOWAT

int

发送调用中发送的数据最小字节数

SO_SNDTIMEO

struct timeval

套接字发送调用的超时值

SO_TYPE

int

getsockopt


optval 根据选项的不同指向一个数据结构或整数。

通常TCP不允许绑定同一个地址,可以用 SO_REUSEADDR

1 #include <errno.h>
 2 #include <sys/socket.h>
 3 
 4 int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen)
 5 {
 6     int fd, err;
 7     int reuse = 1;
 8 
 9     if ((fd = socket(addr->sa_family, type, 0)) < 0)
10         return(-1);
11     if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) < 0) {
12         err = errno;
13         goto errout;
14     }
15     if (bind(fd, addr, alen) < 0) {
16         err = errno;
17         goto errout;
18     }
19     if (type == SOCK_STREAM || type == SOCK_SEQPACKET) {
20         if (listen(fd, qlen) < 0) {
21             err = errno;
22             goto errout;
23         }
24     }
25     return(fd);
26 errout:
27     close(fd);
28     errno = err;
29     return(-1);
30 }



带外数据

带外数据是一些通信协议支持的可选特性,允许更高优先级的数据比普通数据优先传输。TCP支持带外数据,UDP不支持。

在TCP中带外数据称为紧急数据,TCP只支持1字节的紧急数据,允许紧急数据在普通数据数据流之外传输。在 send函数族中指定 MSG_OOB

用 SO_OOBINLINE

1 #include <sys/socket.h>
2 
3 /* 下一个要读的字节在标志处返回1,没在标志处返回0,出错返回-1 */
4 int sockatmark(int sockfd);

在读取队列出现带外数据时, select 函数返回文件描述符并异常挂起。可在普通数据流或用加 MSG_OOB 标志的 recv函数族接收紧急数据,后面的紧急数据会覆盖前面的。



UNIX域套接字

UNIX域套接字用于同一台机器上的进程间通信。因特网域套接字也可以实现但UNIX域套接字效率更高,后者只复制数据而不处理协议和其他和网络相关的工作。

UNIX域套接字有流和数据报两种接口,它的数据报服务是可靠的。

可以用 socketpair

1 #include <sys/socket.h>
2 
3 /* 创建一对无名的相互连接的UNIX域套接字
4  * @return      成功返回0,出错返回-1 */
5 int socketpair(int domain, int type, int protocol, int sv[2]);

例:

1 #include <sys/socket.h>
2 
3 /* Returns a full-duplex "stream" pipe (a UNIX domain socket) with the two file descriptors returned in fd[0] and fd[1]. */
4 int s_pipe(int fd[2])
5 {
6     return(socketpair(AF_UNIX, SOCK_STREAM, 0, fd));
7 }

也可以用 socket 创建命名的UNIX域套接字,用标准的 bind 、 listen 、 accept 、 connect

UNIX域套接字的地址定义在 <sys/un.h> 中,由 sockaddr_un

1 struct sockaddr_un {
2     sa_family_t sun_family;     /* AF_UNIX */
3     char        sun_path[108];  /* 路径名 */
4 };

可以用 offsetof 函数获取 sun_path 的偏移量再加上 sun_path

将地址绑定到UNIX域套接字时,系统会用该路径名创建一个 S_IFSOCK 类型的文件。它只用于向客户进程告诉套接字名字,不能打开,也不能由应用程序用于通信。如果文件已经存在,则 bind



使用套接字的示例



面向连接的ruptime

客户端:

1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <netdb.h>
 5 #include <errno.h>
 6 #include <sys/socket.h>
 7 #include "error.h"
 8 
 9 #define MAXADDRLEN  256
10 #define BUFLEN      128
11 
12 extern int connect_retry(int, const struct sockaddr *, socklen_t);
13 
14 void print_uptime(int sockfd)
15 {
16     int     n;
17     char    buf[BUFLEN];
18 
19     while ((n = recv(sockfd, buf, BUFLEN, 0)) > 0)
20         write(STDOUT_FILENO, buf, n);
21     if (n < 0)
22         err_sys("recv error");
23 }
24 
25 int main(int argc, char *argv[])
26 {
27     struct addrinfo *ailist, *aip;
28     struct addrinfo hint;
29     int             sockfd, err;
30 
31     if (argc != 2)
32         err_quit("usage: ruptime hostname");
33     hint.ai_flags = 0;
34     hint.ai_family = 0;
35     hint.ai_socktype = SOCK_STREAM;
36     hint.ai_protocol = 0;
37     hint.ai_addrlen = 0;
38     hint.ai_canonname = NULL;
39     hint.ai_addr = NULL;
40     hint.ai_next = NULL;
41     if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
42         err_quit("getaddrinfo error: %s", gai_strerror(err));
43     for (aip = ailist; aip != NULL; aip = aip->ai_next) {
44         if ((sockfd = socket(aip->ai_family, SOCK_STREAM, 0)) < 0)
45             err = errno;
46         if (connect_retry(sockfd, aip->ai_addr, aip->ai_addrlen) < 0) {
47             err = errno;
48         } else {
49             print_uptime(sockfd);
50             exit(0);
51         }
52     }
53     fprintf(stderr, "can't connect to %s: %s\n", argv[1], strerror(err));
54     exit(1);
55 }

服务器( popen

1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <netdb.h>
 4 #include <errno.h>
 5 #include <syslog.h>
 6 #include <sys/socket.h>
 7 #include "error.h"
 8 
 9 #define BUFLEN  128
10 #define QLEN 10
11 
12 #ifndef HOST_NAME_MAX
13 #define HOST_NAME_MAX 256
14 #endif
15 
16 extern int initserver(int, struct sockaddr *, socklen_t, int);
17 
18 void serve(int sockfd)
19 {
20     int     clfd;
21     FILE    *fp;
22     char    buf[BUFLEN];
23 
24     for (;;) {
25         clfd = accept(sockfd, NULL, NULL);
26         if (clfd < 0) {
27             syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
28             exit(1);
29         }
30         if ((fp = popen("/usr/bin/uptime", "r")) == NULL) {
31             sprintf(buf, "error: %s\n", strerror(errno));
32             send(clfd, buf, strlen(buf), 0);
33         } else {
34             while (fgets(buf, BUFLEN, fp) != NULL)
35                 send(clfd, buf, strlen(buf), 0);
36             pclose(fp);
37         }
38         close(clfd);
39     }
40 }
41 
42 int main(int argc, char *argv[])
43 {
44     struct addrinfo *ailist, *aip;
45     struct addrinfo hint;
46     int             sockfd, err, n;
47     char            *host;
48 
49     if (argc != 1)
50         err_quit("usage: ruptimed");
51 #ifdef _SC_HOST_NAME_MAX
52     n = sysconf(_SC_HOST_NAME_MAX);
53     if (n < 0)  /* best guess */
54 #endif
55         n = HOST_NAME_MAX;
56     host = malloc(n);
57     if (host == NULL)
58         err_sys("malloc error");
59     if (gethostname(host, n) < 0)
60         err_sys("gethostname error");
61     daemonize("ruptimed");
62     hint.ai_flags = AI_CANONNAME;
63     hint.ai_family = 0;
64     hint.ai_socktype = SOCK_STREAM;
65     hint.ai_protocol = 0;
66     hint.ai_addrlen = 0;
67     hint.ai_canonname = NULL;
68     hint.ai_addr = NULL;
69     hint.ai_next = NULL;
70     if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {
71         syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err));
72         exit(1);
73     }
74     for (aip = ailist; aip != NULL; aip = aip->ai_next) {
75         if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr,
76           aip->ai_addrlen, QLEN)) >= 0) {
77             serve(sockfd);
78             exit(0);
79         }
80     }
81     exit(1);
82 }

服务器( fork

1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <netdb.h>
 4 #include <errno.h>
 5 #include <syslog.h>
 6 #include <fcntl.h>
 7 #include <sys/socket.h>
 8 #include <sys/wait.h>
 9 #include "error.h"
10 
11 #define QLEN 10
12 
13 #ifndef HOST_NAME_MAX
14 #define HOST_NAME_MAX 256
15 #endif
16 
17 extern int initserver(int, struct sockaddr *, socklen_t, int);
18 
19 void serve(int sockfd)
20 {
21     int     clfd, status;
22     pid_t   pid;
23 
24     for (;;) {
25         clfd = accept(sockfd, NULL, NULL);
26         if (clfd < 0) {
27             syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
28             exit(1);
29         }
30         if ((pid = fork()) < 0) {
31             syslog(LOG_ERR, "ruptimed: fork error: %s", strerror(errno));
32             exit(1);
33         } else if (pid == 0) {  /* child */
34             /*
35              * The parent called daemonize ({Prog daemoninit}), so
36              * STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO
37              * are already open to /dev/null.  Thus, the call to
38              * close doesn't need to be protected by checks that
39              * clfd isn't already equal to one of these values.
40              */
41             if (dup2(clfd, STDOUT_FILENO) != STDOUT_FILENO ||
42               dup2(clfd, STDERR_FILENO) != STDERR_FILENO) {
43                 syslog(LOG_ERR, "ruptimed: unexpected error");
44                 exit(1);
45             }
46             close(clfd);
47             execl("/usr/bin/uptime", "uptime", (char *)0);
48             syslog(LOG_ERR, "ruptimed: unexpected return from exec: %s",
49               strerror(errno));
50         } else {        /* parent */
51             close(clfd);
52             waitpid(pid, &status, 0);
53         }
54     }
55 }
56 
57 int main(int argc, char *argv[])
58 {
59     struct addrinfo *ailist, *aip;
60     struct addrinfo hint;
61     int             sockfd, err, n;
62     char            *host;
63 
64     if (argc != 1)
65         err_quit("usage: ruptimed");
66 #ifdef _SC_HOST_NAME_MAX
67     n = sysconf(_SC_HOST_NAME_MAX);
68     if (n < 0)  /* best guess */
69 #endif
70         n = HOST_NAME_MAX;
71     host = malloc(n);
72     if (host == NULL)
73         err_sys("malloc error");
74     if (gethostname(host, n) < 0)
75         err_sys("gethostname error");
76     daemonize("ruptimed");
77     hint.ai_flags = AI_CANONNAME;
78     hint.ai_family = 0;
79     hint.ai_socktype = SOCK_STREAM;
80     hint.ai_protocol = 0;
81     hint.ai_addrlen = 0;
82     hint.ai_canonname = NULL;
83     hint.ai_addr = NULL;
84     hint.ai_next = NULL;
85     if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {
86         syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err));
87         exit(1);
88     }
89     for (aip = ailist; aip != NULL; aip = aip->ai_next) {
90         if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr,
91           aip->ai_addrlen, QLEN)) >= 0) {
92             serve(sockfd);
93             exit(0);
94         }
95     }
96     exit(1);
97 }



无连接的ruptime

客户端:

1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <netdb.h>
 4 #include <errno.h>
 5 #include <sys/socket.h>
 6 #include "error.h"
 7 
 8 #define BUFLEN      128
 9 #define TIMEOUT     20
10 
11 void sigalrm(int signo) {}
12 
13 void print_uptime(int sockfd, struct addrinfo *aip)
14 {
15     int     n;
16     char    buf[BUFLEN];
17 
18     buf[0] = 0;
19     if (sendto(sockfd, buf, 1, 0, aip->ai_addr, aip->ai_addrlen) < 0)
20         err_sys("sendto error");
21     alarm(TIMEOUT);
22     if ((n = recvfrom(sockfd, buf, BUFLEN, 0, NULL, NULL)) < 0) {
23         if (errno != EINTR)
24             alarm(0);
25         err_sys("recv error");
26     }
27     alarm(0);
28     write(STDOUT_FILENO, buf, n);
29 }
30 
31 int main(int argc, char *argv[])
32 {
33     struct addrinfo     *ailist, *aip;
34     struct addrinfo     hint;
35     int                 sockfd, err;
36     struct sigaction    sa;
37 
38     if (argc != 2)
39         err_quit("usage: ruptime hostname");
40     sa.sa_handler = sigalrm;
41     sa.sa_flags = 0;
42     sigemptyset(&sa.sa_mask);
43     if (sigaction(SIGALRM, &sa, NULL) < 0)
44         err_sys("sigaction error");
45     hint.ai_flags = 0;
46     hint.ai_family = 0;
47     hint.ai_socktype = SOCK_DGRAM;
48     hint.ai_protocol = 0;
49     hint.ai_addrlen = 0;
50     hint.ai_canonname = NULL;
51     hint.ai_addr = NULL;
52     hint.ai_next = NULL;
53     if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
54         err_quit("getaddrinfo error: %s", gai_strerror(err));
55     for (aip = ailist; aip != NULL; aip = aip->ai_next) {
56         if ((sockfd = socket(aip->ai_family, SOCK_DGRAM, 0)) < 0) {
57             err = errno;
58         } else {
59             print_uptime(sockfd, aip);
60             exit(0);
61         }
62     }
63     fprintf(stderr, "can't contact %s: %s\n", argv[1], strerror(err));
64     exit(1);
65 }

服务器:

1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <netdb.h>
 4 #include <errno.h>
 5 #include <syslog.h>
 6 #include <sys/socket.h>
 7 #include "error.h"
 8 
 9 #define BUFLEN      128
10 #define MAXADDRLEN  256
11 
12 #ifndef HOST_NAME_MAX
13 #define HOST_NAME_MAX 256
14 #endif
15 
16 extern int initserver(int, struct sockaddr *, socklen_t, int);
17 
18 void serve(int sockfd)
19 {
20     int         n;
21     socklen_t   alen;
22     FILE        *fp;
23     char        buf[BUFLEN];
24     char        abuf[MAXADDRLEN];
25 
26     for (;;) {
27         alen = MAXADDRLEN;
28         if ((n = recvfrom(sockfd, buf, BUFLEN, 0,
29           (struct sockaddr *)abuf, &alen)) < 0) {
30             syslog(LOG_ERR, "ruptimed: recvfrom error: %s", strerror(errno));
31             exit(1);
32         }
33         if ((fp = popen("/usr/bin/uptime", "r")) == NULL) {
34             sprintf(buf, "error: %s\n", strerror(errno));
35             sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)abuf, alen);
36         } else {
37             if (fgets(buf, BUFLEN, fp) != NULL)
38                 sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)abuf, alen);
39             pclose(fp);
40         }
41     }
42 }
43 
44 int main(int argc, char *argv[])
45 {
46     struct addrinfo *ailist, *aip;
47     struct addrinfo hint;
48     int             sockfd, err, n;
49     char            *host;
50 
51     if (argc != 1)