首先介绍一下,TCP/IP其实是一个四层协议系统,主要包括链路层,网络层,传输层,应用层。udp属于传输层内容。
以太网(Ethernet)数据帧的长度必须在46-1500字节之间,这是由以太网的物理特性决定的.这个1500字节被称为链路层的MTU(最大传输单元).但这并不是指链路层的长度被限制在1500字节,其实这个MTU指的是链路层的数据区.并不包括链路层的首部和尾部的18个字节.所以,事实上,这个1500字节就是网络层IP数据报的长度限制.因为IP数据报的首部为20字节,所以IP数据报的数据区长度最大为1480字节.而这个1480字节就是用来放TCP传来的TCP报文段或UDP传来的UDP数据报的.又因为UDP数据报的首部8字节,所以UDP数据报的数据区最大长度为1472字节.这个1472字节就是我们可以使用的字节数。:) 当我们发送的UDP数据大于1472的时候会怎样呢?这也就是说IP数据报大于1500字节,大于 MTU.这个时候发送方IP层就需要分片(fragmentation).把数据报分成若干片,使每一片都小于MTU.而接收方IP层则需要进行数据报的重组.这样就会多做许多事情,而更严重的是,由于UDP的特性,当某一片数据传送中丢失时,接收方便无法重组数据报.将导致丢弃整个UDP数据报。因此,在普通的局域网环境下,我建议将UDP的数据控制在1472字节以下为好.进行Internet编程时则不同,因为Internet上的路由器可能会将MTU设为不同的值.如果我们假定MTU为1500来发送数据的,而途经的某个网络的MTU值小于1500字节,那么系统将会使用一系列的机制来调整MTU值,使数据报能够顺利到达目的地,这样就会做许多不必要的操作.鉴于 Internet上的标准MTU值为576字节,所以我建议在进行Internet的UDP编程时.最好将UDP的数据长度控件在548字节 (576-8-20)以内.
理论上,IP数据报的最大长度是65535字节,这是由IP首部16比特总长度字段所限制的。去除20字节的IP首部和8个字节的UDP首部,UDP数据报中用户数据的最长长度为65507字节。但是,大多数实现所提供的长度比这个最大值小。
UDP(User Datagram Protocol即用户数据报协议)是一个轻量级的,不可靠的,面向数据报的无连接协议。对于UDP我们不再进行过多介绍,如果你对UDP不是很了解,而且不知道它有什么用,那么我们这里就举个简单的例子:我们现在几乎每个人都使用的腾讯QQ,其聊天时就是使用UDP协议进行消息发送的。就像QQ那样,当有很多用户,发送的大部分都是短消息,要求能及时响应,并且对安全性要求不是很高的情况下使用UDP协议。 在Qt中提供了QUdpSocket 类来进行UDP数据报(datagrams)的发送和接收。这里我们还要了解一个名词Socket,也就是常说的“套接字”。Socket简单地说,就是一个IP地址加一个port端口。因为我们要传输数据,就要知道往哪个机子上传送,而IP地址确定了一台主机,但是这台机子上可能运行着各种各样的网络程序,我们要往哪个程序中发送呢?这时就要用一个端口来指定UDP程序。所以说,Socket明了数据报传输的路径。
客户端:首先当客户端登录时,获取本机的用户名,计算机名和ip地址,并广播给局域网的服务器更新用户列表。然后当客户端需要发送信息时,则在聊天输入栏中输入信息并按发送键发送聊天内容,当然于此同时也广播本地系统的各种信息。其流程图如下:
服务器端:建立一个UDP Socket并绑定在固定端口后,用信号与槽的方式进行监听是否有数据来临。如果用,接收其数据并分析数据的消息类型,如果消息是新用户登录则更新用户列表并在聊天显示窗口中添加新用户上线通知;同理,如果是用户下线,则在用户列表中删除该用户且在聊天显示窗口中显示下线通知;如果是聊天消息,则接收该消息并且在窗口中显示。其流程图如下:
代码如下:
mainwindow.h代码如下:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtNetwork/QUdpSocket>
#include <QHostAddress>
#include <QByteArray>
#include <QDateTime>
#include <QString>
#include <QByteArray>
#include <QDataStream>
#include <QIODevice>
#include <QHostInfo>
#include <QList>
#include <QHostAddress>
#include <QNetworkInterface>
#include <QAbstractSocket>
#include <QStringList>
#include <QProcess>
#include <QMessageBox>
#include <QDateTime>
#include <QTableWidgetItem>
namespace Ui {
class MainWindow;
}
//分别表示消息,新用户加入,用户退出,文件名称,拒绝接受文件
enum MessageType{Message, NewParticipant, ParticipantLeft, FileName, Refuse};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QString getIp();//获取ip地址
QString getUserName();//获得用户名
QString getMessage();//获得消息
protected:
void sendMessage(MessageType type,QString serverAddress="");
void newParticipant(QString userName,
QString localHostName, QString ipAddress);
void participantLeft(QString userName, QString localHostName, QString time);
private:
Ui::MainWindow *ui;
QUdpSocket *udpSocket;
qint16 port;
private slots:
void processPendingDatagrams();//接受消息
void on_pushButton_2_clicked();//当点击发送时,发送消息
};
#endif // MAINWINDOW_Hmainwindow.cpp代码如下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
udpSocket =new QUdpSocket(this);
port =45454;
udpSocket->bind(port,QUdpSocket::ShareAddress|QUdpSocket::ReuseAddressHint);
connect(udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()));
sendMessage(NewParticipant);//打开软件时就向外发射本地信息,让其他在线用户得到通知
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::sendMessage(MessageType type, QString serverAddress)
{
QByteArray data;
QDataStream out(&data,QIODevice::WriteOnly);//将二进制数据存入到io设备
QString localHostName=QHostInfo::localHostName();//返回主机名
QString address=getIp();//得到主机的ip地址
out<< type << getUserName() << localHostName;
qDebug() << type;
switch (type)
{
case Message:
if(ui->textEdit->toPlainText()=="")
{
QMessageBox::warning(0,tr("infomation"),tr("fasong xinxi buneng weikong"));
}
out << address << getMessage();//将ip地址和得到的消息内容输入out数据流
break;
case NewParticipant:
out << address;
break;
case ParticipantLeft :
break;
case FileName :
break;
case Refuse :
break;
}
udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast, port);//QHostAddress::Broadcast是指发送数据的目的地址
//使用writeDatagram向外发送信息在整个局域网的所有机器都能够接受到,即局域网广播发送
}
//得到主机的ip地址
QString MainWindow::getIp()
{
/使用allAddresses命令获得所有的ip地址
QList<QHostAddress> list=QNetworkInterface::allAddresses();
foreach (QHostAddress address,list)
{
if(address.protocol()==QAbstractSocket::IPv4Protocol)
return address.toString();
}
return 0;
}
//得到主机的用户名
QString MainWindow::getUserName()
{
QStringList envVariables;
envVariables << "USERNAME.*" << "USER.*" << "USERDOMAIN.*"
<< "HOSTNAME.*" << "DOMAINNAME.*";
QStringList environment=QProcess::systemEnvironment();
foreach (QString string, envVariables) {
//indexOf为返回第一个匹配list的索引,QRegExp类是用规则表达式进行模式匹配的类
int index = environment.indexOf(QRegExp(string));
if (index != -1) {
//stringList中存的是environment.at(index)中出现'='号前的字符串
QStringList stringList = environment.at(index).split('=');
if (stringList.size() == 2) {
return stringList.at(1);//at(0)为文字"USERNAME.",at(1)为用户名
break;
}
}
}
}
//获得消息
QString MainWindow::getMessage()
{
QString msg=ui->textEdit->toHtml();
ui->textEdit->clear();//清空内容
ui->textEdit->setFocus();//重新设置光标输入点
return msg;
}
//接受udp消息
void MainWindow::processPendingDatagrams()
{
while(udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
udpSocket->readDatagram(datagram.data(),datagram.size());//接收一个数据报,并将其存储在data中。返回的是数据报的长度
//将读取到的不大于datagram.size()大小数据输入到datagram.data()中,datagram.data()返回的是一个字节数组中存储
//数据位置的指针
QDataStream in(&datagram,QIODevice::ReadOnly);
int messageType;
in >> messageType;
QString userName,localHostName,ipAddress,message;
QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");//将当前的时间转化到括号中的形式
switch(messageType)
{
case Message:
//in>>后面如果为Qstring,则表示读取一个直到出现'\0'的字符串
in >> userName >> localHostName >> ipAddress >> message;
ui->textBrowser->setTextColor(Qt::blue);//设置文本颜色为蓝色
ui->textBrowser->setCurrentFont(QFont("Times New Roman",12));//设置字体大小为12号字体
// ui->messageBrowser->append("[ " +userName+" ] "+ time);//输出的格式为用户名加时间显示
ui->textBrowser->append("[ " +localHostName+" ] "+ time);
ui->textBrowser->append(message);//消息输出
break;
case NewParticipant:
in >>userName >>localHostName >>ipAddress;
newParticipant(userName,localHostName,ipAddress);
break;
case ParticipantLeft:
in >>userName >>localHostName;
participantLeft(userName,localHostName,time);
break;
case FileName:
break;
case Refuse:
break;
}
}
}
//当点击发送按钮时
void MainWindow::on_pushButton_2_clicked()
{
sendMessage(Message);
}
// 处理新用户加入
void MainWindow::newParticipant(QString userName, QString localHostName, QString ipAddress)
{
//此处的findItems表示找到与内容localHostName匹配的item,其匹配是基于变体的匹配模式
bool isEmpty = ui->tableWidget->findItems(localHostName, Qt::MatchExactly).isEmpty();
if (isEmpty) { //没有找到相应的主机名
//新建3个小的item,分别为user,host,ip
QTableWidgetItem *user = new QTableWidgetItem(userName);
QTableWidgetItem *host = new QTableWidgetItem(localHostName);
QTableWidgetItem *ip = new QTableWidgetItem(ipAddress);
ui->tableWidget->insertRow(0);//先设置的是第0行,即新来的用户放在最上面
ui->tableWidget->setItem(0,0,user);//第0行的第1列...
ui->tableWidget->setItem(0,1,host);
ui->tableWidget->setItem(0,2,ip);
ui->textBrowser->setTextColor(Qt::gray);
ui->textBrowser->setCurrentFont(QFont("Times New Roman",10));
sendMessage(NewParticipant);//该句的功能是让新来的用户也能收到其它在线用户的信息,可拥于更新自己的好友列表
}
}
void MainWindow::participantLeft(QString userName, QString localHostName, QString time)
{
int rowNum=ui->tableWidget->findItems(localHostName,Qt::MatchExactly).first()->row();
ui->tableWidget->removeRow(rowNum);
ui->textBrowser->setTextColor(Qt::gray);//设置文本颜色为灰色
ui->textBrowser->setCurrentFont(QFont("Times New Roman", 10));
ui->textBrowser->append(tr("%1 yu %2 likai").arg(userName).arg(time));
ui->textBrowser->setText(tr("zaixianrenshu:%1").arg(ui->tableWidget->rowCount()));
}main.cpp代码如下所示:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}