一、IO多路复用简介
输入:文件里面的数据输入到内存中
输出:内存中的数据输出到文件中
IO多路复用图解
二、select API介绍
三、select代码编写
select.c
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/select.h>
int main(){
//创建socket
int lfd = socket(PF_INET,SOCK_STREAM,0);
if(lfd == -1){
perror("socket");
return -1;
}
//绑定ip和端口
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(9999);
int ret = bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
if(ret == -1){
perror("bind");
return -1;
}
//监听
ret = listen(lfd,128);
if(ret == -1){
perror("listen");
return -1;
}
//创建一个fd_set的集合,存放的是需要检测的文件描述符
fd_set rdset,tmp;
FD_ZERO(&rdset);
FD_SET(lfd,&rdset);
//最大的文件描述符
int maxfd = lfd;
while(1){
tmp = rdset;
//调用select系统函数,让内核帮检测哪些文件描述符有数据
ret = select(maxfd+1,&tmp,NULL,NULL,NULL);
if(ret == -1){
perror("select");
return -1;
}else if(ret == 0){
// 检测的文件描述符里面没有哪个数据到达
//再循环进行检测
continue;
}else if(ret > 0){
//说明检测到了有文件描述符的对应的缓冲区的数据发生了改变
//FD_ISSET判断fd对应的标志位0还是1, 返回值:fd对应的标志位的值0或者1
if(FD_ISSET(lfd,&tmp)){
//表示有新的客户端连接进来了
struct sockaddr_in cliaddr;
int clilen = sizeof(cliaddr);
int cfd = accept(lfd,(struct sockaddr *)&cliaddr,&clilen);
//将新的文件描述符加入到集合中
FD_SET(cfd,&rdset);
//更新最大的文件描述符
maxfd = maxfd > cfd ? maxfd : cfd;
}
//看剩余的文件描述符是否发生变化
for(int i = lfd +1;i<= maxfd;++i){
if(FD_ISSET(i,&tmp)){
//说明这个文件描述符对应的客户端发来了数据
char buf[1024] = {0};
int rlen = read(i,&buf,sizeof(buf));
if(rlen == -1){
perror("read");
return -1;
}else if(rlen == 0){
printf("client closed....\n");
close(i);
//断开连接,将对应的文件描述符对应的标志位设为0
FD_CLR(i,&rdset);
}else if(rlen > 0){
printf("read buf : %s\n",buf);
//忘记了为啥要加1
write(i,buf,strlen(buf)+1);
}
}
}
}
}
close(lfd);
return 0;
}
client.c
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
// 创建socket
int fd = socket(PF_INET, SOCK_STREAM, 0);
if(fd == -1) {
perror("socket");
return -1;
}
struct sockaddr_in seraddr;
inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(9999);
// 连接服务器
int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if(ret == -1){
perror("connect");
return -1;
}
int num = 0;
while(1) {
char sendBuf[1024] = {0};
sprintf(sendBuf, "send data %d", num++);
write(fd, sendBuf, strlen(sendBuf) + 1);
// 接收
int len = read(fd, sendBuf, sizeof(sendBuf));
if(len == -1) {
perror("read");
return -1;
}else if(len > 0) {
printf("read buf = %s\n", sendBuf);
} else {
printf("服务器已经断开连接...\n");
break;
}
sleep(1);
//usleep(1000);
}
close(fd);
return 0;
}
四、poll API介绍及代码编写
poll.c
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<poll.h>
int main(){
//创建socket
int lfd = socket(PF_INET,SOCK_STREAM,0);
// if(lfd == -1){
// perror("socket");
// return -1;
// }
//绑定ip和端口
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = 0;
saddr.sin_port = htons(9999);
//int ret = bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
// if(ret == -1){
// perror("bind");
// return -1;
// }
//监听
//ret = listen(lfd,8);
listen(lfd,8);
// if(ret == -1){
// perror("listen");
// return -1;
// }
//初始化检测的文件描述符
struct pollfd fds[1024];
for(int i =0;i<1024;++i){
fds[i].fd = -1;
fds[i].events = POLLIN;
}
fds[0].fd = lfd;
int nfds = 0;
while(1){
//调用poll系统函数,让内核帮检测哪些文件描述符有数据
int ret = poll(fds,nfds+1,-1);
if(ret == -1){
perror("poll");
return -1;
}else if(ret == 0){
//没有检测到有数据的文件描述符
continue;
}else if(ret > 0){
//检测到对应文件描述符的对应缓冲区有数据
if(fds[0].revents & POLLIN){
//有新的客户端连接进来了
struct sockaddr_in cliaddr;
int clilen = sizeof(cliaddr);
int cfd = accept(lfd,(struct sockaddr *)&cliaddr,&clilen);
//将新的文件描述符加入到集合中
for(int i = 1; i<1024;i++){
if(fds[i].fd == -1){
fds[i].fd = cfd;
fds[i].events = POLLIN;
break;
}
}
//加入到集合中之后,更新最大的文件描述符的索引
nfds = nfds > cfd ? nfds:cfd;
}//if
//查看连接的客户端有没有发来数据
//fds[0] 是监听文件描述符 fds[1]及以后的才是客户端对应的描述符
for(int i =1; i<=nfds; ++i){
if(fds[i].revents & POLLIN){
//说明这个文件描述符对应的客户端发来了数据
//接收,然后再发送回去(回射服务器)
char buf[1024]={0};
int rlen = read(fds[i].fd,buf,sizeof(buf));
if(rlen == -1){
perror("read");
return -1;
}else if(rlen == 0){
printf("client closed.....\n");
close(fds[i].fd);
fds[i].fd = -1;
}else if(rlen > 0){
//接收到了数据
printf("resv client data : %s\n",buf);
//再把接收到的数据写回去
write(fds[i].fd,buf,strlen(buf)+1);
}
}
}
}//else if(ret > 0)
}//while(1)
close(lfd);
return 0;
}
client.c
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
// 创建socket
int fd = socket(PF_INET, SOCK_STREAM, 0);
if(fd == -1) {
perror("socket");
return -1;
}
struct sockaddr_in seraddr;
inet_pton(AF_INET, "192.168.49.129", &seraddr.sin_addr.s_addr);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(9999);
// 连接服务器
int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if(ret == -1){
perror("connect");
return -1;
}
int num = 0;
while(1) {
char sendBuf[1024] = {0};
sprintf(sendBuf, "send data %d", num++);
write(fd, sendBuf, strlen(sendBuf) + 1);
// 接收
int len = read(fd, sendBuf, sizeof(sendBuf));
if(len == -1) {
perror("read");
return -1;
}else if(len > 0) {
printf("read buf = %s\n", sendBuf);
} else {
printf("服务器已经断开连接...\n");
break;
}
sleep(1);
//usleep(1000);
}
close(fd);
return 0;
}
五、epoll API介绍
六、epoll代码编写
epoll.c (client.c略)
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/select.h>
#include<sys/epoll.h>
int main(){
//创建socket
int lfd = socket(PF_INET,SOCK_STREAM,0);
//绑定ip和端口
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(9999);
bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
//监听
listen(lfd,8);
//调用epoll_create创建一个epoll实例
int epfd = epoll_create(100);
//将监听的文件描述符相关的监测信息添加到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
//接收检测后的数据
struct epoll_event epevs[1024];
while(1){
int ret = epoll_wait(epfd,epevs,1024,-1);
if(ret == -1){
perror("epoll_wait");
return -1;
}
printf("ret = %d\n",ret);
//循环遍历数组epevs,epevs里面有多少个值,通过ret可以知道,ret是发生变化的文件描述符的个数
for(int i =0; i<ret; i++){
if(epevs[i].data.fd == lfd){
//监听的文件描述符有数据到达,有客户端连接
struct sockaddr_in cliaddr;
int clilen = sizeof(cliaddr);
int cfd = accept(lfd,(struct sockaddr *)&cliaddr,&clilen);
//重用之前的epev
epev.events = EPOLLIN;
epev.data.fd = cfd;
//将监听的文件描述符相关的监测信息添加到epoll实例中
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
}else{
//有数据到达,需要通信
//上一个是监听描述符,如果不是那就是连接的描述符
char buf[1024]={0};
int rlen = read(epevs[i].data.fd,buf,sizeof(buf));
if(rlen == -1){
perror("read");
return -1;
}else if(rlen == 0){
printf("client closed.....\n");
epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);
close(epevs[i].data.fd);
//fds[i].fd = -1;
}else if(rlen > 0){
//接收到了数据
printf("resv client data : %s\n",buf);
//再把接收到的数据写回去
write(epevs[i].data.fd,buf,strlen(buf)+1);
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
七、epoll的两种工作模式
epoll默认触发方式为水平触发
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
int main() {
// 创建socket
int lfd = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
// 绑定
bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
// 监听
listen(lfd, 8);
// 调用epoll_create()创建一个epoll实例
int epfd = epoll_create(100);
// 将监听的文件描述符相关的检测信息添加到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
struct epoll_event epevs[1024];
while(1) {
int ret = epoll_wait(epfd, epevs, 1024, -1);
if(ret == -1) {
perror("epoll_wait");
exit(-1);
}
printf("ret = %d\n", ret);
for(int i = 0; i < ret; i++) {
int curfd = epevs[i].data.fd;
if(curfd == lfd) {
// 监听的文件描述符有数据达到,有客户端连接
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
// 设置cfd属性非阻塞
int flag = fcntl(cfd, F_GETFL);
flag | O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
epev.events = EPOLLIN | EPOLLET; // 设置边沿触发
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
} else {
if(epevs[i].events & EPOLLOUT) {
continue;
}
// 循环读取出所有数据
char buf[5];
int len = 0;
while( (len = read(curfd, buf, sizeof(buf))) > 0) {
// 打印数据
// printf("recv data : %s\n", buf);
write(STDOUT_FILENO, buf, len);
write(curfd, buf, len);
}
if(len == 0) {
printf("client closed....");
}else if(len == -1) {
if(errno == EAGAIN) {
printf("data over.....");
}else {
perror("read");
exit(-1);
}
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
八、UDP通信实现
UDP不需要使用多线程多进程就可以实现多个客户端与服务端实现通信
udp_server.c
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
int main(){
// 1、创建一个通信的socket
int lfd = socket(PF_INET,SOCK_DGRAM,0);
if(lfd == -1){
perror("socket");
return -1;
}
// 2、绑定ip和端口
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(9999);
int ret = bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
if(ret == -1){
perror("bind");
return -1;
}
//3、通信
while(1){
//接收数据
char recvbuf[128] = {0};
char ipbuf[16] = {0};
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
int num = recvfrom(lfd,recvbuf,sizeof(recvbuf),0,(struct sockaddr *)&cliaddr,&len);
//默认已经接收到了数据,就不验证了
printf("client ip : %s, port : %d",
inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ipbuf,sizeof(ipbuf)),
ntohs(cliaddr.sin_port));
printf("client say : %s\n",recvbuf);
//发送数据 +1表示把字符结束符也发送回去
sendto(lfd,recvbuf,sizeof(recvbuf)+1,0,(struct sockaddr *)&cliaddr,sizeof(cliaddr));
}
close(lfd);
return 0;
}
udp_client.c
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
int main(){
// 1、创建一个通信的socket
int lfd = socket(PF_INET,SOCK_DGRAM,0);
if(lfd == -1){
perror("socket");
return -1;
}
//服务器的地址信息
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
//saddr.sin_addr.s_addr = htonl("192.168.49.129");
inet_pton(AF_INET,"192.168.49.129",&saddr.sin_addr.s_addr);
saddr.sin_port = htons(9999);
int num = 0;
//3、通信
while(1){
char sendBuf[128];
sprintf(sendBuf,"hello, i am client %d \n",num++);
//发送数据 +1表示把字符结束符也发送回去
sendto(lfd,sendBuf,sizeof(sendBuf)+1,0,(struct sockaddr *)&saddr,sizeof(saddr));
//接收数据
int num = recvfrom(lfd,sendBuf,sizeof(sendBuf),0,NULL,NULL);
//默认已经接收到了数据,就不验证了
printf("server say : %s\n",sendBuf);
}
close(lfd);
return 0;
}
九、广播
bro_server.c
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
int main(){
// 1、创建一个通信的socket
int lfd = socket(PF_INET,SOCK_DGRAM,0);
if(lfd == -1){
perror("socket");
return -1;
}
//2、设置广播属性
int op = 1;
setsockopt(lfd,SOL_SOCKET,SO_BROADCAST,&op,sizeof(op));
// 3、创建一个广播的地址
struct sockaddr_in cliaddr;
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(9999);
inet_pton(AF_INET,"192.168.49.255",&cliaddr.sin_addr.s_addr);
//3、通信
int num = 0;
while(1){
char sendBuf[128];
sprintf(sendBuf,"hello , client .....%d\n",num++);
//发送数据 +1表示把字符结束符也发送回去
sendto(lfd,sendBuf,sizeof(sendBuf)+1,0,(struct sockaddr *)&cliaddr,sizeof(cliaddr));
printf("广播的数据 : %s\n",sendBuf);
sleep(1);
}
close(lfd);
return 0;
}
bro_client.c
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
int main(){
// 1、创建一个通信的socket
int lfd = socket(PF_INET,SOCK_DGRAM,0);
if(lfd == -1){
perror("socket");
return -1;
}
//2、客户端绑定本地的ip和端口
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
//inet_pton(AF_INET,"192.168.49.129",&saddr.sin_addr.s_addr);
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(9999);
int ret = bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
if(ret == -1){
perror("bind");
return -1;
}
//3、通信
while(1){
char resvBuf[128];
//接收数据
int num = recvfrom(lfd,resvBuf,sizeof(resvBuf),0,NULL,NULL);
//默认已经接收到了数据,就不验证了
printf("server say : %s\n",resvBuf);
}
close(lfd);
return 0;
}
十、组播
multi_server.c
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
int main(){
// 1、创建一个通信的socket
int lfd = socket(PF_INET,SOCK_DGRAM,0);
if(lfd == -1){
perror("socket");
return -1;
}
//2、设置多播属性,server端设置多出接口
struct in_addr imr_multiaddr;
//初始化多播地址
inet_pton(AF_INET,"239.0.0.10",&imr_multiaddr.s_addr);
setsockopt(lfd,IPPROTO_IP,IP_MULTICAST_IF,&imr_multiaddr,sizeof(imr_multiaddr));
// 3、初始化客户端的地址信息
struct sockaddr_in cliaddr;
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(9999);
inet_pton(AF_INET,"239.0.0.10",&cliaddr.sin_addr.s_addr);
//3、通信
int num = 0;
while(1){
char sendBuf[128];
sprintf(sendBuf,"hello , client .....%d\n",num++);
//发送数据 +1表示把字符结束符也发送回去
sendto(lfd,sendBuf,sizeof(sendBuf)+1,0,(struct sockaddr *)&cliaddr,sizeof(cliaddr));
printf("组播的数据 : %s\n",sendBuf);
sleep(1);
}
close(lfd);
return 0;
}
multi_client.c
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
int main(){
// 1、创建一个通信的socket
int lfd = socket(PF_INET,SOCK_DGRAM,0);
if(lfd == -1){
perror("socket");
return -1;
}
//2、客户端绑定本地的ip和端口
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
//inet_pton(AF_INET,"192.168.49.129",&saddr.sin_addr.s_addr);
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(9999);
int ret = bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
if(ret == -1){
perror("bind");
return -1;
}
struct ip_mreq op;
inet_pton(AF_INET,"239.0.0.10",&op.imr_multiaddr.s_addr);
op.imr_interface.s_addr = INADDR_ANY;
//加入到多播组
setsockopt(lfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&op,sizeof(op));
//3、通信
while(1){
char resvBuf[128];
//接收数据
int num = recvfrom(lfd,resvBuf,sizeof(resvBuf),0,NULL,NULL);
//默认已经接收到了数据,就不验证了
printf("server say : %s\n",resvBuf);
}
close(lfd);
return 0;
}
十一、本地套接字通信
ipc_server.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/un.h>
#include<string.h>
int main(){
//运行客户端会生成一个server.sock文件,结束,
//再重新运行会产生bind:address already in use错误,
//将server.sock文件删除就能成功运行(会再生成一个server.sock文件)
//而unlink就可以删除这个文件,
//每次运行都会检测有没有这个文件,如果有就删除
unlink("server.sock");
//1、创建套接字
int lfd = socket(AF_LOCAL,SOCK_STREAM,0);
if(lfd == -1){
perror("socket");
return -1;
}
//绑定本地套接字文件
struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
//sprintf(&addr.sun_path,"server.sock");
strcpy(addr.sun_path,"server.sock");
int ret = bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
if(ret == -1){
perror("bind");
return -1;
}
//3、进行监听
ret = listen(lfd,100);
if(ret == -1){
perror("listen");
return 0;
}
//4、接受连接
struct sockaddr_un cliaddr;
int clilen = sizeof(cliaddr);
int cfd =accept(lfd,(struct sockaddr *)&cliaddr,&clilen);
if(cfd == -1){
perror("accept");
return -1;
}
printf("client socket filename : %s\n",cliaddr.sun_path);
//5、通信
while(1){
char buf[1024];
int len = recv(cfd,buf,sizeof(buf),0);
if(len == -1){
perror("recv");
return -1;
}else if(len == 0){
printf("client closed......");
break;
}else if(len > 0){
//接收到了数据
printf("client say : %s \n",buf);
send(cfd,buf,len,0);
}
}
close(cfd);
close(lfd);
return 0;
}
ipc_client
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/un.h>
#include<string.h>
int main(){
//运行客户端会生成一个client.sock文件,结束,
//再重新运行会产生bind:address already in use错误,
//将client.sock文件删除就能成功运行(会再生成一个client.sock文件)
//而unlink就可以删除这个文件
unlink("client.sock");
//1、创建套接字
int cfd = socket(AF_LOCAL,SOCK_STREAM,0);
if(cfd == -1){
perror("socket");
return -1;
}
//绑定本地套接字文件
struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
//sprintf(&addr.sun_path,"server.sock");
strcpy(addr.sun_path,"client.sock");
int ret = bind(cfd,(struct sockaddr *)&addr,sizeof(addr));
if(ret == -1){
perror("bind");
return -1;
}
//3、连接服务端
struct sockaddr_un saddr;
saddr.sun_family = AF_LOCAL;
strcpy(saddr.sun_path,"server.sock");
ret = connect(cfd,(struct sockaddr *)&saddr,sizeof(saddr));
if(ret == -1){
perror("connect");
return -1;
}
//4、通信
int num = 0;
while(1){
//发送数据
char buf[1024];
sprintf(buf,"hello , i am client %d\n",num++);
send(cfd,buf,strlen(buf)+1,0);
printf("client say : %s \n",buf);
//接收数据
int len = recv(cfd,buf,sizeof(buf),0);
if(len == -1){
perror("recv");
return -1;
}else if(len == 0){
printf("server closed......");
break;
}else if(len > 0){
//接收到了数据
printf("server say : %s \n",buf);
}
sleep(1);
}
close(cfd);
return 0;
}