休个国庆休得累死,好吧,今天没出去,我们来看看我们写的那个网络库怎么用于实际运用中,在上一讲里,我们展示了在控制台中的使用(其实都一样,我理解大家想要写一些窗口程序的心情,一开始学习编程语言的时候都想写出一些实际点的东西出来,但是等到后来大家也就不会再有想要去写窗口程序的冲动了),我们展示了三种方法来说明怎么使用这个网络库,这一讲我们使用第四种方法,这也是一种极为重要的方法,我们让新成员函数成为这个网络库的回调函数。
好吧,接触Qt大概也快一个月的时间了(有空的时候看一看书),所以今天就用Qt来给大家演示一下这个网络库的第四种用法,我们这个程序由于是作为一个聊天服务器来使用,所以界面很简单,基础点说就是一个列表框,用来显示所有在线的用户,一个文本编辑框,用来显示用户的聊天信息,他的样子如下这般:
这个窗口很简单,所以我们就简单的开始吧,首先,我们从QMainindow派生一个我们自己的类,这里就叫他Tester吧,这个类看起来像这样子:
//===============================
========class Tester==============
class Tester :public QMainWindow{
Q_OBJECT
public:
Tester(QWidget* parent = 0);
~Tester();
private:
void InitMenu();
void InitLayout();
void SetListIterm();
void SetText();
protected:
void timerEvent(QTimerEvent*);
//计时器
wchar_t* stow(wchar_t**, string&
);
//string转换为wchar_t
char* wtoc(wchar_t*, size_t);
//宽字符转换为多字节字符
private slots:
void SetUser();
void SetItem(QListWidgetItem*);
void SetListItem(std::vector<QListWidgetItem*>&
);
private:
QMenuBar*
p_MenuBar;
//菜单栏
QMenu*
p_Action;
//菜单
QAction*
p_User;
//子菜单
QAction*
p_Close;
//子菜单
QLabel*
p_ListLabel;
//标签贴
QLabel*
p_EditLabel;
//标签贴
ListWidget*
p_List;
//列表框
QTextEdit*
p_Edit;
//编辑框
QHBoxLayout*
p_Hlayout;
//水平布局管理器
QVBoxLayout*
p_Vlayout1;
//垂直布局管理器
QVBoxLayout*
p_Vlayout2;
//垂直布局管理器
QWidget*
p_CentralWidget;
//中心窗口,简单点说就是工作区域
QIcon*
p_usericon;
//用户图标
private:
void ProcMsg(PER_HANDLE_DATA*, std::string&
);
//回调函数
QString
m_ChatString;
int
m_TimerID;
bool
b_setText;
std::vector<QListWidgetItem*>v_ListItem;
//储存所有用户的容器
std::vector<std::wstring> v_listName;
std::vector<std::shared_ptr<PER_HANDLE_DATA>> v_User;
std::shared_ptr<MyNet>
m_Net;
OIFUN
m_fun;
};
===================================
目前我们就只需要这么多,ok,现在我假设大家的C++基础已经足够理解下面我们的实现,所以我可能会跳跃着说一些大家可能不明白的地方,当然关于Qt部分的知识这里就不多说,毕竟想要在一讲两讲内容给大家说清Qt是不可能的,但是我保证给出了界面的一些东西外,我们使用的还是我们最为熟悉的C++知识(当然,Qt也是纯C++写的,只是当前大家可能对Qt还不甚了解)
//==============实现=================
============构造函数=================
Tester::Tester(QWidget* parent) :QMainWindow(parent){
InitMenu();
InitLayout();
this->setWindowTitle("
Chat Service"
);
this->resize(750, 450);
m_path = nullptr;
m_Net = std::make_shared<MyNet>();
m_Net->CreatAndListen();
connect(p_Close, SIGNAL(triggered()), this, SLOT(close()));
this->setWindowIcon(QIcon("
Resources/Classes1_32x32x16.ico"
));
m_TimerID = startTimer(100);
b_setText = false;
//这里注意一点,这是使用我们这个网络库的关键之处,如果大家用的编译器支持C++11,可以选择使用boost的functional库,他提供了相同的功能,而且使用方法也完全一样
//因为成员函数中有个隐藏的参数(this),所以我们想要将成员函数作为我们网络库的回调函数,那就必须将this绑定,也就说让他对网络库的调用来说如同虚设,通过这么bind之后,这个成员函数就是我们想要的类型了,然后通过SetFun来注册一下就可以很好的工作了。
m_fun = std::bind(&
Tester::ProcMsg,this,std::placeholders::_1,std::placeholders::_2);
m_Net->SetFun(m_fun);
//构造列表框图标
p_usericon = new QIcon(tr("
Resources/LogOff.ico"
));
show();
}
===========析构函数============
//因为使用智能指针的话,所以析构函数很简单,除了m_path这个指针需要注意之外,我的想法是谁使用,谁释放,所以我这里就不管他了(这不是个好设计思想)
Tester::~Tester(){
}
//关于wtoc和stow是两个辅助函数,因为我们的网络库用的是多字节来开发的,但是Qt默认情况是UniCode,所以我们会需要用他们来完成字节的转换,可能大家会想,为什么当初我们不直接使用宽字符来开发呢?因为现在很多的C API依然使用const char*作为参数接口。
============两个辅助函数=======
char* Tester::wtoc(wchar_t* str, size_t count)
{
char* m_path = new char[count];
WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, str, -1,m_path, count, NULL, NULL);
return m_path;
}
wchar_t* Tester::stow(wchar_t** result, string&
str){
int w_len = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);
*result = new wchar_t[w_len + 1];
ZeroMemory(*result, w_len + 1);
MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, *result, w_len);
return *result;
}
===========初始化菜单==========
void Tester::InitMenu(){
p_MenuBar = menuBar();
p_Action = p_MenuBar->addMenu(tr("
Action"
));
p_User = new QAction(QIcon("
Resources/LogOff.ico"
), tr("
User(&
U)"
), this);
p_Close = new QAction(QIcon("
Resources/No3_32x32x256.ico"
), tr("
Exit(&
E)"
),this);
p_Action->addAction(p_User);
p_Action->addSeparator();
p_Action->addAction(p_Close);
p_MenuBar->addMenu(p_Action);
}
===========初始化布局===========
void Tester::InitLayout(){
p_CentralWidget = new QWidget(this);
this->setCentralWidget(p_CentralWidget);
p_List = new ListWidget(p_CentralWidget);
p_Edit = new QTextEdit(p_CentralWidget);
p_ListLabel = new QLabel(tr("
User List"
), p_CentralWidget);
p_EditLabel = new QLabel(tr("
Message Box"
), p_CentralWidget);
p_List->setFixedWidth(150);
p_List->setMinimumHeight(400);
p_Edit->setMinimumWidth(400);
p_Edit->setFont(tr("
Arial"
));
p_Edit->setFontPointSize(12);
connect(p_Edit, SIGNAL(cursorPositionChanged()), this, SLOT(setStatusBar()));
p_ListLabel->setFont(tr("
Arial"
));
p_ListLabel->setFixedSize(150, 20);
p_ListLabel->setStyleSheet("
font:12pt;
"
);
p_EditLabel->setFont(tr("
Arial"
));
p_EditLabel->setMidLineWidth(400);
p_EditLabel->setFixedHeight(20);
p_EditLabel->setStyleSheet("
font:12pt"
);
p_Hlayout = new QHBoxLayout(p_CentralWidget);
p_Vlayout1 = new QVBoxLayout(p_CentralWidget);
p_Vlayout1->addWidget(p_ListLabel);
p_Vlayout1->addWidget(p_List);
p_Vlayout2 = new QVBoxLayout(p_CentralWidget);
p_Vlayout2->addWidget(p_EditLabel);
p_Vlayout2->addWidget(Tester::p_Edit);
p_Hlayout->addLayout(p_Vlayout1);
p_Hlayout->addLayout(p_Vlayout2);
p_CentralWidget->setLayout(p_Hlayout);
connect(p_List, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(SetItem(QListWidgetItem*)));
}
============设置列表框============
void Tester::SetListIterm(){
v_User = m_Net->GetUserVector();
if (!v_User.empty()){
auto it = v_User.begin();
while (it != v_User.end()){
if (std::find_if(v_ListItem.begin(), v_ListItem.end(), [&
](const QListWidgetItem* listitem){
return (listitem->text()).toStdString() == (*it)->GetName();
}) == v_ListItem.end()){
wchar_t* temp;
temp = stow(&
temp, (*it)->GetName());
QListWidgetItem* item = new QListWidgetItem(*p_usericon, QString(QString::fromStdWString(temp)));
v_ListItem.push_back(item);
delete[] temp;
}
it++;
}
}
std::sort(v_ListItem.begin(), v_ListItem.end(), [&
](QListWidgetItem*item1, QListWidgetItem* item2){
return item1->text().toStdString() <item2->text().toStdString();
});
SetListItem(v_ListItem);
}
void Tester::SetListItem(std::vector<QListWidgetItem*>&
v){
if (!v.empty()){
int count = v.size();
p_List->setStyleSheet("
font:12pt;
"
);
for (int i = 0;
i <count;
++i){
p_List->insertItem(i, v.at(i));
}
m_Net->SetupdateUserinfo(false);
}
}
===============================
void Tester::SetUser(){
std::vector<std::shared_ptr<PER_HANDLE_DATA>>v_User;
v_User = m_Net->GetUserVector();
std::vector<QListWidgetItem*>v_UserList;
if (!v_User.empty()){
auto it = v_User.begin();
while (it != v_User.end()){
QListWidgetItem* item = new QListWidgetItem(*p_usericon,tr((*it)->GetName().c_str()));
v_UserList.push_back(item);
it++;
}
}
SetListItem(v_UserList);
}
void Tester::SetText(){
p_Edit->setText(m_ChatString);
}
//如果我们用的是MFC,我们完全不需要这个计时器,但是我们现在用的是Qt,所以我们只能用一个计时器来查询信息(也许可能是我接触Qt的时间不多,对他的了解不透彻,觉得在跨线程中MFC做的比Qt好很多,当然在Qt中我们可以用一个计时器来解决窗口部件跨线程的问题
void Tester::timerEvent(QTimerEvent* e){
// 还记得我们网络库里面有一个bool量吗?当时就给大家说他是用在窗口程序中的,就是为Qt准备的,如果是MFC的话就不需要那东西,但是我们现在需要用他来执行通知,使用完成之后,我们会将他重新设置为false
bool b = m_Net->GetupdateUserInfo();
if (b){
this->SetListIterm();
}
if (b_setText){
this->SetText();
b_setText = false;
}
}
//最后我们处理消息的函数就这么简单,因为关于消息的收发都在网络类中自动完成,所以我们只需要取出收到的信息
void Tester::ProcMsg(PER_HANDLE_DATA* pData, std::string&
_str)
{
QDateTime tm = QDateTime::currentDateTime();
QString str = tm.toString("
MM/dd/yyyy hh:mm:ss ddd"
);
m_ChatString += str;
m_ChatString += "
"
;
wchar_t*
temp = nullptr;
stow(&
temp, _str);
//如果没有这步的转换,中文将会显示乱码,可能大家会说怎么不适用翻译呢?用过了,依然没效果,所以最后只好使用显示转码来显示中文的
m_ChatString += QString::fromStdWString(temp);
m_ChatString += "
rn"
;
std::ofstream outFile("
InfoLog.txt"
,std::ios_base::app);
outFile <<m_ChatString.toStdString();
b_setText = true;
delete[] temp;
//使用完成之后记得释放
temp = nullptr;
}
=====================================
最终我们的小程序使用起来如下图所示,每个人的聊天记录都会储存在这里,而且写进日志,驱动程序如下:
//=============main=============
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Tester w;
return a.exec();
}
==================================
运行效果如下:
//================================
======================================
我将列表框响应右键消息的代码省略了,如果大家有兴趣的,其实很简单,从QListWidget派生一个类出来,重写
void contextMenuEvent(QContextMenuEvent*)函数,他就能够响应鼠标右键的消息了。
ok,就这么多吧,关于怎么去写一个服务器程序,想必大家都有自己的想法了,我们的这个实例开发就算是结束了,对了,最近翻了一下More Effective C++,重新审视了我对引用计数的使用方法,发现我和他的用法很不一样,这样吧,假设我们要写一个string库,因为我们对效率有相当的苛求,所以希望这个string库是带引用计数的,大家可以回头去看看我给大家介绍的引用计数的知识,然后试着来写一个这样的库,我们希望得到的结果如同下面这样:
//=======Test MyString==========
MyString str("
Hello"
);
MyString str1(str);
MyString str2;
str2 = str;
//到这里,我们可能保证上面三个MyString对象的数据共享,但是如果对任意一个对象使用[]操作符时,他不再共享数据,如下:
str2[2] = 'w';
这时,str2不再和str,str1共享数据。
==================================
下一讲我们来展示两种构造方法,大家也随便比较一下,是我使用的方法更好一些呢还是More Effective C++上面展示的方法更为高效一些。
=====================================
回复D或者d直接查看目录,回复数字查看当前相应章节内容
原文始发于微信公众号(
C/C++的编程教室
):第九十一讲 综合运用(6)
|