一、使用select模型,解决基本C/S模型中,accept()、recv()、send()阻塞的问题
二、select模型与C/S模型的不同点
- C/S模型中accept()会阻塞一直傻等socket来链接
- select模型只解决accept()傻等的问题,不解决recv(),send()执行阻塞问题
其实select模型解决了 实现多个客户端链接,与多个客户端分别通信
两个模型都存在recv(),send()执行阻塞问题由于服务器端,客户端不需要(客户端只有一个socket,可以通过加线程解决同时recv和send)
server.cpp
-
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <termios.h> #include <signal.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <ctype.h> #define SET_PORT 8000 int main(int argc, char *argv[]) { int sockfd,connfd;//监听套接字 和连接套接字 struct sockaddr_in serveraddr; int i;//主要用于各种for循环的i int on = 1;//只有下方设置可重复性使用的端口用到 //1.创建监听套接字 sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) ;//设置为可重复使用的端口 //2.bind(通信需要套接字 我把我家的地址 门牌号绑上去 ip和端口) serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(SET_PORT); serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); bind(sockfd, (struct sockaddr *)&serveraddr,sizeof(serveraddr)); //3.监听 和服务器连接的总和 listen(sockfd,128) ; int maxfd = sockfd; //初始化两个集合 一个数组 int client[FD_SETSIZE];//数组用于存放客服端fd 循环查询使用 FD_SETSIZE 是一个宏 默认已经最大1024 fd_set rset;//放在select中 集合中都是有动静的fd 一般就一个 fd_set allset;//只做添加fd FD_ZERO(&rset);//清空的动作 FD_ZERO(&allset);//清空的动作 //先将监听套接字放入 FD_SET(sockfd,&allset); int nready; //初始化数组都为-1 应为标识符从0开始 for(i = 0; i < FD_SETSIZE; i++) client[i] = -1; while(1) { //非常关键的一步 rset = allset;//保证每次循环 select能监听所有的文件描述符 因为rset只会剩下有动静的 nready = select(maxfd+1,&rset,NULL,NULL,NULL);//参数一 //新客户端 if(FD_ISSET(sockfd,&rset)) { struct sockaddr_in clientaddr; memset(&clientaddr,0,sizeof(clientaddr)); socklen_t len = sizeof(clientaddr); connfd = accept(sockfd, (struct sockaddr*)&clientaddr,&len); char ipstr[128];//打印用到 printf("client ip%s ,port %d\n",inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ipstr,sizeof(ipstr)), ntohs(clientaddr.sin_port)); //做的事情一:文件df放入数组 for(i = 0; i < FD_SETSIZE; i++) { if(client[i] < 0) { client[i] = connfd; break;//一定要记得及时跳出 易错点 } } //做的事情二:放入集合 FD_SET(connfd,&allset);//放入集合 //防止超出范围//select的第一个参数必须是监视的文件描述符+1 如果不断有新的客户连接 最大值不断变大 超出就赋值 if(connfd > maxfd) maxfd = connfd; //下方表示 如果同一时刻只有 一个动静 就无需进入下方的else判断处理 如果不止一个 nready-1 再进入下方判断 if(--nready <= 0) continue; } else { //已连接FD产生可读事件 for(i = 0; i < FD_SETSIZE; i++)//FD_SEISIZE 是宏 1024 //循环从数组取出元素比对 { if(FD_ISSET(client[i],&rset)) { connfd = client[i]; char buf[1024] = {0}; int nread ; nread = read(connfd, buf, sizeof(buf)); if(nread == 0)//表示客服端断开链接 { //四步处理 打印说明 从集合中删除 从数组中删除 关闭客服端 printf("client is close..\n"); FD_CLR(connfd, &allset); client[i] = -1; close(connfd); } else//正常读到处理 { for(int j=0;j<strlen(buf);j++) { buf[j]=toupper(buf[j]); } write(connfd,buf,nread); memset(buf,0,1024); } //下方表示如果同意时刻如果可读事件只有一个 无需再将数组元素进行循环比对 直接跳出 //不必让循环走完 浪费时间 if(--nready <= 0) break; } } } } return 0; }
client.cpp
-
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> int main(int argc,char* argv[]) { int sockfd; struct sockaddr_in sfdaddr; //指定要连接服务器的结构体 ip 端口 int len; //char buf[1024]; char wbuf[1024]; char rbuf[1024]; //1.socket 通信用套接字,创建一个socket sockfd = socket(AF_INET,SOCK_STREAM,0); char ipstr[] = "127.0.0.1"; //本机测试ip //初始化地址 bzero(&sfdaddr,sizeof(sfdaddr)); sfdaddr.sin_family = AF_INET; sfdaddr.sin_port = htons(8000); inet_pton(AF_INET,ipstr,&sfdaddr.sin_addr.s_addr); //转换ip 保存到结构体内 //2.connect 主动连接服务器 connect(sockfd,(struct sockaddr *)&sfdaddr,sizeof(sfdaddr)); //3.读写 #if 1 while(1) { memset(wbuf,0,1024); memset(rbuf,0,1024); scanf("%s",wbuf); write(sockfd,wbuf,strlen(wbuf)); len=read(sockfd,rbuf,sizeof(rbuf)); //write(STDOUT_FILENO,buf,len); printf("%s\n",rbuf); } #endif //4.close close(sockfd); return 0; }