本章将考察不同计算机(通过网络连接)上的进程相互通信的机制:网络进程间通信。

套接字描述符

正如使用文件描述符访问文件,应用程序用套接字描述符访问套接字。

许多处理文件描述符函数(如read和write)可以用于处理套接字描述符。调用socket函数创建一个套接字

#include <sys/socket.h>
int socket(int domain,int type,int protocol);

参数domain(域)确定通信的特性,包括地址格式。下图总结了POSIX.1指定的各个域,每个域都有自己表示地址的格式

参数type确定套接字的类型,下图总结了POSIX.1定义的套接字类型。

参数protocol通常是0,表示为给定的域和套接字类型选择默认协议。

调用socket与调用open相类似。可以调用close来关闭对文件或套接字的访问,并且释放该描述符以便重新使用。

下图总结了到目前为止所讨论的大多数以文件描述符为参数的函数使用套接字描述符时的行为。

套接字通信是双向的。可以采用shutdown函数来禁止一个套接字的I/O

#include <sys/socket.h>
int shutdown(int sockfd,int how);

如果how是SHUT_RD(关闭读端),那么无法从套接字读取数据。

如果how是SHUT_WR(关闭写端),那么无法使用套接字发送数据。

如果how是SHUT_RDWR,则既无法读取数据,又无法发送数据。

使用close函数直到关闭了最后一个引用它的文件描述符才会释放这个套接字,而shutdown允许使用一个套接字处于不活动状态,和引用它的文件描述符数目无关。

字节序

有4个用来处理处理器字节序和网络字节序之间实施转换的函数

#include <arpa/inet.h>
uint32_t htonl(unit32_t hostint32); //返回值:以网络字节序表示的32位整数
uint16_t htons(unit16_t hostint16); //返回值:以网络字节序表示的16位整数
uint32_t ntohl(unit32_t netint32); //返回值:以主机字节序表示的32位整数
uint32_t ntons(unit16_t netint16); //返回值:以主机字节序表示的16位整数

地址格式

一个地址标识一个特定通信域的套接字端点,地址格式与这个特定的通信域相关。

为使不同的格式地址能够传入到套接字函数,地址会被强制转换成一个通用的地址结构sockaddr(Linux中的定义):

struct sockaddr{
sa_family_t sa_family;
char sa_data[];
};

因特网地址定义在<netinet/in.h>头文件中。在IPv4因特网域中,套接字地址用结构sockaddr_in表示:

struct in_addr{
in_addr_t s_addr;
}; struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};

IPv6因特网域套接字地址用结构sockaddr_in6表示:

struct in6_addr{
uint8_t s6_addr[];
}; struct sockaddr_in6{
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};

下面两个函数用于二进制地址格式与十进制字符表示(a.b.c.d)之间的相互转换:

#include <arpa/inet.h>
const char * inet_ntop (int domain, const void * restrict addr, char * restrict str, socklen_t size);
int inet_pton ( int domain, const char * restrict str, void * restrict addr);

地址查询

通过调用gethostend,可以找到给定计算机系统的主机信息

#include <netdb.h>
struct hostent *gethostent(void);
void sethostent(int stayopen);
void endhostent(void);

如果主机数据库文件没有打开,gethostent会打开它。函数gethostent返回文件中的下一个条目。

函数sethostent会打开文件,如果文件已经被打开,那么将其回绕。

hostent结构如下至少包含以下成员:

 struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses */
};

我们可以用以下函数在协议名字和协议编号之间进行映射:

#include <netdb.h>
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);
void setprotoent(int stayopen);
void endprotoent(void);

POSIX.1定义的protoent结构至少包含以下成员:

struct protoent {
char *p_name; /* official protocol name */
char **p_aliases; /* alias list */
int p_proto; /* protocol number */
};

可以使用函数getservbyname将一个服务名映射到一个端口号,使用函数getservbyport将一个端口号映射到一个服务名,使用函数getservent顺序扫描服务数据库

#include <netdb.h>
struct servent *getservbyname(const char *name,const char *proto);
struct servent *getservbyport(int port,const char *proto); struct servent *getservent(void);
void setservent(int stayopen);
void endservent(void);

servent结构至少包含以下成员:

struct servent {
char *s_name; /* official service name */
char **s_aliases; /* alias list */
int s_port; /* port number */
char *s_proto; /* protocol to use */
};

POSIX.1定义了若干新的函数,允许一个应用程序将一个主机名和一个服务器名映射到一个地址

#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *restrict host,
const char *restrict service,
const struct addrinfo *restrict hint,
struct addrinfo **restrict res);
void freeaddrinfo(struct addrinfo *ai);

需要提供主机名、服务名,或者两者都提供。函数返回一个链表结构addrinfo,可以用freeaddrinfo来释放一个或多个这种结构。

addrinfo结构的定义至少包含以下成员:

 struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};

可以提供一个可选的hint来选择符合特定条件的地址。

如果getaddrinfo失败,需要调用gai_strerror将返回的错误码转换成错误消息

#include <netdb.h>
const char *gai_strerror(int error);

getnameinfo函数将一个地址转换成一个主机名和一个服务名

#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *restrict addr,socklen_t alen,
char *restrict host,socklen_t hostlen,
char *restrict service,socklen_t servlen,int flags);

套接字地址被翻译为一个主机名和一个服务名。flags参数提供了一些控制翻译的方式,下图总结了支持的标志

下面程序说明了getaddrinfo函数的使用方法

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h> void print_family(struct addrinfo *aip)
{
printf("family ");
switch(aip->ai_family)
{
case AF_INET:
printf("inet ");
break;
case AF_INET6:
printf("inet6 ");
break;
case AF_UNIX:
printf("unix ");
break;
case AF_UNSPEC:
printf("unspecified ");
break;
default:
printf("unkown ");
}
} 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 :
printf("default ");
break;
case IPPROTO_TCP:
printf("TCP ");
break;
case IPPROTO_UDP:
printf("UDP ");
break;
case IPPROTO_RAW:
printf("raw ");
break;
default:
printf("unknown (%d)",aip->ai_protocol);
}
} void print_flags(struct addrinfo *aip)
{
printf("flags");
if(aip->ai_flags == )
printf("");
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 ");
}
} int main()
{
struct addrinfo *ailist,*aip;
struct addrinfo hint;
struct sockaddr_in *sinp;
const char *addr;
int err;
char abuf[INET_ADDRSTRLEN];
hint.ai_flags = AI_CANONNAME;
hint.ai_family = ;
hint.ai_socktype = ;
hint.ai_protocol = ;
hint.ai_addrlen = ;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if(getaddrinfo("localhost",NULL,&hint,&ailist) != )
{
printf("getaddrinfo error: %s",gai_strerror(err));
exit(-);
}
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)
{
sinp = (struct sockaddr_in *)aip->ai_addr;
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();
}

下面是程序运行结果

将套接字与地址关联

使用bind函数来关联地址和套接字

#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t len);

可以调用getsockname函数来发现绑定到套接字上的地址

#include <sys/socket.h>
int getsockname(int sockfd,struct sockaddr *restrict addr,socklen_t *restrict alenp);

如果套接字已经和对等方连接,可以调用getpeername函数找到对方的地址

#include <sys/socket.h>
int getpeername(int sockfd,struct sockaddr *restrict addr,socklen_t *restrict alenp);

建立连接

如果处理一个面向连接的网络服务,那么开始交换数据之前,需要使用connect函数来建立连接

#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socklen_t len);

connect中指定的地址是我们想与之通信的服务器地址。

服务器调用listen函数来宣告它愿意接受连接请求

#include <sys/socket.h>
int listen(int sockfd,int backlog);

参数backlog提供了一个提示,提示系统改进程所要入队的未完成连接请求数量。一旦队列满了,系统就会拒绝多余的连接请求。

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

#include <sys/socket.h>
int accept(int fd,struct sockaddr *restrict addr,socklen_t *restrict len);

函数返回的是一个连接到调用connect的客户端的套接字描述符

数据传输

在建立了连接之后,就可以使用read和write来通过套接字通信。

除此之外,有6个位数据传递而设计的套接字函数(3个用于发送数据,3个用于接收数据)。

最简单的是send,它和write很像,但是可以指定标志来改变处理传输数据的方式

#include <sys/socket.h>
ssize_t send(int sockfd,const void *buf,size_t nbytes,int flags);

下面总结了flags可能的取值

函数sendto和send很类似,区别在于sendto可以在无连接的套接字上指定一个目标地址

#include <sys/socket.h>
ssize_t sendto(int sockfd,const void *buf,size_t nbytes,int flags,
const struct sockaddr *destaddr,socklen_t destlen);

对于面向连接的套接字,目标地址是被忽略的。

通过套接字发送数据时,还有一个选择。可以调用带有msghdr结构的sendmsg来指定多重缓冲区传输数据。

#include <sys/socket.h>
ssize_t sendmsg(int sockfd,const struct msghdr *msg,int flags);

POSIX.1定义了msghdr结构,它至少有以下成员:

struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
size_t msg_controllen; /* ancillary data buffer len */
int msg_flags; /* flags on received message */
};

函数recv和read相似,但是recv可以指定标志来控制如何接收数据

#include <sys/socket.h>
ssize_t recv(int sockfd,void *buf,size_t nbytes,int flags);

下图总结了这些标志

如果有兴趣定位发送者,可以使用recvfrom来得到数据发送者的源地址

#include <sys/socket.h>
ssize_t recvfrom(int sockfd,void *restrict buf,size_t len,int flags
struct sockaddr *restrict addr,
socklen_t *restrict addrlen);

如果addr非空,它将包含数据发送者的套接字端点地址。通常用于无连接的套接字。

为了将接收到的数据送入多个缓冲区,或者想接收辅助数据,可以使用recvmsg

#include <sys/socket.h>
ssize_t recvmsg(int sockfd,struct msghdr *msg,int flags);

返回时,msghdr结构中的msg_flags字段都被设为所接收数据的特征。

面向连接实例

下面程序显示了一个与服务器通信的客户端从系统的uptime命令获得输出。

 #include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h> #define BUFLEN 128 extern int connect_retry(int, int, int, const struct sockaddr *,
socklen_t); void
print_uptime(int sockfd)
{
int n;
char buf[BUFLEN]; while ((n = recv(sockfd, buf, BUFLEN, )) > )
write(STDOUT_FILENO, buf, n);
if (n < )
err_sys("recv error");
} int
main(int argc, char *argv[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
int sockfd, err; if (argc != )
err_quit("usage: ruptime hostname");
memset(&hint, , sizeof(hint));
hint.ai_socktype = SOCK_STREAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(argv[], "ruptime", &hint, &ailist)) != )
err_quit("getaddrinfo error: %s", gai_strerror(err));
for (aip = ailist; aip != NULL; aip = aip->ai_next) {
if ((sockfd = connect_retry(aip->ai_family, SOCK_STREAM, ,
aip->ai_addr, aip->ai_addrlen)) < ) {
err = errno;
} else {
print_uptime(sockfd);
exit();
}
}
err_exit(err, "can't connect to %s", argv[]);
}

这个程序连接服务器,读取服务器发送过来的字符串并将其打印到标准输出。

下面展示了服务器程序,用来提供uptime命令的输出到上面的客户端程序

 #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, const struct sockaddr *, socklen_t, int); void
serve(int sockfd)
{
int clfd;
FILE *fp;
char buf[BUFLEN]; set_cloexec(sockfd);
for (;;) {
if ((clfd = accept(sockfd, NULL, NULL)) < ) {
syslog(LOG_ERR, "ruptimed: accept error: %s",
strerror(errno));
exit();
}
set_cloexec(clfd);
if ((fp = popen("/usr/bin/uptime", "r")) == NULL) {
sprintf(buf, "error: %s\n", strerror(errno));
send(clfd, buf, strlen(buf), );
} else {
while (fgets(buf, BUFLEN, fp) != NULL)
send(clfd, buf, strlen(buf), );
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 != )
err_quit("usage: ruptimed");
if ((n = sysconf(_SC_HOST_NAME_MAX)) < )
n = HOST_NAME_MAX; /* best guess */
if ((host = malloc(n)) == NULL)
err_sys("malloc error");
if (gethostname(host, n) < )
err_sys("gethostname error");
daemonize("ruptimed");
memset(&hint, , sizeof(hint));
hint.ai_flags = AI_CANONNAME;
hint.ai_socktype = SOCK_STREAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != ) {
syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s",
gai_strerror(err));
exit();
}
for (aip = ailist; aip != NULL; aip = aip->ai_next) {
if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr,
aip->ai_addrlen, QLEN)) >= ) {
serve(sockfd);
exit();
}
}
exit();
}

另一个版本的服务器程序

 #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, const struct sockaddr *, socklen_t, int); void
serve(int sockfd)
{
int clfd, status;
pid_t pid; set_cloexec(sockfd);
for (;;) {
if ((clfd = accept(sockfd, NULL, NULL)) < ) {
syslog(LOG_ERR, "ruptimed: accept error: %s",
strerror(errno));
exit();
}
if ((pid = fork()) < ) {
syslog(LOG_ERR, "ruptimed: fork error: %s",
strerror(errno));
exit();
} else if (pid == ) { /* child */
/*
* The parent called daemonize ({Prog daemoninit}), 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();
}
close(clfd);
execl("/usr/bin/uptime", "uptime", (char *));
syslog(LOG_ERR, "ruptimed: unexpected return from exec: %s",
strerror(errno));
} else { /* parent */
close(clfd);
waitpid(pid, &status, );
}
}
} int
main(int argc, char *argv[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
int sockfd, err, n;
char *host; if (argc != )
err_quit("usage: ruptimed");
if ((n = sysconf(_SC_HOST_NAME_MAX)) < )
n = HOST_NAME_MAX; /* best guess */
if ((host = malloc(n)) == NULL)
err_sys("malloc error");
if (gethostname(host, n) < )
err_sys("gethostname error");
daemonize("ruptimed");
memset(&hint, , sizeof(hint));
hint.ai_flags = AI_CANONNAME;
hint.ai_socktype = SOCK_STREAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != ) {
syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s",
gai_strerror(err));
exit();
}
for (aip = ailist; aip != NULL; aip = aip->ai_next) {
if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr,
aip->ai_addrlen, QLEN)) >= ) {
serve(sockfd);
exit();
}
}
exit();
}

无连接的实例

下面程序采用数据报套接字接口的uptime客户端命令版本

 #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[] = ;
if (sendto(sockfd, buf, , , aip->ai_addr, aip->ai_addrlen) < )
err_sys("sendto error");
alarm(TIMEOUT);
if ((n = recvfrom(sockfd, buf, BUFLEN, , NULL, NULL)) < ) {
if (errno != EINTR)
alarm();
err_sys("recv error");
}
alarm();
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 != )
err_quit("usage: ruptime hostname");
sa.sa_handler = sigalrm;
sa.sa_flags = ;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) < )
err_sys("sigaction error");
memset(&hint, , sizeof(hint));
hint.ai_socktype = SOCK_DGRAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(argv[], "ruptime", &hint, &ailist)) != )
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, )) < ) {
err = errno;
} else {
print_uptime(sockfd, aip);
exit();
}
} fprintf(stderr, "can't contact %s: %s\n", argv[], strerror(err));
exit();
}

下面是对应的服务器版本

 #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, const struct sockaddr *, socklen_t, int); void
serve(int sockfd)
{
int n;
socklen_t alen;
FILE *fp;
char buf[BUFLEN];
char abuf[MAXADDRLEN];
struct sockaddr *addr = (struct sockaddr *)abuf; set_cloexec(sockfd);
for (;;) {
alen = MAXADDRLEN;
if ((n = recvfrom(sockfd, buf, BUFLEN, , addr, &alen)) < ) {
syslog(LOG_ERR, "ruptimed: recvfrom error: %s",
strerror(errno));
exit();
}
if ((fp = popen("/usr/bin/uptime", "r")) == NULL) {
sprintf(buf, "error: %s\n", strerror(errno));
sendto(sockfd, buf, strlen(buf), , addr, alen);
} else {
if (fgets(buf, BUFLEN, fp) != NULL)
sendto(sockfd, buf, strlen(buf), , addr, alen);
pclose(fp);
}
}
} int
main(int argc, char *argv[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
int sockfd, err, n;
char *host; if (argc != )
err_quit("usage: ruptimed");
if ((n = sysconf(_SC_HOST_NAME_MAX)) < )
n = HOST_NAME_MAX; /* best guess */
if ((host = malloc(n)) == NULL)
err_sys("malloc error");
if (gethostname(host, n) < )
err_sys("gethostname error");
daemonize("ruptimed");
memset(&hint, , sizeof(hint));
hint.ai_flags = AI_CANONNAME;
hint.ai_socktype = SOCK_DGRAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != ) {
syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s",
gai_strerror(err));
exit();
}
for (aip = ailist; aip != NULL; aip = aip->ai_next) {
if ((sockfd = initserver(SOCK_DGRAM, aip->ai_addr,
aip->ai_addrlen, )) >= ) {
serve(sockfd);
exit();
}
}
exit();
}

套接字选项

套接字机制提供了设置跟查询套接字选项的接口。可以获取或设置以下3种选项

1.通用选项,工作在所有套接字类型上

2.在套接字层次管理的选项,但是依赖于下层协议的支持

3.特定于某协议的选项,每个协议独有的

可以使用setsockopt函数来设置套接字选项

#include <sys/socket.h>
int setsockopt(int sockfd,int level,int option,const void *val,socklen_t len);

level标识了选项应用的协议:

如果选项是通用的套接字层次选项,则level设置为SOL_SOCKET,否则,level设置成控制这个选项的协议编号。对于TCP选项,level是IPPROTO_TCP,对于IP,level是IPPROTO_IP。

下面总结了Single UNIX Specification中定义的通用套接字层次选项

可以使用getsockopt函数来查看选项的当前值

#include <sys/socket.h>
int getsockopt(int sockfd,int level,int option,void *restrict val,socklen_t restrict lenp);

带外数据

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

TCP将带外数据称为紧急数据。TCP仅支持一个字节的紧急数据,但是允许紧急数据在普通数据传递机制数据流之外传输。

为了产生紧急数据,在三个send函数中任何一个指定标志MSG_OOB。如果带MSG_OOB标志传输字节超过一个时,最后一个字节被作为紧急数据字节。

如果安排发生套接字信号,当接收到紧急数据时,那么发送信号SIGURG信号。可以通过调用以下函数安排进程接收套接字的信号:

fcntl(sockfd,F_SETTOWN,pid);

F_GETOWN命令可以用来获取当前套接字所有权

owner=fcntl(sockfd,F_GETOWN,);

为帮助判断是否已经达到紧急标记,可以使用函数sockatmark

#include <sys/socket.h>
int sockatmark(int sockfd);

当下一个要读取的字节在紧急标志处时,sockatmark返回1。

非阻塞和异步I/O

在基于套接字异步I/O中,当能够从套接字中读取数据,或者套接字写队列中的空间变得可用时,可以安排发送信号SIGIO。通过两个步骤来使用异步I/O:

1) 建立套接字拥有者关系,信号可以被传送到合适的进程。

2) 通知套接字当I/O操作不会阻塞时发信号告知。

可以使用三种方式来完成第一个步骤:

A、 在fcntl使用F_SETOWN命令

B、 在ioctl中作用FIOSETOWN命令

C、 在ioctl中使用SIOCSPGRP命令。

要完成第二个步骤,有两个选择:

A、 在fcntl中使用F_SETFL命令并且启用文件标志O_ASYNC。

B、 在ioctl中使用FIOASYNC

apue学习笔记(第十六章 网络IPC:套接字)的更多相关文章

  1. UNP学习笔记(第四章 基本TCP套接字编程)

    本章讲解编写一个完整的TCP客户/服务器程序所需要的基本套接字函数. socket函数 #include <sys/socket.h> int socket(int family,int ...

  2. apue学习笔记(第六章 系统数据文件和信息)

    UNIX系统的正常运作需要使用大量与系统有关的数据文件,例如,口令文件/etc/passwd和组文件/etc/group就是经常被多个程序频繁使用的两个文件. 口令文件 UNIX系统口令文件包含如下字 ...

  3. Nodejs学习笔记(十六)--- Pomelo介绍&入门

    目录 前言&介绍 安装Pomelo 创建项目并启动 创建项目 项目结构说明 启动 测试连接 聊天服务器 新建gate和chat服务器 配置master.json 配置servers.json ...

  4. Nodejs学习笔记(十六)—Pomelo介绍&入门

    前言&介绍 Pomelo:一个快速.可扩展.Node.js分布式游戏服务器框架 从三四年前接触Node.js开始就接触到了Pomelo,从Pomelo最初的版本到现在,总的来说网易出品还算不错 ...

  5. 《HTTP 权威指南》笔记:第十六章&第十七章 国际化、内容协商与转码

    <HTTP 权威指南>笔记:第十六章 国际化 客户端通过在请求报文中的 Accept-Language 首部和 Accept-Charset 首部来告知服务器:“我理解这些语言.”服务器通 ...

  6. python3.4学习笔记(二十六) Python 输出json到文件,让json.dumps输出中文 实例代码

    python3.4学习笔记(二十六) Python 输出json到文件,让json.dumps输出中文 实例代码 python的json.dumps方法默认会输出成这种格式"\u535a\u ...

  7. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第六章:在Direct3D中绘制

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第六章:在Direct3D中绘制 代码工程地址: https://gi ...

  8. 《机器学习实战》学习笔记第十四章 —— 利用SVD简化数据

    相关博客: 吴恩达机器学习笔记(八) —— 降维与主成分分析法(PCA) <机器学习实战>学习笔记第十三章 —— 利用PCA来简化数据 奇异值分解(SVD)原理与在降维中的应用 机器学习( ...

  9. UNP学习笔记(第六章 I/O复用)

    I/O模型 首先我们将查看UNIX下可用的5种I/O模型的基本区别: 1.阻塞式I/O 2.非阻塞式I/O 3.I/O复用(select和poll) 4.信号驱动式I/O(SIGIO) 5.异步I/O ...

随机推荐

  1. Linux中Source的用法

    source命令:source命令的功能:使Shell读入指定的Shell程序文件并依次执行文件中的所有语句source命令通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录.用法 ...

  2. button的默认type居然是submit

    今天使用了html中的button标签,用js写了一点代码来完成onclick实践,当我点下它的时候,它不仅执行了我写的function,还把表单给提交了,一查它的button居然是sumbit. 然 ...

  3. linux系统——线程

    linux系统线程 1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的Unix也支持线程的概念,但是在一个 ...

  4. 【06】Vue 之 组件化开发

    组件其实就是一个拥有样式.动画.js逻辑.HTML结构的综合块.前端组件化确实让大的前端团队更高效的开发前端项目.而作为前端比较流行的框架之一,Vue的组件和也做的非常彻底,而且有自己的特色.尤其是她 ...

  5. Javascript&Html-延迟调用和间歇调用

    Javascript&Html-延迟调用和间歇调用 Javascript 是一种单线程语言,所有的javascript任务都会放到一个任务列表中,这些javascript任务会按照插入到列表中 ...

  6. linux内核情景分析之锁机制

    /* * These are the generic versions of the spinlocks and read-write * locks.. *///自旋锁加锁,irqsave表示把标志 ...

  7. Selenium2+python自动化(学习笔记3)

    1.加载firefox配置 参考代码: # coding=utf-8from selenium import webdriver# 配置文件地址,打开Firefox点右上角设置--帮助--故障排除信息 ...

  8. Python 数据类型-3

    字典 (dict) Python中唯一的映射的类型(哈希表) 字典对象是可变的,但是字典的键必须使用不可变的对象,一个字典可以使用不同类型的键值 定义字典: In [68]: dic = {} #使用 ...

  9. DB2 With语句递归

    WITH T1 (T11 , T22 , T33 , T44) AS (SELECT TASKID , REPLY , ROWNUMBER () OVER (PARTITION BY TASKID) ...

  10. Cryptography I 学习笔记 --- 基于陷门置换的公钥加密

    RSA算法的工作流程 1. 生成公钥私钥 生成两个素数p和q,计算n=p*q,计算φ(n)=n-p-q+1,然后生成e与d,使 e * d = 1 mod φ(n). 然后以(n, e)作为公钥,(n ...