先上一张最后运行结果图,顺便说下开发环境:fedora14+qt4.8.1.界面的搭建是用designer画的,以前还没用过,都用程序写界面发现吃力不讨好,用designer画的效果和程序写时完全等价的。他会自动生成一个类似android下的xml布局文件,并且自动关联槽函数,不用白不用,哈哈!基类为widget。
先交代几个重要变量:
192.168.2.211 对应变量 ipEdit
6665 ------------------------portEdit
信息交互栏下面的编辑框------getEdit
发送信息下面的编辑框--------sendEdit
另外几个按钮就不多说了!
I、 widget.h的代码:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QtNetwork/QUdpSocket>
#include<QtNetwork/QHostAddress>
#include <QMessageBox>
#include <QHostInfo>
#include <QNetworkInterface>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
QHostAddress *localHostAddr;
QHostAddress *remoteHostAddr;
QString localIpStr;
QString remoteIpStr;
QString getIp();
void autoScroll();
private slots:
void send();
void receive();
void on_clearButton_clicked();
void on_configButton_clicked();
void on_exitButton_clicked();
private:
Ui::Widget *ui;
QUdpSocket *udpSocket1;
bool configFlag;
};
#endif // WIDGET_H
II、 widget.cpp里的核心代码及说明:
首先初始化如下: ui->setupUi(this);
configFlag = false; //初始化连接参数 为未连接
ui->getTextEdit->ensureCursorVisible();
ui->sendTextEdit->setFocus(); //程序启动时,焦点停在发送对话框
ui->ipEdit->setText("192.168.2.211"); //设置默认的远程端Ip
ui->portEdit->setText("6665"); //设置默认端口号
一、快捷键设置:
ui->udpSendButton->setShortcut(tr("Alt+F"));
这点重点说下,不知道为什么想用Ctrl+Enter,但用不了,用其他快捷键就可以,这里用Alt+F。
二、本地Ip的查找
这块是最曲折的。最初是用
// QHostInfo info = QHostInfo::fromName(QHostInfo::localHostName());
// hostaddr1 = info.addresses().takeFirst();
这样查询出的hostaddr1其实是本地回环Ip地址, hostaddrStr = hostaddr1.toString();,可以用qDebug()<<hostaddrStr打印一下,发现结果是127.0.0.1.并不是真正的本地Ip地址。localHostAddr = new QHostAddress(localIpStr);如果这里用new QhostAddrress(hostaddr1),用这个地址的话可以自己给自己发,但和局域网其他机器通讯时是根本不可能的。
这里提供一下查询本地Ip的函数:
QString Widget::getIp()
{
QList<QHostAddress> list = QNetworkInterface::allAddresses();
foreach (QHostAddress address, list)
{
if(address.protocol() == QAbstractSocket::IPv4Protocol) //我们使用IPv4地址
{
if(address.toString().contains("127.0."))
continue;
qDebug()<<"本机Ip:"<<address.toString();
return address.toString();
}
}
return 0;
}
只有这个函数,打印出来的Ip地址才是你机器上真正的Ip。
所以我们
localIpStr = getIp();
localHostAddr = new QHostAddress(localIpStr);
首先获得本地Ip地址,用这个Ip地址也初始化QHostAddress变量。这是我们的本地Ip.
udpSocket1 = new QUdpSocket(this);
bool bindFlag = udpSocket1->bind(*localHostAddr, 6665, QUdpSocket::ShareAddress);
上面是初始化一个udpsocket,将他和本地Ip及 开放的端口号绑定在一起。
接下来判断绑定失败与否:
if(!bindFlag)
{
QMessageBox box;
box.setText(tr("初始化绑定socket错误!"));
box.exec();
}
else
{
connect(udpSocket1, SIGNAL(readyRead()), this, SLOT(receive()));
connect(ui->udpSendButton, SIGNAL(clicked()), this, SLOT(send()));
}
绑定成功了,就连接槽函数,第一个receive()是udpSocket1在接收到数据时触发的,第二个send()是当按下发送按键时,往对端机器发信息的。接收数据和发送数据时同一个Socket!大家注意了,网上其他人用一个socket接收,一个socket发送。可能也可以把,这里用一个。
this->setWindowTitle(tr("基于Qt的UDP聊天界面-------顶礼准提佛母")); 设置标题。
(顶礼准提佛母,顶礼南师怀瑾,希望大家少杀生少吃肉,孝敬父母, 多多贡献源码 :
南怀瑾老师传承的准提心咒: http://user.qzone.qq.com/707798286/blog/1341824452 ,
南怀瑾老师念诵准提咒100遍30分钟版本: ttp://www.tudou.com/programs/view/7eqpbQ7O8UQ/
南<wbr>怀<wbr>瑾<wbr>老<wbr>师<wbr>2<wbr>0<wbr>0<wbr>2<wbr>年<wbr>香<wbr>港<wbr><span style="color:#ff0000">准</span><wbr><span style="color:#ff0000">提</span><wbr><span style="color:#ff0000">法</span><wbr>开<wbr>示:</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr> http://wenku.baidu.com/view/f86c34d7195f312b3169a577.html
南<wbr>怀<wbr>瑾<wbr>老<wbr>师<wbr>教<wbr>念<wbr>的<wbr><strong><span style="color:#ff0000">准</span><wbr><span style="color:#ff0000">提</span><wbr><span style="color:#ff0000">咒</span>:</wbr></wbr></strong></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr> http://wenku.baidu.com/view/6369277f168884868762d673.html
金<wbr>刚<wbr>上<wbr>师<wbr>南<wbr>公<wbr>怀<wbr>瑾<wbr>传<wbr>授<wbr><span style="color:#ff0000">准</span><wbr><span style="color:#ff0000">提</span><wbr><span style="color:#ff0000">法</span><wbr>修<wbr>持<wbr>要<wbr>领<wbr>开<wbr>示(1978年9月):</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr> http://wenku.baidu.com/view/c5f1c14fc850ad02de804153.html )<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
南师慈悲叮咛:持 准提咒 容易疏忽之处 : http://www.xuefo.net/nr/article10/103024.html
-------------不喜欢的敬请绕过。
三、发送数据的槽函数-----解决发送中文乱码的关键
void Widget::send()
{
autoScroll();
QString sendStr = ui->sendTextEdit->toPlainText();
QByteArray sendByteArray = sendStr.toAscii();
QMessageBox box;
if(sendStr.length()==0)
{
box.setText(tr("请输入发送内容"));
box.exec();
}
else if(configFlag)
{
udpSocket1->writeDatagram(sendByteArray, sendByteArray.length(), *remoteHostAddr, 6665);
//本地发送信息再信息交互窗口的显示
QDateTime time;
QString timeStr = time.currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
ui->getTextEdit->setTextColor(QColor("red"));
ui->getTextEdit->insertPlainText("本机" + localIpStr + ": " + timeStr + "\n");
ui->getTextEdit->setTextColor(QColor("black"));
ui->getTextEdit->insertPlainText( sendStr +"\n");
ui->sendTextEdit->clear(); //点击发送后,发送编辑框内清零
ui->sendTextEdit->setFocus(); //焦点停留在发送编辑框
}
else if(!configFlag)
{
box.setText("请您先点击确认按钮!");
box.exec();
}
}
这里有几点说说:
1,发送数据的实现
QString sendStr = ui->sendTextEdit->toPlainText();
QByteArray sendByteArray = sendStr.toAscii();
udpSocket1->writeDatagram(sendByteArray, sendByteArray.length(), *remoteHostAddr, 6665);
能否正确显示中文就在这一句,必须把QString转成QByteArray再发送,接收端有好几种实现方法,但发送端不这么搞的话,就解析不了中文。这块让我搞了一整天!
2,本地时间显示
QDateTime time;
QString timeStr = time.currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd"); 要包含一个头文件,#include <qdatetime.h>,奇怪的是这个头文件包含在widget.h里就不行,非要搞到widget.cpp里才中。
3,显示文本的时候 ui->getTextEdit->insertPlainText( sendStr +"\n");
ui->sendTextEdit->clear(); //点击发送后,发送编辑框内清零
ui->sendTextEdit->setFocus(); //焦点停留在发送编辑框
一定要搞成insertPlainText,如果搞成setText,就会把显示窗口搞成只有一句话,这个函数是不销毁以前的内容,插入在以前的文本内容之后的函数。
4,自动滚屏的实现,函数实现为:
void Widget::autoScroll()
{
QTextCursor cursor = ui->getTextEdit->textCursor();
cursor.movePosition(QTextCursor::End);
ui->getTextEdit->setTextCursor(cursor);
}
这里插一句,getEdit,的属性里设成enables,read Only,在designer里,点控件,设置属性里这么设置。如果不设自动滚屏是什么效果呢??
只有拖动右边滚动鼠标才能显示,所以一定要设自动滚屏。而且在两个地方,一个是receive里调用这个函数,有东西显示的时候,滚屏到最后显示! 点发送的时候,也要调用这个滚屏函数,如果您聊天的时候,把光标停留在信息交互栏里某个位置A,下一条信息就会显示在A光标后,而不是我们希望的每次都挨着上一条信息后显示。所以点发送按钮后,要先调用,目的在此!
四、接收槽函数的实现
void Widget::receive()
{
while(udpSocket1->hasPendingDatagrams())
{
QTextCodec *tc=QTextCodec::codecForName("UTF-8");
//UTF-8
QDateTime time;
QString timeStr = time.currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
QByteArray data;
data.resize(udpSocket1->pendingDatagramSize());
udpSocket1->readDatagram(data.data(), data.size());
//
QString dataStr = QString::fromUtf8(data.data());
//这样写也是正确的
QString dataStr = tc->toUnicode(data);
ui->getTextEdit->setTextColor(QColor("red"));
ui->getTextEdit->insertPlainText("远程" + remoteIpStr+": "+ timeStr +"\n" );
ui->getTextEdit->setTextColor(QColor("black"));
ui->getTextEdit->insertPlainText(dataStr + "\n" );
autoScroll();
}
}
为了显示中文,关键在三句
QTextCodec *tc=QTextCodec::codecForName("UTF-8");
QString dataStr = tc->toUnicode(data);
如果不写这两句,用这句也可以实现:QString dataStr = QString::fromUtf8(data.data());
五,点确定按钮的槽函数:
void Widget::on_configButton_clicked()
{
remoteIpStr = ui->ipEdit->text();
QString port = ui->portEdit->text();
qDebug()<<"远程端Ip:"<<remoteIpStr<<"端口号:"<<port;
remoteHostAddr = new QHostAddress(remoteIpStr);
QMessageBox box;
if(remoteIpStr.length()==0 || port.length()==0 || port.toInt()<1024)
{
configFlag = false;
box.setText("请正确设置远程端Ip地址和端口号!");
box.exec();
}
else
{
configFlag = true;
box.setText("您设置的远程端Ip:" + remoteIpStr+"端口号:"+port);
box.exec();
}
}
用来初始化远程Ip地址!
至此唯一不完美的是,如何为qt编写的程序添加一个图标?下午搞了一会,没有成功!网上貌似说的都是在windows下,我要再linux下。哪位大神实现了指点一下后学。
源码下载: http://download.csdn.net/detail/yanzi1225627/4475122
Fedora14 基于Qt的UDP传输文字聊天小软件实现 (Qt查询本地Ip、Qt本地时间显示、传输中文汉字实现、Qt的textedit自动滚屏实现、给QPushButton设键盘快捷实现)---续上