C++多人聊天室项目(windows客户端、linux客户端、linux服务端)

小项目多人聊天室,主要用socket进行网络通信,使用花生壳内网穿透进行远程连接,使用dos窗口进行输入显示聊天记录
话不多数,直接进入正题(先整个目录)

linux下服务端

使用epoll进行多路i/o监听,收到某个客户端的消息后进行分发,使用map存储客户端信息,以下为代码

#include<iostream>
#include<string>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<map>
#include <arpa/inet.h>
const int MAX_CONN=1024;
using namespace std;
struct client{
    int sockfd;
    std::string daohao;//道友道号
};
int main(){
    //创建epoll
    int epld=epoll_create(100);
    if(epld<0){
        perror("epoll is error");
        return -1;
    }
    //创建监听套接字
    int listenfd=socket(AF_INET,SOCK_STREAM,0);
    if(listenfd<0){
        perror("listen is eror");
        return -1;
    }

    //绑定地址
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_addr.s_addr=htonl(INADDR_ANY);
    addr.sin_port=htons(6789);

    int ret =bind(listenfd,(sockaddr*)&addr,sizeof(addr));
    if(ret<0){
        perror("bind is error");
        return -1;
    }

    //  保存客户端信息
    map<int,client>conn_clients;

    //开始监听
    ret = listen (listenfd,1024);
    if(ret<0){
        perror("listen is error");
        return -1;
    }
    //加入epoll
    struct epoll_event l_epoll;
    l_epoll.events = EPOLLIN;
    l_epoll.data.fd=listenfd;

    ret = epoll_ctl(epld,EPOLL_CTL_ADD,listenfd,&l_epoll);
    if(ret<0){
        perror("epoll_ctl is error");
        return -1;
    }
    //循环监听
    while(1){
        struct epoll_event c_epoll[MAX_CONN];//监听到有数据的fd放的地方
        int n=epoll_wait(epld,c_epoll,MAX_CONN,-1);

        if(n<0){
        perror("epoll_wait is error");
        return -1;
        }

        for(int i=0;i<n;i++){
            int fd=c_epoll[i].data.fd;
            if(fd==listenfd){
                //有人连进来了
                struct sockaddr_in client_addr;
                socklen_t client_len=sizeof(client_addr);
                int clientfd=accept(listenfd,(sockaddr*)&client_addr,&client_len);
                //打印输出接入客户端
                char clientIP[16];
                inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,clientIP,sizeof(clientIP));
                unsigned short clientport =ntohs(client_addr.sin_port);
                cout<<"ip:"<<clientIP<<" "<<"端口"<<clientport<<endl;
               
                if(clientfd<0){
                    perror("epoll_wait is error");
                    continue;;
                }
                //把这位道友放到epoll里
                struct epoll_event c_epoll;
                c_epoll.events = EPOLLIN;
                c_epoll.data.fd=clientfd;
                ret = epoll_ctl(epld,EPOLL_CTL_ADD,clientfd,&c_epoll);
                
                if(ret<0){
                    perror("epoll_ctl is error");
                    continue;
                }
                client new_client;
                new_client.sockfd=clientfd;
                new_client.daohao="小王八不起名!";
                conn_clients[clientfd]=new_client;
                //通知大家一声
            }else{
                //有道友传音中
                char buffer[1024];
               int n= read(fd,buffer,1024);
               if(n<0){
                //道友传音失败
                perror("read is error");
                continue;
               }else if(n==0){
                //道友离开了
                close(fd);
                epoll_ctl(epld,EPOLL_CTL_DEL,fd,0);
                conn_clients.erase(fd);
               }else{
                //道友传音中成功
                std::string msg(buffer,n);
                if(conn_clients[fd].daohao=="小王八不起名!")
                {//小王八起名了
                    conn_clients[fd].daohao=msg;
                }else{
                    //聊天消息
                    std::string name=conn_clients[fd].daohao;
                    cout<<msg<<endl;
                    //发给所有人
                    for(auto&c:conn_clients){
                        write(c.first,('['+name+']'+": "+msg).c_str(),msg.size()+name.size()+4);

                    }
                }
               }
            }
        }

    }

//关闭epoll
for(auto&c:conn_clients){
close(c.first);
epoll_ctl(epld,EPOLL_CTL_DEL,c.first,0);
}
close(epld);
close(listenfd);
}

Windows下客户端

windows(GBK)与linux(UTF8)编码方式不同,要进行转码工作,通过Unicode进行中继替换,客户端将windows的dos界面划分为两部分,在上面printf表明是大家的聊天信息,下面print则是自己的编辑区,还要根据中英文字符实现删除功能与消息多了自动滚动功能(没linux方便感觉),以下为代码

静态导出

改成release模式,在项目属性的代码生成中改为MT静态链接,然后直接生成,在编译输出目录下找到该exe文件

头文件

#pragma once
#pragma once
#include<stdio.h>
#include<string>
#include<WinSock2.h>
#include <Ws2tcpip.h>
#include<iostream>
#include<mutex>
#include<thread>
#include<conio.h>
#pragma comment (lib,"WS2_32.lib")

#define SERVER_IP "115.236.153.174"
#define SPEECH_PORT 55183

using namespace std;
class doscommend {
public:
	string GbkToUtf8(const char* src_str);
	string Utf8ToGbk(const char* src_str);
	void uiinit();
	void gotoxy(int x, int y);
	void printf_brank();
	void printf_fenge();
	void ui_log();
	void quit();
private:
	char line1[111];//分割线
	char line2[111];//空白字符
};
class client {
public:
	bool init(SOCKET* socket, sockaddr_in* socket_addr);
	void login();
	void edit_working();
	void revcwork(bool* isworing);
	void quit();
private:
	bool isHZ(char str[], int index);
	void printmsg(const string mesg);
	void editprint(int col, char ch);
	void editprint(int col, const char* ch);
	void clcedit();

	bool istoping;
	mutex my_mutex;
	doscommend m_dos;
	SOCKET* m_socket;//套接字
	sockaddr_in* m_socket_addr;//套接字地址

};

源文件

#include"client_speech.h"

string doscommend::GbkToUtf8(const char* src_str)
{
	string result;
	wchar_t* strSrc;
	char* szRes;
	int len = MultiByteToWideChar(CP_ACP, 0, src_str, -1, NULL, 0);
	strSrc = new wchar_t[len + 1];
	MultiByteToWideChar(CP_ACP, 0, src_str, -1, strSrc, len);

	len = WideCharToMultiByte(CP_UTF8, 0, strSrc, -1, NULL, 0, NULL, NULL);
	szRes = new char[len + 1];
	WideCharToMultiByte(CP_UTF8, 0, strSrc, -1, szRes, len, NULL, NULL);
	result = szRes;
	if (strSrc)
		delete[]strSrc;
	if (szRes)
		delete[]szRes;
	return result;
}
string doscommend::Utf8ToGbk(const char* src_str)
{
	string result;
	wchar_t* strSrc;
	char* szRes;
	int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0);
	strSrc = new wchar_t[len + 1];
	MultiByteToWideChar(CP_UTF8, 0, src_str, -1, strSrc, len);

	len = WideCharToMultiByte(CP_ACP, 0, strSrc, -1, NULL, 0, NULL, NULL);
	szRes = new char[len + 1];
	WideCharToMultiByte(CP_ACP, 0, strSrc, -1, szRes, len, NULL, NULL);
	result = szRes;
	if (strSrc)
		delete[]strSrc;
	if (szRes)
		delete[]szRes;
	return result;
}
void doscommend::printf_brank() {
	printf("%s\n", line2);
}
void doscommend::printf_fenge() {
	printf("%s\n", line1);
}
void doscommend::ui_log() {
	system("mode con lines=10 cols=30\n");
}

bool client::init(SOCKET* new_socket, sockaddr_in* new_socket_addr) {
	m_socket = new_socket;
	m_socket_addr = new_socket_addr;
	istoping = false;
	//1.网络服务初始化
	WSADATA data;
	int ret = WSAStartup(MAKEWORD(1, 1), &data);
	if (ret != 0)
		return false;
	//2.网络套接字
	*m_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

	//3.物理地址
	m_socket_addr->sin_family = PF_INET;
	inet_pton(AF_INET, SERVER_IP, &m_socket_addr->sin_addr);
	m_socket_addr->sin_port = htons(SPEECH_PORT);
	return true;

}

void client::login() {
	string daohao;
	m_dos.ui_log();
	printf("欢迎使用修仙聊天群\n\n");
	printf("道友道号为何?");
	getline(cin, daohao);
	m_dos.uiinit();
	string temputf8 = m_dos.GbkToUtf8(daohao.c_str());//转换编码
	send(*m_socket, temputf8.c_str(), temputf8.size(), 0);
}
void doscommend::gotoxy(int x, int y) {
	HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x,y };
	SetConsoleCursorPosition(hout, pos);
}
void doscommend::uiinit() {
	system("mode con lines=36 cols=110");
	system("cls");
	gotoxy(0, 33);//移动光标
	for (int i = 0; i < 110; i++) {
		line1[i] = '-';
		line2[i] = ' ';
	}
	line1[110] = 0;
	line2[110] = 0;
	printf("%s\n", line1);

}
void client::printmsg(const string mesg) {
	//上锁

	my_mutex.lock();
	static POINT pos = { 0,0 };
	m_dos.gotoxy(pos.x, pos.y);
	cout << mesg << endl;
	HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_SCREEN_BUFFER_INFO info;
	GetConsoleScreenBufferInfo(hout, &info);
	pos.x = info.dwCursorPosition.X;
	pos.y = info.dwCursorPosition.Y;
	if (pos.y >= 33) {
		m_dos.printf_brank();
		printf("\n\n");
		m_dos.gotoxy(0, 33);
		m_dos.printf_fenge();
		pos.y -= 1;
	}
	m_dos.gotoxy(1, 34);
	my_mutex.unlock();
}
void client::editprint(int col, char ch) {
	my_mutex.lock();
	m_dos.gotoxy(col, 34);
	printf("%c", ch);
	my_mutex.unlock();

}
void client::editprint(int col, const char* ch) {
	my_mutex.lock();
	m_dos.gotoxy(col, 34);
	printf("%s", ch);
	my_mutex.unlock();

}
void client::clcedit() {
	my_mutex.lock();
	m_dos.gotoxy(0, 34);
	m_dos.printf_brank();
	my_mutex.unlock();
}
void client::revcwork(bool* isworing) {
	char buff[4096];
	while (!istoping) {
		int ret = recv(*m_socket, buff, sizeof(buff), 0);
		if (ret <= 0) {
			printf("服务器出错!\n");
			break;
		}
		string temputf8(buff, ret);
		printmsg(m_dos.Utf8ToGbk(temputf8.c_str()));
	}
	*isworing = false;

}
bool client::isHZ(char str[], int index) {
	//一个汉字两个字节,第一个字节<0
	//一个英文字符,只有一个字节,>0
	//从头遍历出现一个负数就是汉字
	int i = 0;
	while (i < index) {
		if (str[i] > 0) {
			i++;
		}
		else {
			i += 2;
		}
	}
	if (i == index)
		return false;
	else
		return true;
}
void client::edit_working() {
	while (!istoping) {
		char buff[1024];//保存字符串
		memset(buff, 0, sizeof(buff));
		clcedit();
		editprint(0, '>');
		int len = 0;
		while (1) {
			if (_kbhit()) {
				char c = _getch();
				if (c == '\r') {
					//输入回车
					break;
				}
				else if (c == 8) {
					//退格键阿斯克码为8
					if (len == 0)
						continue;
					if (isHZ(buff, len - 1)) {
						editprint(len + 1, "\b\b  \b\b");
						buff[len - 1] = 0;
						buff[len - 2] = 0;
						len -= 2;
					}
					else {
						editprint(len + 1, "\b \b");
						buff[len - 1] = 0;
						len -= 1;
					}
					continue;
				}
				my_mutex.lock();
				do {
					printf("%c", c);
					buff[len++] = c;
				} while (_kbhit() && (c = _getch()));
				my_mutex.unlock();
			}
		}
		if (len == 0) {
			continue;
		}
		//判断是否为退出信息
		string tempgbk(buff, len);
		if (strncmp(buff, "我妈叫我回去吃饭!", 4) == 0)
			istoping = true;
		//发送信息
		string temputf8 = m_dos.GbkToUtf8(buff);//转换编码
		send(*m_socket, temputf8.c_str(), temputf8.size(), 0);
	}
}
void client::quit() {
	my_mutex.lock();
	for (int i = 0; i < 33; i++) {
		m_dos.gotoxy(0, i);
		m_dos.printf_brank();
		Sleep(50);
		m_dos.gotoxy(0, i);
		printf("%s", "正在回家吃饭!");
	}
	my_mutex.unlock();
}
int main(void) {
	client windows_client;
	SOCKET server_socket;//套接字
	sockaddr_in socket_addr;//套接字地址
	bool isworing = true;//运行标志
	if (!windows_client.init(&server_socket, &socket_addr)) {
		printf("初始化失败!\n");
		return -1;
	}
	int ret = connect(server_socket, (SOCKADDR*)&socket_addr, sizeof(socket_addr));
	if (ret != 0) {
		printf("连接失败\n");
		return -2;
	}
	//登录聊天室
	windows_client.login();
	//接受信息
	thread m_revcmes(&client::revcwork, &windows_client, &isworing);

	//编辑信息
	windows_client.edit_working();

	m_revcmes.join();

	while (isworing) {
		Sleep(2000);
	}
	windows_client.quit();

	return 0;
}

linux下客户端

linux比较简单,也不用转码,直接收发就行,自带删除功能,就不上下分两部分了,类对讲机型聊天(笑,半双工)
想开个新窗口专门用来聊天整个启动器

编译

g++ 1.cpp -o start
g++ client_linux.cpp -o client_linux -pthread

启动器

#include<stdlib.h>
int main(){
    system("gnome-terminal -e ./client_linux");
}

源代码

#include<iostream>
#include<string>
#include<string.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<map>
#include <arpa/inet.h>
#include <fcntl.h>
#include <termios.h>

#define IP "115.236.153.174"
#define PORT 55183
#define BUF_SIZE 1024
using namespace std;

bool isstoping=true;
pthread_mutex_t mutex;

void *revc_work(void *arg){
    int socket_temp=*((int *)arg);
    char recbuf[BUF_SIZE];
    while(!isstoping){
        
        int len=recv(socket_temp,recbuf,sizeof(recbuf)-1,0);
        if(len==-1){
           break;
        }
        recbuf[len]='\0';
        pthread_mutex_lock(&mutex);
        printf("%s\n",recbuf);
        pthread_mutex_unlock(&mutex);
    }
}

int main(){
    isstoping=false;
    pthread_mutex_init(&mutex,NULL);
    struct termios tm, tm_old;

    //创建socket
    int c_socket=socket(AF_INET,SOCK_STREAM,0);
    if(c_socket<0){
        perror("c_socket is eror");
        return -1;
    }
    //绑定服务器端口
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(PORT);
    inet_pton(AF_INET,IP,&addr.sin_addr);

    int ret =connect(c_socket,(sockaddr*)&addr,sizeof(addr));
    if(ret<0){
        perror("connect is error");
        return -1;
    }else{
        cout<<"欢迎进入修仙聊天群"<<endl;
        cout<<"  道友道号为何?";
    }
 
    //接收消息
     pthread_t thread_recv;
      if(pthread_create(&thread_recv, NULL, revc_work,&c_socket)!= 0) {
        perror("thread_recv is error");
        return -1;
        }

    //发消息
    string s;
    while(!isstoping){
       //if(kbhit){
        //pthread_mutex_lock(&mutex);
        cout<<">";
        getline(cin,s); 
        send(c_socket,s.c_str(),s.size(),0);
        printf("\033[1A"); //先回到上一行
        printf("\033[K");  //清除该行
        if(s=="我妈叫我"){
            isstoping=true;
            for(int i=0;i<5;i++){
                printf("\033[K");  //清除该行
                printf("%s","我妈叫我回去吃饭!\n");
                sleep(1);
                printf("\033[1A"); //先回到上一行
                printf("\033[1A"); //先回到上一行
            }
        }
        // pthread_mutex_unlock(&mutex);
   // }
    }

    pthread_join(thread_recv,NULL);
    return 0;

}

花生内网穿透

附加穿透教程

sudo dpkg -i phddns_5_1_amd64.deb

检查

phddns
(phddns  |start|status|stop|restart|reset|enable|disable|version)

然后上官网
主机就填私有ip就行,端口写服务器的端口(可用22shell的测试下)
1.
在这里插入图片描述
2.
在这里插入图片描述

在这里插入图片描述
然后诊断找到域名ip,记下以后客户端填这个ip地址
如果诊断不成功别慌可能是你服务器没开,在编辑里吧端口改成22,再诊断测试下

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值