本文讲到在qt环境下多线程实现的的c和c++的tcp通信,具体原理不做描述,如三次握手四次挥手。
服务器:
1、定义文件描述符,套接字结构体sockaddr_in。
2、创建socket(AF_INET,SOCK_STREAM,0)和给文件描述符赋值;
3、套接字结构体置0,bzero获取memset实现。
4、设置套接字结构体,分别有地址类型,端口和ip,注ip和端口的赋值涉及到字节序转换,需要用到htons,htonl,inet_addr等函数。
5、绑定文件描述符和套接字结构体。bind函数实现。
6、监听客户端,listen函数实现。
7、接收客户端请求,如果有请求创建新的套接字结构体和文件描述符,accept函数实现,注它为阻塞函数。
8、接收或者发送。recv和send函数实现,注recv默认为阻塞模式,关闭程序前需要使用close或者shutdown来关闭文件文件描述符。
客户端:
1、定义文件描述符和套接字结构体。
2、创建socket,且返回值赋给文件描述符。
3、清空套接字结构体。
4、设置套接字结构体。
5、连接服务器,使用connect函数实现。
6、接收或者发送。
服务器
#ifndef WIDGET_H
#define WIDGET_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "cthread.h"
#include "clientwidget.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
public slots:
void slotRead(QString str, sockaddr_in socket);
void slotNewConnect(int socketfileId, sockaddr_in socketaddr);
private slots:
void on_startlisten_pushButton_clicked();
void on_craete_client_pushButton_clicked();
void on_send_pushButton_clicked();
signals:
void sendSig(QString str);
protected:
void closeEvent(QCloseEvent *event);
private:
Ui::Widget *ui;
int m_serversocket;
sockaddr_in m_socketaddr;
QList<CThread*> m_threadList;
QList<ClientWidget*> m_clientList;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include
#include
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
qRegisterMetaType<sockaddr_in>("sockaddr_in");
//1、定义套接字,文件描述符,在头文件实现
//2、创建socket (第一个参数表示使用的地址类型,一般都是ipv4,AF_INET;第二个参数表示套接字类型:tcp:面向连接的稳定数据传输SOCK_STREAM;第三个参数设置为0)
m_serversocket = socket(AF_INET,SOCK_STREAM,0);
if (m_serversocket < 0)
{
QMessageBox::information(this,"error","创建套接字失败");
}
bzero(&m_socketaddr,sizeof(m_socketaddr)); //清空套接字
//3、设置套接字 (初始化服务器端的套接字,并用htons和htonl将端口和地址由主机字节序转成网络字节序)
m_socketaddr.sin_family = AF_INET;
m_socketaddr.sin_port = htons(5555);
m_socketaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//4、绑定套接字 (绑定文件描述符和套接字结构体)
int ret = bind(m_serversocket,(struct sockaddr*)&m_socketaddr,sizeof(m_socketaddr));
if (ret < 0)
{
QMessageBox::information(this,"error","绑定主机失败");
}
}
Widget::~Widget()
{
delete ui;
}
void Widget::closeEvent(QCloseEvent *event)
{
for (int i = 0; i < m_clientList.size(); ++i)
m_clientList.at(i)->close();
for (int i = 0; i < m_threadList.size(); ++i)
m_threadList.at(i)->exitthread();
}
//开始监听
void Widget::on_startlisten_pushButton_clicked()
{
//5、监听客户端,是否有新的连接
int ret = listen(m_serversocket,5);
if (ret < 0)
{
QMessageBox::information(this,"error","监听开启失败");
return;
}
//创建线程,在线程中来循环接收客户端请求
CThread* thread = new CThread(this,m_serversocket,1);
connect(thread,SIGNAL(newconnect(int,sockaddr_in)),this,SLOT(slotNewConnect(int,sockaddr_in)));
thread->start();
m_threadList.append(thread);
}
//创建客户端
void Widget::on_craete_client_pushButton_clicked()
{
ClientWidget* w = new ClientWidget;
w->show();
m_clientList.append(w);
}
//发送
void Widget::on_send_pushButton_clicked()
{
emit sendSig(ui->send_textEdit->toPlainText());
}
//接收
void Widget::slotRead(QString str,sockaddr_in socket)
{
ui->recv_textEdit->append(QString("端口:%1-%2").arg(socket.sin_port).arg(str));
}
//新来的客户端请求
void Widget::slotNewConnect(int socketfileId,sockaddr_in socketaddr)
{
ui->listen_textEdit->append(QString("新连接:%1,地址:%2, 端口:%3").arg(socketfileId).arg(inet_ntoa(socketaddr.sin_addr)).arg(socketaddr.sin_port));
//创建线程来接收消息和发送消息
CThread* recvthread = new CThread(this,socketfileId,0,0);
connect(recvthread,SIGNAL(recvSig(QString,sockaddr_in)),this,SLOT(slotRead(QString,sockaddr_in)));
recvthread->setsocket(socketaddr);
recvthread->start();
CThread* sendthread = new CThread(this,socketfileId,0,1);
connect(this,SIGNAL(sendSig(QString)),sendthread,SLOT(slotsend(QString)));
sendthread->start();
m_threadList.append(recvthread);
m_threadList.append(sendthread);
}
客户端
#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H
#include
#include
#include
#include
#include
#include "cthread.h"
namespace Ui {
class ClientWidget;
}
class ClientWidget : public QWidget
{
Q_OBJECT
public:
explicit ClientWidget(QWidget *parent = nullptr);
~ClientWidget();
public slots:
void slotRead(QString str, sockaddr_in socket);
private slots:
void on_send_pushButton_clicked();
signals:
void sendSig(QString str);
protected:
void closeEvent(QCloseEvent *event);
private:
Ui::ClientWidget *ui;
int m_socketfileId;
sockaddr_in m_clientsocket;
QList<CThread*> m_threadList;
};
#endif // CLIENTWIDGET_H
#include "clientwidget.h"
#include "ui_clientwidget.h"
#include
ClientWidget::ClientWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ClientWidget)
{
ui->setupUi(this);
qRegisterMetaType<sockaddr_in>("sockaddr_in");
//1、定义套接字和文件描述符,在头文件实现
//2、创建套接字
m_socketfileId = socket(AF_INET,SOCK_STREAM,0);
//3、设置套接字结构体
m_clientsocket.sin_family = AF_INET;
m_clientsocket.sin_port = htons(5555);
m_clientsocket.sin_addr.s_addr = inet_addr("192.168.27.89"); //inet_addr从字符串类型转换为网络字符序
//4、连接服务器
if (::connect(m_socketfileId,(struct sockaddr*)&m_clientsocket,sizeof(m_clientsocket)) < 0)
{
QMessageBox::information(this,"error","连接失败");
return ;
}
//创建线程来接收消息和发送消息
CThread* recvthread = new CThread(this,m_socketfileId,0,0);
connect(recvthread,SIGNAL(recvSig(QString,sockaddr_in)),this,SLOT(slotRead(QString,sockaddr_in)));
recvthread->start();
CThread* sendthread = new CThread(this,m_socketfileId,0,1);
connect(this,SIGNAL(sendSig(QString)),sendthread,SLOT(slotsend(QString)));
sendthread->start();
m_threadList.append(recvthread);
m_threadList.append(sendthread);
}
ClientWidget::~ClientWidget()
{
delete ui;
}
void ClientWidget::closeEvent(QCloseEvent *event)
{
for (int i = 0; i < m_threadList.size(); ++i) {
m_threadList.at(i)->exitthread();
}
}
void ClientWidget::slotRead(QString str,sockaddr_in socket)
{
ui->recv_textEdit->append(str);
}
void ClientWidget::on_send_pushButton_clicked()
{
emit sendSig(ui->send_textEdit->toPlainText());
}
线程
#ifndef CTHREAD_H
#define CTHREAD_H
#include
#include
#include
#include
#include
class CThread : public QThread
{
Q_OBJECT
public:
CThread(QObject* parent,int socketId,int type = 0,int model = 0);
~CThread();
void run();
void setsocket(sockaddr_in socket);
void exitthread();
public slots:
void slotsend(QString str);
signals:
void recvSig(QString str,sockaddr_in);
void newconnect(int socketfileId,sockaddr_in socket);
private:
int m_socketclientfileId;
int m_socketfileId;
sockaddr_in m_clientsocket;
sockaddr_in m_socket;
QString m_sendstr;
int m_type;
int m_model;
bool m_sendflag;
bool m_exit;
};
#endif // CTHREAD_H
#include "cthread.h"
#include
#include
CThread::CThread(QObject *parent, int socketId, int type, int model)
:QThread(parent),m_socketfileId(socketId),m_type(type),m_model(model),m_sendflag(false),m_exit(false)
{
}
CThread::~CThread()
{
quit();
wait();
}
void CThread::exitthread()
{
shutdown(m_socketclientfileId, SHUT_WR);//关闭输出流
shutdown(m_socketfileId, SHUT_WR);//关闭输出流
m_exit = true;
}
void CThread::run()
{
if (m_type == 1)
{
while (1)
{
if (m_exit)
break;
if (m_type == 1)
{
int len = sizeof(m_clientsocket);
//6、接收客户端请求,accept为阻塞函数 (参数为文件描述符、新的连接套接字结构体和套接字长度。返回值:获取到的客户端文件描述符)
m_socketclientfileId = accept(m_socketfileId,(struct sockaddr*)&m_clientsocket,(socklen_t*)&len);
if (m_socketclientfileId < 0)
{
QMessageBox::information(0,"error","接收开启失败");
}
else
{
emit newconnect(m_socketclientfileId,m_clientsocket);
}
}
QThread::msleep(200);
}
}
else
{
while (1)
{
if (m_model == 0)
{
char buf[1024] = {0};
//接收
if (recv(m_socketfileId,buf,sizeof(buf),0) <= 0)
break;
emit recvSig(QString::fromUtf8(buf),m_socket);
}
else if(m_model == 1)
{
if (m_sendflag)
{
m_sendflag = false;
//发送
send(m_socketfileId,m_sendstr.toUtf8().data(),1024,0);
}
}
if (m_exit)
break;
QThread::msleep(200);
}
}
}
void CThread::setsocket(sockaddr_in socket)
{
m_socket = socket;
}
void CThread::slotsend(QString str)
{
m_sendflag = true;
m_sendstr = str;
}