gpt4 book ai didi

Linux下Select多路复用实现简易聊天室示例

转载 作者:qq735679552 更新时间:2022-09-28 22:32:09 45 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章Linux下Select多路复用实现简易聊天室示例由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

前言

和之前的udp聊天室有异曲同工之处,这次我们客户端send的是一个封装好了的数据包,recv的是一个字符串,服务器recv的是一个数据包,send的是一个字符串,在用户连接的时候发送一个login请求,然后服务器端处理,并广播到其他客户端去 。

多路复用的原理

Linux下Select多路复用实现简易聊天室示例

  。

基本概念

多路复用指的是:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。其实就是一种异步处理的操作,等待可运行的描述符.

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销.

多路复用大体有三种实现方式分别是

select 。

poll 。

epoll 。

本次代码主要是展示select的用法:

select

int select(int nfds, fd_set *readfds, fd_set *writefds,                fd_set *exceptfds, struct timeval *timeout);

这个是Linux的man手册给出的select的声明 。

第一个参数ndfs 。

第一个参数是nfds表示的是文件描述集合中的最大文件描述符+1,因为select的遍历使用是[0,nfds)的 。

第二个参数readfds 。

readfds表示的是读事件的集合 。

第三个参数writefds 。

writefds表示的是读事件的集合 。

第四个参数exceptfds 。

exceptfds表示的是异常参数的集合 。

第五个参数timeout 。

表示的是超时时间,timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数.

struct timeval{  long tv_sec;    //second  long tv_usec;   //microseconds  }

fd_set

fd_set结构体的定义实际包含的是fds_bits位数组,该数组的每个元素的每一位标记一个文件描述符其大小固定,由FD_SETSIZE指定,一般而言FD_SETSIZE的大小为1024 。

我们只用关心怎么使用即可:

下面几个函数就是操作fd_set的函数 。

void FD_ZERO(fd_set *fdset);           //清空集合void FD_SET(int fd, fd_set *fdset);   //将一个给定的文件描述符加入集合之中void FD_CLR(int fd, fd_set *fdset);   //将一个给定的文件描述符从集合中删除int FD_ISSET(int fd, fd_set *fdset);   // 检查集合中指定的文件描述符是否可以读写 

  。

服务器Code

实现的功能是:

客户端连接到客户端时,服务器向其他客户端进行广播上线 。

向服务器发送消息,然后服务器向其他客户端广播上线 。

客户端退出,服务器向其他客户端广播 。

#include <stdio.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#include <fcntl.h>#include <netdb.h>#include <signal.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <arpa/inet.h>#define N 1024int fd[FD_SETSIZE];//用户集合,最大承受量typedef struct Msg{//消息的结构  char type;//消息类型  char name[20];  char text[N];//消息内容}MSG;typedef struct User{  int fd;  struct User *next;}USE;USE *head;USE *init() {  USE *p = (USE *)malloc(sizeof(USE));  memset(p,0,sizeof(USE));  p->next = NULL;  return p;}void Link(int new_fd) {//将新连接加入用户列表里面  USE *p = head;  while(p->next) {      p=p->next;  }  USE *k = (USE*)malloc(sizeof(USE));  k->fd = new_fd;  k->next = NULL;  p->next = k;}void login(int fd,MSG msg) {  USE *p = head;  char buf[N+30];  strcpy(buf,msg.name);  strcat(buf,"上线啦!快来找我玩叭!");  printf("fd = %d  %s\n",fd,buf);  while(p->next) {//给其他用户发上线信息      if(fd != p->next->fd)          send(p->next->fd,&buf,sizeof(buf),0);      p = p->next;  }//    puts("Over login");}void chat(int fd,MSG msg) {//    printf("%d\n",msg.text[0]);  if(strcmp(msg.text,"\n") == 0) return;  USE *p = head;  char buf[N+30];  strcpy(buf,msg.name);  strcat(buf,": ");  strcat(buf,msg.text);  printf("%s\n",buf);  while(p->next) {//给其他用户发信息      if(fd != p->next->fd)          send(p->next->fd,&buf,sizeof(buf),0);      p = p->next;  }}void quit(int fd,MSG msg) {  USE *p = head;  char buf[N+30];  strcpy(buf,msg.name);  strcat(buf,"伤心的退出群聊!");  printf("%s\n",buf);  while(p->next) {//给其他用户发上线信息      if(fd != p->next->fd)          send(p->next->fd,&buf,sizeof(buf),0);      p = p->next;  }}/** 初始化TCP服务器,返回服务器的socket描述符* */int init_tcp_server(unsigned short port) {  int ret;  int opt;  int listen_fd;  struct sockaddr_in self;        // 监听描述符  listen_fd = socket(AF_INET, SOCK_STREAM, 0);  if (listen_fd < 0) {      perror("socket");      return -1;  }  // 配置监听描述符地址复用属性  opt = 1;  ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));  if (ret < 0) {      perror("set socket opt");      return -1;  }  // 填充服务器开放接口和端口号信息  memset(&self, 0, sizeof(self));  self.sin_family = AF_INET;  self.sin_port = htons(port);  self.sin_addr.s_addr = htonl(INADDR_ANY);  ret = bind(listen_fd, (struct sockaddr *)&self, sizeof(self));  if (ret == -1) {      perror("bind");      return -1;  }  // 默认socket是双向,配置成监听模式  listen(listen_fd, 5);  return listen_fd;}// 监听处理器int listen_handler(int listen_fd) {  int new_fd;  new_fd = accept(listen_fd, NULL, NULL);  if (new_fd < 0) {      perror("accpet");      return -1;  }  return new_fd;}// 客户端处理器int client_handler(int fd) {  int ret;  MSG msg;  // 读一次  ret = recv(fd, &msg, sizeof(MSG), 0);//读取消息//    printf("name = %s\n",msg.name);  if (ret < 0) {      perror("recv");      return -1;  } else if (ret == 0) {//断开连接      quit(fd,msg);      return 0;  } else {//数据处理      if(msg.type == 'L') {//登陆处理          login(fd,msg);      }      else if(msg.type == 'C') {//聊天处理          chat(fd,msg);      }      else if(msg.type == 'Q') {//退出处理          quit(fd,msg);      }  }//    puts("Over client_handler");  return ret;}// 标准输入处理器int input_handler(int fd) {  char buf[1024];  fgets(buf, sizeof(buf), stdin);  buf[strlen(buf) - 1] = 0;  printf("user input: %s\n",buf);  return 0;}void main_loop(int listen_fd) {  fd_set current, bak_fds;  int max_fds;  int new_fd;  int ret;  // 把监听描述符、标准输入描述符添加到集合  FD_ZERO(&current);  FD_SET(listen_fd, &current);  FD_SET(0, &current);  max_fds = listen_fd;  while (1) {      bak_fds = current;      // 备份集合      ret = select(max_fds+1, &bak_fds, NULL, NULL, NULL);      if (ret < 0) {          perror("select");          break;      }      // 判断内核通知哪些描述符可读,分别处理      for (int i = 0; i <= max_fds; ++i) {          if (FD_ISSET(i, &bak_fds)) {              if (i == 0) {//服务器的输入端,可以做成广播                  // 标准输入可读 fgets                  input_handler(i);              } else if (i == listen_fd) {//新连接,也就是有用户上线                  // 监听描述符可读  accept                  new_fd = listen_handler(i);                  if (new_fd < 0) {                      fprintf(stderr, "listen handler error!\n");                      return;                  }                  if(new_fd >= FD_SETSIZE) {                      printf("客户端连接过多!");                      close(new_fd);                      continue;                  }                  // 正常连接更新系统的集合,更新系统的通信录                  Link(new_fd);//将新的连接描述符放进链表里面                  FD_SET(new_fd, &current);                  max_fds = new_fd > max_fds ? new_fd : max_fds;              } else {                  // 新的连接描述符可读  recv                  ret = client_handler(i);                  if (ret <= 0) {                      // 收尾处理                      close(i);                      FD_CLR(i, &current);                  }              }          }      }//        puts("over loop!\n");  }}int main(){  int listen_fd;  head = init();  listen_fd = init_tcp_server(6666);  if (listen_fd < 0) {      fprintf(stderr, "init tcp server failed!\n");      return -1;  }  printf("等待连接中...\n");  main_loop(listen_fd);  close(listen_fd);  return 0;}

  。

客户端Code

创建了 一个父子进程,父进程用于接受信息并打印到屏幕,子进程用于输入并发送信息 。

//// Created by Mangata on 2021/11/30.//#include <stdio.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#include <fcntl.h>#include <netdb.h>#include <signal.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <arpa/inet.h>#define N 1024char *ip = "192.168.200.130"; //106.52.247.33int port = 6666;char name[20];typedef struct Msg{//消息的结构  char type;//消息类型  char name[20];  char text[N];//消息内容}MSG;/** 初始化TCP客户端,返回客户端的socket描述符* */int init_tcp_client(const char *host) {  int tcp_socket;  int ret;  struct sockaddr_in dest;  tcp_socket = socket(AF_INET, SOCK_STREAM, 0);  if (tcp_socket == -1) {      perror("socket");      return -1;  }  memset(&dest, 0, sizeof(dest));  dest.sin_family = AF_INET;  dest.sin_port = htons(port);  dest.sin_addr.s_addr = inet_addr(host);  ret = connect(tcp_socket, (struct sockaddr *)&dest, sizeof(dest));  if (ret < 0) {      perror("connect");      return -1;  }//    int flags = fcntl(tcp_socket, F_GETFL, 0);       //获取建立的sockfd的当前状态(非阻塞)//    fcntl(tcp_socket, F_SETFL, flags | O_NONBLOCK);  //将当前sockfd设置为非阻塞  printf("connect %s success!\n", host);  return tcp_socket;}void login(int fd) {  MSG msg;  fputs("请输入您的名字: ",stdout);  scanf("%s",msg.name);  strcpy(name,msg.name);  msg.type = 'L';  send(fd,&msg,sizeof(MSG),0);}void chat_handler(int client_fd) {  int ret;  char buf[N+30];  pid_t pid = fork();  if(pid == 0) {      MSG msg;      strcpy(msg.name,name);      while (fgets(buf, sizeof(buf), stdin)) {          if (strncmp(buf, "quit", 4) == 0) {// 客户端不聊天了,准备退出              msg.type = 'q';              send(client_fd,&msg,sizeof(MSG),0);              exit(1);          }          strcpy(msg.text,buf);          msg.type = 'C';          // 发送字符串,不发送'\0'数据          ret = send(client_fd, &msg, sizeof(MSG), 0);          if (ret < 0) {              perror("send");              break;          }          printf("send %d bytes success!\n", ret);      }  }  else {      while(1){          int rrt = recv(client_fd,&buf,sizeof(buf),0);          printf("rrt = %d\n",rrt);          if(rrt <= 0) {              printf("断开服务器!\n");              break;          }          fprintf(stdout,"%s\n",buf);      }  }}int main(int argc,char *argv[]){  int client_socket;  client_socket = init_tcp_client(ip);  if (client_socket < 0) {      fprintf(stderr, "init tcp client failed!\n");      return -1;  }  login(client_socket);  chat_handler(client_socket);  close(client_socket);  return 0;}

  。

效果演示

select服务器

Linux下Select多路复用实现简易聊天室示例

客户端Ⅰ

Linux下Select多路复用实现简易聊天室示例

客户端Ⅱ

Linux下Select多路复用实现简易聊天室示例

到此这篇关于Linux下Select多路复用实现简易聊天室示例的文章就介绍到这了,更多相关Linux下Select易聊天室内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。

原文链接:https://blog.csdn.net/m0_46201544/article/details/121657413 。

最后此篇关于Linux下Select多路复用实现简易聊天室示例的文章就讲到这里了,如果你想了解更多关于Linux下Select多路复用实现简易聊天室示例的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

45 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com