打印主机和服务信息
如果由多个协议为指定的主机提供相应的服务,程序性会打印出超过一条的信息。本例中,仅打印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);
}
支持重试的连接
使指数补偿算法用处理瞬时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
用于显示命令直接写到套接字的服务器程序
以前的方式采用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);
}