Linux网络
*************基本概念***************
【1】计算机与网络发展的7个阶段
1. 批处理(20世纪50年代)
是指实现将用户每个数据装入卡带或者磁带。并有计算机按照一定的顺序读取,
是用户索要执行的这些程序和数据能够一并批量得到处理的方式。
2. 分时系统(20世纪60年代)
是指多个终端(包含鼠标、键盘、显示器等输入输出设备组成,最初还包括打
印机)与一台计算机连接,允许多个用户同时使用一台计算机的系统。
特性:多路性、独占性、交互性和及时性。
3.计算机之间的通信(20世纪70年代)
4. 计算机网络的产生(20世纪80年代)
5. 互联网的普及(20世纪90年代)
6. 以互联网技术为中心的时代(2000年)
7. 从“单纯建立连接”到“安全建立连接”(2010年)
【2】网络体系结构即指网络的层次结构和每层所使用协议的集合
【3】
1. 协议
一组控制数据通信的规则。
三要素:语法(包括数据格式、编码及信号电平等)、
语义(包括用于协议和差错处理的控制信息)、
时序(包括速度匹配和排序)。
2. 标准
一致同意的规则。
分类:
事实上的标准:实际情况或者习惯
合法标准:法律或者规章制度
3. 标准化组织
缓慢发展:
ISO:国际标准化组织
ITU-T:国际电联-电信标准部
ANSI:美国国家标准化局
IEEE:电气电子工程师协会(主要是以太网、局域网方面的)
EIA:电子工业协会(物理传输标准、光钎传输)
快速发展:
论坛:帧中继论坛、ATM论坛
管理机构:FCC 联邦通信委员会
Internet标准:RFC
【4】OSI开放系统互联模型
OSI模型是一个理想化的模型,尚未有完整的实现
应用层 应用程序:FTP、E-mail、Telnet
表示层 数据格式定义、数据转换/加密
会话层 建立通信进程的逻辑名字与物理名字之间的联系
传输层 差错处理/恢复,流量控制,提供可靠的数据传输
网络层 数据分组、路由选择
数据链路层 数据组成可发送、接收的帧
物理层 传输物理信号、接口、信号形式、速率
【5】7层通信
(1)应用层:指定特定应用的协议(比如发送和接受文件的软件按钮,发送者输入“早上好”并附上收件人,按下发送按钮,接受者收到信息会将其存储在硬盘或者非易失存储器(数据不会因为断电而丢失的一种存储设备)上,这些都是在应用层上的)
(2)表示层:设备固有数据格式和网络标准数据格式的转换(接受者和发送者如果使用的邮件客户端不一样,那么就会出现问题,如何实现用户之间的通信,那么就需要在表示层来起作用,使得在不同的客户端上拥有相同的网络格式)
(3)会话层:通信管理,负责建立或者断开通信连接(发送者一次性发送5份邮件,那么接受者如何接受,是一次性接受所有的文件然后断开连接还是没接受一次就断开,然后在此进行,发送者同理)
(4)传输层:管理两个节点(互联的网络中断)之间的数据传输。负责可靠传输(确保数据被可靠地传送到目标地址)(确保发送者和接受者之间的通信,会话层负责决定建立连接和断开连接的时机,而传输层进行实际的建立和断开处理)
(5)网络层:地址管理与路由选择,作用:在网络相互连接的环境中,将数据从发送端主机发送到接受端主机
(6)数据链路层:互连设备之间传送和识别数据帧
(7)物理层:以“0”、“1”代表的电压的高低、灯光的闪灭。界定连接器和网络的规格。
【6】TCP/IP协议:传输控制/网际协议(Transfer Control Protocol/Internet Protocol) 又称作网络通讯协议
TCP/IP协议是Internet事实上的工业标准。
传输控制/网际协议(Transfer Control Protocol/Internet Protocol) 又称作网络通讯协议
应用层 TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet
传输层 TCP,UDP
网络层 IP,ICMP,RIP,OSPF,BGP,IGMP
物理层 SLIP,CSLIP,PPP,ARP,RARP,MTU ISO2110,IEEE802.1,EEE802.2
TCP(Transport Control Protocol)传输控制协议
UDP(User Datagram Protocol)用户数据报协议
IP(Internetworking Protocol)网间协议
HTTP(Hypertext Transfer Protocol) 超文本传输协议
SMTP(Simple Mail Transfer Protocol)简单邮件传输协议
【7】UDP和TCP
共同点:同为传输层协议
不同点:
TCP:有连接,可靠
UDP:无连接,不保证可靠
TCP:(全双工)
TCP(即传输控制协议):是一种面向连接的传输层协议,
它能提供高可靠性通信(即数据无误、数据无丢失、数据
无失序、数据无重复到达的通信)
适用情况:
适合于对传输质量要求较高,以及传输大量数据的通信。
在需要可靠数据传输的场合,通常使用TCP协议
MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
TCP“三次握手”
UDP:(全双工)
UDP(User Datagram Protocol)用户数据报协议,是不可靠
的无连接的协议。在数据发送前,因为不需要进行连接,所以
可以进行高效率的数据传输。
适用情况:
发送小尺寸数据(如对DNS服务器进行IP地址查询时)
在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)
适合于广播/组播式通信中。
MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议
流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输
【8】socket
是一个编程接口
是一种特殊的文件描述符 (everything in Unix is a file)
并不仅限于TCP/IP协议
面向连接 (Transmission Control Protocol - TCP/IP)、
无连接 (User Datagram Protocol -UDP 和 Inter-network Packet Exchange - IPX)
在OSI模型中,主要位于会话层和传输层之间
类型:
流式套接字(SOCK_STREAM)
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
--------> TCP协议
数据报套接字(SOCK_DGRAM)
提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
--------> UDP协议
原始套接字(SOCK_RAW)
可以对较低层次协议如IP、ICMP直接访问。
【9】IP地址
IP地址是Internet中主机的唯一标识
Internet中的主机要与别的机器通信必须具有一个IP地址
IP地址为32位(IPv4)或者128位(IPv6)
每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由
ipv4表示形式:点分十进制形式,如202.38.64.10,最后都会转换为一个32位的无符号整数。
IP地址分类(基于ipv4地址前八位来区分)
A类 0000 0000 - 0111 1111 0.x.x.x - 127.x.x.x
B类 1000 0000 - 1011 1111 128.x.x.x - 191.x.x.x
C类 1100 0000 - 1101 1111 192.x.x.x - 223.x.x.x
D类 1110 0000 - 1110 1111 224.x.x.x - 239.x.x.x 表示组播地址
E类 1111 0000 - 1111 1111 240.x.x.x - 255.x.x.x 属于保留测试
127.x.x.x 表示主机地址
子网掩码:表示链接的主机最大数
A类 255.0.0.0
B类 255.255.0.0
C类 255.255.255.0
192.168.2.x
192.168.2.1 表示网关
192.168.2.255 表示该网段下的广播地址
#include <arpa/inet.h>
将点分十进制ip地址转化为网络字节序的整型数据
in_addr_t inet_addr(const char *cp);
将网络字节序的整型数据转化为点分十进制ip地址
char *inet_ntoa(struct in_addr in);
例子:
inet_addr("192.168.2.189");
【10】端口号(vi /etc/services 查看端口号)
为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别
众所周知端口:1~1023(1~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)
已登记端口:1024~49151
动态或私有端口:49152~65535
一般使用:8888 9999 10000 10001
【11】字节序
不同类型CPU的主机中,内存存储多字节整数序列有两种方法,称为主机字节序(HBO):
小端序(little-endian) - 低序字节存储在低地址
将低字节存储在起始地址,称为“Little-Endian”字节序,Intel、AMD等采用的是这种方式;
大端序(big-endian)- 高序字节存储在低地址
将高字节存储在起始地址,称为“Big-Endian”字节序,由ARM、Motorola等所采用
如何测试主机字节序:
方法1:使用指针
方法2:使用file命令,file a.out 其中LSB的L代表小端存储
方法3:共用体
网络中传输的数据必须按网络字节序,即大端字节序
#include <arpa/inet.h>
将主机字节序转化为网络字节序
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
将网络字节序转化为主机字节序
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
例子:
htons(9999);
****************TCP网络编程******************
【1】流程
举个例子:
买个手机、买张卡
手机和卡必须匹配
将卡与手机绑定
设置为非飞行模式
进行通信
服务器:server.c
创建套接字 socket( )
填充服务器的网络信息结构体 sockaddr_in
将套接字与服务器的网络信息结构体绑定 bind( )
将套接字设置为监听模式 listen( )
阻塞等待客户端的连接请求 accept( )
进行通信 recv( )/send( )
客户端:client.c
创建套接字 socket( )
填充服务器的网络信息结构体 sockaddr_in
发送客户端的连接请求 connect( )
进行通信 send( )/recv( )
【2】ctags的创建
第一步:在/usr/include 里面执行
sudo ctags -R,生成一个tags 的索引文件
测试:vim -t sockaddr_in
第二步:实现全局
在家目录下的 .vimrc 添加
set tags+=/usr/include/tags
使用:ctrl ] 追代码
ctrl t 返回上一层
【3】socket( )
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建一个套接字,返回一个文件描述符
参数:
domain:通信域或者协议族
AF_UNIX,AF_LOCAL 本地通信
AF_INET ipv4网络通信
AF_PACKET 底层通信
type:类型
SOCK_STREAM 流式套接字 TCP
SOCK_DGRAM 数据报套接字 UDP
SOCK_RAW 底层通信
protocol:协议,一般为0
返回值:
成功:文件描述符
失败:-1
例子:
int sockfd;
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("fail to socket");
//return -1;
exit(1);
}
【4】bind( )
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将套接字与网络信息结构体绑定
参数:
sockfd:文件描述符,socket的返回值
addr:网络信息结构体
通用的(一般不用)
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
一般使用sockaddr_in
#include <netinet/in.h>
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
==>
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
==>
在函数宏里面,##代表字符串的拼接
sa_family_t sin_family; //协议族 AF_INET
in_port_t sin_port; //端口号
struct in_addr sin_addr;
==>
struct in_addr
{
in_addr_t s_addr; //ip地址
};
//这个没有用。为了使得sockaddr_in与sockaddr大小一致
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
addrlen:addr的大小
返回值:
成功:0
失败:-1
例子:
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr("192.168.2.222");
serveraddr.sin_port = htons(9999);
if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0)
{
perror("fail to bind");
exit(1);
}
【5】listen( )
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:将套接字设置为监听模式
参数:
sockfd:文件描述符,socket的返回值
backlog:同时响应客户端的连接的个数,一般设置为5,10
返回值:
成功:0
失败:-1
例子:
if(listen(sockfd, 10) < 0)
{
perror("fail to listen");
exit(1);
}
【6】accept
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:阻塞等待文件描述符准备就绪
参数:
sockfd:文件描述符,socket的返回值
addr:网络信息结构体(自动填充的客户端的网络信息结构体)
addrlen:addr的大小
返回值:
成功:新的文件描述符(用于通信)
失败:-1
例子:
int acceptfd;
struct sockaddr_in clientaddr;
socklen_t addrlen = sizeof(clientaddr);
if((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) <0 )
{
perror("fail to accept");
exit(1);
}
【7】connect
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:发送客户端的连接请求
参数:
sockfd:文件描述符,socket的返回值
addr:网络信息结构体(自己填充的服务器的网络信息结构体)
addrlen:addr的大小
返回值:
成功:0
失败:-1
例子:
if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("fail to connect");
exit(1);
}
【8】send( )
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
sockfd:文件描述符
服务器:accept的返回值
客户端:socket的返回值
buf:发送的数据
len:buf的长度
flags:标志位
0 阻塞
MSG_DONTWAIT 非阻塞
返回值:
成功:发送的数据的长度
失败:-1
【9】recv( )
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:接收数据
参数:
sockfd:文件描述符
服务器:accept的返回值
客户端:socket的返回值
buf:接收的数据
len:buf的长度
flags:标志位
0 阻塞
MSG_DONTWAIT 非阻塞
返回值:
成功:接收的数据的长度
0 发送端异常退出或者关闭文件描述符
失败:-1
****************UDP网络编程*******************
【1】流程
服务器:
创建套接字 socket( )
填充服务器的网络信息结构体 sockaddr_in
将套接字与服务器的网络信息结构体绑定 bind( )
进行通信 recvfrom( )/sendto( )
客户端:
创建套接字 socket( )
填充服务器的网络信息结构体 sockaddr_in
进行通信 sendto( )/recvfrom( )
【2】sendto( )
#include <sys/socket.h>
ssize_t sendto(int socket, const void *message, size_t length,
int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
功能:发送数据
参数:
socket:文件描述符,socket的返回值
message:发送的数据
length:数据的长度
flags:标志位,一般为0
dest_addr:目的地址(给谁发送数据)
dest_len:addr的大小
返回值:
成功:发送的数据的长度
失败:-1
【3】recvfrom( )
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:
sockfd:文件描述符
buf:接收的数据
len:数据的长度
flags:标志位,一般为0
src_addr:源的地址(接收谁的信息)
addrlen:addr的长度
返回值:
成功:接收的数据的长度
失败:-1
作业:基于tcp 的文件服务器
功能:
客户端可以查看服务器所在目录的文件 opendir readdir
客户端可以下载服务器所在目录的文件
客户端可以上传文件到服务器
******************day_2*******************
***********IO模型***************
【1】定义
在UNIX/Linux下主要有4种I/O 模型:
阻塞I/O:
最常用、最简单、效率最低
非阻塞I/O:
可防止进程阻塞在I/O操作上,需要轮询
I/O 多路复用:
允许同时对多个I/O进行控制
信号驱动I/O:
一种异步通信模型
【2】阻塞I/O 模式
阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。
缺省情况下,套接字建立后所处于的模式就是阻塞I/O 模式
前面学习的很多读写函数在调用过程中会发生阻塞。
读操作中的read、recv、recvfrom
写操作中的write、send
其他操作:accept、connect
【3】非阻塞模式I/O
当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,
不要这么做,请马上返回一个错误给我。”
当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。
应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。
这种模式使用中不普遍。
使用fcntl函数实现非阻塞IO
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:操作一个文件描述符
参数:
fd:文件描述符
cmd:具体的命令或者选项
F_GETFL 获取文件状态标志位
F_SETFL 设置文件状态标志位
O_NONBLOCK 非阻塞
arg:可变参,根据cmd的后面括号决定,如果是void则不使用,如果为long,则需要
返回值:
成功:
F_GETFL 获取到的文件状态标志位
F_SETFL 0
失败:-1
注意:对寄存器或者位的操作,一般执行读、改、写三步
第一步:获取标志位
int flags;
if((flags = fcntl(0, F_GETFL)) < 0)
{
perror("fail to fcntl");
exit(1);
}
第二步:修改标志位
//flags = flags | O_NONBLOCK;
flags |= O_NONBLOCK;
第三步:将新的标志位写回去
if(fcntl(0, F_SETFL, flags) < 0)
{
perror("fail to fcntl");
exit(1);
}
【4】多路复用I/O
应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;
比较好的方法是使用I/O多路复用。其基本思想是:
先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。
使用select实现IO多路复用
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:允许与一个程序可以操作多个文件描述符,阻塞等待文件描述符准备就绪,
如果有一个或者多个文件描述符准备就绪,函数立即返回,并执行相应的IO操作
参数:
nfds:最大的文件描述符加1
readfds:读文件描述符集合
writefds:写文件描述符集合
exceptfds:其他或者异常的文件描述符集合
timeout:超时
NULL 阻塞
返回值:
成功:准备就绪的文件描述符的个数
失败:-1
void FD_ZERO(fd_set *set);
清空一个集合
void FD_SET(int fd, fd_set *set);
将文件描述符fd添加到集合set里面
void FD_CLR(int fd, fd_set *set);
将文件描述符fd从集合set里面移除
int FD_ISSET(int fd, fd_set *set);
判断文件描述符fd是否在集合set里面
***************服务器模型*********************
【1】定义
在网络程序里面,通常都是一个服务器处理多个客户机。
为了处理多个客户机的请求, 服务器端的程序有不同的处理方式。
目前最常用的服务器模型.
循环服务器:
循环服务器在同一个时刻只能响应一个客户端的请求
TCP循环服务器
UDP循环服务器
并发服务器:
并发服务器在同一个时刻可以响应多个客户端的请求
TCP并发服务器
UDP并发服务器
【2】如何实现tcp并发服务器
方法1:使用父子进程实现tcp并发服务器
socket()
sockaddr_in
bind()
listen()
//如何处理僵尸进程
while(1)
{
accept()
pid = fork;
if(pid > 0) //父进程负责连接
{
}
else if(pid == 0) //子进程负责通信
{
while(1)
{
recv()/send()
}
}
}
方法2:使用select函数实现tcp并发服务器
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:允许与一个程序可以操作多个文件描述符,阻塞等待文件描述符准备就绪,
如果有一个或者多个文件描述符准备就绪,函数立即返回,并执行相应的IO操作
参数:
nfds:最大的文件描述符加1
readfds:读文件描述符集合
writefds:写文件描述符集合
exceptfds:其他或者异常的文件描述符集合
timeout:超时
NULL 阻塞
返回值:
成功:准备就绪的文件描述符的个数
失败:-1
void FD_ZERO(fd_set *set);
清空一个集合
void FD_SET(int fd, fd_set *set);
将文件描述符fd添加到集合set里面
void FD_CLR(int fd, fd_set *set);
将文件描述符fd从集合set里面移除
int FD_ISSET(int fd, fd_set *set);
判断文件描述符fd是否在集合set里面
推荐书籍:
tcp/ip 详解 卷一 卷二 卷三
UNIX环境高级编程
UNIX网络编程 卷一 卷二
unix/linux系统编程手册
作业:
使用poll实现io多路复用,fgets、accept
基于udp的网络聊天室
*********************day_3********************
【1】网络信息检索函数
getsockopt( ) 获取一个套接口选项
#include <sys/socket.h>
int getsockopt(int socket, int level, int option_name,
void *restrict option_value, socklen_t *restrict option_len);
功能:获取一个套接字的选项
参数:
socket:文件描述符
level:层次
SOL_SOCKET 套接字层次
IPPROTO_IP IP层次
IPPROTO_TCP TCP层次
option_name:选项的名称(SOL_SOCKET)
SO_BROADCAST 是否允许发送广播信息
SO_REUSEADDR 是否允许重复使用本地地址
SO_SNDBUF 获取发送缓冲区的大小
SO_RCVBUF 获取接收缓冲区的大小
SO_RCVTIMEO 设置接收超时时间
SO_SNDTIMEO 设置发送超时时间
option_value:获取到的值
option_len:option_value的大小
返回值:
成功:0
失败:-1
**************网络超时检测***************
【1】必要性
在网络通信中,很多操作会使得进程阻塞
TCP套接字中的recv/accept/connect
UDP套接字中的recvfrom
超时检测的必要性
避免进程在没有数据时无限制地阻塞
当设定的时间到时,进程从原操作返回继续运行
【2】本质
阻塞函数如果没有数据到来时会一直等待
非阻塞即使没有数据到来,函数也会立即返回
网络超时检测的本质是设定一定的时间,在时间到达之前如果没有数据到来,
则一直阻塞等待,如果时间到达是还没有数据到来,则函数立即返回
【3】方法1:使用setsockopt设置网络超时检测
#include <sys/socket.h>
int setsockopt(int socket, int level, int option_name,
const void *option_value, socklen_t option_len);
功能:设置一个套接字的选项
参数:
socket:文件描述符
level:层次
SOL_SOCKET 套接字层次
option_name:选项的名称
SO_RCVTIMEO 设置接收超时时间
option_value:设置的值
struct timeval
{
__time_t tv_sec; 秒
__suseconds_t tv_usec; 微秒
};
option_len:option_value的大小
返回值:
成功:0
失败:-1
【4】方法2:使用select设置网络超时检测
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:允许与一个程序可以操作多个文件描述符,阻塞等待文件描述符准备就绪,
如果有一个或者多个文件描述符准备就绪,函数立即返回,并执行相应的IO操作
参数:
nfds:最大的文件描述符加1
readfds:读文件描述符集合
writefds:写文件描述符集合
exceptfds:其他或者异常的文件描述符集合
timeout:超时
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
>0 超时时间
0 立即返回
NULL 一直阻塞
返回值:
成功:准备就绪的文件描述符的个数
失败:-1
void FD_ZERO(fd_set *set);
清空一个集合
void FD_SET(int fd, fd_set *set);
将文件描述符fd添加到集合set里面
void FD_CLR(int fd, fd_set *set);
将文件描述符fd从集合set里面移除
int FD_ISSET(int fd, fd_set *set);
判断文件描述符fd是否在集合set里面
【5】使用alarm闹钟实现网络超时检测
如果直接使用alarm,当时间到达时,会退出整个进程
如果结合信号,当时间到达时,触发SIGALRM信号,执行信号处理函数
当信号处理函数执行完毕之后,会接着刚才的位置继续执行,这一属性
称之为自重启属性,如果想实现超时,需要关闭这一属性,
使用sigaction函数设置信号的行为
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
功能:设置一个信号的行为
参数:
signum:信号
act:新的行为
oldact:旧的行为
struct sigaction {
void (*sa_handler)(int); 信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *); 信号处理函数
sigset_t sa_mask; 掩码(有关阻塞)
int sa_flags; 标志位
SA_RESTART 自重启属性
void (*sa_restorer)(void); 没有用
};
返回值:
成功:0
失败:-1
注意:对寄存器或者位的操作,一般执行读、改、写三步
********************广播*************
【1】定义
前面介绍的数据包发送方式只有一个接受方,称为单播
如果同时发给局域网中的所有主机,称为广播
只有用户数据报(使用UDP协议)套接字才能广播
广播地址
以192.168.3.0 (255.255.255.0) 网段为例,最大的主机地址192.168.3.255代表该网段的广播地址
发到该地址的数据包被所有的主机接收
255.255.255.255在所有网段中都代表广播地址
【2】流程(基于udp)
发送者:
创建套接字 socket( )
填充广播信息结构体 sockaddr_in
设置为允许发送广播权限 setsockopt( )
接收数据 sendto( )
接收者:
创建套接字 socket( )
填充广播信息结构体 sockaddr_in
将套接字与广播信息结构体绑定 bind( )
接收数据 recvfrom( )
【3】设置为允许发送广播权限
#include <sys/socket.h>
int setsockopt(int socket, int level, int option_name,
const void *option_value, socklen_t option_len);
功能:设置一个套接字的选项
参数:
socket:文件描述符
level:层次
SOL_SOCKET 套接字层次
option_name:选项的名称
SO_BROADCAST 设置接收超时时间
option_value:设置的值
0 不允许
1 允许
option_len:option_value的大小
返回值:
成功:0
失败:-1
***************组播***************
【1】定义
单播方式只能发给一个接收方。
广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。
组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。
多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)
D类地址(组播地址)
不分网络地址和主机地址,第1字节的前4位固定为1110
224.0.0.1 – 239.255.255.255
【2】流程
发送者:
创建套接字 socket( )
填充组播信息结构体 sockaddr_in
发送数据 sendto( )
接收者:
创建套接字 socket( )
填充组播信息结构体 sockaddr_in
将套接字与组播信息结构体绑定 bind( )
设置为加入多播组 setsockopt( )
接收数据 recvfrom( )
【3】加入多播组
#include <sys/socket.h>
int setsockopt(int socket, int level, int option_name,
const void *option_value, socklen_t option_len);
功能:设置一个套接字的选项
参数:
socket:文件描述符
level:层次
IPPROTO_IP IP层次
option_name:选项的名称
IP_ADD_MEMBERSHIP 加入多播组
option_value:设置的值
struct ip_mreq {
struct in_addr imr_multiaddr; 组播地址
struct in_addr imr_interface; 本地地址
INADDR_ANY 任意主机地址
};
option_len:option_value的大小
返回值:
成功:0
失败:-1
例子:
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
{
perror("fail to setsockopt");
exit(1);
}
*************使用poll实现IO多路复用****************
实现fgets、accept两个阻塞函数
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:同select
参数:
fds:结构体数组
struct pollfd {
int fd; 文件描述符
short events; 请求的事件
POLLIN 普通或优先级带数据可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级数据可读
POLLOUT 普通数据可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件
short revents; 返回的事件
};
nfds:文件描述符的个数
timeout:超时
<0 永远等待
0 立即返回,不阻塞进程
>0 等待指定数目的毫秒数
返回值:
成功:准备就绪的文件描述符的个数
失败:-1
******************本地通信********************
【1】定义
socket同样可以用于本地通信
创建套接字时使用本地协议PF_UNIX(或PF_LOCAL)。
分为流式套接字和用户数据报套接字
和其他进程间通信方式相比使用方便、效率更高
常用于前后台进程通信
【2】本地信息结构体 sockaddr_un
#include <sys/un.h>
struct sockaddr_un
{
__SOCKADDR_COMMON (sun_);
==>
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
==>
sa_family_t sun_family; //地址族 AF_UNIX
char sun_path[108]; 路径名
};
【3】TCP本地通信
服务器:
创建套接字 socket( )
填充本地信息结构体 sockaddr_un
将套接字与本地信息结构体绑定 bind( )
将套接字设置为监听模式 listen( )
阻塞等待客户端的连接请求 accept( )
进行通信 recv( )/send( )
客户端:
创建套接字 socket( )
填充本地信息结构体 sockaddr_un
发送客户端的连接请求 connect( )
进行通信 send( )/recv( )