本文主要是介绍网络编程(学习)2024.8.30,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
IO多路复用 select、poll、epoll
IO多路复用机制
一.select
1.函数
2.流程
3.案例使用select创建全双工客户端
4.并发服务器
5.案例使用select创建全双工服务端
二.poll
1.函数
2.流程
3.案例使用poll创建全双工客户端
4.案例使用poll创建全双工服务端
三、epoll
1.流程
2.案例使用epoll创建全双工服务端
select,poll和epoll的特点:
1.select特点
2.poll特点
3.epoll特点
IO多路复用 select、poll、epoll
案例分析:键盘鼠标事件
同时对键盘和鼠标进行监听,当敲击键盘按下回车,就打印键盘输入的东西,动鼠标就要打印鼠标写入的内容。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>#define N 64
int main(int argc, char const *argv[])
{int mouse = open("/dev/input/mouse0", O_RDONLY);if (mouse < 0){perror("open失败");return -1;}char buf[N];while (1){memset(buf, 0, N);gets(buf);printf("buf:%s\n", buf);int ret = read(mouse, buf, N);if (ret < 0){perror("read失败");return -1;}else{printf("mouse:%s\n", buf);}}
}
IO多路复用机制
使用I/O多路复用技术。其基本思想是:
1.先构造一张有关描述符的表,然后调用一个函数。
2.当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
3.函数返回时告诉进程哪个描述符已就绪,可以进行I/O操作。
一.select
1.函数
#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(超时时长));
一般写:select(int nfds,fd_set *readfds,NULL,NULL,NULL);
void FD_CLR(int fd, fd_set *set); //将某一文件描述符在表里去除
int FD_ISSET(int fd, fd_set *set); //判断某一文件描述符是否在表里
void FD_SET(int fd, fd_set *set); //将某一文件描述符放入表中
void FD_ZERO(fd_set *set); //将表置零
2.流程
第一步:建表初始化
第二步:填表
第三步:监听表
第四步:判断,操作
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>#define N 64
int main(int argc, char const *argv[])
{int mouse = open("/dev/input/mouse0", O_RDONLY);if (mouse < 0){perror("open失败");return -1;}char buf[N];// 第一步:建表初始化fd_set readfds, tempfds;FD_ZERO(&readfds);// 第二步:填表FD_SET(0, &readfds);FD_SET(mouse, &readfds);// 第三步:监听表while (1){memset(buf, 0, N);temphfds = readfds;int ret = select(4, &tempfds, NULL, NULL, NULL);// 第四步:判断,操作if (ret == -1){perror("select失败");return -1;}if (FD_ISSET(0, &tempfds)){gets(buf);printf("buf:%s\n", buf);}if (FD_ISSET(mouse, &tempfds)){int n = read(mouse, buf, N);if (n < 0){perror("read失败");return -1;}else{printf("mouse:%s\n", buf);}}}return 0;
}
3.案例使用select创建全双工客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>int main(int argc, char const *argv[])
{// 1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);printf("sorkfd:%d\n", sockfd);// 2.连接unsigned short post = 0;char ip[15];printf("请输入ip地址");scanf("%s", ip);getchar();printf("请输入端口号");scanf("%hd", &post);getchar();struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(post);saddr.sin_addr.s_addr = inet_addr(ip);socklen_t addrlen = sizeof(saddr);if (connect(sockfd, (struct sockaddr *)&saddr, addrlen) < 0){perror("connect失败\n");return -1;}// 3.接收
#define N 64char buf[N];while (1){// 第一步:建表初始化fd_set readfds, tempfds;FD_ZERO(&readfds);// 第二步:填表FD_SET(0, &readfds);FD_SET(sockfd, &readfds);// 第三步:监听表while (1){memset(buf, 0, N);tempfds = readfds;int ret = select(4, &tempfds, NULL, NULL, NULL);// 第四步:判断,操作if (ret == -1){perror("select失败");return -1;}if (FD_ISSET(0, &tempfds)){scanf("%s", buf);send(sockfd, buf, N, 0);}if (FD_ISSET(sockfd, &tempfds)){int ret = recv(sockfd, buf, N, 0);printf("服务端:%s\n", buf);}}}close(sockfd);return 0;
}
4.并发服务器
可以同时接收多个客户端的连接
5.案例使用select创建全双工服务端
sockfd只要创建并监听listen,就可以接收连接请求,即只要sockfd的读缓冲区可读,就可以创建连接。
客户端的连接请求会发给sockfd,如果可以建立连接的话,会在sockfd的缓冲区内保存
accept其实就是去sockfd的缓冲区里取连接
select创建并发服务器过程
创建并发服务器
创建连接 accept --> sockfd
发送消息 gets --> 0
接收 recv --> 所有已连接的acceptfd
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/select.h>#define N 64
char buf[N];
#define ERR_MSG(msg) \do \{ \fprintf(stderr, "line:%d ", __LINE__); \perror(msg); \} while (0)int main(int argc, char const *argv[])
{if (argc != 2){printf("用法:<port>\n");return -1;}// 1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);printf("sorkfd:%d\n", sockfd);// 2.bind绑定IP和Port端口号struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));// saddr.sin_addr.s_addr = inet_addr("192.168.50.213");socklen_t addrlen = sizeof(saddr);
#if 0saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
#elsesaddr.sin_addr.s_addr = INADDR_ANY;
#endifif (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0){ERR_MSG("bind失败");return -1;}printf("bind成功\n");// 3.监听listen将主动套接字变为被动套接字if (listen(sockfd, 7) < 0){ERR_MSG("lisren失败");return -1;}printf("listen成功\n");// 第一步:建表初始化fd_set readfds, tempfds;FD_ZERO(&readfds);FD_SET(sockfd, &readfds);FD_SET(0, &readfds);int max = sockfd;while (1){memset(buf, 0, N);tempfds = readfds;int ret = select(max + 1, &tempfds, NULL, NULL, NULL);if (ret == -1){perror("select失败");return -1;}if (FD_ISSET(sockfd, &tempfds)){// 4.accept阻塞等待链接int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);if (acceptfd < 0){ERR_MSG("accept失败\n");return -1;}printf("acceptfd:%d\n", acceptfd);printf("客户端ip:%s\t 端口号:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));FD_SET(acceptfd, &readfds);if (max < acceptfd){max = acceptfd;}}// 5.发送else if (FD_ISSET(0, &tempfds)){scanf("%s", buf);for (int i = 4; i <= max; i++){if (FD_ISSET(i, &readfds)){send(i, buf, N, 0);}}}// 6.接收for (int n = 4; n <= max; n++){if (FD_ISSET(n, &tempfds)){int ret = recv(n, buf, N, 0);if (ret < 0){perror("recv失败");return -1;}else if (ret > 0){printf("客户端:%s\n", buf);}else{printf("客户端acceptfd:%d退出\n", n);FD_CLR(n, &readfds);close(n);while (!FD_ISSET(max, &readfds)){max--;}}}}}close(sockfd);return 0;
}
二.poll
1.函数
#include <poll.h>
int poll(suct trpollfd *fds, nfds_t nfds, int timeout);
参数:
fds:创建的pollfd结构体类型的数组
nfds:数组的大小
timeout:超时检测的时间,一般不用的话设置为-1
返回值:
成功:0
失败:-1
struct pollfd {
int fd; //第一个成员变量 fd是向poll说明要监听哪个文件描述符
short events; //第二个成员变量 events 是向poll说明要对这个文件描述符的哪种事件进行监听,一般设置为POLLIN
short revents; //第三个成员变量 revents 是poll函数自动生成,当fd发生了events事件时,poll函数会将events(POLLIN)写入revents
};
2.流程
3.案例使用poll创建全双工客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>int main(int argc, char const *argv[])
{// 1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);printf("sorkfd:%d\n", sockfd);// 2.连接unsigned short post = 0;char ip[15];printf("请输入ip地址");scanf("%s", ip);getchar();printf("请输入端口号");scanf("%hd", &post);getchar();struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(post);saddr.sin_addr.s_addr = inet_addr(ip);socklen_t addrlen = sizeof(saddr);if (connect(sockfd, (struct sockaddr *)&saddr, addrlen) < 0){perror("connect失败\n");return -1;}// 3.接收
#define N 64char buf[N];while (1){// 第一步:建表初始化struct pollfd fds[2];// 第二步:填表fds[0].fd = 0;fds[0].events = POLLIN;fds[1].fd = sockfd;fds[1].events = POLLIN;// 第三步:监听表while (1){memset(buf, 0, N);poll(fds, 2, -1);// 第四步:判断,操作// if (ret == -1)// {// perror("select失败");// return -1;// }for (int i = 0; i <= 1; i++){if (fds[i].revents == POLLIN){if (i == 0){scanf("%s", buf);send(sockfd, buf, N, 0);}if (i == 1){int ret = recv(sockfd, buf, N, 0);if (ret < 0){perror("recv失败");return -1;}else{printf("服务端:%s\n", buf);}}}}}}close(sockfd);return 0;
}
4.案例使用poll创建全双工服务端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <poll.h>#define N 64
char buf[N];
#define ERR_MSG(msg) \do \{ \fprintf(stderr, "line:%d ", __LINE__); \perror(msg); \} while (0)int main(int argc, char const *argv[])
{if (argc != 2){printf("用法:<port>\n");return -1;}// 1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);printf("sorkfd:%d\n", sockfd);// 2.bind绑定IP和Port端口号struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = inet_addr("192.168.50.213");socklen_t addrlen = sizeof(saddr);// #if 0// saddr.sin_addr.s_addr = inet_addr("0.0.0.0");// #else// saddr.sin_addr.s_addr = INADDR_ANY;// #endifif (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0){ERR_MSG("bind失败");close(sockfd);return -1;}printf("bind成功\n");// 3.监听listen将主动套接字变为被动套接字if (listen(sockfd, 7) < 0){ERR_MSG("lisren失败");close(sockfd);return -1;}printf("listen成功\n");// 第一步:建表初始化struct pollfd fds[100];int last = -1;// 第二步:填表fds[++last].fd = 0;fds[last].events = POLLIN;fds[last].revents = 0;fds[++last].fd = sockfd;fds[last].events = POLLIN;fds[last].revents = 0;while (1){memset(buf, 0, N);int po = poll(fds, last + 1, -1);if (po == -1){perror("select失败");close(sockfd);return -1;}for (int i = 0; i <= last; i++){if (fds[i].revents == POLLIN){if (fds[i].fd == sockfd){// 4.accept阻塞等待链接int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &addrlen);if (acceptfd < 0){ERR_MSG("accept失败\n");return -1;}printf("acceptfd:%d\n", acceptfd);printf("客户端ip:%s\t 端口号:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));fds[++last].fd = acceptfd;fds[last].events = POLLIN;fds[last].revents = 0;}else if (fds[i].fd == 0){scanf("%s", buf);for (int j = 2; j <= last; j++){send(fds[j].fd, buf, N, 0);}}else{int ret = recv(fds[i].fd, buf, N, 0);if (ret < 0){perror("recv失败");close(sockfd);return -1;}else if (ret > 0){printf("客户端%s:%s\n", inet_ntoa(caddr.sin_addr), buf);}else{printf("客户端acceptfd:%d退出\n", fds[i].fd);close(fds[i].fd);fds[i] = fds[last];last--;}}}}}close(sockfd);return 0;
}
三、epoll
1.流程
2.案例使用epoll创建全双工服务端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/epoll.h>
int main(int argc, char const *argv[])
{if (argc != 2){printf("usage: <port>\n");return -1;}// 1.创建套接字-->tcp 流式套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);printf("sockfd:%d\n", sockfd);// 2.绑定IP和端口号// 填充通信结构体struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET; // 选定ipv4协议族saddr.sin_port = htons(atoi(argv[1])); // 绑定端口// saddr.sin_addr.s_addr = inet_addr(argv[1]); // 绑定ip地址
#if 1saddr.sin_addr.s_addr = INADDR_ANY; // 绑定ip地址
#elsesaddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 绑定ip地址
#endifsocklen_t len = sizeof(saddr);// 绑定if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0){perror("bind err");return -1;}printf("bind ok\n");// 3.启动监听 将主动套接字变成被动套接字if (listen(sockfd, 8) < 0){perror("listen err");return -1;}printf("listen ok\n");// 1.创建红黑树,拿到根节点---》建表int epfd = epoll_create(99);// 2.将关心的文件描述符挂载到树上---》填表struct epoll_event event;struct epoll_event events[10];event.data.fd = sockfd;event.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);// 3.去链表中拿事件char buf[128];while (1){int ret = epoll_wait(epfd, events, 10, -1);if (ret < 0){perror("errr");return -1;}else if (ret == 0){printf("nothing \n");}else{for (int i = 0; i < ret; i++){if (events[i].data.fd == sockfd){int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);if (acceptfd < 0){perror("accept err");return -1;}printf("%d,login\n", acceptfd);event.data.fd = acceptfd;event.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, acceptfd, &event);}else{int ret = recv(events[i].data.fd, buf, sizeof(buf), 0);if (ret < 0){perror("recv err");return -1;}else if (ret > 0){printf("%d:%s\n", events[i].data.fd, buf);}else{printf("%d exit\n", events[i].data.fd);close(events[i].data.fd);event.data.fd = events[i].data.fd;event.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event);}}}}}return 0;
}
select,poll和epoll的特点:
1.select特点
优点:可跨平台,Linux、macos、windows都可用
可以监听多个文件描述符
轻量级缺点:最大监听1024个文件描述符,最大监听1020个客户端连接
每次都要进行轮询,消耗CPU
每次都要拷贝一遍表
2.poll特点
优点:
1、优化了文件描述符的数量,监听的文件描述符数取决于数组的大小,数组大小受内存容量的限制。
2、不需要每次都拷贝一遍表
缺点:
1、需要轮询
2、只能用在UNIX原生系统下,不支持跨平台
3.epoll特点
优点:
1、超高并发,百万级并发
2、不需要轮询,因为有异步通知机制
3、不需要拷贝表
缺点:
只能跑在Linux
这篇关于网络编程(学习)2024.8.30的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!