服务器端实现:
第一步:封装自己的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 &
三、运行客户端,修改房间号和昵称登录。