国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - C/C++ - Linux下Select多路復用實現簡易聊天室示例

Linux下Select多路復用實現簡易聊天室示例

2022-03-08 15:24MangataTS C/C++

大家好,本篇文章主要講的是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 1024
int 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 1024
char *ip = "192.168.200.130"; //106.52.247.33
int 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

延伸 · 閱讀

精彩推薦
  • C/C++C語言實現電腦關機程序

    C語言實現電腦關機程序

    這篇文章主要為大家詳細介紹了C語言實現電腦關機程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    xiaocaidayong8482021-08-20
  • C/C++C語言中炫酷的文件操作實例詳解

    C語言中炫酷的文件操作實例詳解

    內存中的數據都是暫時的,當程序結束時,它們都將丟失,為了永久性的保存大量的數據,C語言提供了對文件的操作,這篇文章主要給大家介紹了關于C語言中文件...

    針眼_6702022-01-24
  • C/C++詳解c語言中的 strcpy和strncpy字符串函數使用

    詳解c語言中的 strcpy和strncpy字符串函數使用

    strcpy 和strcnpy函數是字符串復制函數。接下來通過本文給大家介紹c語言中的strcpy和strncpy字符串函數使用,感興趣的朋友跟隨小編要求看看吧...

    spring-go5642021-07-02
  • C/C++c++ 單線程實現同時監聽多個端口

    c++ 單線程實現同時監聽多個端口

    這篇文章主要介紹了c++ 單線程實現同時監聽多個端口的方法,幫助大家更好的理解和學習使用c++,感興趣的朋友可以了解下...

    源之緣11542021-10-27
  • C/C++深入理解goto語句的替代實現方式分析

    深入理解goto語句的替代實現方式分析

    本篇文章是對goto語句的替代實現方式進行了詳細的分析介紹,需要的朋友參考下...

    C語言教程網7342020-12-03
  • C/C++學習C++編程的必備軟件

    學習C++編程的必備軟件

    本文給大家分享的是作者在學習使用C++進行編程的時候所用到的一些常用的軟件,這里推薦給大家...

    謝恩銘10102021-05-08
  • C/C++C/C++經典實例之模擬計算器示例代碼

    C/C++經典實例之模擬計算器示例代碼

    最近在看到的一個需求,本以為比較簡單,但花了不少時間,所以下面這篇文章主要給大家介紹了關于C/C++經典實例之模擬計算器的相關資料,文中通過示...

    jia150610152021-06-07
  • C/C++C++之重載 重定義與重寫用法詳解

    C++之重載 重定義與重寫用法詳解

    這篇文章主要介紹了C++之重載 重定義與重寫用法詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下...

    青山的青6062022-01-04
主站蜘蛛池模板: 精品久久久久久久久久 | 久久精品一区二区三区中文字幕 | 久久久久国产精品一区二区 | 99国产精品99久久久久久 | 成人欧美一区二区三区在线播放 | 女教师高潮叫床视频在线观看 | 国产精品成人国产乱一区 | 高清一区二区三区日本久 | 成人久久久 | 免费色视频 | 狠狠色噜噜狠狠狠8888米奇 | 91视频入口 | 五月天综合网 | 日韩精品在线免费观看 | 久久av综合 | 日韩精品一区不卡 | 日日夜夜精品视频 | 一区二区三区国产 | 色婷婷av一区二区三区大白胸 | 日韩在线 | 欧美欧美欧美 | 中文在线中文a | 久久mm| 精品欧美日韩 | 中文字幕一区在线 | 美女一级毛片 | 中文字幕网站 | 精品在线看 | 日韩一区二区三区视频 | www.国产一区 | 国产一区二区三区久久久久久久久 | 一区视频 | 91av在 | 欧美一区二区三区在线播放 | 欧美黄色性视频 | 色天天综合 | 日韩成人片| 亚洲国产成人av好男人在线观看 | 精品一区二区三区中文字幕 | 黄桃av| 欧美a级成人淫片免费看 |