美文网首页
linux下聊天demo支持私聊和群发

linux下聊天demo支持私聊和群发

作者: Magic11 | 来源:发表于2018-12-28 15:39 被阅读0次

1、公共头文件 Common.h

#ifndef  CHATROOM_COMMON_H
#define CHATROOM_COMMON_H
 
#include <iostream>
#include <list>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
// 默认服务器端IP地址
#define SERVER_IP "127.0.0.1"
 
// 服务器端口号
#define SERVER_PORT 8888
 
// int epoll_create(int size)中的size
// 为epoll支持的最大句柄数
#define EPOLL_SIZE 5000
 
// 缓冲区大小65535
#define BUF_SIZE 0xFFFF
    
// 新用户登录后的欢迎信息
#define SERVER_WELCOME "Welcome you join to the chat room! Your chat ID is: Client #%d"
 
// 其他用户收到消息的前缀
#define SERVER_MESSAGE "ClientID %d say >> %s"
#define SERVER_PRIVATE_MESSAGE "Client %d say to you privately >> %s"
#define SERVER_PRIVATE_ERROR_MESSAGE "Client %d is not in the chat room yet~"
// 退出系统
#define EXIT "EXIT"
 
// 提醒你是聊天室中唯一的客户
#define CAUTION "There is only one in the char room!"
 
 
// 注册新的fd到epollfd中
// 参数enable_et表示是否启用ET模式,如果为True则启用,否则使用LT模式
static void addfd(int epollfd, int fd, bool enable_et)
{
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = EPOLLIN;
    if( enable_et )
        ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
    // 设置socket为非阻塞模式
    // 套接字立刻返回,不管I/O是否完成,该函数所在的线程会继续运行
    //eg. 在recv(fd...)时,该函数立刻返回,在返回时,内核数据还没准备好会返回WSAEWOULDBLOCK错误代码
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, 0) | O_NONBLOCK);
    printf("fd added to epoll!\n\n");
}
 
//定义信息结构,在服务端和客户端之间传送
struct Msg
{
    int type;
    int fromID;
    int toID;
    char content[BUF_SIZE];
 
};
#endif // CHATROOM_COMMON_H

server端头文件 Server.h

#ifndef CHATROOM_SERVER_H
#define CHATROOM_SERVER_H
 
#include <string>
 
#include "Common.h"
 
using namespace std;
 
// 服务端类,用来处理客户端请求
class Server {
 
public:
    // 无参数构造函数
    Server();
 
    // 初始化服务器端设置
    void Init();
 
    // 关闭服务
    void Close();
 
    // 启动服务端
    void Start();
 
private:
    // 广播消息给所有客户端
    int SendBroadcastMessage(int clientfd);
 
    // 服务器端serverAddr信息
    struct sockaddr_in serverAddr;
    
    //创建监听的socket
    int listener;
 
    // epoll_create创建后的返回值
    int epfd;
    
    // 客户端列表
    list<int> clients_list;
};

#endif

server端实现 Server.cpp

//Server.cpp
#include <iostream>
 
#include "Server.h"
 
using namespace std;
 
// 服务端类成员函数
 
// 服务端类构造函数
Server::Server(){
    
    // 初始化服务器地址和端口
    serverAddr.sin_family = PF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
 
    // 初始化socket
    listener = 0;
    
    // epool fd
    epfd = 0;
}
// 初始化服务端并启动监听
void Server::Init() {
    cout<<"Init Server..."<<endl;
    
     //创建监听socket
    listener = socket(PF_INET, SOCK_STREAM, 0);
    if (listener < 0) { 
        perror("listener"); 
        exit(-1);
    }
    
    //绑定地址
    if (bind(listener, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
        perror("bind error");
        exit(-1);
    }
 
    //监听
    int ret = listen(listener, 5);
    if (ret < 0) {
        perror("listen error"); 
        exit(-1);
    }
 
    cout<<"Start to listen: "<<SERVER_IP<<endl;
 
    //在内核中创建事件表 epfd是一个句柄 
    epfd = epoll_create(EPOLL_SIZE);
    
    if (epfd < 0) {
        perror("epfd error");
        exit(-1);
    }
 
    //往事件表里添加监听事件
    addfd(epfd, listener, true);
 
}
 
// 关闭服务,清理并关闭文件描述符
void Server::Close() {
 
    //关闭socket
    close(listener);
    
    //关闭epoll监听
    close(epfd);
}
 
// 发送广播消息给所有客户端
int Server::SendBroadcastMessage(int clientfd)
{
    // buf[BUF_SIZE] 接收新消息
    // message[BUF_SIZE] 保存格式化的消息
    char recv_buf[BUF_SIZE];
    char send_buf[BUF_SIZE];
    Msg msg;
    bzero(recv_buf, BUF_SIZE);
    // 接收新消息
    cout<<"read from client(clientID = "<<clientfd<<")"<<endl;
    int len = recv(clientfd, recv_buf, BUF_SIZE, 0);
    //清空结构体,把接受到的字符串转换为结构体
    memset(&msg, 0, sizeof(msg));
    memcpy(&msg, recv_buf, sizeof(msg));
    //判断接受到的信息是私聊还是群聊
    msg.fromID = clientfd;
    if (msg.content[0] == '\\'&&isdigit(msg.content[1])) {
        msg.type = 1;
        msg.toID = msg.content[1] - '0';
        memcpy(msg.content, msg.content + 2, sizeof(msg.content));
    } else {
        msg.type=0;
    }
    // 如果客户端关闭了连接
    if (len == 0) 
    {
        close(clientfd);
        
        // 在客户端列表中删除该客户端
        clients_list.remove(clientfd);
        cout << "ClientID = " << clientfd 
             << " closed.\n now there are " 
             << clients_list.size()
             << " client in the char room"
             << endl;
 
    }
    // 发送广播消息给所有客户端
    else 
    {
        // 判断是否聊天室还有其他客户端
        if (clients_list.size() == 1) { 
            // 发送提示消息
            memcpy(&msg.content, CAUTION, sizeof(msg.content));
            bzero(send_buf, BUF_SIZE);
            memcpy(send_buf, &msg, sizeof(msg));
            send(clientfd, send_buf, sizeof(send_buf), 0);
            return len;
        }
        //存放格式化后的信息
        char format_message[BUF_SIZE];
        //群聊
        if (msg.type == 0) {
            // 格式化发送的消息内容 #define SERVER_MESSAGE "ClientID %d say >> %s"
            sprintf(format_message, SERVER_MESSAGE, clientfd, msg.content);
            memcpy(msg.content, format_message, BUF_SIZE);
            // 遍历客户端列表依次发送消息,需要判断不要给来源客户端发
            list<int>::iterator it;
            for (it = clients_list.begin(); it != clients_list.end(); ++it) {
               if (*it != clientfd) {
                    //把发送的结构体转换为字符串
                    bzero(send_buf, BUF_SIZE);
                    memcpy(send_buf, &msg, sizeof(msg));
                    if (send(*it, send_buf, sizeof(send_buf), 0) < 0 ) {
                        return -1;
                    }
               }
            }
        }
        //私聊
        if (msg.type == 1) {
            bool private_offline = true;
            sprintf(format_message, SERVER_PRIVATE_MESSAGE, clientfd, msg.content);
            memcpy(msg.content,format_message,BUF_SIZE);
            // 遍历客户端列表依次发送消息,需要判断不要给来源客户端发
            list<int>::iterator it;
            for (it = clients_list.begin(); it != clients_list.end(); ++it) {
               if (*it == msg.toID) {
                    private_offline = false;
                    //把发送的结构体转换为字符串
                    bzero(send_buf, BUF_SIZE);
                    memcpy(send_buf, &msg, sizeof(msg));
                    if (send(*it, send_buf, sizeof(send_buf), 0) < 0) {
                        return -1;
                    }
               }
            }
            //如果私聊对象不在线
            if (private_offline) {
                sprintf(format_message, SERVER_PRIVATE_ERROR_MESSAGE, msg.toID);
                memcpy(msg.content, format_message, BUF_SIZE);
                bzero(send_buf, BUF_SIZE);
                memcpy(send_buf, &msg, sizeof(msg));
                if (send(msg.fromID, send_buf, sizeof(send_buf), 0) < 0) {
                    return -1;
                }
            }
        }
    }
    return len;
}
 
// 启动服务端
void Server::Start() {
 
    // epoll 事件队列
    static struct epoll_event events[EPOLL_SIZE]; 
 
    // 初始化服务端
    Init();
 
    //主循环
    while (1)
    {
        //epoll_events_count表示就绪事件的数目
        int epoll_events_count = epoll_wait(epfd, events, EPOLL_SIZE, -1);
 
        if (epoll_events_count < 0) {
            perror("epoll failure");
            break;
        }
 
        cout<< "epoll_events_count =\n" << epoll_events_count << endl;
 
        //处理这epoll_events_count个就绪事件
        for (int i = 0; i < epoll_events_count; ++i)
        {
            int sockfd = events[i].data.fd;
            //新用户连接
            if (sockfd == listener)
            {
                struct sockaddr_in client_address;
                socklen_t client_addrLength = sizeof(struct sockaddr_in);
                int clientfd = accept(listener, ( struct sockaddr* )&client_address, &client_addrLength);
 
                cout << "client connection from: "
                     << inet_ntoa(client_address.sin_addr) << ":"
                     << ntohs(client_address.sin_port) << ", clientfd = "
                     << clientfd << endl;
 
                addfd(epfd, clientfd, true);
 
                // 服务端用list保存用户连接
                clients_list.push_back(clientfd);
                cout << "Add new clientfd = " << clientfd << " to epoll" << endl;
                cout << "Now there are " << clients_list.size() << " clients int the chat room" << endl;
 
                // 服务端发送欢迎信息  
                cout << "welcome message" << endl;                
                char message[BUF_SIZE];
                bzero(message, BUF_SIZE);
                sprintf(message, SERVER_WELCOME, clientfd);
                int ret = send(clientfd, message, BUF_SIZE, 0);
                if (ret < 0) {
                    perror("send error");
                    Close();
                    exit(-1);
                }
            }
            //处理用户发来的消息,并广播,使其他用户收到信息
            else {   
                int ret = SendBroadcastMessage(sockfd);
                if(ret < 0) {
                    perror("error");
                    Close();
                    exit(-1);
                }
            }
        }
    }
 
    // 关闭服务
    Close();
}

客户端头文件 Client.h

#ifndef CHATROOM_CLIENT_H
#define CHATROOM_CLIENT_H
 
#include <string>
#include "Common.h"
 
using namespace std;
 
// 客户端类,用来连接服务器发送和接收消息
class Client {
 
public:
    // 无参数构造函数
    Client();
 
    // 连接服务器
    void Connect();
 
    // 断开连接
    void Close();
 
    // 启动客户端
    void Start();
 
private:
 
    // 当前连接服务器端创建的socket
    int sock;
 
    // 当前进程ID
    int pid;
    
    // epoll_create创建后的返回值
    int epfd;
 
    // 创建管道,其中fd[0]用于父进程读,fd[1]用于子进程写
    int pipe_fd[2];
 
    // 表示客户端是否正常工作
    bool isClientwork;
 
    // 聊天信息
    Msg msg;
    //结构体要转换为字符串
    char send_buf[BUF_SIZE];
    char recv_buf[BUF_SIZE];
    //用户连接的服务器 IP + port
    struct sockaddr_in serverAddr;
};

#endif

客户端实现 Client.cpp

#include <iostream>
 
#include "Client.h"
 
using namespace std;
 
// 客户端类成员函数
 
// 客户端类构造函数
Client::Client() {
    
    // 初始化要连接的服务器地址和端口
    serverAddr.sin_family = PF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
    
    // 初始化socket
    sock = 0;
    
    // 初始化进程号
    pid = 0;
    
    // 客户端状态
    isClientwork = true;
    
    // epool fd
    epfd = 0;
}
 
// 连接服务器
void Client::Connect() {
    cout<<"Connect Server: "<<SERVER_IP<<" : "<<SERVER_PORT<<endl;
    
    // 创建socket
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("sock error");
        exit(-1); 
    }
 
    // 连接服务端
    if (connect(sock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
        perror("connect error");
        exit(-1);
    }
 
    // 创建管道,其中fd[0]用于父进程读,fd[1]用于子进程写
    if (pipe(pipe_fd) < 0) {
        perror("pipe error");
        exit(-1);
    }
 
    // 创建epoll
    epfd = epoll_create(EPOLL_SIZE);
    
    if (epfd < 0) {
        perror("epfd error");
        exit(-1); 
    }
 
    //将sock和管道读端描述符都添加到内核事件表中
    addfd(epfd, sock, true);
    addfd(epfd, pipe_fd[0], true);
 
}
 
// 断开连接,清理并关闭文件描述符
void Client::Close() {
 
    if (pid) {
       //关闭父进程的管道和sock
        close(pipe_fd[0]);
        close(sock);
    } else {
        //关闭子进程的管道
        close(pipe_fd[1]);
    }
}
 
// 启动客户端 
void Client::Start() {
 
    // epoll 事件队列
    static struct epoll_event events[2];
    
    // 连接服务器
    Connect();
    
    // 创建子进程
    pid = fork();
    
    // 如果创建子进程失败则退出
    if (pid < 0) {
        perror("fork error");
        close(sock);
        exit(-1);
    } else if (pid == 0) {
        // 进入子进程执行流程
        //子进程负责写入管道,因此先关闭读端
        close(pipe_fd[0]); 
 
        // 输入exit可以退出聊天室
        cout<< "Please input 'exit' to exit the chat room"<<endl;
        cout<<"\\ + ClientID to private chat "<<endl;
        // 如果客户端运行正常则不断读取输入发送给服务端
        while (isClientwork) {
            //清空结构体
            memset(msg.content,0,sizeof(msg.content));
            fgets(msg.content, BUF_SIZE, stdin);
            // 客户输出exit,退出
            if (strncasecmp(msg.content, EXIT, strlen(EXIT)) == 0) {
                isClientwork = 0;
            } else { // 子进程将信息写入管道
                //清空发送缓存
                memset(send_buf,0,BUF_SIZE);
                //结构体转换为字符串
                memcpy(send_buf,&msg,sizeof(msg));
                if (write(pipe_fd[1], send_buf, sizeof(send_buf)) < 0) { 
                    perror("fork error");
                    exit(-1);
                }
            }
        }
    } else { 
        //pid > 0 父进程
        //父进程负责读管道数据,因此先关闭写端
        close(pipe_fd[1]); 
 
        // 主循环(epoll_wait)
        while (isClientwork) {
            int epoll_events_count = epoll_wait(epfd, events, 2, -1);
 
            //处理就绪事件
            for (int i = 0; i < epoll_events_count; ++i)
            {
                memset(recv_buf, 0, sizeof(recv_buf));
                //服务端发来消息
                if (events[i].data.fd == sock)
                {
                    //接受服务端广播消息
                    int ret = recv(sock, recv_buf, BUF_SIZE, 0);
                    //清空结构体
                    memset(&msg, 0, sizeof(msg));
                    //将发来的消息转换为结构体
                    memcpy(&msg, recv_buf, sizeof(msg));
 
                    // ret= 0 服务端关闭
                    if (ret == 0) {
                        cout<<"Server closed connection: "<<sock<<endl;
                        close(sock);
                        isClientwork = 0;
                    } else {
                        cout<<msg.content<<endl;
                    }
                } else { //子进程写入事件发生,父进程处理并发送服务端
                    //父进程从管道中读取数据
                    int ret = read(events[i].data.fd, recv_buf, BUF_SIZE);
                    // ret = 0
                    if (ret == 0) {
                        isClientwork = 0;
                    } else {
                        // 将从管道中读取的字符串信息发送给服务端
                        send(sock, recv_buf, sizeof(recv_buf), 0);
                    }
                }
            }//for
        }//while
    }
    
    // 退出进程
    Close();
}

客户端启动 ClientMain.cpp

#include "Client.h"
 
// 客户端主函数
// 创建客户端对象后启动客户端
int main(int argc, char *argv[]) {
    Client client;
    client.Start();
    return 0;
}

服务器端启动 ServerMain.cpp

#include "Server.h"
 
// 服务端主函数
// 创建服务端对象后启动服务端
int main(int argc, char *argv[]) {
    Server server;
    server.Start();
    return 0;
}

makefile文件 Makefile

CC = g++
CFLAGS = -std=c++11
 
all: ClientMain.cpp ServerMain.cpp Server.o Client.o
    $(CC) $(CFLAGS) ServerMain.cpp  Server.o -o chatroom_server
    $(CC) $(CFLAGS) ClientMain.cpp Client.o -o chatroom_client
 
Server.o: Server.cpp Server.h Common.h
    $(CC) $(CFLAGS) -c Server.cpp
 
Client.o: Client.cpp Client.h Common.h
    $(CC) $(CFLAGS) -c Client.cpp
 
clean:
    rm -f *.o chatroom_server chatroom_client

代码下载: https://download.csdn.net/download/boo12355/10883530
引自: https://blog.csdn.net/lewis1993_cpapa/article/details/80589717

相关文章

网友评论

      本文标题:linux下聊天demo支持私聊和群发

      本文链接:https://www.haomeiwen.com/subject/dwzslqtx.html