《unix网络编程》学习笔记(简要)
3 UNIX网络编程
3.1 查看网络拓扑
-
netstat -i
-
netstat -r
-
引用是指向变量的指针
3.2 套接字编程
3.2.1 套接字地址结构
- sockaddr_in
包括sin_family sin_addr sin_port三个字段
- 地址族
分为AF_INET和AF_INET6
- 结果的传递方向:
通常传地址结构的引用,内核到进程accept,进程到内核connect
getpeername(unixfd, (struct sockaddr *)&cli, &len);
len:套接字地址结构长度,值-结果参数,由整数改为指针,调用时告诉结构大小,返回时告诉写入长度
3.2.1 字节操作函数
- 网络字节序
TCP/IP协议在网络上上传输采用大端模式
htons:将unsigned short型数据转化为网络字节序
htonl:将unsigned int型数据转化为网络字节序
int inet_aton(const char * cp, struct in_addr *inp); 将字符串转化为网络字节序
char * inet_ntoa(struct in_addr in);将网络字节序转化为字符串
inet_ntop;功能同上,协议无关
- sock族函数
用来处理套接字地址结构
sock_cmp_addr
sock_cmp_port
sock_get_port
sock_set_addr
sock_set_port
sock_set_wild 设置为通配地址
sock_ntop 将套接字地址结构转化为字符串,返回指针,用来打印地址结构
3.2.2 socket输入输出
- socket缓冲区
write函数用于将应用进程缓冲区数据复制到内核输出缓冲区,内核协议栈将缓冲区中的内容发送到对端主机,不受应用程序控制,加上滑动窗口,拥塞控制
- 常用缓冲区输入输出函数
readn\writen\readline
3.3 基本socket编程
3.3.1 socket
int socket(int af, int type, int protocol);
1.af为地址族,分为AF_INET和AF_INET6
2.type为套接字类型,分为SOCK_STREAM和SOCK_DGRAM
3.protocol表示传输协议,IPPROTO_TCP和IPPROTO_UDP
4.返回文件描述符,windows区分socket和普通文件,返回SOCKET类型
3.3.2 bind
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
- 服务器端要用bind函数将套接字与特定的 IP 地址和端口绑定起来,只有这样,流经该IP地址和端口的数据才能交给套接字处理。
3.3.3 connect
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
- 客户端调用connect发起三次握手,如果connect前不bind,由内核选择IP地址和临时端口
- INADDR_ANY表示通配地址
- 让套接字从CLOSED状态到SEND状态
3.3.4 listen
int listen(int sock, int backlog);
sock为需要进入监听状态的套接字,backlog为请求队列的最大长度。
- listen函数可以让套接字进入被动监听状态,让套接字从CLOSED状态到LISTEN状态
- 内核为监听套接字维护两个队列:已完成连接队列(已完成三次握手)和未完成连接队列(SYN分节到达,等待三次握手)
- 未完成连接队列的首项在完成三次握手后进入连接队列
- backlog是两个队列之和
3.3.5 accept
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); //Linux
accept() 返回一个新的套接字来和客户端通信
-
accept取已完成连接队头返回进程
-
listen只是监听,后面的代码会继续执行,直到accept
-
accept会阻塞程序执行,后面的代码不会执行,直到获取到新的套接字
3.3.6 fork
- 并发服务器的实现:在accept后fork子进程,在子进程中读写
- fork后,listenfd和connfd两个描述符在父子进程中共享,父进程关闭connfd,子进程关闭listenfd
for(; ;){
connfd = Accept(listenfd, ...);
if (pid=fork()==0){
close(listenfd);
doit(connfd);
close(connfd);
exit(0);
}
close(connfd);
}
3.3.7 getsocketname和getpeername
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
- getsockname用于获取套接字本地的地址结构
- getpeername用于获取套接字对端的地址结构
3.4 TCP客户/服务器程序示例
3.4.1 正常启动
- netstat -a查看套接字状态
- fgets和fputs同标准输入输出关联,writen和readline同套接字关联
3.4.2 正常终止
- 键入EOF字符(ctrl+D)后,客户端fgets函数返回空指针,返回main函数
- 客户端exit结束,同时关闭所有描述符,套接字处于FIN_WAIT状态
- 同时发送一个FIN给服务器,服务器套接字进入CLOSE_WAIT状态,服务器子进程阻塞于read调用,read返回0,返回main函数,子进程exit终止
- 服务器子进程的描述符被关闭,并发送FIN到客户端,客户端套接字进入TIME_WAIT状态
- 服务器捕获子进程终止信号并处理
3.4.3 信号处理
- 服务器父进程中用一个waitpid()循环来处理所有子进程的退出
signal(SIGCHLD, sig_chld);
3.4.4 服务器进程终止
- 服务器子进程终止,向子进程发送FIN并得到ACK响应
- 客户端上没有发生变化,阻塞在标准输入的fgets调用上
- 输入一行文本后,客户端writen把数据发送给服务端(允许这么做,服务端发送FIN只是表示TCP服务端被关闭,不向其中发送和接收数据)
- 服务端进程已经终止,响应客户端一个RST
- 客户端在writen完以后立即readn,并且由于之前接收到的FIN,readn函数立即返回0,进程退出
- 这个问题说明进程不能单纯的阻塞在套接字和用户输入的任意一个上
3.5 IO复用
3.5.1 IO模型
-
5种:阻塞式IO、非阻塞式IO、IO复用、信号驱动IO、异步IO
-
默认情况下,所有套接字都是阻塞的
-
将套接字设成非阻塞后,遇到会把进程投入睡眠的IO操作,则立即返回错误,通常绑定轮询
-
IO复用,是阻塞在两个系统调用中,而不是真正的IO系统调用,可以等待多个描述符就绪
3.5.2 select函数
https://zhuanlan.zhihu.com/p/115220699#
-
客户端用select同时监听标准输入和connfd,就绪后读入
-
close是把描述符引用计数减1,减为0时才发送FIn,关闭套接字
-
shutdown不用管引用计数,可以直接关闭套接字
-
shutdown的参数howto可以指定关闭连接的读或者写这一半,写半关闭后,留在套接字缓冲区的数据将被发送,后面不能再对该套接字调用写函数
-
shutdown的应用:如果客户端标准输入读入错误,则立即关闭本轮connfd的写入
3.6 名字地址转换
- gethostbyname
struct hostent *gethostbyname(const char *hostname);
给定域名,返回IP地址
- gethostbyaddr
struct hostent *gethostbyaddr(const char *addr, socklen_t len, int family);
给定二进制IP地址,返回域名
- getservbyname
给定服务,查找端口等信息
- getservbyport
给定端口,查找服务信息
3.7 高级IO函数
3.7.1 套接字超时设置
- 通过alarm设置超时
alarm设置时间,超时后发出SIGALRM信号,中断connect等系统调用
慢系统调用:类似connect、read等可能永远阻塞的系统调用
慢系统调用如果被信号等中断,会立即返回EINTR错误
- 借助select函数设置超时
select函数的timeval结构参数,可设置超时时间,等待一个描述符在最多指定秒数内变为可读或可写
3.7.2 读写函数
- recv和send
类似标准read和wirte,多了第四个参数flags,用来控制读写
- recvmsg和sendmsg
把参数分装到msghdr结构中,作为指针传递值-结果参数,可被内核更新标志返回给进程
- 套接字默认状态是阻塞的,通过fnctl或ioctl改变