目录
1. 复习
1.1 UDP 与TCP
1.2 IP地址与端口号
2. 前期准备
3. 编程内容
UDP TCP 协议相同点:都存在于传输层
是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、
数据无失序、数据无重复到达的通信)
适用情况:
1、适合于对传输质量要求较高,以及传输大量数据的通信。
2、在需要可靠数据传输的场合,通常使用TCP协议
3、MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
适用情况:
1、发送小尺寸数据(如对DNS服务器进行IP地址查询时)
2、在接收到数据,给出应答较困难的网络中使用UDP。
3、适合于广播/组播式通信中。
4、MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议
5、流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输
基本概念:
IP地址是Internet中主机的标识,Internet中的主机要与别的机器通信必须具有一个IP地址。
基本概念:
为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区分。
众所周知端口:1~1023(1~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)
已登记端口:1024~49151
动态或私有端口:49152~65535
自定义程序的端口号建议:2000-65535,去除连号,例如8888
跟数据库一样,网络功能也需要在.pro文件中增加network模块。
Qt的TCP通信结构图,如下所示(原理大概下图所示,但是具体的API有所不同)。
本次要实现一个基于TCP的聊天程序,需要使用的类有:
服务器管理类:管理服务器的多个连接,直接继承了QObject,因此不具备IO能力。
相关函数如下:
// 构造函数
QTcpServer::QTcpServer(QObject * parent = 0)
// 服务器开启监听,等待客户主动发起连接
// 参数1:监听来自于哪个IP地址的请求,默认值为不限制IP地址,QHostAddress类是IP地址的封装类。
// 参数2:服务器端口号
// 返回值:监听开启结果
bool QTcpServer::listen(
const QHostAddress & address = QHostAddress::Any,
quint16 port = 0)
// 有新连接建立的通知信号
void QTcpServer::newConnection() [signal]
// 服务器是否还在监听
bool QTcpServer::isListening() const
// 关闭服务器
void QTcpServer::close()
// 返回一个就绪的连接对象,此对象用于跟某个客户端进行IO操作
QTcpSocket * QTcpServer::nextPendingConnection() [virtual]
TCP连接类:进行网络IO操作,间接继承QIODevice类。
相关函数如下:
// 构造函数
QTcpSocket::QTcpSocket(QObject * parent = 0)
// 连接到服务器
// 参数1:服务器的IP地址
// 参数2:服务器的端口号
// 参数3:读写模式,默认为可读可写
void QAbstractSocket::connectToHost(const QString & hostName,
quint16 port,
OpenMode openMode = ReadWrite) [virtual]
// 连接是否处于打开状态
bool QIODevice::isOpen() const
// 关闭连接
void QIODevice::close() [virtual]
// 拿到对面的IP地址封装类对象,如果没有连接返回QHostAddress::Null
QHostAddress QAbstractSocket::peerAddress() const
// 连接断开发射的信号
void QAbstractSocket::disconnected() [signal]
// 有数据可读时发射的信号
void QIODevice::readyRead() [signal]
文本流类:是一种高效地文本数据IO的辅助类。
// 构造函数
// 参数为QIODevice的派生类对象
QTextStream::QTextStream(QIODevice * device)
// 发送字符串
// 参数必须是QString类型,注意不要使用const char*
// 返回值是当前类型的引用,表示支持链式调用,因此连续追加发送的内容
QTextStream & QTextStream::operator<<(const QString & string)
// 读取最大值为maxlen个字符的内容到返回值
QString QTextStream::read(qint64 maxlen)
// 读取最大字符数maxlen的一行字符到返回值
QString QTextStream::readLine(qint64 maxlen = 0)
// 读取所有字符到返回值
//
QString QTextStream::readAll()
Client:(QTcpSocket)
①创建QTcpSocket对象
②当对象与Server连接成功时会发送connected 信号
③调用成员函数connectToHost连接服务器,需要的参数是地址和端口号
④connected信号的槽函数开启发送数据
⑤使用write发送数据,read接收数据
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include
//连接类
#include
//文件流类
#include
#include
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
QTcpSocket *client; //连接对象
private slots:
void btnConnClickedSlot();
void btnSendClickedSlot();
//连接和断开的检测槽函数
void connectedSlot();
void diaconnectedSlot();
void readReadSlot();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
connect(ui->pushButtonConn,SIGNAL(clicked()),this,SLOT(btnConnClickedSlot()));
connect(ui->pushButtonSend,SIGNAL(clicked()),this,SLOT(btnSendClickedSlot()));
//设置窗口标记为顶层
setWindowFlags(Qt::WindowStaysOnTopHint);
// 创建连接对象
client = new QTcpSocket(this);
//client = new QTcpServer(this);
//连接状态检测的信号槽
connect(client,SIGNAL(connected()),this,SLOT(connectedSlot()));
connect(client,SIGNAL(disconnected()),this,SLOT(diaconnectedSlot()));
connect(client,SIGNAL(readyRead()),this,SLOT(readReadSlot()));
}
Dialog::~Dialog()
{
// 如果客户端还在连着,关掉
if(client->isOpen()){
client->close();
}
delete ui;
}
void Dialog::btnConnClickedSlot(){
//默认输入有效,连接到服务器
//参数1:服务器的IP地址
//参数2:服务器的端口号
client->connectToHost(ui->lineEditIp->text(),8887);
}
void Dialog::btnSendClickedSlot(){
//获取用户输入的内容
QString msg = ui->lineEditMsg->text();
if(msg == ""){
QMessageBox::warning(this,"提示","请输入要发送的内容!");
return;
}
//创建文本流对象
QTextStream output(client);
//发送内容
output << msg;
//清空输入框
ui->lineEditMsg->clear();
QString time = QDateTime::currentDateTime().toString("hh:mm:ss");
ui->textBrowser->append(time);
ui->textBrowser->append("客户端:");
ui->textBrowser->append(msg);
ui->textBrowser->append("");
}
void Dialog::connectedSlot()
{
//屏蔽连接按钮
ui->pushButtonConn->setEnabled(false);
ui->pushButtonConn->setText("已连接");
//释放发送按钮
ui->pushButtonSend->setEnabled(true);
}
void Dialog::diaconnectedSlot()
{
//恢复连接按钮
ui->pushButtonConn->setEnabled(true);
ui->pushButtonConn->setText("连接!");
//屏蔽发送按钮
ui->pushButtonSend->setEnabled(false);
}
void Dialog::readReadSlot(){
QString time = QDateTime::currentDateTime().toString("hh:mm:ss");
ui->textBrowser->append(time);
QTextStream input(client);
QString msg = input.readAll();
ui->textBrowser->append("服务端:");
ui->textBrowser->append(msg);
ui->textBrowser->append("");
}
ui界面
Server:(QTcpServer)
①创建QTcpServer对象
②监听list需要的参数是地址和端口号
③当有新的客户端连接成功回发送newConnect信号
④在newConnection信号槽函数中,调用nextPendingConnection函数获取新连接QTcpSocket对象
⑤连接QTcpSocket对象的readRead信号
⑥在readRead信号的槽函数使用read接收数据
⑦调用write成员函数发送数据
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include
#include
//网络相关类
#include
#include
#include
#include
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
// 管理类服务器对象
QTcpServer *server;
QTcpSocket*socket = NULL; //简单期间,只保留一个客户端连接
private slots:
//新连接建立的槽函数
void newConnSlot();
//网络连接断开的槽函数
void disconnectedSlot();
//读取信息的槽函数
void readReadSlot();
void btnSendClickSlot();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
// 设置窗口标记,始终前台显示
setWindowFlags(Qt::WindowStaysOnTopHint);
// 创建管理类对象
server = new QTcpServer(this);
//server = new QTcpSocket(this);
//连接服务器通知的信号槽
connect(server,SIGNAL(newConnection()),this,SLOT(newConnSlot()));
// 开启监听,等待客户主动发起连接
//参数1:监听来自哪个IP地址的请求,默认值为不限制IP地址
//QHostAddress类是ip地址的封装类
//参数2:服务器端口号
server->listen(QHostAddress::Any,8887);
}
Dialog::~Dialog()
{
if(server->isListening()) // 如果在监听
// 关闭服务器
server->close();
delete ui;
}
void Dialog::newConnSlot()
{
//如果不是第一次连接,先踢掉之前的连接
if(socket !=NULL){
socket->close();
}
//拿到与客户端进行连接的QTcpSocket对象(绿蛋)
socket = server->nextPendingConnection();
//建立断开连接的通知的信号槽
connect(socket,SIGNAL(disconnected()),this,SLOT(disconnectedSlot()));
//建立读取消息的信号槽
connect(socket,SIGNAL(readyRead()),this,SLOT(readReadSlot()));
connect(ui->pushButtonSend,SIGNAL(clicked()),this,SLOT(btnSendClickSlot()));
//拿到对面客户端的IP与端口号
QString ip = socket->peerAddress().toString();
quint16 port = socket->peerPort();
//输出信息
QString time = QDateTime::currentDateTime().toString("hh:mm:ss");
ui->textBrowser->append(time);
ui->textBrowser->append("新连接来了!");
ui->textBrowser->append(ip.append(":").append(QString::number(port)));
ui->textBrowser->append("");
}
void Dialog::disconnectedSlot()
{
//拿到对面客户端的IP与端口号
QString ip = socket->peerAddress().toString();
quint16 port = socket->peerPort();
//输出信息
QString time = QDateTime::currentDateTime().toString("hh:mm:ss");
ui->textBrowser->append(time);
ui->textBrowser->append("老连接走了!");
ui->textBrowser->append(ip.append(":").append(QString::number(port)));
ui->textBrowser->append("");
}
void Dialog::readReadSlot()
{
QString time = QDateTime::currentDateTime().toString("hh:mm:ss");
ui->textBrowser->append(time);
QTextStream input(socket);
//读取数据
QString msg = input.readAll();
//展示
ui->textBrowser->append("客户端:");
ui->textBrowser->append(msg);
ui->textBrowser->append("");
}
void Dialog::btnSendClickSlot()
{
QString msg2 = ui->lineEditMsg->text();
if(msg2 == ""){
QMessageBox::warning(this,"提示","请输入要发送的内容!");
return;
}
QTextStream output(socket);
output << msg2;
ui->lineEditMsg->clear();
QString time = QDateTime::currentDateTime().toString("hh:mm:ss");
ui->textBrowser->append(time);
ui->textBrowser->append("服务端:");
ui->textBrowser->append(msg2);
ui->textBrowser->append("");
}
ui界面
运行结果: