APUE第十六章笔记

本文详细介绍了UNIX系统中网络IPC的套接字概念,包括套接字描述符、地址格式、寻址过程以及数据传输等方面。讲解了套接字创建、连接、地址绑定、数据发送和接收的函数用法,如socket、bind、connect、read、write等,并提到了非阻塞I/O和带外数据等高级特性。
摘要由CSDN通过智能技术生成

本笔记参考了BrianLeeLXT的笔记

第十六章 网络IPC:套接字

16.2 套接字描述符

套接字是通信端点的抽象。套接字描述符在UNIX系统中被当作是一种文件描述符。

使用socket函数创建一个套接字

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//返回值:若成功,返回套接字描述符;若出错,返回 -1

参数domain(域)确定通信的特性:
AF_INET:IPv4因特网域
AF_INET6:IPv6因特网域
AF_LOCALAF_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

#include <sys/socket.h>
int shutdown(int sockfd, int how);
//返回值:若成功,返回 0;若出错,返回 -1

how参数取值:
SHUT_RD:关闭读端
SHUT_WR:关闭写端
SHUT_RDWR:关闭读写端

16.3 寻址

16.3.1 字节序

#include <arpa/inet.h>
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; /* address family */
    char sa_data[]; /* variable-length address */
};

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

struct in_addr_t {
    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因特网域套接字地址用结构 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将网络字节序的二进制地址转换成文本字符串格式。函数inet_pton将文本字符串格式转换成网络字节序的二进制地址。
参数domain仅支持两个值:AF_INET和AF_INET6。
两个常数用于简化工作:
INET_ADDRSTRLEN:定义了足够大的空间来存放一个表示IPv4地址的文本字符串。
INET6_ADDRSTRLEN:定义了足够大的空间来存放一个表示IPv6地址的文本字符串。

16.3.3 地址查询

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

#include <netdb.h>
struct hostent *gethostent(void);
//返回值:若成功,返回指针;若出错,返回 NULL
void sethostent(int stayopen);
void endhostent(void);
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 **haddr_list; /* pointer to array of network addresses */
};

如果主机数据库文件没有打开,gethostent会打开它。函数 gethostent返回文件中的下一个条目。函数 sethostent会打开文件,如果文件已经被打开,那么将其回绕。当stayopen参数设置成非0值时,调用gethostent之后,文件将依然是打开的。函数endhostent可以关闭文件。
返回的地址采用网络字节序。


一套相似的接口来获得网络名字和网络编号。

#include <netdb.h>
struct 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; /* network name */
    char **naliases; /* 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)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 *getserbyport(int port, const char *proto);
struct servent *getservent(void);
///3个函数的返回值:若成功,返回指针,若出错,返回NULL
void setservent(int stayopen);
void endservent(void);
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 nes);
//返回值:若成功,返回0;若出错,返回非0错误码
void freeaddrinfo(struct addrinto *ai);
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_canonname; /* canonical name of host */
    struct addrinfo *ai_next; /* next in list */
}

主机名可以是一个节点名或点分格式的主机地址。
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将返回的错误码转换成错误消息。

#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值

flag标志:
NI_DGRAM:服务基于数据报而非基于流
NI_NAMEREQD:如果找不到主机名,将其作为一个错误对待
NI_NOFQDN:对于本地主机,仅返回全限定域名的节点名部分
NI_NUMERICHOST:返回主机地址的数字形式,而非主机名
NI_NUMERICSCOPE:对于Pv6,返回范围ID的数字形式,而非名字
NI_NUMERICSERV:返回服务地址的数字形式(即端口号),而非名字

16.3.4 将套接字与地址关联

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

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
//返回值:若成功,返回 0;若出错,返回 -1

对于因特网域,如果指定IP地址为INADDR_ANY,套接字端点可以被绑定到所有的系统网络接口上。

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

#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
//返回值:若成功,返回 0;若出错,返回 -1

返回时,alenp指向的整数会被设置成返回地址的大小。

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

#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
//返回值:若成功,返回 0;若出错,返回 -1

16.4 建立连接

使用connect函数来建立连接

#include <sys/socket.h>
int 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函数来宣告它愿意接受连接请求。

#include <sys/socket.h>
int listen(int sockfd, int backlog);
//返回值:若成功,返回 0;若出错,返回 -1

参数backlog提供了一个提示,提示系统该进程要入队的未完成连接请求数量。

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

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
//返回值:若成功,返回套接字描述符;若出错,返回 -1

如果没有连接请求在等待,accept会阻塞直到一个请求到来。如果sockfd处于非阻塞模式,accept会返回-1,并将errno设置为EAGAIN或EWOULDBLOCK。

16.5 数据传输

只要建立连接,就可以使用read和write来通过套接字通信。

send函数

#include <sys/socket.h>
ssize_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函数可以在无连接的套接字上指定一个目标地址

#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 sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *destaddr, socklen_t destlen);
//返回值:若成功,返回发送的字节数;若出错,返回 -1
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

flag参数:
MSG_DONTWAIT:启用非阻塞操作
MSG_ERRQUEUE:接收错误信息作为辅助数据
MSG_00B:如果协议支持,获取带外数据
MSG_PEEK:返回数据包内容而不真正取走数据包
MSG_TRUNC:即使数据包被截断,也返回数据包的实际长度
MSG_WAITALL:等待直到所有的数据可用(仅SOCK_STREAM)

如果发送者已经调用shutdown来结束传输,或者网络协议支持按默认的顺序关闭并且发送端已经关闭,那么当所有的数据接收完毕后,recv会返回0。

使用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

进入recvmsg时msg_flags被忽略,使用参数flags代替
MSG_CTRUNC:控制数据被截断
MSG_EOR:接收记录结束符
MSG_ERRQUEUE:接收错误信息作为辅助数据
MSG_00B:接收带外数据
MSG_TRUNC:一般数据被截断

16.6 套接字选项

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

#include <sys/socket.h>
int 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。

选项参数val的类型描述
SO_ACCEPTCONNint返回信息指示该套接字是否能被监听(仅getsockopt)
SO_BROADCASTint如果*val非0,广播数据报
SO_DEBUGint如果*val非0,启用网络驱动调试功能
SO_DONTROUTEint如果*va1非0,绕过通常路由
SO_ERRORint返回挂起的套接字错误并清除(仅 getSOckopt)
SO_KEEPALIVEint如果*val非0,启用周期性keep-alive报文
SO_LINGERstruct 1inger当还有未发报文而套接字已关闭时,延迟时间
SO_OOBINLINEint如果*val非0,将带外数据放在普通数据中
SO_RCVBUEint接收缓冲区的字节长度
SO_RCVLOWATint接收调用中返回的最小数据字节数
SO_RCVTIMEOstruct timeval套接字接收调用的超时值
SO_REUSEADDRint如果*val非0,重用bind中的地址
SO_SNDBUEint发送缓冲区的字节长度
SO_SNDLOWATint发送调用中传送的最小数据字节数
SO_SNDTIMEOstruct timeval套接字发送调用的超时值
SO_TYPEint标识套接字类型(仅getsockopt)

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

#include <sys/socket.h>
int getsockopt(int socyd, int level, int option, void *restrict val, socklen_t *restrict lenp);
//返回值:若成功,返回0;若出错,返回-1

16.7 外带数据

带外数据允许更高优先级的数据传输。带外数据先行传输,即使传输队列已经有数据。TCP支持带外数据,但是UDP不支持。

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

为了产生紧急数据,可以在3个send函数中的任何一个里指定MSG_OOB标志。如果带MSG_OOB标志发送的字节数超过一个时,最后一个字节将被视为紧急数据字节。

如果通过套接字安排了信号的产生,那么紧急数据被接收时,会发送SIGURG信号。
可以通过调用以下函数安排进程接收套接字的信号:fcntl(sockfdm F_SETOWNM, pid);

TCP支持在普通数据流中紧急数据所在的位置。如果采用套接字选项SO_OOBINLINE,那么可以在普通数据中接收紧急数据。为帮助判断是否已经到达紧急标记,可以使用函数sockatmark。

#include  <sys/socket.h>
int 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);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值