unix network programming(3rd)Vol.1 [第6~12章]《读书笔记系列》
第6章 i/o 复用 select和poll函数(重点)
应用场合:
- 当客户同时处理多个描述符(通常是交互式输入和网络套接字)时,必须使用I/O复用
- 一个客户同时处理多个套接字(select,poll,epoll,kqueue ) 详见16.5节
- 如果一个服务器既要处理 TCP,又要处理UDP, 详见6.8节
- 即使当服务器 (listen ,accept), 又当客户端(connect) 详见8.15节
- 如果一个服务器要处理多个服务,或者多个协议, 详见13.5节 inetd守护进程
IO模型
- 阻赛IO
- 非阻赛IO
- IO复用(select poll)
- 信号驱动式IO (SIGIO)
- 异步IO (POSIX的aio_ 系列函数)
5种IO模型的比较:
前4种是同步IO,最后一种是异步IO, 因为IO操作(recvfrom)将阻赛进程。只有异步IO模型与POSIX定义的异步IO模型相匹配。
同步IO在数据从内核复制到调用者的缓冲区期间,进程阻塞于recvfrom()调用。
- 6.3 select函数 (使用最多的函数)
该函数允许进程指示内核等待多个事件中的任何一个发生, 并只在有一个或者多个事件发生或经历一段 指定的时间后才唤醒它。
比如 共有10个要轮训的描述符
{1,3,9,10}的描述符准备好读
{2,8}的描述符准备好写
{4,5,6,7}的描述符有异常条件处理(TIMEOUT ,select 时间到了。。等)
select最大描述符数的修改
4.4 BSD 在<sys/type.h>
#ifndef FD_SETSIZE
#define FD_SETSIZE 256
#endif
如果想提高轮询 用户数量,修改FD_SETSIZE 这个值后,必须重新编译内核。(否则无法生效!)
- 6.6 shutdown()函数
通常 终止网络连接的方式是调用close().不过close()有两个限制,却可以使用shutdown()来避免
1)close 把描述符的引用计数-1,仅在该计数变为0时才关闭socket(详见4.8节 ),
使用shutdown可以不管引用计数就激发TCP的正常连接终止序列( 详见 图2-5 由FIN开始的4个分节)
2)close终止读和写两个方向的数据传送。
既然TCP连接是双全工,我们就有这种场景(即 发送完毕,我还需要接收一些数据)
include <sys/socket.h>
int shutdown(int sockfd, int howto);//return 0:ok, -1:failed
参数:howto
SHUT_RD 读关闭
SHUT_WR 写关闭
SHUT_RDWR 读关闭,写关闭
- 6.7 str_cli 函数 修订版
//code
其中select 之后, 有检查FD_ISSET(读fd),FD_ISSET(写fd) 如果缓冲中有数据就做相应操作
可以在 发送完数据之后(写数据到了 EOF),可以关闭写socket,
if( FD_ISSET( fileno(fp), &rset) )// 发送缓冲 是否已经发送完毕
{
if( (n= Read(fileno(fp), buf,MAXLINE )) == 0 ) //本地的数据是否已经读完了,读到EOF
stdineof = 1;//falg 当文件读完,遇到EOF 就修改flag
Shutdown(sockfd, SHUT_WR); //关闭写socket , TCP发送FIN
FD_CLR(fileno(fp), &rset); //reset
continue;
}
Writen(sockfd,n); //发送最后一次数据
为什么要尽早关闭socket?
因为FD_SIZE 有限,所以业务做完了,能早点关闭就早点关闭。
当然如果写失败(服务器crash,网络连接失败,超时)、 读失败(网络连接失败,超时) 也会得到相应返回值
-
6.8 TCP echo服务器程序 (用select函数)
-
6.9 pselect 函数
pselect函数是由POSIX发明
include <sys/select.h>
include <signal.h>
include <time.h>
int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timespec *timeout, const sigset_t *sigmask);
//return:返回就绪描述符的数量,若超时则为0,若出错则为-1
把时间精确度从select 的timeval微妙 增加到timespec 纳秒 级,
并采用一个指向信号集的指针作为他的一个新参数。 当有信号需要捕获时,该参数能让我们避免竞争条件,详见20.5
- 6.10 poll函数
inclue <poll.h>
int poll(struct pollfd * fdArray, unsigned long nfds, int timeout);//return:返回就绪描述符的数量,若超时则为0,若出错则为-1
出自SystemV的poll的函数提供类似遇select的功能,不过能够为流设备提供额外的信息。
POSIX对select和poll都有需要,不过select使用更频繁。
- 6.11 TCP echo服务器程序 (用poll函数)
第7章 套接字选项(重点)
分为两大基本类型:
1.启用或者禁用某个特性的二元选项(称为标志选项)
2.取得并返回 我们可以设置 或 检查的特定值的选项(称为值选项)
include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void* optval, socklen_t * optlen);
int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t * optlen);
//return: 成功返回0, 出错返回-1
图7-1 套接字层和IP层的套接字选项汇总 后续添加!!!
-
7.11
POSIX规定fcntl()是操作fd首选的
//file control 可执行各种描述符控制操作
include <fcntl.h>
int fcntl(int fd, int cmd,.../*int arg*/);//return:成功 取决遇cmd, 失败返回-1
fcntl函数提供了 遇网络编程 相关的如下特性:
- 非阻塞式I/O。 通过使用F_SETFL 命令设置O_NONBLOCK 文件状态标志, 我们可以把一个socket设置为非阻塞。 (第16章 详细描述 非阻塞IO)
- 信号驱动式I/O。通过使用F_SETFL 命令设置O_ASYNC 文件状态标志, 我们可以把一个socket设置成 一旦其状态发生变化,内核就产生一个SIGIO信号。(详见 第25章 )
- F_SETOWN 命令允许我们指定用于接收SIGIO和SIGURG信号的套接字 宿主(进程ID 或者 进程组ID)。
socket进程组ID ,存放在socket 结构的so_pgid成员(TCPv2 page438)
ioctl()//第17章讨论
sysctl()//获取路由列表
第8章 基本UDP套接字编程
UDP 缺乏流量控制
第9章 基本STCP套接字编程
第10章 STCP client/server程序例子
第11章 名字与地址转换
- 11.2 DNS (Domian Name system)
- 11.3 gethostbyname()函数 (仅支持IPV4)
- 11.4 gethostbyaddr()函数 (仅支持IPV4)
#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);
#include <sys/socket.h> /* for AF_INET */
struct hostent *gethostbyaddr(const void *addr,
socklen_t len, int type);
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 */
}
#define h_addr h_addr_list[0] /* for backward compatibility */
gethostbyname
、gethostbyaddr
这2个旧的API
- 函数不可重入(函数中有static 变量)
- 若网络延迟,阻塞,不通,则会阻塞(主进程假死 所以要用新的API)
- 11.19中的gethostbyname_r()、gethostbyaddr_r() 可重入
- 11.20中的gethostbyname2() 支持IPV6
建议用新接口,参考man page
**The gethostbyname*()
and gethostbyaddr*()
functions are obsolete(废除了). Applications should use getaddrinfo(3)
and getnameinfo(3)
instead. **
对于那些事可重入,那些是race的参考
http://man7.org/linux/man-pages/man3/gethostbyname.3.html
http://pubs.opengroup.org/onlinepubs/009695399/functions/gethostbyname.html
-
11.5 getservbyname(), getservbyport()函数
-
11.6 getaddrinfo函数(支持IPV6)
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res);
void freeaddrinfo(struct addrinfo *res);
const char *gai_strerror(int errcode);//return 指向错误描述消息字符串的指针
- 11.7 gai_strerror()
- 11.8 freeaddrinfo()
由getaddrinfo返回的所有存储空间都是动态获取的(比如 来自malloc调用),包括addrinfo结构、ai_addr结构和ai_cononname字符串。
这些存储空间都由freeaddrinfo 返还给系统。
要添加图片
图11-8 getaddrinfo 函数及其行为和结果汇总
以下是自己作者实现的一些函数,因为某些特定原因 ,具体看书上介绍
-
11.11 host_serv()函数
-
11.12 tcp_connect()函数
-
11.13 tcp_listen()函数
-
11.14 udp_client()函数
-
11.15 udp_connect()函数
-
11.16 udp_server()函数
-
11.17 getnameinfo 函数 (重点)
getnameinfo()是getaddrinfo()的互补函数,以一个套接字地址为参数,返回 描述其中的主机的一个字符串和描述其中服务的另一个字符串。
以协议无关的方式提供这些信息,调用者不必关心存放在套接字地址结构中的协议地址类型。
include<netdb.h>
int getnameinfo(const struct sockaddr *sockaddr, socklen_t addrlen,
char *host, socklent_t hostlen,
char *serv, socklen_t servlen, int flags);
-
11.18 可重入函数(重点)
函数中有static 变量,导致函数不可重入, 参考reentrant -
11.19 gethostbyname_r()、gethostbyaddr_r() 可重入函数版本
-
11.20.2 gethostbyname2 函数(对比gethostbyname()支持IPV6
include <sys/socket.h>
include <netdb.h>
struct hostent * gethostbyname2(const char* name, int af);