最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時間:2021年12月02日 11:22:10   作者:MangataTS  
大家好,本篇文章主要講的是Linux下Select多路復用實現簡易聊天室示例,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽

前言

和之前的udp聊天室有異曲同工之處,這次我們客戶端send的是一個封裝好了的數據包,recv的是一個字符串,服務器recv的是一個數據包,send的是一個字符串,在用戶連接的時候發(fā)送一個login請求,然后服務器端處理,并廣播到其他客戶端去

多路復用的原理

在這里插入圖片描述

基本概念

多路復用指的是:通過一種機制,可以監(jiān)視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。其實就是一種異步處理的操作,等待可運行的描述符。

與多進程和多線程技術相比,I/O多路復用技術的最大優(yōu)勢是系統(tǒng)開銷小,系統(tǒng)不必創(chuàng)建進程/線程,也不必維護這些進程/線程,從而大大減小了系統(tǒng)的開銷。

多路復用大體有三種實現方式分別是:

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

實現的功能是:

客戶端連接到客戶端時,服務器向其他客戶端進行廣播上線

向服務器發(fā)送消息,然后服務器向其他客戶端廣播上線

客戶端退出,服務器向其他客戶端廣播

#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) {//給其他用戶發(fā)上線信息
        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) {//給其他用戶發(fā)信息
        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) {//給其他用戶發(fā)上線信息
        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;        // 監(jiān)聽描述符

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) {
        perror("socket");
        return -1;
    }
    // 配置監(jiān)聽描述符地址復用屬性
    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是雙向,配置成監(jiān)聽模式
    listen(listen_fd, 5);

    return listen_fd;
}

// 監(jiān)聽處理器
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;

    // 把監(jiān)聽描述符、標準輸入描述符添加到集合
    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) {//新連接,也就是有用戶上線
                    // 監(jiān)聽描述符可讀  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;
                    }
                    // 正常連接更新系統(tǒng)的集合,更新系統(tǒng)的通信錄
                    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

創(chuàng)建了 一個父子進程,父進程用于接受信息并打印到屏幕,子進程用于輸入并發(fā)送信息

//
// 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的當前狀態(tài)(非阻塞)
//    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';
            // 發(fā)送字符串,不發(fā)送'\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易聊天室內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Qt增加版本公司等信息兩種方式

    Qt增加版本公司等信息兩種方式

    在項目中生成exe或者動態(tài)庫過程中可能需要加入公司信息、版本號、說明等等,下面這篇文章主要給大家介紹了關于Qt增加版本公司等信息的兩種方式,需要的朋友可以參考下
    2024-01-01
  • Qt 使用 canon edsdk 實現實時預覽的示例代碼

    Qt 使用 canon edsdk 實現實時預覽的示例代碼

    這篇文章主要介紹了Qt 使用 canon edsdk 實現實時預覽的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-11-11
  • C++實現完整功能的通訊錄管理系統(tǒng)詳解

    C++實現完整功能的通訊錄管理系統(tǒng)詳解

    來了來了,通訊錄管理系統(tǒng)踏著七彩祥云飛來了,結合前面的結構體知識和分文件編寫方法,我總結并碼了一個帶菜單的通訊錄管理系統(tǒng),在這篇文章中將會提到C的清空屏幕函數,嵌套結構體具體實現,簡單且充實,跟著我的思路,可以很清晰的解決這個項目
    2022-05-05
  • 基于C++實現簡單日期計算器

    基于C++實現簡單日期計算器

    這篇文章主要介紹了基于C++實現簡單日期計算器,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • C語言中memcpy 函數的用法詳解

    C語言中memcpy 函數的用法詳解

    這篇文章主要介紹了C語言中memcpy 函數的用法詳解的相關資料,需要的朋友可以參考下
    2017-07-07
  • VsCode搭建C語言運行環(huán)境詳細過程及終端亂碼問題解決方案

    VsCode搭建C語言運行環(huán)境詳細過程及終端亂碼問題解決方案

    這篇文章主要介紹了VsCode搭建C語言運行環(huán)境以及終端亂碼問題解決,在VsCode中搭建C/C++運行環(huán)境需要先安裝幾個插件,具體插件文中給大家詳細介紹,需要的朋友可以參考下
    2022-12-12
  • 使用C++實現位圖處理

    使用C++實現位圖處理

    本文介紹了如何使用C++語言處理位圖圖像,包括讀取、修改、保存等操作。通過具體的代碼示例,讀者可以學習到如何利用C++中的位運算、數組和文件操作等知識點完成位圖處理任務。同時,本文也提供了一些常用的圖像處理算法供讀者參考,幫助讀者更好地理解位圖處理過程
    2023-04-04
  • C語言實現煙花表白程序代碼

    C語言實現煙花表白程序代碼

    大家好,本篇文章主要講的是C語言實現煙花表白程序代碼,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-02-02
  • FFmpeg獲取網絡攝像頭數據解碼

    FFmpeg獲取網絡攝像頭數據解碼

    這篇文章主要為大家詳細介紹了FFmpeg獲取網絡攝像頭數據解碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-06-06
  • C語言使用strcmp()函數比較兩個字符串的實現

    C語言使用strcmp()函數比較兩個字符串的實現

    這篇文章主要介紹了C語言使用strcmp()函數比較兩個字符串的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01

最新評論

城固县| 阳谷县| 沾益县| 兴安盟| 莲花县| 营口市| 南开区| 新营市| 庄浪县| 信宜市| 横峰县| 柳林县| 格尔木市| 淅川县| 天峨县| 阜宁县| 吐鲁番市| 绥阳县| 合肥市| 宝鸡市| 东乡县| 历史| 洪雅县| 措勤县| 界首市| 深水埗区| 威远县| 北辰区| 长治市| 儋州市| 陆河县| 榆林市| 陆良县| 裕民县| 陈巴尔虎旗| 专栏| 泰州市| 广安市| 忻城县| 湖口县| 绥宁县|