I/O多路复用的功能:使得程序能同时监听多个文件描述符,能够提高程序的性能.
select
操作步骤:
- 首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中。
- 调用一个系统函数,监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I/O
操作时,该函数才返回。- 这个函数是阻塞
- 函数对文件描述符的检测的操作是由内核完成的
- 在返回时,它会告诉进程有多少(哪些)描述符要进行I/O操作。
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- 参数
- nfds: 委托内核检测的最大文件描述符的值+1
- readfds: 要检测读的文件描述符表,一般是检测是否有新数据到达(读缓冲区),传入传出参数
- writefds: 要检测写的文件描述符表,其实就是让内核去检测描述符表是否还可以继续写数据(未满就可以写)
- exceptfds: 检测异常的文件符表
- timeout: 设置超时时间
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
- NULL:一直阻塞直至有改变
- 均为0则不阻塞
- 总的大于0则阻塞对应的时间
- 返回值:-1失败,大于0则表示发生变化的文件描述符表的数量
// 将参数文件描述符fd对应的标志位设置为0
void FD_CLR(int fd, fd_set *set);
// 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1
int FD_ISSET(int fd, fd_set *set);
// 将参数文件描述符fd 对应的标志位,设置为1
void FD_SET(int fd, fd_set *set);
// fd_set一共有1024 bit, 全部初始化为0
void FD_ZERO(fd_set *set);
- poll
#include <poll.h>
struct pollfd {
int fd; /* 委托内核检测的文件描述符 */
short events; /* 委托内核检测文件描述符的什么事件 */
short revents; /* 文件描述符实际发生的事件 */
};
struct pollfd myfd;
myfd.fd = 5;
myfd.events = POLLIN | POLLOUT;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- 参数:
- fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合
- nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1
- timeout : 阻塞时长,int类型,单位为毫秒
0 : 不阻塞
-1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞
>0 : 阻塞的时长
- 返回值:
-1 : 失败
>0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化
- epoll
#include <sys/epoll.h>
// 创建epoll实例,其中包含文件描述符的信息(存储在红黑树中)和就绪列表,存放检测到数据改变的文件描述符(用双向列表存储)
int epoll_create(int size);
- 参数:size无意义,大于0即可
- 返回值,-1则失败,>0则为文件描述符
// 对epoll实例进行管理:添加文件描述符信息,删除信息,修改信息
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 参数
- epfd:epoll实例对应的文件描述符
- op:
- EPOLL_CTL_ADD: 添加
- EPOLL_CTL_MOD:修改
- EPOLL_CTL_DEL:删除
- fd: 要检测的文件描述符
- event: 要检测文件描述符的操作(读写),EPOLLIN,EPOLLOUT,EPOLLERR
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
- 参数:
- epfd: epoll实例的文件描述符
- events: 传出参数,保存发生变化的文件描述符信息
- maxenvents: 上一个参数的数组大小
- timeout: 0不阻塞,-1阻塞到有改变,>0阻塞时间(毫秒)
- 返回值:失败返回-1,成功返回发生变化的描述符个数
- UDP
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
- 参数:
- sockfd : 通信的fd
- buf : 要发送的数据
- len : 发送数据的长度
- flags : 0
- dest_addr : 通信的另外一端的地址信息
- addrlen : 地址的内存大小
- 返回值:成功返回发送的数据的字节数,失败返回-1
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
- 参数:
- sockfd : 通信的fd
- buf : 接收数据的数组
- len : 数组的大小
- flags : 0
- src_addr : 用来保存另外一端的地址信息,不需要可以指定为NULL
- addrlen : 地址的内存大小
- 广播
服务器向子网中的所有计算机发送消息,客户端只要绑定了服务器广播所使用的端口,那么就可以收到广播消息,但是广播只能在局域网中使用。具体的实现方法是服务端创建一个socket,然后通过setsockopt()
来使能广播,然后服务端向广播地址发送数据;客户端创建一个socket,然后绑定本地端口及IP地址,便可接收到来自服务端的数据。
// 设置广播属性的函数
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
- sockfd : 文件描述符
- level : SOL_SOCKET
- optname : SO_BROADCAST
- optval : int类型的值,为1表示允许广播
- optlen : optval的大小
- 组播
单播地址标识单个 IP 接口,广播地址标识某个子网的所有 IP 接口,多播地址标识一组 IP 接口。多播(组播)只会向加入了多播组的接口发送数据,可用于局域网和广域网。
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
- 参数
// 服务器设置多播
- level: IPPROTO_IP
- optname: IP_MULTICAST_IF
- optval: struct in_addr
// 客户端加入多播组
- level: IPPROTO_IP
- optname:IP_ADD_MEMBERSHIP
- optval: struct ip_mreq类型指针
struct ip_mreq
{
/* IP multicast address of group. */
struct in_addr imr_multiaddr; // 组播的IP地址
/* Local IP address of interface. */
struct in_addr imr_interface; // 本地的IP地址
};
- 本地socket
本地socket可以用于进程间通信,且一般使用TCP通信流程。
// 服务器端
1. 创建监听的套接字
int lfd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
2. 监听的套接字绑定本地的套接字文件 -> server端
struct sockaddr_un addr;
// 绑定成功之后,指定的sun_path中的套接字文件会自动生成。
bind(lfd, addr, len);
3. 监听
listen(lfd, 100);
4. 等待并接受连接请求
struct sockaddr_un cliaddr;
int cfd = accept(lfd, &cliaddr, len);
5. 通信
接收数据:read/recv
发送数据:write/send
6. 关闭连接
close();
// 客户端的流程
1. 创建通信的套接字
int fd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
2. 监听的套接字绑定本地的IP 端口
struct sockaddr_un addr;
// 绑定成功之后,指定的sun_path中的套接字文件会自动生成。
bind(lfd, addr, len);
3. 连接服务器
struct sockaddr_un serveraddr;
connect(fd, &serveraddr, sizeof(serveraddr));
4. 通信
接收数据:read/recv
发送数据:write/send
5. 关闭连接
close();
// 头文件: sys/un.h
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; // 地址族协议 af_local
char sun_path[UNIX_PATH_MAX]; // 套接字文件的路径, 这是一个伪文件, 大小永远=0
};