IO多路复用

xiaoxiao2021-02-28  60

读写IO            阻塞I/O:     读:: read一个文件,假如文件没有数据则读取会阻塞直到有数据时,返回数据的大小或读错误    写:  write一个文件,写一般不会阻塞,但是当要写入的空间写满时,写就会阻塞,等待空间中有地方可写时,才再次写入,或写失败。          非阻塞I/O:                O_NONBLOCK            读写都不会阻塞,他会立即返回读取或写入的数据,假如没有数据可读,或空间写满,他不会等待,只会立即返回-1. 日常使用的IO都为阻塞IO,因为这样可以确保每次都读写顺利,但是这些阻塞IO数量太多的话,我们就必须进行多任务处理。 select多路复用解决阻塞问题:      int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 参数一:需要监听的最大文件描述符+1 for(int i=0;i<nfds+1;i++)  {              fd_set[i]  } 参数二:需要监视的读集合 (用户需要获取的读文件描述符) 参数三:需要监视的写集合  -》一般为NULL        用户需要获取的写文件描述符) 参数四:需要监视的出错集合 -》一般为NULL            用户需要获取的出错文件描述符) 参数五:监视的时长    struct timeval {                long    tv_sec;         /* seconds */秒                long    tv_usec;        /* microseconds */微妙            }; 返回值:》0,监视的集合中,活跃的文件描述符数量           =0,监视超时,(超过监视的时间后都没有任何,文件描述符活跃)                  《0,select函数出错         void FD_ZERO(fd_set *set);//清空文件描述符集合         void FD_SET(int fd, fd_set *set);//把需要监视的文件描述符加到集合中         void FD_CLR(int fd, fd_set *set);//把文件描述符在集合中删除          int  FD_ISSET(int fd, fd_set *set);//查看文件描述符是否活跃                        FD_ISSET假如判断的文件描述符集合正确则返回  true   或者是 false 注意假如select超时后,他就会清空所有文件描述符集合    -------------------------------------------------------------------------------------------------------------------------------------- 练习:使用select函数制作一个服务器 (不用使用线程和进程,处理服务器中的任务) /* * 函数功能:实现局域网之间好友之间上线,下线提醒,发消息的功能 * 其中发的消息格式必须是”好友id:消息内容“ * 端口号为6666 */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <signal.h> char status[20],ip[20],id[20]; int port = 0;//用来保存别的客户端发送消息切割后的消息 struct sockaddr_in recv_addr;//用来保存发送方的ip的结构体 int sockfd; typedef struct cli_node //存储好友的信息 { char ip[20]; //ip地址 int port; //端口号 char id[20]; //昵称 struct cli_node *next; }CLI; typedef struct head_node { CLI *head; }H_NODE; H_NODE *cli_list = NULL;//用来存储客户端信息的链表 H_NODE *creat_list() { H_NODE *list; list = (H_NODE *)malloc(sizeof(H_NODE)); if (NULL == list) return NULL; list->head = NULL; return list; } /* * 头结点保存的是自己的信息 */ int wait_cli()//接收信息并存到链表中 { CLI *node = (CLI *)malloc(sizeof(CLI)); if(node == NULL){ perror("malloc error\n"); return 0; } strcpy(node->ip,ip); node->port = port; strcpy(node->id,id); node->next = NULL; if(cli_list->head == NULL){ cli_list->head = node; return 0; } CLI *tmp = cli_list->head; while(tmp->next != NULL){ tmp = tmp->next; } tmp->next = node; return 0; } void del_node()//删除指定ip的节点 { CLI *tmp = cli_list->head->next; CLI *pre = cli_list->head; while(tmp!= NULL){ //printf("%s----%s\n",ip,tmp->ip); if(strcmp(tmp->ip,ip) == 0) break ; tmp = tmp->next; pre = pre->next; } if(tmp->next == NULL){//最后一个节点需要删除 pre->next = NULL; free(tmp); tmp = NULL; return ; } //中间节点 pre->next = tmp->next; tmp->next = NULL; free(tmp); tmp = NULL; return ; } void print_list() { printf("******************friend list************************\n"); //printf("port \t ip \t id"); CLI *tmp = cli_list->head; while(tmp) { printf("friend msg:port = %d,ip = %s,id = %s\n",tmp->port,tmp->ip,tmp->id); tmp = tmp->next; } } CLI *serach_ip(char *id1)//找到指定id的节点 { CLI *tmp = cli_list->head;//遍历链表找到对应的ip和昵称 while(tmp->next != NULL){ if(strcmp(tmp->id,id1) == 0) break ; tmp = tmp->next; } return tmp; } // void *answer_func(void *argc) { struct sockaddr_in answer_addr;//回复消息的结构体 char answer_msg[50] = {0};//用来保存回复的消息内容 char msg[50] = {0}; char id1[10] = {0}; //printf("please input the msg to answer:id-xianxo\n"); while(1){ scanf("%s",answer_msg); if(strstr(answer_msg,":") == NULL){ printf("please input id:msg\n"); continue; } sscanf(answer_msg,"%[^:]:%s",id1,msg); printf("%s,%s\n",id1,msg); CLI *tmp = serach_ip(id1); answer_addr.sin_family = AF_INET; answer_addr.sin_port = htons(6666); //接收端的端口号 answer_addr.sin_addr.s_addr = inet_addr(tmp->ip); //发送广播数据到7段的IP里面 printf("reply ip = %s,port = %d\n",inet_ntoa(answer_addr.sin_addr),ntohs(answer_addr.sin_port)); //printf("%s :%s\n",tmp->id,buf); int ret = sendto(sockfd,msg,strlen(msg),0,(struct sockaddr *)&answer_addr,sizeof(answer_addr)); if(ret > 0){ printf("send msg OK to %s\n",tmp->id); } else{ perror("answer msg error\n"); } } } //下线广播 void off_func(int arc) { struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(6666); //接收端的端口号 addr.sin_addr.s_addr = inet_addr("192.168.7.255"); //发送广播数据到7段的IP里面 char off_buf[50] = {"OFFLINE-192.168.7.8-6666-czj"}; int on = 1; //使能标记位 1 开启 0关闭 int ret = setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on)); if(ret<0){ perror("setsocketopt fail"); } else{ printf("set sock ok\n"); } ret=sendto(sockfd,off_buf,strlen(off_buf),0,(struct sockaddr *)&addr,sizeof(addr)); //发送广播 if(ret>0){ printf("buf=%s\tsize=%d\n",off_buf,ret); } exit(0); } //下线等待ctrl+c线程 void *wait_signal(void *arc) { signal(SIGINT,off_func);//捕捉到信号发送离线广播 pause(); } int main(int argv,char **argc) { cli_list = creat_list();//创建头结点 //1.创建socket sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd<0){ perror("create sock fail\n"); } //设置接收端的信息 struct sockaddr_in addr,cli_addr,serv_addr; bzero(&addr,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(atoi(argc[1])); //接收端的端口号 addr.sin_addr.s_addr = inet_addr("192.168.7.255"); //发送广播数据到7段的IP里面 serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(atoi(argc[1])); //接收端的端口号 serv_addr.sin_addr.s_addr = inet_addr("0.0.0.0"); int ret = 0; char send_buf[50]={"LOGIN_IN-192.168.7.8-6666-czj"};// 登录客户端的时候,发送的广播消息 char reply_buf[50]={"ONLINE-192.168.7.8-6666-czj"};//别的客户端上线时,接收到广播后,回复的消息r //设置广播属性 int on = 1; //使能标记位 1 开启 0关闭 ret = setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on)); if(ret<0){ perror("setsocketopt fail"); } else{ printf("set sock ok\n"); } bind(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)); ret=sendto(sockfd,send_buf,strlen(send_buf),0,(struct sockaddr *)&addr,sizeof(addr)); //发送广播 if(ret>0){ printf("buf=%s\tsize=%d\n",send_buf,ret); } //关闭广播 int off = 0; ret = setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&off,sizeof(off)); if(ret<0){ perror("close socketopt fail"); } else{ printf("close sock ok\n"); } //用来接收终端信号的线程 pthread_t tid1; pthread_create(&tid1,NULL,wait_signal,NULL); //用来回复消息的线程 pthread_t tid; pthread_create(&tid,NULL,answer_func,NULL); socklen_t recv_addr_len = sizeof(recv_addr); char buf[50] = {0}; while(1){ bzero(buf,50); bzero(status,20); bzero(ip,20); bzero(id,20); port = 0; int size = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&recv_addr,&recv_addr_len); printf("size = %d,buf = %s\n",size,buf); if(strstr(buf,"LOGIN_IN") != NULL || strstr(buf,"ONLINE") != NULL || strstr(buf,"OFFLINE") != NULL){//如果是广播消息就加入链表 sscanf(buf,"%[^-]-%[0-9,.]-%d-%s",status,ip,&port,id); printf("your friend %s is online\n",id); //假如是登陆信息则添加到链表中 if(strcmp(status,"LOGIN_IN") == 0){ CLI *tmp1 = cli_list->head;//遍历链表,如果此ip已经在链表里面了,就继续接收广播,而不添加链表 while(tmp1){ if(strcmp(inet_ntoa(recv_addr.sin_addr),tmp1->ip) == 0) goto cat; tmp1 = tmp1->next; } //加入链表,并回复消息 wait_cli(); cat: print_list(); //接收到广播信息后回馈给对方 ret = sendto(sockfd,reply_buf,strlen(reply_buf),0,(struct sockaddr *)&recv_addr,sizeof(recv_addr)); if(ret > 0){ printf("send msg ok\n"); } else{ perror("send error\n"); } } else if(strcmp(status,"ONLINE") == 0){ //加入链表,不回复消息 if(strcmp(inet_ntoa(recv_addr.sin_addr),"192.168.7.8") == 0)//如果ip是自己的ip就不加入链表 continue ; printf("your friend %s is online\n",id); wait_cli(); print_list(); } else if(strcmp(status,"OFFLINE") == 0){ //删除下线的节点 del_node(); printf("your friend %s is offline\n",id); print_list(); } } else{ CLI *tmp = serach_ip(inet_ntoa(recv_addr.sin_addr)); printf("friend %s say:buf = %s\n",tmp->id,buf); } } close(sockfd); }
转载请注明原文地址: https://www.6miu.com/read-69656.html

最新回复(0)