一、概要设计

  登录对话框(继承自QDialog类)进行用户登录查询数据库用户是否存在,注册插入数据到用户表。用户表字段:

(chatid int primary key, passwd varchar(30), name varchar(30), email varchar(30), history int)

显示好友列表(继承自QWidget类),窗体间数据传递,显示登录用户头像及昵称。轮询数据库用户表显示好友列表。点击好友跳出聊天窗口(继承自MainWindow类),窗体间数据传递,显示好友昵称,实现多用户聊天,支持中文通信,设置快捷键,保存聊天记录。

关键字:数据库、窗体传值、中文通信、udp协议、快捷键、文件操作。

二、详细设计

  1.登录对话框:

  数据库操作:

  (1)创建数据库和数据表

// 设置参数
QString select_table = "select tbl_name name from sqlite_master where type = 'table'";
QString create_sql = "create table user (chatid int primary key, passwd varchar(30), name varchar(30), email varchar(30), history int)";
QString select_max_sql = "select max(chatid) from user";
QString insert_sql = "insert into user values (?, ?, ?, ? ?)";
//QString update_sql = "update user set name = :name where chatid = :chatid";
QString select_sql = "select name from user";
//QString select_all_sql = "select * from user";
//QString delete_sql = "delete from user where chatid = ?";
//QString clear_sql = "delete from user"; QString select_nameInfo = "selcet * from user where name="; database = QSqlDatabase::addDatabase("QSQLITE");
database.setDatabaseName("database.db"); //打开数据库
if(!database.open())
{
qDebug()<<database.lastError();
qFatal("failed to connect.") ;
}
else
{
qDebug()<<"open success";
QSqlQuery sql_query; //变量必须在成功打开数据库后定义才有效
sql_query.prepare(select_table);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
QString tableName;
while(sql_query.next())
{
tableName = sql_query.value(0).toString();
qDebug()<<tableName;
if(tableName.compare("user"))
{
tableFlag=false;
qDebug()<<"table is not exist";
}
else
{
tableFlag=true;
qDebug()<<"table is exist";
}
}
}
// 创建数据表
if(tableFlag==false)
{
sql_query.prepare(create_sql);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
qDebug()<<"table created!";
}
} //database.close();
}

  (2)注册验证及插入数据

if(ui->passwd1LineEdit->text()==""||ui->passwd2LineEdit->text()=="")
{
passwdFlag=false;
}
else if(ui->passwd1LineEdit->text()==ui->passwd2LineEdit->text()) //两次密码相同
{
//newpasswd=ui->passwd1LineEdit->text();
passwdFlag=true;
}
else
{
QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("密码输入不一致!"));
qDebug()<<"passwd err";
passwdFlag=false;
//return;
} //以下为数据库的操作
QSqlQuery sql_query; //查询最大id
max_id = 0;
sql_query.prepare(select_max_sql);
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
while(sql_query.next())
{
max_id = sql_query.value(0).toInt();
qDebug()<<QString("max chatid:%1").arg(max_id);
}
} //查询部分数据(name)
if(!sql_query.exec(select_sql))
{
qDebug()<<sql_query.lastError();
}
else
{
while(1)
{
if(sql_query.next()) //name有数据
{
QString name = sql_query.value("name").toString();
qDebug()<<QString("name=%1").arg(name); if(ui->nameLineEdit->text()==name) //用户名已经存在
{
QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("用户名已存在!"));
qDebug()<<"name existed";
nameFlag=false;
break;
}
else
{
//newname=ui->nameLineEdit->text();
nameFlag=true;
}
}
else
{ //name列为空
nameFlag=true;
break;
}
}
} newchatid=max_id+1;
if(nameFlag==true) newname=ui->nameLineEdit->text();
else return;
if(passwdFlag==true) newpasswd=ui->passwd1LineEdit->text();
else return; //插入数据
sql_query.prepare(insert_sql);
sql_query.addBindValue(newchatid); //chatid
sql_query.addBindValue(newpasswd); //passwd
sql_query.addBindValue(newname); //name
sql_query.addBindValue(newemail); //email
sql_query.addBindValue(0); //history
if(!sql_query.exec())
{
qDebug()<<sql_query.lastError();
}
else
{
QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("注册成功!"));
qDebug()<<"inserted!";
}

  登录界面:

  (1)登录验证

if(matchFlag==false)
{
//用户名错误
QMessageBox::warning(this, tr("警告"), tr("用户不存在!"), QMessageBox::Yes);
this->ui->et_username->clear();
this->ui->et_pwd->clear();
this->ui->et_username->setFocus();
}
else
{
if(usr_passwd!=ui->et_pwd->text())
{
//密码错误
QMessageBox::warning(this, tr("警告"), tr("用户不存在!"), QMessageBox::Yes);
this->ui->et_username->clear();
this->ui->et_pwd->clear();
this->ui->et_username->setFocus();
}
else
{
//用户名和密码均正确
// ChatWindow cw(this);
// this->hide();
// cw.show();
// cw.exec();
// this->close();
LoginDialog::NICKNAME = usr_name;
accept();
}

  (2)用户头像

QSqlQuery sql_query;        //变量必须在成功打开数据库后定义才有效

    //查询部分数据(name)

    QString tempstring="select * from user where name='"+name+"'";
qDebug()<<tempstring;
if(!sql_query.exec(tempstring))
{
qDebug()<<sql_query.lastError();
matchFlag=false;
}
else
{
while(sql_query.next())
{
usr_id = sql_query.value(0).toInt();
usr_passwd = sql_query.value(1).toString();
usr_name = sql_query.value(2).toString();
usr_email = sql_query.value(3).toString();
usr_history = sql_query.value(4).toInt(); qDebug()<<QString("chatid=%1 passwd=%2 name=%3 email=%4 history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);
}
if(usr_name==name) matchFlag=true;
else matchFlag=false;
} qDebug()<<matchFlag;
if(matchFlag==true)
{
QString path=":/images/facex.jpg";
QString diff="face"+QString::number(usr_id);
path.replace("facex",diff);
qDebug()<<path; QImage img;
img.load(path);
QPixmap pic=QPixmap::fromImage(img.scaled(ui->userPic->width(),ui->userPic->height()));
ui->userPic->setPixmap(pic);
}
else
{ QPixmap pic;
ui->userPic->setPixmap(pic);
}

  2.好友列表:

  数据库查询及列表显示:

database = QSqlDatabase::addDatabase("QSQLITE");
database.setDatabaseName("database.db"); QSqlQuery sql_query; //改变量必须在成功打开数据库后定义才有效 //打开数据库
if(!database.open())
{
qDebug()<<database.lastError();
qFatal("failed to connect.") ;
}
else
{ QSqlQuery query; //改变量必须在成功打开数据库后定义才有效
QString execstring="select * from user"; ctoolTip = new CToolTip();
// 定义全局的ToolTip,方便使用
g_toolTip = ctoolTip; // 本行代码主要针对ListWidgetItem右键点击时才生效的
ui->listWidget->setMouseTracking(true); if(!query.exec(execstring))
{
qDebug()<<QString("chatid");
qDebug()<<query.lastError();
// matchFlag=false;
}
else
{
// 添加测试数据
while (query.next()) {
usr_id = query.value(0).toInt();
usr_passwd = query.value(1).toString();
usr_name = query.value(2).toString();
usr_email = query.value(3).toString();
usr_history = query.value(4).toInt(); qDebug()<<QString("chatid=%1 passwd=%2 name=%3 email=%4 history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history); ItemWidget *itemWidget = new ItemWidget(ui->listWidget);
itemWidget->setText(QPixmap(QString(":/images/face%1").arg(usr_id)).scaled(80, 80),
QString("%1").arg(usr_name), QString("127.0.0.1:800%1").arg(usr_id));
QListWidgetItem *listItem = new QListWidgetItem(ui->listWidget);
// 此处的size如果不设置,界面被压缩了看不出ItemWidget的效果,高度一定要设置
listItem->setSizeHint(QSize(200, 70));
ui->listWidget->setItemWidget(listItem, itemWidget); }
}
}
QObject::connect(ui->listWidget,SIGNAL(itemClicked(QListWidgetItem*)),this,SLOT(conChat(QListWidgetItem*)));
qDebug()<<"LoginWidget:"+LoginDialog::NICKNAME;
ui->nickname->setText(LoginDialog::NICKNAME);
QString tempstring="select * from user where name='"+LoginDialog::NICKNAME+"'";
qDebug()<<tempstring;
if(!sql_query.exec(tempstring))
{
qDebug()<<sql_query.lastError();
matchFlag=false;
}
else
{
while(sql_query.next())
{
usr_id = sql_query.value(0).toInt();
usr_passwd = sql_query.value(1).toString();
usr_name = sql_query.value(2).toString();
usr_email = sql_query.value(3).toString();
usr_history = sql_query.value(4).toInt(); qDebug()<<QString("chatid=%1 passwd=%2 name=%3 email=%4 history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history); }
if(usr_name==LoginDialog::NICKNAME) matchFlag=true;
else matchFlag=false;

  登录用户信息设置:

QString path=":/images/facex.jpg";
QString diff="face"+QString::number(usr_id);
path.replace("facex",diff);
qDebug()<<path; QImage img;
img.load(path);
QPixmap pic=QPixmap::fromImage(img.scaled(ui->userPic->width(),ui->userPic->height()));
ui->userPic->setPixmap(pic);
ui->ipaddress->setText(QString("127.0.0.1:800%1").arg(usr_id));

  列表项:

    labelIcon = new QLabel(this);
labelName = new QLabel(this);
labelName->setStyleSheet("QLabel{color: green; font: 23pt bold;}");
labelInfo = new QLabel(this);
labelInfo->setStyleSheet("QLabel{color: gray;}"); verlayout = new QVBoxLayout();
verlayout->setContentsMargins(0, 0, 0, 0);
verlayout->addWidget(labelName);
verlayout->addWidget(labelInfo); horLayout = new QHBoxLayout(this);
horLayout->setContentsMargins(2, 2, 2, 2);
horLayout->addWidget(labelIcon, 1, Qt::AlignTop);
horLayout->addLayout(verlayout, 4);

  悬浮窗口:

    this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint);
this->resize(200, 100); ; this->setObjectName("CToolTip");
this->setStyleSheet("QWidget#CToolTip {border: 2px solid green; background-color: skyblue;}"); groupBox = new QGroupBox(this);
groupBox->setGeometry(10, 10, 180, 80);
groupBox->setTitle("用户信息"); labelIcon = new QLabel(groupBox);
labelName = new QLabel(groupBox);
labelInfo = new QLabel(groupBox); verlayout = new QVBoxLayout();
verlayout->setContentsMargins(0, 0, 0, 0);
verlayout->addWidget(labelName);
verlayout->addWidget(labelInfo); horLayout = new QHBoxLayout(groupBox);
horLayout->setContentsMargins(10, 10, 10, 10);
horLayout->addWidget(labelIcon, 1, Qt::AlignTop);
horLayout->addLayout(verlayout, 4);

  选择列表项:

QObject::connect(ui->listWidget,SIGNAL(itemClicked(QListWidgetItem*)),this,SLOT(conChat(QListWidgetItem*)));

void LoginWidget::conChat(QListWidgetItem*)
{
qDebug()<<QString("onChat:%1").arg(usr_id);
LoginWidget::ID = ui->listWidget->currentRow()+1;
chatWindow=new ChatWindow;
chatWindow->show();
}

  3.聊天窗口:

  数据库查询、信息设置及绑定端口号:

database = QSqlDatabase::addDatabase("QSQLITE");
database.setDatabaseName("database.db"); QSqlQuery chat_query; //变量必须在成功打开数据库后定义才有效 //打开数据库
if(!database.open())
{
qDebug()<<database.lastError();
qFatal("failed to connect.") ;
}
else
{
QString tempstring="select * from user where name='"+LoginDialog::NICKNAME+"'";
qDebug()<<tempstring;
if(!chat_query.exec(tempstring))
{
qDebug()<<chat_query.lastError();
matchFlag=false;
}
else
{
while(chat_query.next())
{
usr_id = chat_query.value(0).toInt();
usr_passwd = chat_query.value(1).toString();
usr_name = chat_query.value(2).toString();
usr_email = chat_query.value(3).toString();
usr_history = chat_query.value(4).toInt(); qDebug()<<QString("chatid=%1 passwd=%2 name=%3 email=%4 history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);
port = 8000+usr_id;
}
}
QString receiverId="select * from user where chatid="+QString("%1").arg(LoginWidget::ID)+"";
qDebug()<<receiverId;
if(!chat_query.exec(receiverId))
{
qDebug()<<chat_query.lastError();
matchFlag=false;
}
else
{
while(chat_query.next())
{
usr_id = chat_query.value(0).toInt();
usr_passwd = chat_query.value(1).toString();
usr_name = chat_query.value(2).toString();
usr_email = chat_query.value(3).toString();
usr_history = chat_query.value(4).toInt(); qDebug()<<QString("chatid=%1 passwd=%2 name=%3 email=%4 history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);
ui->name->setText(usr_name);
}
}

  UDP接收端:

senderAno = new QUdpSocket(this);
receiver = new QUdpSocket(this);
receiver->bind(port, QUdpSocket::ShareAddress);
connect(receiver, &QUdpSocket::readyRead, this, &ChatWindow::processPendingDatagram); void ChatWindow::processPendingDatagram()
{
//中文支持
// QTextCodec *codec = QTextCodec::codecForName("GBK");
// 拥有等待的数据报
while(receiver->hasPendingDatagrams())
{
QDateTime time = QDateTime::currentDateTime();//获取系统现在的时间
QString str = time.toString("yyyy-MM-dd hh:mm:ss ddd"); //设置显示格式
QByteArray datagram; // 让datagram的大小为等待处理的数据报的大小,这样才能接收到完整的数据
datagram.resize(receiver->pendingDatagramSize()); // 接收数据报,将其存放到datagram中
receiver->readDatagram(datagram.data(), datagram.size());
//ui->label->setText(datagram);
ui->listWidget->addItem(str);
chat += str+"\n";
qDebug()<<datagram<<QString::fromLocal8Bit(datagram);
ui->listWidget->addItem(usr_name+":"+QString::fromLocal8Bit(datagram));
chat += usr_name+":"+datagram+"\n";
}
}

  UDP发送端:

//中文支持
// QTextCodec *codec = QTextCodec::codecForName("GBK");
QDateTime time = QDateTime::currentDateTime();//获取系统现在的时间
QString str = time.toString("yyyy-MM-dd hh:mm:ss ddd"); //设置显示格式
int port = LoginWidget::ID+8000;
qDebug()<<port;
QByteArray datagram = ui->textEdit->toPlainText().toUtf8(); senderAno->writeDatagram(datagram.data(), datagram.size(),
QHostAddress("127.0.0.1"), port);
ui->listWidget->addItem(str);
chat += str+"\n";
qDebug()<<ui->textEdit->toPlainText()<<QString::fromLocal8Bit(ui->textEdit->toPlainText().toUtf8());
ui->listWidget->addItem("me:"+ui->textEdit->toPlainText());
chat += "me:"+ui->textEdit->toPlainText()+"\n";
this->ui->textEdit->clear();
this->ui->textEdit->setFocus();

  快捷键设置:

ui->pushButton->setShortcut(tr("ctrl+return"));
ui->pushButton_3->setShortcut(tr("alt+c"));
ui->pushButton_8->setShortcut(tr("ctrl+s"));

  文件操作:

/*
* 通过QFile实现数据操作
*/
qDebug()<<tr("Save File");
qDebug()<<chat;
QFile file("/Users/apple/Documents/QT/ChatLog.txt");
// 以只写方式打开,如果文件不存在,那么会创建该文件
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
qDebug() << file.errorString();
file.write(chat);
file.close();
QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("聊天记录保存成功!"));

  4.窗体传值:

  使用QT中的Signal&Slot机制进行传值:

  QT中的Signal&Slot机制相比于MFC中的消息机制简单了许多,它保证了任何对象之间均可以通过这种方式进行通信,甚至可以得到消息的sender。这里就拿一个简单的窗体间传值作为例子。

首先看一下主窗体MainWindow:

在设计器中拖拽一个Label和一个TextEdit控件到界面上,TextEdit用于显示传递过来的数据。

  创建一个右下有两个按键的对话框,放置一个Label和一个LineEdit。

  下面就是编码的操作了,我们需要在Dialog中声明一个信号,当用户点击OK时传递LineEdit中的内容到mainWindow中,具体的dialog.h代码为:

    #ifndef DIALOG_H
#define DIALOG_H #include <QDialog> namespace Ui {
class Dialog;
} class Dialog : public QDialog
{
Q_OBJECT public:
explicit Dialog(QWidget *parent = 0);
~Dialog(); private:
Ui::Dialog *ui;
signals:
void sendData(QString);
private slots:
void on_buttonBox_accepted();
}; #endif // DIALOG_H

  其中的signals:void sendData(QString)便是我们需要的信号函数,同时声明了一个槽函数

void on_buttonBox_accepted();用于相应确定按钮的click事件。下面就是需要在该函数中产生一个信号。代码如下:

    void Dialog::on_buttonBox_accepted()
{
emit sendData(ui->lineEdit->text());
}

  代码异乎寻常的简单,只需要用emit的方式调用sendData函数,将需要的参数传递进去即可。而MainWindow中则需要声明接收的槽函数,注意槽函数参数只能与信号函数少或相等,而不能多于信号函数参数个数。在MainWindow的头文件中声明槽函数:

    private slots:
void receiveData(QString data);

  为了便于测试,我只在MainWindow的构造函数中创建了一个Dialog对象,并连接了信号和槽,具体为:

    MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//信号槽方式下父子窗体传值的测试
Dialog *dlg = new Dialog;
//关联信号和槽函数
connect(dlg,SIGNAL(sendData(QString)),this,SLOT(receiveData(QString)));
// dlg->setModal(true); 不论是模态或者非模态都可以正常传值
dlg->show();
}

  这里,我没有将父窗口的指针传递到Dialog中,如new Dialog(this),这种方式下,实际上可以归结到第三类传值方式中去。因为此时,可以使用MainWindow中的父窗口的函数进行数据的赋值和操作。

这里,可能还有一个问题就是,父窗口如何给子窗口传值,一方面,仍然可以使用信号和槽的方式进行,但是,我感觉更便利的方式倒是使用这种public接口的方式进行传值。这种来的更直接和明显。当然,可以看出Signal&Signal方式进行此类的处理会更有通用性。

在receiveData(QString)的槽函数中进行接收到数据的处理,这里仅仅进行了简单的显示:

    void MainWindow::receiveData(QString data)
{
ui->textEdit->setText(data);
}

  最后看下结果:

  

  最终的结果,因为信号和槽可以是多对多的,所以,在类似多个窗体广播信息时,这种方式就很有用,当然也可以使用全局变量的形式。

   使用全局变量;

使用public形式的函数接口;

使用QT中的Event机制(这种没有把握,但是感觉应该是可以的),但是实现起来应该比前几种复杂,这里不做讨论。

   5.中文支持:

  网上搜索一下,找到的都是这种:

#include < QTextCodec >
int main(int argc, char **argv)
{
....................
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF8"));
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF8"));
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF8"));
..........................
}

  Qt5中, 取消了QTextCodec::setCodecForTr()和QTextCodec::setCodecForCString()这两个函数,而且网上很多都是不推荐这种写法。

有一下几种转换方法:

1、

QTextCodec * BianMa = QTextCodec::codecForName ( "GBK" );

    QMessageBox::information(this, "提示", BianMa->toUnicode("中文显示!"));

2、也可以通过QString定义的静态函数,先转换成Unicode类型:

QString::fromLocal8Bit("提示")

3、在Qt5中,提供了一个专门的处理宏,来支持中文常量,那就是QStringLiteral,但它只能处理常量。

QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("中文显示"));const char* info = "中文显示";
//不支持
QString strInfo = QStringLiteral(info);
//支持
QString strInfo = QString::fromLocal8Bit(info);

QByteArray QString::toLatin1() const

  Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。
  ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。

  toLatin1压缩掉了QString自动给每个英文字符加上的那些00字节.

QString与QByteArray互相转换的方法

  QString转QByteArray方法

//Qt5.3.2
QString str("hello");
QByteArray bytes = str.toUtf8(); // QString转QByteArray方法1 QString str("hello");
QByteArray bytes = str.toLatin1(); // QString转QByteArray方法2

  QByteArray转QString方法

//Qt5.3.2
QByteArray bytes("hello world");
QString string = bytes; // QByteArray转QString方法1 QByteArray bytes("hello world");
QString string;
string.prepend(bytes);// QByteArray转QString方法2 qDebug() << string;

  QByteArray类同样不以’\0’为结尾:如

QByteArray bytes;
bytes.resize(5);
bytes[0] = '1';
bytes[1] = '2';
bytes[2] = '3';
bytes[3] = '\0';
bytes[4] = 'a'; cout << bytes << endl;

三、结果图

登录界面:

好友列表:

注册界面:

聊天窗口:

聊天记录:

QT开发之旅-Udp聊天室编程的更多相关文章

  1. 与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室

    原文:与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室 [索引页][源码下载] 与众不同 windows phon ...

  2. XMPPFrameWork IOS 开发(六)聊天室

    原始地址:XMPPFrameWork IOS 开发(六)聊天室 聊天室 //初始化聊天室 XMPPJID *roomJID = [XMPPJID jidWithString:ROOM_JID]; xm ...

  3. 与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室

    原文:与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室 [索引页][源码下载] 与众不同 windows phon ...

  4. Java Socket聊天室编程(二)之利用socket实现单聊聊天室

    这篇文章主要介绍了Java Socket聊天室编程(二)之利用socket实现单聊聊天室的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下 在上篇文章Java Socket聊天室编程(一)之 ...

  5. Java Socket聊天室编程(一)之利用socket实现聊天之消息推送

    这篇文章主要介绍了Java Socket聊天室编程(一)之利用socket实现聊天之消息推送的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下 网上已经有很多利用socket实现聊天的例子了 ...

  6. Qt NetWork即时通讯网络聊天室(基于TCP)

    本文使用QT的网络模块来创建一个网络聊天室程序,主要包括以下功能: 1.基于TCP的可靠连接(QTcpServer.QTcpSocket) 2.一个服务器,多个客户端 3.服务器接收到某个客户端的请求 ...

  7. QT开发之旅一DS7400主机调试工具

    接触QT三年有余,期间因为工作需要断断续续学习过,2010年开始接触,当时好像是4.7版本,现在都已经到5.2版本了,更新真快,前阵子安装了下5.2版本,还是有很多变化的,不过感觉好像编译速度慢了很多 ...

  8. 基于Websocket开发的仿微信聊天室

    一.运行环境及涉及技术:----------------------------------* Visual Studio 2019* SQL SERVER 2008 R2* .Net FrameWo ...

  9. java 开发 websocket 网页端聊天室

    博客地址:https://ainyi.com/67 WebSocket协议是基于TCP的一种新的网络协议.它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端. ...

随机推荐

  1. Rust hello world 语法解说

    Rust的hello world代码例如以下: fn main() { println!("Hello, world!"); } 1.fn main() fn main(){   ...

  2. ubuntu12.04安装NVIDIA显卡驱动和CUDA

    1.安装显卡驱动 vim /etc/modprobe.d/blacklist.conf #编辑该文件 blacklist nouveau #行末添加,禁用原来的显卡驱动 apt-get install ...

  3. 图像处理之基础---图像缩放中的双线性插值c实现

    在进入频域变换之前, 我们还是轻松一下,再搞点平面上的变化来看看.这把选了一个双线性插值(Bilinear interpolation)来实现是源于看到了csdn上别人的问题, 权且实现一个函数,方便 ...

  4. C#下JSON字符串的反序列化

    C#下JSON字符串的反序列化,一般都是用newtonsoft.json,比较方便..net当然也有提供相应功能,但觉得比较复杂. 所谓反序列化,就是将一个包含JSON内容的字符串,转换回指定对象(不 ...

  5. srcset

    <div id="pg-334-2" class="panel-grid panel-has-style"> <div style=" ...

  6. hbase 增删改查 api 简单操作

    package com.utils; import java.io.IOException; import java.util.ArrayList; import java.util.List; im ...

  7. ios25---图片拉伸

    控制器: // // ViewController.m // 12-图片的拉伸问题 // // Created by xiaomage on 15/12/30. // Copyright © 2015 ...

  8. react-native 项目更名步骤

    刚开始开发项目的时候,更没有想好一个项目名称,如何才能更名一个RN APP名称呢,可按照如下方式操作即可. 使用说明 更改package.json { "name": " ...

  9. Python: PS 滤镜--马赛克

    本文利用 Python 实现PS 滤镜中的马赛克效果,具体的算法原理和效果可以参考之前的博客: http://blog.csdn.net/matrix_space/article/details/30 ...

  10. 从EJB规范理解微服务

    晚上看了一篇从JavaEE谈微服务的文章,里面不少观点还是很有启发的,下面最有感触的,从失败的EJB谈微服务. 说起JavaEE规范,要先从EJB(Enterprise Java Bean),他是一种 ...