linux在线聊天报告,linux下聊天demo支持私聊和群发

1、公共头文件 Common.h

#ifndef CHATROOM_COMMON_H

#define CHATROOM_COMMON_H

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

// 默认服务器端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

#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 clients_list;

};

#endif

server端实现 Server.cpp

//Server.cpp

#include

#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<

//创建监听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<

//在内核中创建事件表 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<

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::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::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

#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

#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<

// 创建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"<

cout<

// 如果客户端运行正常则不断读取输入发送给服务端

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<

close(sock);

isClientwork = 0;

} else {

cout<

}

} 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值