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
网友评论