基于Qt的简易聊天室设计

服务器端实现:
第一步:封装自己的tcp通信类
//tcpserver.h
#ifndef TCPSOCKETSERVER_H
#define TCPSOCKETSERVER_H

#include <string>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>         /* superset of previous */
#include <arpa/inet.h>

/*in_addr_t inet_addr(const char *cp);*/
/*uint16_t htons(uint16_t hostshort);*/
/*int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);*/
/*ssize_t recv(int sockfd, void *buf, size_t len, int flags);*/

class CTcpSocket{
public:
    CTcpSocket(int port);            
    ~CTcpSocket();                     

    int createSocket();                   /*创建套接字*/
    int getAccpet();                      /*接收客户端请求并创建一个用于通信的套接字*/
    std::string socketRecv(int clientSocketfd);             /*接收数据*/
    bool socketSend(int clientSocketfd,std::string strSend); /*发送数据*/
    void closeCliSocket(int clientfd);
    void closeScvSocket();

private:
    int m_nPort;    
    int m_nServerfd;
	struct sockaddr_in m_sock_addr,m_client_addr;
	socklen_t m_addrlen;

    char *m_pRcvbuf;
	char *m_pSendbuf;
};

#endif

//tcpserver.cpp
#include "tcpserver.h"

#define BUF_SIZE 1024

CTcpSocket::CTcpSocket(int port)
{
	m_nPort = port;
	m_pSendbuf = new char[BUF_SIZE];
	if(!m_pSendbuf)
	{
		fprintf(stderr,"mem alloc failure!\n");
	}
	m_pRcvbuf = new char[BUF_SIZE];
	if(!m_pRcvbuf)
	{
		fprintf(stderr,"mem alloc failure!\n");
	}
}

CTcpSocket::~CTcpSocket()
{	
	if (m_pSendbuf)
	{
		delete[] m_pSendbuf;
		m_pSendbuf = NULL;
	}
	if (m_pRcvbuf)
	{
		delete[] m_pRcvbuf;
		m_pRcvbuf = NULL;
	}
}

int CTcpSocket::createSocket()
{
	//step 1 create socket
	m_nServerfd = socket(AF_INET, SOCK_STREAM, 0);
	if (m_nServerfd < 0)
	{
		perror("socket()");
		return -1;
	}
	int nZero = 1;
	int ret = setsockopt(m_nServerfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nZero, sizeof(int));
	if (ret < 0)
	{
		perror("setsocket()");
		return -2;
	}
	//step 2 bind socket
	m_sock_addr.sin_family = AF_INET;
	m_sock_addr.sin_port = htons(m_nPort);
	//sock_addr.sin_addr.s_addr = inet_addr("192.168.155.6");
	m_sock_addr.sin_addr.s_addr = INADDR_ANY; //INADDR_ANY
	ret = bind(m_nServerfd, (struct sockaddr *)&m_sock_addr, sizeof(m_sock_addr));
	if (ret < 0)
	{
		perror("bind()");
		return -3;
	}
	//step 3 start listen
	ret = listen(m_nServerfd, 5); //Up to 11 device connections are allowed
	if (ret < 0)
	{
		perror("listen()");
		return -4;
	}
	return m_nServerfd;
}

int CTcpSocket::getAccpet()
{
	//step 4 receive client request and create a new socket
	int nRetClientfd = accept(m_nServerfd, (struct sockaddr *)&m_client_addr, &m_addrlen);
	if (nRetClientfd < 0)
	{
		perror("accept()");
		return -1;
	}
	return nRetClientfd;
}

std::string CTcpSocket::socketRecv(int clientSocketfd)
{
	//step 4 read data buf
	memset(m_pRcvbuf, 0, BUF_SIZE);
	int ret = recv(clientSocketfd, m_pRcvbuf, BUF_SIZE, 0);
	if (ret < 0)
	{
		perror("recv()");
		return "recv error";
	}
	if (ret == 0)
	{
		close(clientSocketfd);
		return "client exit";
	}
	std::string retstr = std::string(m_pRcvbuf, ret);
	return retstr;
}

bool CTcpSocket::socketSend(int clientSocketfd,std::string strSend)
{
	memset(m_pSendbuf, '\0', BUF_SIZE);
	strcpy(m_pSendbuf, strSend.c_str());
	int ret = send(clientSocketfd, m_pSendbuf, BUF_SIZE, 0);
	if (ret < 0)
	{
		perror("send()");
		return false;
	}
	return true;
}

void CTcpSocket::closeScvSocket()
{
	close(m_nServerfd);
}

void CTcpSocket::closeCliSocket(int clientfd)
{
	close(clientfd);
}
第二步:main.cpp逻辑处理
#include "tcpserver.h"
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <iterator>
#include <pthread.h>
#include <unistd.h>
#include <ctime>

struct chatroom 
{
    std::string room;
    std::string user;
};

std::map<int, struct chatroom> mapClientfd; //用于保存客户端套接字
CTcpSocket tcpsocket(8889);

void *thread_start(void *arg);
bool deleteRoom(int clientfd);
std::vector<std::string> mysplite(const std::string &strSrc, const std::string &strSep);
bool addUser(std::string strRoomNo, std::string strUser, int clientfd);
void sendUserInfo(int clientfd, struct chatroom mychatroom);
std::string getSysDate();
void sendMsgtoAllcli(int clientfd, std::string strSend);

int main()
{
    pthread_t tid;
    tcpsocket.createSocket();
    while (1)
    {
        int clientfd = tcpsocket.getAccpet();
        if (clientfd < 0)
        {
            std::cout << "server accept exeception!" << std::endl;
            exit(-1);
        }
        printf("clientfd:%d",clientfd);
        int nRet = pthread_create(&tid, NULL, thread_start, (void *)&clientfd);
        if (nRet != 0)
        {
            perror("pthreadd_create()");
            exit(-2);
        }
        pthread_detach(tid);
    }
    return 0;
}

//线程并发
void *thread_start(void *arg)
{
    int clientfd = *((int *)arg);
    while (1)
    {
        std::string strRet = tcpsocket.socketRecv(clientfd);
        if (strRet == "client exit")
        {
            //先保存要删除的房间号和用户名
            struct chatroom mychatroom = mapClientfd[clientfd];
            //删除键为clientfd的元素
            deleteRoom(clientfd);
            sendUserInfo(clientfd,mychatroom);
            tcpsocket.closeCliSocket(clientfd);
            return NULL;
        }

        std::cout << "服务器收到:" << strRet << std::endl;
        //命令解析 判断前面几个字符
        std::cout << "(0,7):" << strRet.substr(0,7) << std::endl;  
        if(strRet.substr(0,7) == "useradd")
        {
            //获得房间号
            std::string strRoomNo = mysplite(strRet.substr(7,strRet.length())," ")[0];
            //获得用户昵称
            std::string strUser = mysplite(strRet.substr(7,strRet.length())," ")[1];
            //添加用户
            addUser(strRoomNo,strUser,clientfd);
        }
        else if (strRet.substr(0,7) == "dispsvr")
        {
            //获取系统时间
            std::string strDate = getSysDate();
            //拼装数据
            std::string strSend = strRet.substr(0, 7); //dispsvr
            strSend += strDate;
            strSend += "@";
            strSend += mapClientfd[clientfd].user;
            strSend += ":";
            strSend += strRet.substr(7, strRet.length());
            std::cout << strSend << std::endl;
            //群发
            sendMsgtoAllcli(clientfd, strSend);
        }
    }
}

//删除map指定元素
bool deleteRoom(int clientfd)
{
    bool bRet = false;
    for (std::map<int, struct chatroom>::iterator iter = mapClientfd.begin(); iter != mapClientfd.end(); iter++)
    {
        if (iter->first == clientfd)
        {
            std::cout << "删除房间:" << iter->second.room << "用户:" << iter->second.user << std::endl;
            mapClientfd.erase(iter);
            bRet = true;
        }
    }
    return bRet;
}

//添加好友
bool addUser(std::string strRoomNo, std::string strUser, int clientfd)
{
    //判断是否已有该用户
    bool flag = true;
    for (std::map<int, struct chatroom>::iterator iter = mapClientfd.begin(); iter != mapClientfd.end(); iter++)
    {
        //房间中用户已存在
        if (strRoomNo == iter->second.room && strUser == iter->second.user)
        {
            flag = false;
        }
    }
    if (flag) //该昵称可以在该房间使用
    {
        struct chatroom myroom;
        myroom.room = strRoomNo;
        myroom.user = strUser;
        //添加用户
        mapClientfd[clientfd] = myroom;
        sendUserInfo(clientfd,myroom);
    }
    else
    {
        bool bRet = tcpsocket.socketSend(clientfd, "existed");
        if (bRet == false)
        {
            std::cout << "send data failure! 165" << std::endl;
        }
    }
    return true;
}

//转发当前用户
void sendUserInfo(int clientfd, struct chatroom mychatroom)
{
     //封装即将发送给客户端的数据
    std::string strSend = "curroom";
    for (std::map<int, struct chatroom>::iterator iter = mapClientfd.begin(); iter != mapClientfd.end(); iter++)
    {
        if(iter->second.room == mychatroom.room)
        {
            strSend += iter->second.user;
            strSend += " ";
        }
    }
    //将当前所有用户广播
    for (std::map<int, struct chatroom>::iterator iter = mapClientfd.begin(); iter != mapClientfd.end(); iter++)
    {
        //转发到同组房间
        if(iter->second.room == mychatroom.room)
        {
            //发送数据
            bool bRet = tcpsocket.socketSend(iter->first, strSend);
            if (bRet == false)
            {
                std::cout << "send data failure! 151" << std::endl;
            }
            else
            {
                std::cout << strSend << "转发到:" << iter->first << "成功" << std::endl;
            } 
        }
    }
}

//自定义字符串分割函数
std::vector<std::string> mysplite(const std::string &strSrc, const std::string &strSep)
{
    using namespace std;
    vector<string> vecResult;
    int offset = 0;
    int index = 0;
    while (index != std::string::npos)
    {
        index = strSrc.find(strSep, offset);
        string strTemp = strSrc.substr(offset, index - offset);
        vecResult.push_back(strTemp);
        offset = index + strSep.length();
    }
    return vecResult;
}

//获取系统时间
std::string getSysDate()
{
    //获取系统时间
    time_t timep;
    struct tm *p;
    time(&timep);
    p = gmtime(&timep);
    int year = p->tm_year + 1990;
    int month = p->tm_mon + 1;
    int day = p->tm_mday;
    int hour = p->tm_hour + 8;
    int minute = p->tm_min;
    int second = p->tm_sec;
    char charDate[256] = {'\0'};
    sprintf(charDate,"%d-%d-%d %d:%d:%d",year,month,day,hour,minute,second);
    return charDate;
}

void sendMsgtoAllcli(int clientfd, std::string strSend)
{
    for (std::map<int, struct chatroom>::iterator iter = mapClientfd.begin(); iter != mapClientfd.end(); iter++)
    {
        //转发到同组房间
        if(iter->second.room == mapClientfd[clientfd].room)
        {
            //发送数据
            bool bRet = tcpsocket.socketSend(iter->first, strSend);
            if (bRet == false)
            {
                std::cout << "send data failure! 248" << std::endl;
            }
            else
            {
                std::cout << strSend << "转发到:" << iter->first << "成功" << std::endl;
            } 
        }
    }
}


第三步:makefile文件
//makefile文件
CC		=       g++
MV      =       mv
CP      =       cp
RM      =       rm

LIBTHREAD = -lpthread 

CFLAGS = -g 

BIN = ../bin

all: $(BIN)/server

$(BIN)/server:tcpserver.o main.o
	$(CC) -o $@ $^ $(LIBTHREAD) 
	
.SUFFIXES:.cpp .o
.cpp.o:
	$(CC) $(CFLAGS) -c $*.cpp -o $*.o -Wall

.PHONY:clean
clean:
	rm -rf *.o  $(BIN)/server

Qt客户端实现
#include "chatwidget.h"
#include "ui_chatwidget.h"

ChatWidget::ChatWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ChatWidget)
{
    ui->setupUi(this);
    ui->textEdit_ALL->setStyleSheet("QTextEdit { background: #cc9966 }");
    ui->textEdit_SEND->setStyleSheet("QTextEdit { background: #cccccc }");
    ui->textEdit_RECV->setStyleSheet("QTextEdit { background: #cccccc }");
    ui->textEdit_SEND->installEventFilter(this);//设置完后自动调用其eventFilter函数

    //设置焦点
    setFocustoEnd();

    tcpsocket = new QTcpSocket(this);
    //接收数据
    connect(tcpsocket,&QTcpSocket::readyRead,[=]()
    {
        QString strRecv = tcpsocket->readAll().data();
        qDebug() << strRecv;
        if(!strRecv.isEmpty())
        {
            if(strRecv.mid(0,7) == "curroom")
            {
                //只获取昵称
                ui->textEdit_ALL->clear();
                QStringList strList = strRecv.mid(7,strRecv.length()).split(" ");
                for(int i=0; i<strList.size(); i++)
                {
                    QString strMsg = QString("%1%2%3").arg("<font color=\"#FFFFFF\">").arg(strList[i]).arg("</font>");
                    ui->textEdit_ALL->append(strMsg);
                }
            }
            else if(strRecv.mid(0,7) == "dispsvr")
            {
                QString strMsg = QString("%1%2%3").arg("<font color=\"#111111\">").arg(strRecv.mid(7,strRecv.length())).arg("</font>");
                ui->textEdit_RECV->append(strMsg);
            }
            else
            {
                QMessageBox::warning(this,"温馨提示","昵称已被使用,请更换");
            }
            QApplication::alert(this);
        }
    });
}

ChatWidget::~ChatWidget()
{
    delete ui;
    delete tcpsocket;
}

//退出
void ChatWidget::on_pushButton_EXIT_clicked()
{
    this->close();
}

//清空
void ChatWidget::on_pushButton_CLEAR_clicked()
{
    ui->textEdit_SEND->clear();
    setFocustoEnd();
}

//发送
void ChatWidget::on_pushButtonSEND_clicked()
{
    //获取用户输入的信息
    QString strData = "dispsvr";
    strData += ui->textEdit_SEND->toPlainText();
    if(strData.mid(0,7) == "dispsvr" && strData.length() > 7)
    {
        tcpsocket->write(strData.toUtf8());
        tcpsocket->waitForBytesWritten();
        this->tcpsocket->flush();
        ui->textEdit_SEND->clear();
        setFocustoEnd();
    }
}

//回车键发送
bool ChatWidget::eventFilter(QObject *target, QEvent *event)
{
    if(target == ui->textEdit_SEND)
    {
        if(event->type() == QEvent::KeyPress)//回车键
        {
             QKeyEvent *k = static_cast<QKeyEvent *>(event);
             if(k->key() == Qt::Key_Return)
             {
                 //获取用户输入的信息
                 QString strData = "dispsvr";
                 strData += ui->textEdit_SEND->toPlainText();
                 //发送消息
                 tcpsocket->write(strData.toUtf8());
                 tcpsocket->waitForBytesWritten();
                 this->tcpsocket->flush();
                 if(strData.mid(0,7) == "dispsvr" && strData.length() > 7)
                 {
                     ui->textEdit_SEND->clear();
                     setFocustoEnd();
                 }
                 return true;
             }
        }
    }
    return QWidget::eventFilter(target,event);
}

//登录
void ChatWidget::on_pushButton_login_clicked()
{
    //连接到服务器
    QString ip = ui->lineEdit_IP->text();
    quint16 port = ui->lineEdit_PORT->text().toUShort();
    tcpsocket->connectToHost(QHostAddress(ip),port);
    //发送房间号+昵称
    QString room = "useradd";
    room += ui->lineEdit_ROOM->text();
    room += " ";
    room += ui->lineEdit->text();
    tcpsocket->write(room.toUtf8());
    setFocustoEnd();
}

void ChatWidget::setFocustoEnd()
{
    //设置自动换行
    ui->textEdit_SEND->setFocus();
    // 光标移动到最后, 并设置拥有焦点
    QTextCursor textcusor = ui->textEdit_SEND->textCursor();
    textcusor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
    ui->textEdit_SEND->setTextCursor(textcusor);
    ui->textEdit_SEND->setFocus(Qt::MouseFocusReason);
}

void ChatWidget::on_pushButtot_CLEAR_clicked()
{
    ui->textEdit_RECV->clear();
    ui->textEdit_SEND->clear();
}

运行步骤

一、服务器端程序放到Linux系统下使用make编译
二、将编译生成的目标文件放到阿里服务器下执行:nohup ./server &
三、运行客户端,修改房间号和昵称登录。
运行效果如图所示

  • 5
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fjxx_psy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值