赞
踩
本章将考察不同计算机(通过网络相连)上的进程相互通信的机制:网络进程间通信(network IPC)。
为创建一个套接字,调用socket
函数:
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
// 返回值:若成功,返回文件(套接字描述符);若出错,返回-1
套接字通信是双向的,可以采用shutdown
来禁止一个套接字的I/O:
#include <sys/socket.h>
int shutdown(int sockfd, int how);
// 返回值:若成功,返回0;若出错,返回-1
进程标识由两部分组成:
对于TCP/IP应用程序,有4个用来在处理器字节序和网络字节序之间实施转换的函数:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32);
// 返回值:以网络字节序表示的32位整数
uint16_t htons(uint16_t hostint16);
// 返回值:以网络字节序表示的16位整数
uint32_t ntohl(uint32_t netint32);
// 返回值:以主机字节序表示的32位整数
uint16_t ntohs(uint16_t netint16);
// 返回值:以主机字节序表示的16位整数
为使不同格式地址能够传入到套接字函数,地址会被强制转换成一个通用的地址结构 sockaddr:
struct sockaddr {
sa_family_t sa_family; /* address family */
char sa_data[]; /* variable-length address */
...
};
在IPv4因特网域(AF_INET)中,套接字地址用结构 sockaddr_in 表示:
struct in_addr {
in_addr_t s_addr; /* IPv4 address */
};
struct sockaddr_in {
sa_family_t sin_family; /* address family */
in_port_t sin_port; /* port number */
struct in_addr sin_addr; /* IPv4 address */
};
IPv6因特网域(AF_INET6)套接字地址用结构 sockaddr_in6 表示:
struct in6_addr {
uint8_t s6_addr[16]; /* IPv6 address */
};
struct sockaddr_in6 {
sa_family_t sin6_family; /* address family */
in_port_t sin6_port; /* port number */
uint32_t sin6_flowinfo; /* traffic class and flow info */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* set of interfaces for scope */
};
inet_ntop
和inet_pton
用于二进制地址格式与点分十进制字符(a.b.c.d)之间的相互转换:
#include <arpa/inet.h>
const char *inet_ntop(int domain, const void *restrict addr,
char *restrict str, socklen_t size);
// 返回值:若成功,返回地址字符串指针;若出错,返回NULL
int inet_pton(int domain, const char *restrict str,
void *restrict addr);
// 返回值:若成功,返回1;若格式无效,返回0;若出错,返回-1
inet_ntop
,参数size指定了保存文本字符串的缓冲区(str)的大小,INET_ADDRSTRLEN、INET6_ADDRSTRLEN分别定义了足够大的空间来存放一个表示IPv4和IPv6地址的文本字符串;通过调用gethostent
,可以找到给定计算机系统的主机信息:
#include <netdb.h>
struct hostent *gethostent(void);
// 返回值:若成功,返回指针;若出错,返回NULL
void sethostent(int stayopen);
void endhostent(void);
gethostent
会打开它,函数gethostent
返回文件中的下一个条目,得到一个指向 hostent 结构的指针,该结构至少包含以下成员:struct hostent {
char *h_name; /* name of host */
char **h_aliases; /* pointer to alternate host name array */
int h_addrtype; /* address type */
int h_length; /* length in bytes of address */
char **h_addr_list; /* pointer to array of network addresses */
};
sethostent
会打开文件,如果文件已经被打开,那么将其回绕;当stayopen参数设置成非0值时,调用gethostent
之后,文件将依然是打开的;endhostent
可以关闭文件。以下接口用于获得网络名字和网络编号:
#include <netdb.h>
struct netent *getnetbyaddr(uint32_t net, int type);
struct netent *getnetbyname(const char *name);
struct netent *getnetent(void);
// 3个函数的返回值:若成功,返回指针;若出错,返回NULL
void setnetent(int stayopen);
void endnetent(void);
netent 结构至少包含以下字段:
struct netent {
char *n_name; /* network name */
char **n_aliases; /* alternate network name array pointer */
int n_addrtype; /* address type */
uint32_t n_net; /* network number */
...
};
下列函数在协议名字和协议编号之间进行映射:
#include <netdb.h>
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);
// 3个函数的返回值:若成功,返回指针;若出错,返回NULL
void setprotoent(int stayopen);
void endprotoent(void);
POSIX.1定义的 protoent 结构至少包含以下成员:
struct protoent {
char *p_name; /* protocol name */
char **p_aliases; /* pointer to altername protocol name array */
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);
// 3个函数的返回值:若成功,返回指针;若出错,返回NULL
void setservent(int stayopen);
void endservent(void);
servent 结构至少包含以下成员:
struct servent {
char *s_name; /* service name */
char **s_aliases; /* pointer to alternate service name array */
int s_port; /* port number */
char *s_proto; /* name of protocol */
...
};
getaddrinfo
函数允许将一个主机名和一个服务名映射到一个地址:
#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);
// 返回值:若成功,返回0;若出错,返回非0错误码
void freeaddrinfo(struct addrinfo *ai);
getaddrinfo
函数返回一个链表结构 addrinfo,可以用freeaddrinfo
来释放一个或多个这种结构,这取决于用 ai_next 字段链接起来的机构由多少;addrinfo 结构的定义至少包含以下成员:struct addrinfo {
int ai_flags; /* customize behavior */
int ai_family; /* adddress family */
int ai_sockettype; /* socket type */
int ai_protocol; /* protocol */
socklen_t ai_addrlen; /* length in bytes of address */
struct sockaddr *ai_addr; /* address */
char *ai_canonname; /* canonical name of host */
struct addrinfo *ai_next; /* next in list */
...
};
如果getaddrinfo
失败,不能使用perror
或strerror
来生成错误消息,而是要调用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);
// 返回值:若成功,返回0;若出错,返回非0值
对于服务器,需要给一个接收客户端请求的服务器套接字关联上一个众所周知的地址;使用bind
函数来关联地址和套接字:
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
// 返回值:若成功,返回0;若出错,返回-1
可以调用getsockname
函数来发现绑定到套接字上的地址:
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *restrict addr,
socklen_t *restrict alenp);
// 返回值:若成功,返回0;若出错,返回-1
如果套接字已经和对等方连接,可以调用getpeername
函数来找到对方的地址:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *restrict addr
socklen_t *restrict alenp);
// 返回值:若成功,返回0;若出错,返回-1
如果要处理一个面向连接的网络服务(SOCK_STREAM 或 SOCK_SEQPACKET),那么在开始交换数据以前,需要在请求服务的进程套接字(客户端)和提供服务的进程套接字(服务器)之前建立一个连接;使用connect
函数来建立连接:
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
// 返回值:若成功,返回0;若出错,返回-1
connect
中指定的地址是我们想与之通信的服务器地址;connect
会给调用者绑定一个默认地址。服务器调用listen
函数来宣告它愿意接受连接请求:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
// 返回值:若成功,返回0;若出错,返回-1
服务器使用accept
函数获得连接请求并建立连接:
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *restrict addr,
socklen_t *restrict len);
// 返回值;若成功,返回文件(套接字)描述符;若出错,返回-1
send
函数用于发送数据,它可以指定标志来改变处理传输数据的方式:
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
// 返回值:若成功,返回发送的字节数;若出错,返回-1
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);
// 返回值:若成功,返回发送的字节数;若出错,返回-1
可以调用带有msghdr结构的sendmsg
来指定多重缓冲区传输数据:
#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
// 返回值:若成功,返回发送的字节数;若出错,返回-1
POSIX.1定义了msghdr结构,它至少有以下成员:
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* address size in bytes */
struct iovec *msg_iov; /* array of I/O buffers */
int msg_iovlen; /* number of elements in array */
void *msg_control; /* ancillary data */
socklen_t msg_controllen; /* number of ancillary bytes */
int msg_flags; /* flags for received message */
...
};
函数recv
可以指定标志来控制如何接收数据:
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
// 返回值:返回数据的字节长度;若无可用数据或对等方已经按序结束,返回0;若出错,返回-1
可以使用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);
// 返回值:返回数据的字节长度;若无可用数据或对等方已经按序结束,返回0;若出错,返回-1
为了将接收到的数据送入多个缓冲区,类似于readv
,或者想接收辅助数据,可以使用recvmsg
:
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
// 返回值:返回数据的字节长度;若无可用数据或对等方已经按序结束,返回0;若出错,返回-1
recvmsg
的默认行为;返回时,msghdr结构中的msg_flags字段被设为所接收数据的各种特征(进入recvmsg
时msg_flags被忽略);recvmsg
中返回的各种可能值如下:可以使用setsockopt
函数来设置套接字选项:
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
// 返回值:若成功,返回0;若出错,返回-1
SOL_SOCKET
;否则,设置成控制这选项的协议编号,对于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 len);
带外数据(out-of-band data)是一些通信协议所支持的可选功能,与普通数据相比,它允许更高优先级的数据传输;带外数据先行传输,即使传输队列已经有数据。TCP支持带外数据,但是UDP不支持。
TCP将带外数据称为紧急数据。TCP仅支持一个字节的紧急数据,但是允许紧急数据在普通数据传递机制数据流之外传输。为了产生紧急数据,可以在3个send函数中的任何一个里指定MSG_OOB标志。如果带MSG_OOB标志发送的字节数超过一个时,最后一个字节将被视为紧急数据字节。
如果通过套接字安排了信号的产生,那么紧急数据被接收时,会发送SIGURG信号。
TCP支持紧急标记(urgent mark)的概念,即在普通数据流中紧急数据所在的位置。如果采用套接字选项SO_OOBINLINE,那么可以在普通数据中接收紧急数据。为帮助判断是否已经到达紧急标记,可以使用函数sockatmark
:
#include <sys/socket.h>
int sockatmark(int sockfd);
// 返回值:若在标记处,返回1;若没在标记处,返回0;若出错,返回-1
sockatmark
返回1。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。