本笔记参考了BrianLeeLXT的笔记
第十六章 网络IPC:套接字 16.2 套接字描述符套接字是通信端点的抽象。套接字描述符在UNIX系统中被当作是一种文件描述符。
使用socket函数创建一个套接字
#includeint socket(int domain, int type, int protocol); //返回值:若成功,返回套接字描述符;若出错,返回 -1
参数domain(域)确定通信的特性:
AF_INET:IPv4因特网域
AF_INET6:IPv6因特网域
AF_LOCAL、AF_UNIX:UNIX域
AF_UPSPEC:未指定,“任何”域
参数type确定套接字的类型,进一步确定通信特征。
SOCK_DGRAM:固定长度的、无连接的、不可靠的报文传递
SOCK_RAW:IP协议的数据报接口
SOCK_SEQPACKET:固定长度的、有序的、可靠的、面向连接的报文传递
SOCK_STREAM:有序的、可靠的、双向的、面向连接的字节流
参数protocol通常是0,表示为给定的域和套接字类型选择默认协议。
IPPROTO_IP:IPv4网际协议
IPPROTO_IPV6:IPv6网际协议
IPPROTO_ICMP:因特网控制报文协议
IPPROTO_RAW:原始IP数据包协议
IPPROTO_TCP:传输控制协议
IPPROTO_UDP:用户数据报协议
套接字通信是双向的 ,可以采用 shutdown 函数来禁止一个套接字的 I/O
#includeint shutdown(int sockfd, int how); //返回值:若成功,返回 0;若出错,返回 -1
how参数取值:
SHUT_RD:关闭读端
SHUT_WR:关闭写端
SHUT_RDWR:关闭读写端
#include uint32_t htonl(uint32_t hostint32); //返回:以网络字节序表示的 32 位整数 uint64_t htons(uint16_t hostint16); //返回:以网络字节序表示的 16 位整数 uint32_t ntohl(uint32_t netint32); //返回:以主机字节序表示的 32 位整数 uint16_t ntohs(uint16_t netint16); //返回:以主机字节序表示的 16 位整数16.3.2 地址格式
为使不同格式地址能够传入到套接字函数,地址会被强制转换成一个通用的地址结构sockaddr
struct sockaddr { sa_family_t sa_family; char sa_data[]; };
因特网地址定义在
在IPv4因特网域中,套接字地址用结构 sockaddr_in表示:
struct in_addr_t { 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[16]; }; 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; };
inet_ntop函数和inet_pton函数完成二进制地址格式与点分十进制字符表示(a.b.c.d)之间的相互转换:
#include 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将网络字节序的二进制地址转换成文本字符串格式。函数inet_pton将文本字符串格式转换成网络字节序的二进制地址。
参数domain仅支持两个值:AF_INET和AF_INET6。
两个常数用于简化工作:
INET_ADDRSTRLEN:定义了足够大的空间来存放一个表示IPv4地址的文本字符串。
INET6_ADDRSTRLEN:定义了足够大的空间来存放一个表示IPv6地址的文本字符串。
通过调用gethostent,可以找到给定计算机系统的主机信息
#includestruct hostent *gethostent(void); //返回值:若成功,返回指针;若出错,返回 NULL void sethostent(int stayopen); void endhostent(void); struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **haddr_list; };
如果主机数据库文件没有打开,gethostent会打开它。函数 gethostent返回文件中的下一个条目。函数 sethostent会打开文件,如果文件已经被打开,那么将其回绕。当stayopen参数设置成非0值时,调用gethostent之后,文件将依然是打开的。函数endhostent可以关闭文件。
返回的地址采用网络字节序。
一套相似的接口来获得网络名字和网络编号。
#includestruct netent *getnetbyaddr(uint32_t net, int bype); struct netent *getnetbyname(const char *name); struct netent *getnetent(void); //3个函数的返回值:若成功,返回指针;若出错,返回 NULL void setnetent(int stayopen); void endnetent(void); struct netent { char *n_name; char **naliases; int n_addrtype; uint32_t n_net; };
我们可以用以下函数在协议名字和协议编号之间进行映射。
#includestruct protoent *getprotobyname(const char*name); struct protoent *getprotobynumber(int proto); struct protoent *getprotoent(void); //3个函数的返回值:若成功,返回指针;若出错,返回 NULL void setprotoent(int stayopen); void endprotoent(void); struct protoent { char *p_name; char **p_aliases; int p_proto; }
使用函数getservbyname将一个服务名映射到一个端口号,使用函数getservbyport将一个端口号映射到一个服务名,使用函数getservent顺序扫描服务数据库。
#includestruct servent *getservbyname(const char *name, const char *proto); struct servent *getserbyport(int port, const char *proto); struct servent *getservent(void); ///3个函数的返回值:若成功,返回指针,若出错,返回NULL void setservent(int stayopen); void endservent(void); struct servent { char *s_name; char **s_aliases; int s_port; char *s_proto; };
getaddrinfo函数允许将一个主机名和一个服务名映射到一个地址。
#include#include int getaddrinfo(const char *restrict host, const char *restrict service, const struct addrinfo *restrict hint, struct addrinfo **restrict nes); //返回值:若成功,返回0;若出错,返回非0错误码 void freeaddrinfo(struct addrinto *ai); 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; }
主机名可以是一个节点名或点分格式的主机地址。
getaddrinfo函数返回一个链表结构addrinfo。
freeaddrinfo函数释放一个或多个这种结构。
可以提供一个可选的hint来选择符合特定条件的地址。hint是一个用于过滤地址的模板,包括ai_family、ai_flags、ai_protocol和ai_socktype字段。剩余的整数字段必须设置为0,指针字段必须为空。
ai_family:
AI_ADDRCONEIG:查询配置的地址类型(IPv4或IPv6)
AI_ALL:查找IPv4和IPv6地址(仅用于AI_V4MAPPED)
AI_CANONNAME:需要一个规范的名字(与别名相对)
AI_NUMERICHOST:以数字格式指定主机地址,不翻译
AI_NUMERICSERV:将服务指定为数字端口号,不翻译
AI_PASSIVE:套接字地址用于监听绑定
AI_V4MAPPED:如没有找到IPv6地址,返回映射到IPv6格式的IPv4地址
如果getaddrinfo失败要调用gai_strerror将返回的错误码转换成错误消息。
#includeconst char *gai_strerror(int error); //返回值:指向描述错误的字符串的指针
getnameinfo函数将一个地址转换成一个主机名和一个服务名。
#include#include 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值
flag标志:
NI_DGRAM:服务基于数据报而非基于流
NI_NAMEREQD:如果找不到主机名,将其作为一个错误对待
NI_NOFQDN:对于本地主机,仅返回全限定域名的节点名部分
NI_NUMERICHOST:返回主机地址的数字形式,而非主机名
NI_NUMERICSCOPE:对于Pv6,返回范围ID的数字形式,而非名字
NI_NUMERICSERV:返回服务地址的数字形式(即端口号),而非名字
使用bind函数来关联地址和套接字
#includeint bind(int sockfd, const struct sockaddr *addr, socklen_t len); //返回值:若成功,返回 0;若出错,返回 -1
对于因特网域,如果指定IP地址为INADDR_ANY,套接字端点可以被绑定到所有的系统网络接口上。
可以调用getsockname函数来发现绑定到套接字上的地址。
#includeint getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp); //返回值:若成功,返回 0;若出错,返回 -1
返回时,alenp指向的整数会被设置成返回地址的大小。
如果套接字已经和对等方连接,可以调用getpeername函数来找到对方的地址。
#include16.4 建立连接int getpeername(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp); //返回值:若成功,返回 0;若出错,返回 -1
使用connect函数来建立连接
#includeint connect(int sockfd, const struct sockaddr *addr, socklen_t len); //返回值:若成功,返回 0;若出错,返回 -1
在connect中指定的地址是我们想与之通信的服务器地址。如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址。
可迁移的应用程序需要关闭套接字。
若套接字处于非阻塞模式,那么在连接不能马上建立时,connect会返回−1并将errno设置为特殊的错误码EINPROGRESS。应用程序可以使用poll或者select来判断套接字描述符何时可写,如果可写,连接完成。
connect函数还可以用于无连接的网络服务(SOCK_DGRAM),传送的报文的目标地址会设置成connect调用中指定的地址,这样每次传送报文时就不需要再提供地址。另外,仅能接收来自指定地址的报文。
服务器调用listen函数来宣告它愿意接受连接请求。
#includeint listen(int sockfd, int backlog); //返回值:若成功,返回 0;若出错,返回 -1
参数backlog提供了一个提示,提示系统该进程要入队的未完成连接请求数量。
一旦服务器调用了listen,所用的套接字就能接收连接请求。使用accept函数获得连接请求并建立连接
#includeint accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len); //返回值:若成功,返回套接字描述符;若出错,返回 -1
如果没有连接请求在等待,accept会阻塞直到一个请求到来。如果sockfd处于非阻塞模式,accept会返回-1,并将errno设置为EAGAIN或EWOULDBLOCK。
16.5 数据传输只要建立连接,就可以使用read和write来通过套接字通信。
send函数
#includessize_t send(int sockfd, const void *buf, size_t nbytes, int flags); //返回值:若成功,返回发送的字节数;若出错,返回 -1
使用send时套接字必须已经连接。
flag参数:
MSG_CONEIRM:提供链路层反馈以保持地址映射有效
MSG_DONTROUTE:勿将数据包路由出本地网络
MSG_DONTWAIT:允许非阻塞 *** 作
MSG_EOF:发送数据后关闭套接字的发送端
MSG_EOR:如果协议支持,标记记录结束
MSG_MORE.:延迟发送数据包允许写更多数据
MSG_NOSIGNAL:在写无连接的套接字时不产生SIGPIPE信号
MSG_00B:如果协议支持,发送带外数据
对于支持报文边界的协议,如果尝试发送的单个报文的长度超过协议所支持的最大长度,那么send会失败,并将errno设为EMSGSIZE。对于字节流协议,send会阻塞直到整个数据传输完成。
sendto函数可以在无连接的套接字上指定一个目标地址
#includessize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *destaddr, socklen_t destlen); //返回值:若成功,返回发送的字节数;若出错,返回 -1
调用带有msghdr结构的sendmsg来指定多重缓冲区传输数据
#includessize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *destaddr, socklen_t destlen); //返回值:若成功,返回发送的字节数;若出错,返回 -1 struct msghdr { void *msg_name; socklen_t msg_namelen; struct iovec *msg_iov; int msg_iovlen; void *msg_control; socklen_t msg_controllen; int msg_flags; };
recv函数
#includessize_t recv(int sockfd, void *buf, size_t nbytes, int flags); //返回值:返回数据的字节长度;若无可用数据或对等方已经按序结束,返回 0;若出错,返回 -1
flag参数:
MSG_DONTWAIT:启用非阻塞 *** 作
MSG_ERRQUEUE:接收错误信息作为辅助数据
MSG_00B:如果协议支持,获取带外数据
MSG_PEEK:返回数据包内容而不真正取走数据包
MSG_TRUNC:即使数据包被截断,也返回数据包的实际长度
MSG_WAITALL:等待直到所有的数据可用(仅SOCK_STREAM)
如果发送者已经调用shutdown来结束传输,或者网络协议支持按默认的顺序关闭并且发送端已经关闭,那么当所有的数据接收完毕后,recv会返回0。
使用recvfrom可以得到数据发送者的地址
#includessize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags, struct sockaddr *restrict addr, socklen_t *restrict addrlen); //返回值:返回数的字节长度;若无可用数据或对等方已经按序结束,返回 0;若出错,返回 -1
进入recvmsg时msg_flags被忽略,使用参数flags代替
MSG_CTRUNC:控制数据被截断
MSG_EOR:接收记录结束符
MSG_ERRQUEUE:接收错误信息作为辅助数据
MSG_00B:接收带外数据
MSG_TRUNC:一般数据被截断
可以使用setsockopt函数来设置套接字选项。
#includeint setsockopt(int sockfd, int level, int option, const void *val, socklent len); //返回值:若成功,返回0:若出错,返回-1
参数level标识了选项应用的协议。如果选项是通用的套接字层次选项,则level设置成SOL_SOCKET。否则,level设置成控制这个选项的协议编号。对于TCP选项,level是IPPROTO_TCP,对于IP,level是rpPROTO_IP。
可以使用getsockopt函数来查看选项的当前值。
#include16.7 外带数据int getsockopt(int socyd, int level, int option, void *restrict val, socklen_t *restrict lenp); //返回值:若成功,返回0;若出错,返回-1
带外数据允许更高优先级的数据传输。带外数据先行传输,即使传输队列已经有数据。TCP支持带外数据,但是UDP不支持。
TCP将带外数据称为紧急数据,仅支持一个字节的紧急数据,但是允许紧急数据在普通数据传递机制数据流之外传输。
为了产生紧急数据,可以在3个send函数中的任何一个里指定MSG_OOB标志。如果带MSG_OOB标志发送的字节数超过一个时,最后一个字节将被视为紧急数据字节。
如果通过套接字安排了信号的产生,那么紧急数据被接收时,会发送SIGURG信号。
可以通过调用以下函数安排进程接收套接字的信号:fcntl(sockfdm F_SETOWNM, pid);
TCP支持在普通数据流中紧急数据所在的位置。如果采用套接字选项SO_OOBINLINE,那么可以在普通数据中接收紧急数据。为帮助判断是否已经到达紧急标记,可以使用函数sockatmark。
#includeint sockatmark(int sockfd); //返回值:若在标记处,返回1;若没在标记处,返回0;若出错,返回-1
当下一个要读取的字节在紧急标志处时,sockatmark返回1。
16.8 非阻塞和异步I/O在基于套接字的异步VO中,当从套接字中读取数据时,或者当套接字写队列中空间变得可用时,可以安排要发送的信号SIGIO。
fcntl(fd, F_SETOWN, pid)
ioctl(fd, FIOSETOWN, pid)
ioctl(fd, SIOCSPGRP, pid)
fcntl(fd, F_SETFL, flags l O_ASYNC)
ioctl(fd, FIOASYNC, &n);
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)