多人聊天小项目2.0版(socket编程、epoll、多线程)

本文介绍了在1.0版本的基础上,通过引入epoll优化了服务器性能,避免内存消耗,并添加了一个检测config.txt的线程来智能关闭连接。通过封装客户端为类并考虑跨平台移植,提升了代码可维护性和扩展性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

改进思路

基于1.0版本的改进:

1.0版对于每个请求连接的客户端都会创建一个线程进行接收发送,如果连接的客户端太多,可能把内存占满,造成资源浪费。于是引入epoll,对每个存入epoll的套接字检测,对有消息传入的套接字处理,效率更高。

2.0版加入一个新线程检测同一文件夹下文件config.txt内容是否为 “close”,如果是,则关闭所有连接。

效果图

在这里插入图片描述

服务端源码

main.cpp
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<unistd.h>//linux下头文件
#include<string.h>
#include<netdb.h>//linux特有,与网络有关的结构体、宏、函数
#include<sys/types.h>//linux
#include<sys/socket.h>
#include<thread>
#include<vector>
#include<arpa/inet.h>//一些转换函数
#include"recv_and_send.h"
#include<unordered_map>
#include<sys/epoll.h>
using namespace std;
#define MAX_FD_NUMBER 500

int main(int argc, char *argv[]){
    unordered_map<int, string> clientfd_to_name;
    check_parameter(argc);
    int listenfd;
    int epfd;
    struct epoll_event events[MAX_FD_NUMBER];
    memset(events,0,sizeof(events));
    creat_sever_socket(listenfd);
    bind_socket(htons(atoi(argv[1])),listenfd);//绑定listenfd
    begin_listen(listenfd);//开始监听端口
    create_epoll(epfd, listenfd);
    
    epoll_control(epfd, listenfd, events,clientfd_to_name);
    
    cout << "服务端开始关闭......" <<endl;
    close(listenfd);
    close_socket(clientfd_to_name);
    close(epfd);
    return 0;
}


recv_and_send.h
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<unistd.h>//linux下头文件
#include<string.h>
#include<netdb.h>//linux特有,与网络有关的结构体、宏、函数
#include<sys/types.h>//linux
#include<sys/socket.h>
#include<thread>
#include<vector>
#include<arpa/inet.h>//一些转换函数
#include<unordered_map>
using namespace std;
#define MAX_FD_NUMBER 500


void check_parameter(const int &argc);
void creat_sever_socket(int & sockfd);
void bind_socket(short port,int &sockfd);
void begin_listen(int &sockfd);
bool recv_message(char *buffer,int buffer_size,int &clientfd);
bool send_message(char *buffer,int buffer_size,const int &clientfd);
void close_socket(unordered_map<int,string> &);
void recv_and_send_message(int clientfd,int &epfd,unordered_map<int,string> &clientfd_to_name,char *buffer,int buffer_size,char *send_buffer,int send_buffer_size);
void create_epoll(int &epfd,int &listenfd);
void accept_new_client(int &epfd, int &listenfd);
void epoll_control(int &epfd,int &listenfd, struct epoll_event *events,unordered_map<int,string> &clientfd_to_name);
void shutdown_server();
void delete_client(int &epfd,int &clientfd,unordered_map<int,string> &clientfd_to_name);



recv_and_send.cpp
#include<iostream>
#include<fstream>
#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<unistd.h>//linux下头文件
#include<string.h>
#include<netdb.h>//linux特有,与网络有关的结构体、宏、函数
#include<sys/types.h>//linux
#include<sys/socket.h>
#include<thread>
#include<vector>
#include<arpa/inet.h>//一些转换函数
#include<unordered_map>
#include<sys/epoll.h>//epoll头文件
#include"recv_and_send.h"
using namespace std;
#define MAX_FD_NUMBER 500
bool if_shutdown = false;


void check_parameter(const int &argc);//检查参数个数
void creat_sever_socket(int & sockfd);//创建服务端socket套接字
void bind_socket(short port,int &sockfd);//绑定端口
void begin_listen(int &sockfd);//服务端开始监听
bool recv_message(char *buffer,int buffer_size,int &clientfd);//发送一条信息 
bool send_message(char *buffer,int buffer_size,const int &clientfd);//接收一条信息
void close_socket(const vector<int> &sockfd);//关闭clientfd
void recv_and_send_message(int clientfd,int &epfd,unordered_map<int,string> &clientfd_to_name,char *buffer,int buffer_size,char *send_buffer,int send_buffer_size);//接收和发送
void create_epoll(int &epfd,int &listenfd);//创建epollfd
void accept_new_client(int &epfd, int &listenfd);//发现有新用户连接
void epoll_control(int &epfd,int &listenfd, struct epoll_event *events,unordered_map<int,string> &clientfd_to_name);//epoll主体,实现epoll_ctl和epoll_wait
void shutdown_server();//一直监听同一文件夹下的config.txt,发现键入close后,关闭服务端和所有客户端连接
void delete_client(int &epfd,int &clientfd,unordered_map<int,string> &clientfd_to_name);//出现客户端断开后,在epoll中删除客户端

void check_parameter(const int &argc){
    cout << "check_parameter starting..." << endl;
    if(argc != 2){
        cout << "参数个数错误!!!" << endl;
        exit(0);
    }
}

void creat_sever_socket(int & sockfd){
    cout << "creat_sever_socket starting..." << endl;
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1){
        cout <<"创建服务端socket错误" <<endl;
        exit(0);
    }
}
void bind_socket(short port,int &sockfd){
    cout << "bind_socket starting..." << endl;
    struct sockaddr_in servaddr;
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = port;
    
    int ans = bind(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    if(ans != 0){
        cout << "bind 绑定错误!" << endl;
        exit(0);
    }
}
void begin_listen(int &sockfd){
    cout << "begin_listen starting..." << endl;
    int ans = listen(sockfd,5);

    if(ans != 0){
        cout << "listen 监听错误!"<<endl;
        exit(0);
    }
}

bool recv_message(char *buffer,int buffer_size,int &clientfd){
    memset(buffer,0,buffer_size);
    int ans = recv(clientfd,buffer,buffer_size,0);
    return ans > 0 ? true : false;
}

bool send_message(char *buffer,int buffer_size,int &clientfd){

    int ans = send(clientfd,buffer,buffer_size,0);
    return ans > 0 ? true : false;
}


void close_socket(unordered_map<int,string> &client_to_name){
    for(unordered_map<int,string> :: iterator iter = client_to_name.begin(); iter != client_to_name.end(); iter++){
        close(iter->first);
    }
}

void create_epoll(int &epfd,int &listenfd){
    cout << "create_epoll starting..." << endl;
    epfd = epoll_create(MAX_FD_NUMBER);
    struct epoll_event ev;
    memset(&ev,0,sizeof(ev));
    ev.data.fd = listenfd;
    ev.events = EPOLLIN | EPOLLET;
    if(epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1){//把linstenfd存入epoll池
        cout << "epoll error" << endl;
        exit(0);
    }
}

void shutdown_server(){
    FILE * fd = freopen("config.txt","w+",stdin);
    char s[10];
    char c[10] = "close";
    while(!if_shutdown){
        fseek(fd,0L,SEEK_SET);
        fscanf(fd,"%[^\n]",s);
        sleep(2);
        if(strcmp(s,c) == 0){
            if_shutdown = true;
            break;
        }
    }
    cout << "关闭程序" <<endl;
}

void epoll_control(int &epfd,int &listenfd, struct epoll_event *events ,unordered_map<int,string> &clientfd_to_name){
    cout << "epoll_control starting..." << endl;
    thread t(shutdown_server);
    t.detach();
    char buffer[1024];
    char send_buffer[2048];
    while(!if_shutdown){
        int num = 0;
        num = epoll_wait(epfd, events, MAX_FD_NUMBER, 20);
        if(num == -1){
            //epoll错误
            cout << "epoll出现错误!" <<endl;
            break;
        }
        for(int i = 0 ; i < num; i++){
            if((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || !(events[i].events & EPOLLIN)){
                cout << "错误响应" <<endl;
            }
            else if(events[i].data.fd == listenfd){
                accept_new_client(epfd, listenfd);
            }
            else{
                memset(buffer,0,sizeof(buffer));
                memset(send_buffer,0,sizeof(send_buffer));
                recv_and_send_message(events[i].data.fd,epfd,clientfd_to_name,buffer,sizeof(buffer),send_buffer,sizeof(send_buffer));
            }
        }
    }
}
void accept_new_client(int &epfd, int &listenfd){
    cout << "accept_new_client starting..." << endl;
    int clientfd;
    struct sockaddr_in clientaddr;
    int socklen = sizeof(struct sockaddr_in);
    memset(&clientaddr, 0, sizeof(clientaddr));
    clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, (socklen_t*)&socklen);
    struct epoll_event ev;
    memset(&ev, 0, sizeof(ev));
    ev.data.fd = clientfd;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);//加入容器


    cout << "****客户端 " << inet_ntoa(clientaddr.sin_addr) << " 已连接****"<<endl;
    cout << "****socket :"<< clientfd << " ****" << endl;
}

void recv_and_send_message(int clientfd,int &epfd,unordered_map<int,string> &clientfd_to_name,char *buffer,int buffer_size,char *send_buffer,int send_buffer_size){

    if(recv_message(buffer, buffer_size, clientfd) == false){

        cout << "接收错误 " << clientfd << " 退出" <<endl;
        //接受错误说明此clinet退出,需要在epoll_ctl中去掉它
        delete_client(epfd,clientfd,clientfd_to_name);
        return ;
    }

    if(clientfd_to_name.find(clientfd) == clientfd_to_name.end()){
        //没有名字,创建
        string name = buffer;

        clientfd_to_name.insert(make_pair(clientfd,name));
        cout << clientfd << "创建姓名成功 : " << clientfd_to_name[clientfd] << endl; 
        char ok[] = "------昵称创建成功!------";
        if(send_message(ok,sizeof(ok),clientfd) == false){
            delete_client(epfd,clientfd,clientfd_to_name);
            cout << clientfd << " 昵称创建失败, 断开连接" << endl;

        }
        return;
    }
    
    
    char tmp[] = " : ";
    memset(send_buffer, 0, send_buffer_size);
    strcat(send_buffer,clientfd_to_name[clientfd].c_str());
    strcat(send_buffer,tmp);
    strcat(send_buffer, buffer);
   
    for(unordered_map<int,string> :: iterator iter = clientfd_to_name.begin() ; iter != clientfd_to_name.end(); iter++){
        int i = iter->first;
        if(iter->first != clientfd){
            cout << "已向 " <<iter->second<<" (clientfd : " << iter->first << " ) 发送 " <<buffer << endl;
            if(send_message(send_buffer,send_buffer_size,i) == false){
                cout << "向 " << iter->first << " 发送失败" <<endl;
                continue;
            }
        }
    }
}
void delete_client(int &epfd,int &clientfd,unordered_map<int,string> &clientfd_to_name){
    struct epoll_event ev;
    ev.data.fd = clientfd;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd,EPOLL_CTL_DEL,clientfd, &ev);
    clientfd_to_name.erase(clientfd);
}



需要改进的地方

可以把client写成类,便于操作

linux聊天不方便,如何移植到windows

等等。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值