函数解释
三:套接口编程简介
void bzero(void* dest,size_t nbytes);
- 将内存块(字符串)的前nbytes个字节清零
- 参数:
- dest:内存(字符串)指针
- nbytes:需要清零的字节数
void memset(void dest,int c,size_t len);
- 将指针变量 dest所指向的前len字节的内存单元用一个“整数” c 替换,注意 c 是 int 型。dest是 void* 型的指针变量,所以它可以为任何类型的数据进行初始化。
- 参数:
- dest:指针变量
- c:替换后的结果
- len:需要初始化的前len个字节
- bzero和memset辨析:
- bzero:清零
- memset:初始化为int型的任意值
uint16_t htons(uint16_t host16bitvalue)
- 将16位主机字符顺序转换成网络字符顺序
- 参数:
- host16bitvalue:主机字符顺序
uint16_t ntohs(uint16_t net16bitvalue);
- ntohs()用来将参数指定的16 位netshort 转换成主机字符顺序.
- 参数
- net16bitvalue:网络字符顺序
int inet_aton(const char cp, struct in_addr inp);
- 将网络地址转成网络二进制的数字
- inet_aton()用来将参数cp 所指的网络地址字符串转换成网络使用的二进制的数字, 然后存于参数inp 所指的in_addr 结构中.
- 返回值:
- 成功则返回非0值,
- 失败则返回0.
in_addr_t inet_addr(const char* strptr);
- 将网络地址转成二进制的数字
- inet_addr()用来将参数cp 所指的网络地址字符串转换成网络所使用的二进制数字. 网络地址字符串是以数字和点组成的字符串, 例如:"163. 13. 132. 68".
- 返回值:
- 成功则返回对应的网络二进制的数字
- 失败返回-1.
int inet_pton(int family,const char strptr,void addrptr);
- 将点分十进制串转换成网络字节序二进制值
- 参数:
- family:选择的协议簇
- AF_INET:IPv4
- AF_INET6:IPv6
- strptr:指向点分十进制串的指针
- addrptr:指向转换后的网络字节序的二进制值的指针
- family:选择的协议簇
const char inet_ntop(int af, const void src, char *dst, socklen_t cnt);
- 将网络字节序二进制值转换成点分十进制串
- 参数:
- af:选择的协议簇
- AF_INET:IPv4
- AF_INET6:IPv6
- src:指向网络字节序的二进制值的指针;
- dst:指向转换后的点分十进制串的指针;
- cnt:参数是目标的大小,以免函数溢出其调用者的缓冲区。
- af:选择的协议簇
void bcopy(const void src, void dest, size_t nbytes)
- bcopy() 函数用来复制内存(字符串)。
- bcopy() 不检查内存(字符串)中的空字节 NULL。
- 参数:
- src:源内存块(字符串)指针
- dest:目标内存块(字符串)指针
- nbytes:要复制的内存(字符串)的前 n 个字节长度。
void memcpy ( void dest, const void * src, size_t num );
- 复制内存内容(忽略\0)
- bcopy和memcpy辨析:
- bcopy()与memcpy()一样都是用来拷贝src 所指的内存内容前n 个字节到dest 所指的地址,不过参数src 与dest 在传给函数时是相反的位置。
- bcopy() 不检查内存(字符串)中的空字节 NULL。
- memcpy() 并不关心被复制的数据类型,只是逐字节地进行复制,这给函数的使用带来了很大的灵活性,可以面向任何数据类型进行复制。
int bcmp(const void ptr1,const voidptr2,size_t nbytes);
- 比较内存(字符串)的前n个字节是否相等
- bcmp() 函数不检查NULL。
- 参数:
- ptr1, ptr2 为需要比较的两块内存(或两个字符串),
- nbytes 为要比较的长度。
- 返回值:
- 如果 ptr1, ptr2 的前 nbytes 个字节相等或者 nbytes 等于 0,则返回 0,
- 否则返回非 0 值。
int memcmp(const voids1,const void*s2,size_t n);
- 比较内存前n个字节
- memcmp()用来比较s1 和s2 所指的内存区间前n 个字符。
- 字符串大小的比较是以ASCII 码表上的顺序来决定,次顺序亦为字符的值。memcmp()首先将s1 第一个字符值减去s2 第一个字符的值,若差为0 则再继续比较下个字符,若差值不为0 则将差值返回。例如,字符串"Ac"和"ba"比较则会返回字符'A'(65)和'b'(98)的差值(-33)。
- 返回值:
- 若参数s1 和s2 所指的内存内容都完全相同则返回0 值。
- s1 若大于s2 则返回大于0 的值。
- s1 若小于s2 则返回小于0 的值。
- bcmp和memcmp辨析:
- bcmp不检查NULL值
- memcmp不仅检查是否相等,还能检测比较字符串的大小
四:基本 TCP 套接口编程
int socket(int family,int type,int protocol);
- 创建socket套接字
- 参数:
- family:协议栈
- AF_INET:IPv4
- AF_INET:IPv6
- type:类型
- SOCK_STREAM(TCP):字节流套接字
- SOCK_DGRAM(UDP):数据报套接字
- protocol:协议,默认为0
- family:协议栈
int connect(int sockfd, const struct sockaddr * servaddr, socklen_t addrlen);
- 建立socket连线
- connect()用来将参数sockfd 的socket 连至参数serv_addr 指定的网络地址. 结构sockaddr请参考bind(). 参数addrlen 为sockaddr 的结构长度.
- 参数:
- sockfd:套接字ID
- addr_len:结构体sockaddr的长度,通过sizeof取值
- servaddr:在输入servaddr参数时,记得强制类型转
struct in_addr
{
in_addr_t s_addr; //表示32位的IP地址,32位无符号整型
}
struct sockaddr_in
{
uint8_t sin_len; //表示该结构体的长度,8位无符号整型
sa_family_t sin_family; //表示套接口使用的协议族,8位无符号整型
in_port_t sin_port; //表示套接口使用的端口号,16位无符号整型
struct in_addr sin_addr; //表示IP地址,32位无符号整型
char sin_zero[8]; //该成员基本不使用,总是置为0
}
int bind(int sockfd,const struct sockaddr * myaddr,socklen_t addrlen);
- 对socket定位
- 用来给socket绑定对应IP和Port
- 参数:
- sockfd:套接字ID
- addrlen:结构体myaddr的长度,通过sizeof取值
- myaddr:在输入myaddr参数时,记得强制类型转
int listen(int sockfd,int backlog)
- 设置服务器能处理的最大连接数
- 参数:
- sockfd:套接字ID
- backlog:最大连接数
- 返回值:
- 0:成功
- -1:失败
int accept(int s, struct sockaddr addr, int addrlen);
- 接受socket连线
- accept()用来接受参数s 的socket 连线. 参数s 的socket 必需先经bind()、listen()函数处理过, 当有连线进来时accept()会返回一个新的socket 处理代码, 往后的数据传送与读取就是经由新的socket处理, 而原来参数s 的socket 能继续使用accept()来接受新的连线要求. 连线成功时, 参数addr 所指的结构会被系统填入远程主机的地址数据, 参数addrlen 为scokaddr 的结构长度. 关于机构sockaddr 的定义请参考bind().
- 返回值:
- 成功:返回新的socket 处理代码,
- 失败:返回-1, 错误原因存于errno 中.
int close(int sockfd);
- 关闭套接字连接
- 对套接字描述符的访问计数值减1,当减为0时,才执行关闭动作。
int getsockname(int sockfd,struct sockaddr localaddr,socklen_t addrlen);
- 返回与套接口关联的本地协议地址
int getpeername(int sockfd,struct sockaddr localaddr,socklen_t addrlen);
- 返回与套接口关联的远程协议地址
pid_t wait(int * statloc);
- 结束(中断)进程函数(常用)
- wait()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在调用wait()时子进程已经结束, 则wait()会程识别码也会一快返回. 如果不在意结束状态值, 则参数 status 可以设成NULL. 子进程的结束状态值请参考waitpid().
- 返回值:
- 成功:子进程识别码(PID),
- 失败:返回-1. 失败原因存于errno 中
pid_t waitpid(pid_t pid,int * statloc,int options)(非阻塞版wait函数);
- 中断(结束)进程函数(等待子进程中断或
- waitpid()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数status 返回, 而子进程的进程识别码也会一快返回. 如果不在意结束状态值, 则参数status 可以设成NULL. 参数pid 为欲等待的子进程识别码, 其他数值意义如下:
- pid<-1 等待进程组识别码为pid 绝对值的任何子进程
- pid=-1 等待任何子进程, 相当于wait()
- pid=0 等待进程组识别码与目前进程相同的任何子进程
- pid>0 等待任何子进程识别码为pid 的子进程
const char inet_ntop(int family,const void addrptr,char * strptr,size_t l en);
- 将网络字节序二进制值转换成点分十进制串
- 参数:
- af:选择的协议簇
- AF_INET:IPv4
- AF_INET6:IPv6
- src:指向网络字节序的二进制值的指针;
- dst:指向转换后的点分十进制串的指针;
- cnt:参数是目标的大小,以免函数溢出其调用者的缓冲区。
- af:选择的协议簇
六:I/O 复用:select 和 poll 函数
int select( int maxfdp1,fd_set readset,fd_set writeset,fd_set exceptset ,const struct timeval timeout);
- I/O多工机制
- select()用来等待文件描述词状态的改变. 参数maxfdp1代表最大的文件描述词加1, 参数readset、writeset和exceptset 称为描述词组, 是用来回传该描述词的读, 写或例外的状况. 底下的宏提供了处理这三种描述词组的方式:
- FD_CLR(inr fd, fd_set* set); 用来清除描述词组set 中相关fd 的位
- FD_ISSET(int fd, fd_set *set); 用来测试描述词组set 中相关fd 的位是否为真
- FD_SET(int fd, fd_set*set); 用来设置描述词组set 中相关fd 的位
- FD_ZERO(fd_set *set); 用来清除描述词组set 的全部位
- 参数 timeout 为结构timeval, 用来设置select()的等待时间, 其结构定义如下:
- 微秒级
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
int pselect(int maxfdp1,fd_set readset,fd_set writeset,fd_set exceptset ,const struct timespec timeout,const sygset_t * sigmask);
- 纳秒级
pselect相对于正常的select有两个变化:
- pselect使用结构timespec,这是Posix.lg实时标准的一个发明,而不使用结构timeval。
struct timespec
{
time_t tv_sec;
long tv_nsec;
};
- 这两个结构的区别在第二个成员上:新结构的成员tv-nsec规定纳秒数,而老结构的成员tv-usec规定微秒数。
- 函数pselect增加了第六个参数:指向信号掩码的指针。这允许程序禁止递交某些信号,测试由这些当前禁止的信号的信号处理程序所设置的全局变量,然后调用pselect,告诉它临时重置信号掩码。
int poll(struct pollfd * fdarray,unsigned long nfds,int timeout);
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。
poll允许工作在任何描述字上。poll提供了与select相似的功能,但当涉及到流设备时,它还提供附加信息。
int shutdown(int sockfd , int howto);
- 断开连接
- 参数:
- sockfd:套接字
- howto:断开方式
- SHUT_RD:关闭读连接
- SHUT_WR:关闭写连接
- SHUT_RDWR:关闭读写连接,相当于调用了两遍shutdown函数
uint32_t ntohl(uint32_t net32bitvalue);
- 将32位网络字符顺序转换成主机字符顺序
- 参数:
- net32bitvalue:32位网络字节顺序
ssize_t readline(int filedes,void * buff,size_t maxlen);
- 从文件描述符filedes一次读取一行数据
- 参数:
- filedes:需要读取的文件
- buff:要将读取的内容保存的缓冲区
- maxlen:最大读取长度
ssize_t writen(int filedes,const void * buff,size_t nbytes);
- 向文件描述符filedes一次写长度位n的数据
八:基本 UDP 套接口编程
ssize_t recvfrom(int sockfd,void buff,size_t nbytes,int flags,struct sockad dr from,socklen_t * addrlen);
- 经socket接收数据
- recv()用来接收远程主机经指定的socket 传来的数据, 并把数据存到由参数buf 指向的内存空间, 参数len 为可接收数据的最大长度. 参数flags 一般设0, 其他数值定义请参考recv(). 参数from 用来指定欲传送的网络地址, 结构sockaddr 请参考bind(). 参数fromlen 为sockaddr 的结构长度.
- 参数:
- sockfd:指向的描述字
- buff:指向读入或写出缓冲区的指针
- nbytes:读写字节数
- flags:一般设0
ssize_t sendto(int sockfd,const void buff,size_t nbytes,int flags,const str uct sockaddr to,socklen_t addrlen);
- 经socket传送数据
- sendto() 用来将数据由指定的socket 传给对方主机. 参数s 为已建好连线的socket, 如果利用UDP协议则不需经过连线操作. 参数msg 指向欲连线的数据内容, 参数flags 一般设0, 详细描述请参考send(). 参数to 用来指定欲传送的网络地址, 结构sockaddr 请参考bind(). 参数tolen 为sockaddr 的结果长度.
- 参数:
- sockfd:指向的描述字
- buff:指向读入或写出缓冲区的指针
- nbytes:读写字节数
- flags:一般设0
char sock_ntop(const struct sockaddr sockaddr,socklen_t addrlen);
uint32_t htonl(uint32_t host32bitvalue)
- 将32位主机字符顺序转换成网络字符顺序
- htonl ()用来将参数指定的32 位hostlong 转换成网络字符顺序.
char inet_ntoa(struct in_addr inaddr);
- 将网络二进制的数字转换成网络地址
- inet_ntoa()用来将参数in 所指的网络二进制的数字转换成网络地址, 然后将指向此网络地址字符串的指针返回.
ssize_t readn(int filedes,void * buff,size_t nbytes);
- 读取长度为n的数据
- 参数:
- filedes:文件描述符
- buff:指向读入或写出缓冲区的指针
- nbytes:读写字节数
九:基本名字与地址转换
struct hostent gethostbyname(const char hostname);
- 通过域名获取IP地址
struct hostent{
char *h_name; // 主机名
char **h_aliases; // 别名列表
int h_addrtype; // 主机地址类型
int h_length; // 地址长度
char **h_addr_list; // 地址列表
}
struct hostent gethostbyname2(const char hostname,int family);
- 通过域名获取IP地址
- 允许指定地址簇,支持IPv6
struct servent getservbyport(int port,const char protoname);
- 在给定端口号和可选协议后查找相应的服务。
int gethostname(char * name,size_t namelen);
- 函数gethostname也返回当前主机的名字
struct hostent gethostbyaddr(const char addr,size_t len,int family
- 通过IP地址获取主机的完整信息
struct servent getservbyname(const char servname,const char * protoname);
- 根据给定名字查找服务。
简答题
三:套接口编程简介
在套接口编程中,经常看到函数htons的使用,它的作用是什么?
将16位主机字符顺序转换成网络字符顺序。使sort(port)类型的数据能按照网络中的传输方式进行传输。
函数 bcopy 和 memcpy 的区别是什么?
- bcopy()与memcpy()一样都是用来拷贝src 所指的内存内容前n 个字节到dest 所指的地址,不过参数src 与dest 在传给函数时是相反的位置。
- bcopy() 不检查内存(字符串)中的空字节 NULL。
- memcpy() 并不关心被复制的数据类型,只是逐字节地进行复制,这给函数的使用带来了很大的灵活性,可以面向任何数据类型进行复制。
为什么函数 readn 和 writen 都将 void 型指针转换为 char 型指针?
在ANSI C标准中,不允许对void指针进行算术运算如pvoid++或pvoid+=1等,需要转换为char类型指针才能对指针进行加减操作。
为什么诸如套接口地址结构长度这样的值-结果参数要用指针来传递?
调用时,需要传递结构体长度,避免写操作越界,在调用后,参数的结构又是另一个结果,所以需要使用值-结果参数。
在并发服务器程序中,需要调用什么函数派生一个进程,这个函数的特点是什么?
fork函数
特点:一次调用,两次返回,在父进程中,返回子进程的标识,在子进程中,返回0
简述 TCP 的四分节终止序列(四次挥手)。
- 客户端向服务器返送FIN J(终止序列)已经ACK应答K
- 服务器收到客户端的请求后,发送收到ACK应答J+1
- 服务器向客户端发送断开请求FIN K,及ACK应答J+1
- 客户端收到请求后,向服务器发送收到应答 K+ 1
在并发服务器程序中,常用 fork 函数派生一个进程,简述其特点
特点:一次调用,两次返回,在父进程中,返回子进程的标识,在子进程中,返回0
四:基本TCP套接口编程
accept 函数的最后一个参数为什么要使用值-结果参数?
因为在accept函数中,调用时,需要传递结构体长度,避免写操作越界,在调用后,参数的结构又是另一个结果,所以需要使用值-结果参数。
简述 TCP 套接口编程中,服务器和客户端的几个主要步骤,TCP 的三路握手是在哪个步骤完成的?
服务器:
- 调用 socket 函数创建 socket(侦听socket)
- 调用 bind 函数 将 socket绑定到某个ip和端口的二元组上
- 调用 listen 函数 开启侦听
- 当有客户端请求连接上来后,调用 accept 函数接受连接,产生一个新的 socket(客户端 socket)
- 基于新产生的 socket 调用 send 或 recv 或 write 或 read 函数开始与客户端进行数据交流
- 通信结束后,调用 close 函数关闭侦听 socket
客户端:
- 调用 socket函数创建客户端 socket
- 调用 connect 函数尝试连接服务器
- 连接成功以后调用 send 或 recv 或 write 或 read 函数开始与服务器进行数据交流
- 通信结束后,调用 close 函数关闭侦听socket
三次握手:三次握手在客户端实在connect函数发起的,客户端调用connect向服务器发起连接请求。服务器在accept函数监听到客户端的连接请求,与客户端建立连接,生成与该客户端对应的套接字。
当派生一个进程后,套接口描述字一般需要调用两次 close 才能引发四分节终止序列,为什么?
当创建socket套接字后,与该套接字关联的访问计数值为1,当fork派生一个进程后,访问计数值加1,调用close函数时,是对访问计数值进行减1操作,当访问计数值为0时,才能终止序列。所以需要两次,如果想直接终止程序的话,可以通过shutdown函数操作。
当调用 fork 函数派生一个进程时,返回值有几个?分别是什么含义?
两个,分别在父进程和子进程返回一个返回值。
父进程:返回子进程的标识,通过标识可以对子进程进行操作。
子进程:返回0,子进程拥有唯一的父进程,可以通过函数获取,同时,在子进程内部,不需要获取自身的标识,通过返回值0,可以在接收返回值时,有效的判断是父进程还是子进程。
简述 TCP 套接口编程中,服务器程序所调用的套接口函数有哪些?
- 调用 socket 函数创建 socket(侦听socket)
- 调用 bind 函数 将 socket绑定到某个ip和端口的二元组上
- 调用 listen 函数 开启侦听
- 当有客户端请求连接上来后,调用 accept 函数接受连接,产生一个新的 socket(客户端 socket)
- 基于新产生的 socket 调用 send 或 recv 或 write 或 read 函数开始与客户端进行数据交流
- 通信结束后,调用 close 函数关闭侦听 socket
当派生一个进程后,套接口描述字一般需要调用几次 close 才能正常关闭,为什么?
两次,见 当派生一个进程后,套接口描述字一般需要调用两次 close 才能引发四分节终止序列,为什么?
getsockname 函数的最后一个参数为什么要使用值-结果参数?
getpeername 函数的最后一个参数为什么要使用值-结果参数?
函数原型:
- int getsockname(int sockfd,struct sockaddr localaddr, socklen_t addrlen):返回与套接口关联的本地协议地址
- int getpeername(int sockfd,struct sockaddr peeraddr,socklen_t addrlen):返回与套接口关联的远程协议地址
因为在getsockname和getpeername函数中,调用时,需要传递结构体长度,避免写操作越界,在调用后,参数的结构又是另一个结果,所以需要使用值-结果参数。
简述 TCP 套接口编程中,客户程序所调用的套接口函数有哪些?
- 调用 socket函数创建客户端 socket
- 调用 connect 函数尝试连接服务器
- 连接成功以后调用 send 或 recv 函数 或 write 或 read 开始与服务器进行数据交流
- 通信结束后,调用 close 函数关闭侦听socket
当派生一个进程后,已存在的套接口描述字的访问计数有什么变化?有什么影响?
已存在套接口描述字访问计数值加1,当调用close函数时,不会直接关闭tcp连接,而是访问计数值减1,所以派生进程后,需要调用2次close才能真正关闭tcp连接。
当派生多个进程后,套接口描述字的访问计数值是如何变化的?
每派生一个进程,套接口描述字的访问计数值加1,每调用一次close,套接口描述字的访问计数值减1.
在 TCP 编程中,三路握手一般是由哪一端调用什么函数发起的?简述三次握手的过程
客户端
- 客户端发送连接请求SYN,在连接请求包中带上本次连接序号J。
- 服务器收到客户端连接请求后,向客户端返回收到应答J+1(SYN长度为1),并带上服务器向客户端的连接请求SYN K
- 客户端收到应答后,客户端连接建立完成,客户端向服务器发送应答K+1
五:TCP 客户-服务器程序例子
请说明 wait 和 waitpid 的区别。
- 在一个子进程终止前,wait使其调用者阻塞
- waitpid有一个选项,可以使调用者不阻塞
- waitpid等待一个指定的子进程,wait等待所有的子进程,返回任一一个终止的子进程
处理 SIGCHLD 信号的作用是什么?
- 维护子进程的信息,以便父进程在稍后的某个时候取回
- 结束后告诉父进程wait回收
在套接口编程中,调用函数 Signal(SIGCHLD, sig_chld)的作用是什么?
- 通知内核对子进程的结束不关心,由内核回收
- 表示父进程忽略SIGCHLD信号。
- SIGCHLD信号:该信号是子进程退出的时候向父进程发送的。 子进程结束时, 父进程会收到这个信号。
在 TCP 编程中,为了避免僵尸进程的出现,常用什么函数?为什么?
waitpid。
避免僵尸进程:通过捕获信号SIGCHLD来处理。接着,信号处理程序调用waitpid。
wait函数容易产生阻塞,且在发生多个信号时只执行一次,当有多个僵尸进程产生时,wait不足以避免所有的僵尸进程。
在 TCP 编程中,常用 waitpid 函数避免僵尸进程的出现,而不用 wait 函数,为什么?
见 在 TCP 编程中,为了避免僵尸进程的出现,常用什么函数?为什么?
在 TCP 编程中,如果调用 wait 函数来避免僵尸进程的出现,会出现什么后果?为什么?
wait函数等待所有的子进程,不能针对指定的子进程进行等待,wait在使用时会阻塞调用者,所以使用wait来避免僵尸进程时,可能存在多个子进程终止时,仍有部分子进程作为僵尸进程存在,不足以避免僵尸进程出现。
在 I/O 复用中,要经常使用宏 void FD_ZERO(fd_set *fdset),其作用是什么?
将指定文件的描述符集清空
在 I/O 复用中,要经常使用宏 int FD_ISSET(int fd, fd_set *fdset),其作用是什么?
测试文件描述符是否在该集合中
在函数 int select(int maxfdp1,fd_set readset,fd_set writeset,fd_set exce ptset, const struct timeval timeout)中,如果把第 2、3、4 个参数全部置空,这个函数的作用是什么?
微秒级计时器
在函数 int pselect(int maxfdp1,fd_set readset,fd_set writeset,fd_set exc eptset, const struct timespec timeout,const sygset_t * sigmask)中,如果把第 2 、3、4 个参数全部置空,这个函数的作用是什么?
纳秒级计时器
六:I/O 复用:select 和 poll 函数
I/O 有几种模型?分别是什么?
5种:
- 阻塞I/O
- 非阻塞I/O
- I/O复用(select和poll )
- 信号驱动I/O ( SIGIO )
- 异步I/O ( Posix.1的aio-系列函数)
阻塞 I/O 和 I/O 复用两种模型的区别是什么?
- 阻塞IO:在准备阶段即同步阻塞,应用进程调用I/O操作时阻塞,只有等待要操作的数据准备好,并复制到应用进程的缓冲区中才返回;
- IO复用:多路IO共用一个同步阻塞接口,任意IO可操作都可激活IO操作,这是对阻塞IO的改进(主要是select和poll、epoll,关键是能实现同时对多个IO端口进行监听)。此时阻塞发生在select/poll的系统调用上,而不是阻塞在实际的I/O系统调用上。IO多路复用的高级之处在于:它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select等函数就可以返回。
请说明 close 和 shutdown 的区别。
close:描述符的访问计数值减1,当计数值为0时,同时关闭写缓存和读缓存
shutdown:根据指定的关闭方式进行关闭,有读关闭,写关闭,读写关闭,可以实现半关闭,可以直接关闭,而不是调用多次后才完成关闭
I/O 复用模型阻塞于哪个函数调用?它比起阻塞 I/O 模型优势在哪里?
阻塞与select/poll的函数调用。
能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select等函数就可以返回。
请说明 shutdown 的特点。
可以实现TCP的半关闭,可以根据关闭方式实现读关闭,写关闭,读写关闭。调用后就直接关闭,无需多次调用。
在 select 和 pselect 两个函数中,都带有一个参数,分别是结构体 timeval 和 timespec, 这两个结构体有什么不同?
timeval:微秒级
timespec:纳秒级
I/O 复用模型的优点是什么?
阻塞发生在select/poll的系统调用上,而不是阻塞在实际的I/O系统调用上。IO多路复用的高级之处在于:它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select等函数就可以返回。
函数 select 中,第一个参数 int maxfdp1 的含义是什么?
请说明函数 int select(int maxfdp1,fd_set readset,fd_set writeset,fd_set exceptset,const struct timeval timeout);第 2、3、4 个参数分别代表的含义。
请说明函数 int pselect(int maxfdp1,fd_set readset,fd_set writeset,fd_set exceptset, const struct timespec timeout,const sygset_t * sigmask);第 2、3、4
各参数分别代表的含义。
- maxfdp1:指集合中所有文件描述符的范围,即所有文件描述符的最大值加1
- readset:可读文件集合,若集合中对应的文件描述符可写,则返回一个大于0的值
- writeset:可写文件集合,若集合中对应的文件描述符可写,则返回一个大于0的值
- exceptset:异常文件集合,若集合中对应的文件描述符可存在,则返回一个大于0的值
shutdown 可以避免 close 的两个限制,分别是什么?
- close仅将访问计数值减1,为0时才进行关闭
- close直接将读写的两个方向的数据传输全部关闭
八:基本 UDP 套接口编程
recvfrom 函数的最后一个参数为什么要使用值-结果参数?
因为在recvfrom 函数中,调用时,需要传递结构体长度,避免写操作越界,在调用后,参数的结构又是另一个结果,所以需要使用值-结果参数。
简述 UDP 套接口编程中,服务器所调用的套接口函数有哪些?
- 调用 socket 函数创建 socket(侦听socket)
- 调用 bind 函数 将 socket绑定到某个ip和端口的二元组上
- 调用 recvfrom 函数阻塞到收到客户端发送数据
- 调用 sendto 函数向客户端发送应答
- 通信结束后,调用 close 函数关闭侦听 socket
简述 UDP 套接口编程中,客户端所调用的套接口函数有哪些?
- 调用 socket 函数创建 socket(侦听socket)
- 调用 recvfrom 函数阻塞到收到客户端发送数据
- 调用 sendto 函数向客户端发送应答
- 通信结束后,调用 close 函数关闭侦听 socket
在 UDP 套接口编程中,调用 connect 函数后,程序功能发生了哪些主要变化?
没有三路握手过程。内核只是记录对方的IP地址和端口号,它们包含在传递给connect的套接口地址结构中,并立即返回给调用进程。
对于已连接UDP套接口,与缺省的未连接UDP套接口相比,发生了三个变化:
- 我们再也不能给输出操作指定目的IP地址和端口号。也就是说,我们不使用sendto,而使用write或send写到已连接UDP套接口上的任何东西都自动发送到由connect所指定的协议地址(例如IP地址和端口号)
- 我们不用recvfrom而用read或recv。在已连接UDP套接口上由内核为输入操作返回的唯一数据报是那些来自connect 所指定协议地址的数据报。目的地址为已连接UDP套接口的本地协议地址(例如IP地址和端口号),但不是从connect所指定套接口协议地址到达的数据报,不传递给已连接套接口。这就限制了已连接UDP套接口能且只能与一个对方交换数据报。
- 异步错误由已连接UDP套接口返回给进程,由此推断,未连接UDP套接口不接收任伺异步错误
给一个 UDP 套接口多次调用 connect 可以达到两个目的,分别是什么?
- 指定新的IP地址和端口号;断开套接口。
- 断开套接口。
在 TCP 和 UDP 编程中,客户端都可以调用 connect,它们的主要区别是什么?
没有三路握手过程。内核只是记录对方的IP地址和端口号,它们包含在传递给connect的套接口地址结构中,并立即返回给调用进程。
在 UDP 编程中,调用 connect 与不调用 connect 有什么不同?
对于已连接UDP套接口,与缺省的未连接UDP套接口相比,发生了三个变化:
- 我们再也不能给输出操作指定目的IP地址和端口号。也就是说,我们不使用sendto,而使用write或send写到已连接UDP套接口上的任何东西都自动发送到由connect所指定的协议地址(例如IP地址和端口号)
- 我们不用recvfrom而用read或recv。在已连接UDP套接口上由内核为输入操作返回的唯一数据报是那些来自connect 所指定协议地址的数据报。目的地址为已连接UDP套接口的本地协议地址(例如IP地址和端口号),但不是从connect所指定套接口协议地址到达的数据报,不传递给已连接套接口。这就限制了已连接UDP套接口能且只能与一个对方交换数据报。
- 异步错误由已连接UDP套接口返回给进程,由此推断,未连接UDP套接口不接收任伺异步错误
对于 UDP/IPv4 套接口,可传递给 sendto 的最大长度是多少?
65535-20(IP头)-8(UDP头)=65507
函数 poll 和 select 有什么不同?
- poll涉及流设备时,提供附加信息。
- poll没有最大连接数限制
九:基本名字与地址转换
已知 struct hostent 结构体定义如下,请说明各个成员的含义。(写在结构体的右边)
struct hostent{
char *h_name; // 主机名
char **h_aliases; // 别名列表
int h_addrtype; // 主机地址类型
int h_length; // 地址长度
char **h_addr_list; // 地址列表
}
在选项 RES_USE_INET6 打开时,函数 gethostbyname 和 gethostbyname2 的操作有什么不同?
- gethostbyname:搜索AAAA记录,若找到,返回 IPv6 地址(h-length=16),否则搜索A记录。若找到,返回IPv4映射的IPv6地址(h-length=16),否则返回错误。
- gethostbyname2:
- AF_INET:搜索A记录,若找到,返回IPv4映射的IPv6地址(h-length=16),否则返回错误。
- AF_INET6:搜索AAAA记录,若找到返回 IPv6地址(h-length=16),否则返回错误。
为什么应用程序会以参数 SHUT_RDWR 来调用 shutdown,而不是仅仅调用 close?
close函数首先减少1个访问计数值,在访问计数值为0时才进行关闭,而shutdown函数的SHUT_RDWR参数不管访问计数值是多少,都可以直接关闭描述字,断开连接。
将 shutdown 的第二个参数改为 SHUT_RD 时,会产生什么结果?
将调用者读数据传输方向的缓存区进行关闭,仍然允许向写缓存区写入数据。