在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9613861.html
好了,继上一篇说到多进程服务端也是有缺点的,每创建一个进程就代表大量的运算与内存空间占用,相互进程数据交换也很麻烦。 本章的I/O模型就是可以解决这个问题的其中一种模型。。。废话不多说进入主题--
I/O复用技术主要就是select函数的使用。
一.I/O复用预备知识--select()函数用法与作用
函数原型为:int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout);
接下来根据函数原型一点点的介绍一下select()函数。
(1),struct fd_set 这是一个集合,这个集合中存放的是文件描述符(在unix、linux系统中任何的设备、管道、FIFO等都可通过文件描述符的形式来访问)。当然一个socket也是一个文件描述符啦。相关的操作有: FD_ZERO(fd_set *)将某一个集合清空 FD_SET(int, fd_set *)将一个给定的文件描述符加入到集合之中 FD_CLR(int, fd_set *)从集合中删除指定的文件描述符。 FD_ISSET(int, fd_set *)检查集合中指定的文件描述符是否准备好(可读或可写) (2),struct timeval这是常用的一个结构体,用来表示时间值,有两个结构体成员:tv_sec表示秒数和tv_usec表示毫秒数。
接下来具体解释一下select的参数:
nfds:一个整数值,表示的是所要监视的文件描述符的范围。即你所要监听的文件描述符的最大值+1(因为select()函数进行遍历的时候是从0-文件描述符开始遍历的)。 readfds:是指向fd_set结构的指针,这个集合中加入我们所需要监视的文件可读操作的文件描述符。 writefds:指向fd_set结构的指针,这个集合中加入我们所需要监视的文件可写操作的文件描述符。 exceptfds:指向fd_set结构的指针,这个集合中加入我们所需要监视的文件错误异常的文件描述符。 timeout:指向timeval结构体的指针,通过传入的这个timeout参数来决定select()函数的三种执行方式: 1.传入的timeout为NULL,则表示将select()函数置为阻塞状态,直到我们所监视的文件描述符集合中某个文件描述符发生变化是,才会返回结果。 2.传入的timeout为0秒0毫秒,则表示将select()函数置为非阻塞状态,不管文件描述符是否发生变化均立刻返回继续执行。 3.传入的timeout为一个大于0的值,则表示这个值为select()函数的超时时间,在timeout时间内一直阻塞,超过时间即返回结果。
然后该说一说select()函数的返回值了: 返回-1:select()函数错误,并将所有描述符集合清0,具体的错误可以通过errno输出来查看(在windows下通过GetLastError获取相应的错误代码)。 返回0:表示select()函数超时。 返回正数:返回的正数值表示已经准备好的描述符数。 注意在每次select()函数调用以后,都需要将集合清空,因为状态已经改变,若需要重新监视就需要重新清空后在加入需要监视的文件描述符。
下面通过示例把select函数所有知识点进行整合,希望各位通过如下示例完全理解之前的内容。 linux下监控键盘数据: #include <sys/time.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <assert.h> int main () { int keyboard; int ret,i; char c; fd_set readfd; struct timeval timeout; keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK); assert(keyboard>0); while(1) { //设置select函数的超时 timeout.tv_sec=1; timeout.tv_usec=0; //初始化fd_set结构体变量 FD_ZERO(&readfd); FD_SET(keyboard,&readfd); ///监控函数 ret=select(keyboard+1,&readfd,NULL,NULL,&timeout); if(ret == -1) //错误情况 cout<<"error"<<endl ; else if(ret) //返回值大于0 有数据到来 if(FD_ISSET(keyboard,&readfd)) { i=read(keyboard,&c,1); if('\n'==c) continue; printf("hehethe input is %c\n",c); if ('q'==c) break; } else //超时情况 { cout<<"time out"<<endl; continue; } } }
好了大概对select函数有一定的认知了,下面通过select函数实现I/O复用服务端。
二.基于I/O复用的回声服务端
下面给出LINUX下基于I/O复用服务端实现代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/select.h> #define BUF_SIZE 100 void error_handling(char *message); int main(int argc, const char * argv[]) { int serv_sock, clnt_sock; struct sockaddr_in serv_adr, clnt_adr; struct timeval timeout; fd_set reads, cpy_reads; socklen_t adr_sz; int fd_max, str_len, fd_num; char buf[BUF_SIZE]; if (argc != 2) { printf("Usage: %s <port> \n", argv[0]); exit(1); } serv_sock = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if(bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1) error_handling("bind() error"); if(listen(serv_sock, 5) == -1) error_handling("listen() error"); FD_ZERO(&reads); //向要传到select函数第二个参数的fd_set变量reads注册服务器端套接字 FD_SET(serv_sock, &reads); fd_max = serv_sock; while (1) { cpy_reads = reads; timeout.tv_sec = 5; timeout.tv_usec = 5000; //监听服务端套接字和与客服端连接的服务端套接字的read事件 if ((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1) break; if(fd_num == 0) continue; if (FD_ISSET(serv_sock, &cpy_reads))//受理客服端连接请求 { adr_sz = sizeof(clnt_adr); clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz); FD_SET(clnt_sock, &reads); if(fd_max < clnt_sock) fd_max = clnt_sock; printf("connected client: %d \n", clnt_sock); } else//转发客服端数据 { str_len = read(clnt_sock, buf, BUF_SIZE); if (str_len == 0)//客服端发送的退出EOF { FD_CLR(clnt_sock, &reads); close(clnt_sock); printf("closed client: %d \n", clnt_sock); } else { //接收数据为字符串时执行回声服务 write(clnt_sock, buf, str_len); } } } close(serv_sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } 下面给出LINUX下基于I/O复用客户端实现代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 1024 void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, const char * argv[]) { int sock; char message[BUF_SIZE]; int str_len, recv_len, recv_cnt; struct sockaddr_in serv_adr; if(argc != 3) { printf("Usage: %s <IP> <port> \n", argv[0]); exit(1); } sock = socket(PF_INET, SOCK_STREAM, 0); if(sock == -1) error_handling("socket() error"); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = inet_addr(argv[1]); serv_adr.sin_port = htons(atoi(argv[2])); if (connect(sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1) error_handling("connect() error"); else puts("Connected ..............."); while (1) { fputs("Input message(Q to quit): ", stdout); fgets(message, BUF_SIZE, stdin); if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) break; str_len = write(sock, message, strlen(message)); /*这里需要循环读取,因为TCP没有数据边界,不循环读取可能出现一个字符串一次发送 但分多次读取而导致输出字符串不完整*/ recv_len = 0; while (recv_len < str_len) { recv_cnt = read(sock, &message[recv_len], BUF_SIZE - 1); if(recv_cnt == -1) error_handling("read() error"); recv_len += recv_cnt; } message[recv_len] = 0; printf("Message from server: %s", message); } close(sock); return 0; }
下面给出windows下I/O复用socket服务端代码: #include<iostream> #include<WinSock2.h> #pragma comment(lib,"ws2_32.lib") #define bufsize 1024 using namespace std; void main() { WSADATA wsadata; SOCKET serverSocket,clientSocket; int szClientAddr,fdnum,str_len; SOCKADDR_IN serverAddr, clientAddr; fd_set reads, cpyReads; TIMEVAL timeout; char message[bufsize] = "\0"; if(WSAStartup(MAKEWORD(2, 2), &wsadata)!=0) cout<<"WSAStartup() error"<<endl; serverSocket = socket(PF_INET, SOCK_STREAM, 0); if(serverSocket == INVALID_SOCKET) cout<<"socket() error"<<endl; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); serverAddr.sin_port = htons(9999); if (bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) cout << "bind () error" << endl; listen(serverSocket, 5); cout << "服务器启动成功!" << endl; FD_ZERO(&reads); //所有初始化为0 FD_SET(serverSocket, &reads); //将服务器套接字存入 while (1) { cpyReads = reads; timeout.tv_sec = 5; //5秒 timeout.tv_usec = 5000; //5000毫秒 //找出监听中发出请求的套接字 if ((fdnum = select(0, &cpyReads, 0, 0, &timeout)) == SOCKET_ERROR) break; if (fdnum == 0) { cout << "time out!" << endl; continue; } for (unsigned int i = 0; i < reads.fd_count; i++) { if (FD_ISSET(reads.fd_array[i], &cpyReads)) { //判断是否为发出请求的套接字 if (reads.fd_array[i] == serverSocket) { //是否为服务器套接字 szClientAddr = sizeof(clientAddr); clientSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &szClientAddr); if (clientSocket == INVALID_SOCKET) cout << "accept() error" << endl; FD_SET(clientSocket, &reads); cout << "连接的客户端是:" << clientSocket << endl; } else {//否 就是客户端 str_len = recv(reads.fd_array[i], message, bufsize - 1, 0); if (str_len == 0) {//根据接受数据的大小 判断是否是关闭 FD_CLR(reads.fd_array[i], &reads); //清除数组中该套接字 closesocket(cpyReads.fd_array[i]); cout << "关闭的客户端是:" << cpyReads.fd_array[i] << endl; } else { send(reads.fd_array[i], message, str_len, 0); } } } } } closesocket(clientSocket); closesocket(serverSocket); WSACleanup(); }
下面给出windows下I/O复用socket客户端代码:
#include<iostream> #include<WinSock2.h> #pragma comment(lib,"ws2_32.lib") #define bufsize 1024 using namespace std; void main() { WSADATA wsadata; SOCKET clientSocket; SOCKADDR_IN serverAddr; int recvCnt; char message[bufsize] = "\0"; if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) cout << "WSAStartup() error" << endl; if ((clientSocket = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) cout << "socket() error" << endl; serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); serverAddr.sin_port = htons(9999); if(connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr))==SOCKET_ERROR) cout<<"connect() error"<<endl; while (1) { cout << "输入Q或q退出:"; cin >> message; if (!strcmp(message, "Q") || !strcmp(message, "q")) break; send(clientSocket, message, strlen(message), 0); memset(message, 0, sizeof(message)); recv(clientSocket, message, bufsize, 0); cout << "服务器结果:" << message << endl; } closesocket(clientSocket); WSACleanup(); }
最后说一句啦。本网络编程入门系列博客是连载学习的,有兴趣的可以看我博客其他篇。。。。c++ 网络编程课设入门超详细教程 ---目录
参考博客:https://blog.csdn.net/zl908760230/article/details/70257229 参考博客:https://blog.csdn.net/hshl1214/article/details/45872243 参考博客:https://blog.csdn.net/u010223072/article/details/48133725 参考书籍:《TCP/IP 网络编程 --尹圣雨》
若有兴趣交流分享技术,可关注本人公众号,里面会不定期的分享各种编程教程,和共享源码,诸如研究分享关于c/c++,python,前端,后端,opencv,halcon,opengl,机器学习深度学习之类有关于基础编程,图像处理和机器视觉开发的知识
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论