前言

  前面实现了基础的跳转,那么动态交互中登录是常用功能。
  本篇实现一个动态交互的简单登录和注销功能,在Qt中使用Session和Cookie技术。

 

Demo

  

下载地址

 

Html处理用户输入Session与Cookie

表单登录submit

  Web应用程序通常处理用户输入。将开发一个登录表单,看看进展如何。
创建一个名为LoginController的新类。同样,它是从HttpRequestHandl派生的

logincontroller.h:

#ifndef LOGINCONTROLLER_H
#define LOGINCONTROLLER_H #include "httprequesthandler.h" using namespace stefanfrings; class LoginController : public HttpRequestHandler {
Q_OBJECT
public:
LoginController(QObject* parent=0);
void service(HttpRequest& request, HttpResponse& response);
}; #endif // LOGINCONTROLLER_H

logincontroller.cpp:

#include "logincontroller.h"

LoginController::LoginController(QObject* parent)
:HttpRequestHandler(parent) {
// empty
} void LoginController::service(HttpRequest &request, HttpResponse &response) {
QByteArray username=request.getParameter("username");
QByteArray password=request.getParameter("password"); qDebug("username=%s",username.constData());
qDebug("password=%s",password.constData()); response.setHeader("Content-Type", "text/html; charset=UTF-8");
response.write("<html><body>"); if (username=="test" and password=="hello") {
response.write("Yes, correct");
}
else {
response.write("<form method='POST' action='/login'>");
if (!username.isEmpty()) {
response.write("No, that was wrong!<br><br>");
}
response.write("Please log in:<br>");
response.write("Name: <input type='text' name='username'><br>");
response.write("Password: <input type='password' name='password'><br>");
response.write("<input type='submit'>");
response.write("</form");
} response.write("</body></html>",true);
}

  (PS:html代表是提交表单)
  将这个新控制器添加到请求映射器中,修改requestmapper.h:

#ifndef REQUESTMAPPER_H
#define REQUESTMAPPER_H #include "httprequesthandler.h"
#include "helloworldcontroller.h"
#include "listdatacontroller.h"
#include "logincontroller.h" class RequestMapper : public HttpRequestHandler {
Q_OBJECT
public:
RequestMapper(QObject* parent=0);
void service(HttpRequest& request, HttpResponse& response);
private:
HelloWorldController helloWorldController;
ListDataController listDataController;
LoginController loginController;
}; #endif // REQUESTMAPPER_H

  修改requestmapper.cpp(切入了/login,调用loginController):

#include "requestmapper.h"

RequestMapper::RequestMapper(QObject* parent)
: HttpRequestHandler(parent) {
// empty
} void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
QByteArray path=request.getPath();
qDebug("RequestMapper: path=%s",path.data()); if (path=="/" || path=="/hello") {
helloWorldController.service(request, response);
}
else if (path=="/list") {
listDataController.service(request, response);
}
else if (path=="/login") {
loginController.service(request, response);
}
else {
response.setStatus(404,"Not found");
response.write("The URL is wrong, no such document.");
} qDebug("RequestMapper: finished request");
}

  运行程序并打开URLhttp://localhost:8080/login.将看到以下表格:

  
  尝试使用错误的名称和密码登录。然后浏览器显示错误消息“That was wrong”,并提示重试。如果输入了正确的凭据(用户名“test”和密码“hello”),则会收到成功消息。

  HTML表单定义了两个名为“username”和“password”的输入字段。控制器使用request.getParameter()来获取这些值。

  当参数为空或传入的HTTP请求中没有这样的参数时,Request.getParameter() 返回一个空的QByteArray。后一种情况发生在打开URL时http://localhost:8080/login开始只有当用户单击提交按钮时,表单字段才会从web浏览器发送到web服务器。

  如果需要区分空字段和缺失字段,那么可以使用request.getParameterMap(),然后检查所需参数是否在返回的映射中。

  作为表单的替代方案,参数也可以作为URL的一部分进行传输。例如,也可以通过打开URL登录http://localhost:8080/login?username=test&password=hello.

  在URL中使用某些特殊字符时,必须将其编码为转义序列。例如,如果用户名是“Stefan Frings”,那么必须写http://localhost:8080/login?username=Stefan%20Frings&password=hello.HttpRequest类会自动将其解码回原始形式“Stefan Frings”。

  如果需要将字符串编码为URL格式,可以使用QUrl类。

服务器本地会话session

  (PS:session和cookie是一起搭配使用的,cookie存在本地 session可以拿到cookie来判断是否登录了,等一些已有的状态)
  下一个合乎逻辑的步骤是处理会话数据。这意味着,将当前用户的数据保存在某个地方,并在后续请求中使用这些数据。将存储的第一个数据是用户的姓名和登录时间。
  QtWebApp使用隐藏的cookie来识别用户。
  必须在控制会话存储类的配置文件webapp1.ini中添加一个新的部分:

[sessions]
expirationTime=3600000
cookieName=sessionid
;cookieDomain=mydomain.com
cookiePath=/
cookieComment=Identifies the user

  过期时间定义从内存中删除未使用的会话后的毫秒数。当用户在该时间之后返回时,他的会话将丢失,因此他必须再次登录。

  • expirationTime:保存时间,实际上从后面的cookie截图可以看到是3600000ms,则是3600秒一小时的时间过期,这个保存后的默认过期时间。
      
  • sessionid:cookie名称可以是任意名称,但通常使用名称“sessionid”。有些负载均衡器依赖于这个名称,所以除非有充分的理由,否则不应该更改它。
  • cookieDomain:每个cookie总是绑定到一个域。由google.com生成的cookie只发送到该域上的服务器。如果将cookieDomain参数留空或将其注释掉,则该参数将由web浏览器自动设置。可以指定另一个域名,但不知道男人为什么要这么做。所以,除非知道自己在做什么,否则就把它空着。
  • cookiePath:cookie路径可用于将cookie限制在的域的一部分。如果将cookiePath更改为/my/very/cool/online/shop,则浏览器将仅针对以该路径开头的页面将cookie发送到的服务器。默认值为“/”,这意味着cookie对域中的所有网站都有效。
  • cookieComment:cookieComment是一些网络浏览器在cookie管理屏幕中显示的文本。
      需要HttpSessionStore类的一个实例,整个程序都可以访问该实例,因此可以在全局空间中访问。因此,创建了两个新文件,第一个是global.h:
#ifndef GLOBAL_H
#define GLOBAL_H #include "httpsessionstore.h" using namespace stefanfrings; extern HttpSessionStore* sessionStore; #endif // GLOBAL_H

  global.cpp:

#include "global.h"

HttpSessionStore* sessionStore;

  现在有了一个名为“sessionStore”的全局静态指针,整个程序可以通过包含global.h文件来访问该指针。让加载新的配置设置并初始化sessionStore。
  main.cpp中的更改:

#include "global.h"

int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QString configFileName=searchConfigFile(); // Session store
QSettings* sessionSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
sessionSettings->beginGroup("sessions");
sessionStore=new HttpSessionStore(sessionSettings,&app); // HTTP server
QSettings* listenerSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
listenerSettings->beginGroup("listener");
new HttpListener(listenerSettings,new RequestMapper(&app),&app); return app.exec();
}

  请注意,main.cpp现在加载配置文件两次。sessionSettings对象选择“sessions”部分,而listenerSettings选择“listener”部分。对于每个部分,需要一个单独的QSettings实例,否则这些部分可能会混淆。
  既然已经为会话数据创建了一个存储,就可以开始使用它了。添加到logincontroller.cpp:

#include <QTime>
#include "logincontroller.h"
#include "global.h" LoginController::LoginController(QObject* parent)
:HttpRequestHandler(parent) {
// empty
} void LoginController::service(HttpRequest &request, HttpResponse &response) {
HttpSession session=sessionStore->getSession(request,response,true);
QByteArray username=request.getParameter("username");
QByteArray password=request.getParameter("password"); qDebug("username=%s",username.constData());
qDebug("password=%s",password.constData()); response.setHeader("Content-Type", "text/html; charset=UTF-8");
response.write("<html><body>"); if (session.contains("username")) {
QByteArray username=session.get("username").toByteArray();
QTime logintime=session.get("logintime").toTime();
response.write("You are already logged in.<br>");
response.write("Your name is: "+username+"<br>");
response.write("You logged in at: "+logintime.toString("HH:mm:ss")+"<br>");
}
else {
if (username=="test" and password=="hello") {
response.write("Yes, correct");
session.set("username",username);
session.set("logintime",QTime::currentTime());
}
else {
response.write("<form method='POST' action='/login'>");
if (!username.isEmpty()) {
response.write("No, that was wrong!<br><br>");
}
response.write("Please log in:<br>");
response.write("Name: <input type='text' name='username'><br>");
response.write("Password: <input type='password' name='password'><br>");
response.write("<input type='submit'>");
response.write("</form");
}
} response.write("</body></html>",true);
}

  在这里,重要的是在第一次调用response.write()之前调用sessionStore->getSession(),因为它创建或刷新会话cookie,从技术上讲,它是一个HTTP头。并且所有HTTP标头都必须在HTML文档之前发送。
  通过打开来运行和测试程序http://localhost:8080/login.
  
  现在查看web服务器的控制台窗口时,看到一个cookie和一个会话是用一个唯一的id创建的,这个id是一个长的随机十六进制数。
  从Chromium浏览器中截取了以下截图,可以在其中看到cookie:
  
  用户会话最初为空。它只是存在,并且有一个唯一的id号。没有别的。输入用户名“test”和密码“hello”。然后会得到预期的确认。
  
  在确认登录成功的同时,服务器将用户名和登录时间输入到用户会话中。可以将任何对象放入QVariant支持的会话中。当将一个对象放入会话时,会给它一个符号名称,以便以后访问。
  现在再次打开URLhttp://localhost:8080/login.然后会看到所有这些工作的结果:
  
  因此,在成功验证用户名和密码后,服务器使用会话来记住该用户的数据。
  会话存储保存在内存中。重新启动web服务器时,会话存储中的所有数据都会丢失。因此,只将其用于一些临时数据。持久性数据属于一个数据库。

用户本地终端的存储cookies

  作为会话存储的替代方案,也可以在cookie中存储少量数据。Cookie存储在web浏览器的客户端,而不是服务器端。Cookie只能存储8位的文本,并且只能保证4 KB的容量。此外,每个域的cookie数量是有限的,因此请保留使用它们。
  为了尝试这一点,添加一个名为CookieTestController的新控制器类,并将其绑定到路径“/cookie”。

cookietestcontroller.cpp:

#include "cookietestcontroller.h"

CookieTestController::CookieTestController(QObject* parent)
: HttpRequestHandler(parent) {
// empty
} void CookieTestController::service(HttpRequest &request, HttpResponse &response) { QByteArray cookie=request.getCookie("testcookie");
if (!cookie.isEmpty()) {
response.write("Found cookie with value: "+cookie,true);
}
else {
HttpCookie cookie("testcookie","123456789",60);
response.setCookie(cookie);
response.write("A new cookie has been created.",true);
} }

  cookie的名称为“testcookie”,其值为“123456789”。60是以秒为单位的有效时间。 此cookie在创建一分钟后过期。这段短暂的时间来看看cookie过期后会发生什么。通常会使用更大的时间,可能是几天。
  Request.getCookie()只返回cookie的值,而不是整个HttpCookie对象。这是出于性能原因。
  运行程序并打开http://localhost:8080/cookie.
  

  然后再次加载同一页面,将看到服务器正确接收到cookie:
  

  Cookie存储在网络浏览器中,并随每个HTTP请求一起发送到网络服务器。除了会话数据之外,如果重新启动服务器,cookie不会丢失,因为cookie存储在客户端。
  Chromium浏览器的屏幕截图:
  

  在这里,可以看到会话cookie仍然存在。现在有两个本地主机域的cookie。
  如果等待两分钟,然后再次加载http://localhost:8080/cookie将看到测试cookie已过期。服务器会创建一个新的。
  如果想防止cookie在用户处于活动状态时过期,那么必须在每次请求时重新创建cookie。然后,浏览器会为每个请求计算一个新的截止日期。

 

Demo增量:实战用户登录

步骤一:准备代码模板

  准备之前的demo v1.2.0模板:
  

步骤二:新增登录入口处理LoginRequestHandler

  

  修改一下:
  

  

步骤三:将登录界面的html的添加进登录入口

  

void LoginRequestHandler::service(HttpRequest &request, HttpResponse &response)
{ QString str; QString path = request.getPath();
LOG << path; str = "<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>长沙红胖子Qt</title>"
"</head>"
"<body>"
" <a href=\"javascript:history.back(-1);\">返回上一页</a>"
" <form method='POST' action='/login'>"
" <p>Please login</p>"
" <p>Name: <input type='text' name='username'></p>"
" <p>Password: <input type='password' name='password'></p>"
" <p><input type='submit'></p>"
" </form>"
"</body>"; // 返回文本(需要在浏览器上看,所以将Qt内部编码都转成GBK输出即可,不管他本身是哪个编码)
// QByteArray byteArray = _pTextCodec->fromUnicode(str);
QByteArray byteArray = str.toUtf8();
response.write(byteArray);
}

步骤四:在原来Index上新增一个连接地址跳转登录

  
  

  这个时候是可以跳转了。
  
  此时,登录打印:
  
  可以,其路径提交也是触发了一次进入login的service,这时候就需要通过Session的变量来判断是第一次进入还是提交登录。

步骤五:添加sessionStore头文件

  SessionStore头文件在httpserver模块中。
  

步骤六:添加唯一实例类sessionStore,封装session存储类

  
  
  

步骤七:添加配置文件

[sessions]
expirationTime=3600000
cookieName=sessionid
;cookieDomain=mydomain.com
cookiePath=/
cookieComment=Identifies the user

  

步骤八:添加session加载配置代码

  
  

步骤九:通过session判断

  在没有session之前是可以获取表单提交的参数:
  

  添加session判断后:

void LoginRequestHandler::service(HttpRequest &request, HttpResponse &response)
{ QString str; QString path = request.getPath();
LOG << path; // 这里获取到的param是直接获取的
QString userName = request.getParameter("username");
QString password = request.getParameter("password");
LOG << userName << password; // 获取session(单例模式封装后的全局获取)
HttpSession httpSession =
HttpSessionStoreManager::getInstance()->getHttpSessionStore()->getSession(request,response,true); // 先判断是否session能获取到
if(!httpSession.contains("username"))
{
// 这里没有cookie,提交登录则是可以保存本地cookie
if(userName == "root" && password == "root123456")
{
httpSession.set("username" , userName);
httpSession.set("logintime", QTime::currentTime());
httpSession.set("timeout" , 60); QString username = httpSession.get("username").toString();
QTime logintime = httpSession.get("logintime").toTime();
int timeout = httpSession.get("timeout").toInt(); str = QString("<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>长沙红胖子Qt</title>"
"</head>"
"<body>"
" <a href=\"javascript:history.back(-1);\">注销登录</a>"
" <p>登录账户:%1</p>"
" <p>登录时间:%2</p>"
" <p>超时时间:%3</p>"
"</body>")
.arg(username)
.arg(logintime.toString("hh:mm:ss:zzz"))
.arg(timeout); }else{
str = "<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>长沙红胖子Qt</title>"
"</head>"
"<body>"
" <a href=\"javascript:history.back(-1);\">返回上一页</a>"
" <form method='POST' action='/login'>"
" <p>Please login</p>"
" <p>Name: <input type='text' name='username'></p>"
" <p>Password: <input type='password' name='password'></p>"
" <p><input type='submit'></p>"
" </form>"
"</body>";
}
}else {
// 之前已经登录过了
QString username = httpSession.get("username").toString();
QTime logintime = httpSession.get("logintime").toTime();
int timeout = httpSession.get("timeout").toInt(); str = QString("<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>长沙红胖子Qt</title>"
"</head>"
"<body>"
" <a href=\"javascript:history.back(-1);\">注销登录</a>"
" <p>登录账户:%1</p>"
" <p>登录时间:%2</p>"
" <p>超时时间:%3</p>"
"</body>")
.arg(username)
.arg(logintime.toString("hh:mm:ss:zzz"))
.arg(timeout);
} // 返回文本(需要在浏览器上看,所以将Qt内部编码都转成GBK输出即可,不管他本身是哪个编码)
// QByteArray byteArray = _pTextCodec->fromUnicode(str);
QByteArray byteArray = str.toUtf8();
response.write(byteArray);
}

  查看session
  

步骤十:添加一个“注销登录”

  

// 注销,清空session
if(path == "/login/out")
{
HttpSession httpSession =
HttpSessionStoreManager::getInstance()->getHttpSessionStore()->getSession(request,response,true); HttpSessionStoreManager::getInstance()->getHttpSessionStore()->removeSession(httpSession);
}
 

Demo源码

LoginRequestHandler.h

#ifndef LOGINREQUESTHANDLER_H
#define LOGINREQUESTHANDLER_H #include "httprequesthandler.h" #include "HelloWorldRequestHandler.h"
#include "ListRequestHandler.h" using namespace stefanfrings; class LoginRequestHandler : public HttpRequestHandler
{
public:
LoginRequestHandler(QObject *parent = 0); public:
void service(HttpRequest& request, HttpResponse& response); private:
QTextCodec *_pTextCodec; private: };
#endif // LoginRequestHandler_H

LoginRequestHandler.cpp

#include "LoginRequestHandler.h"

#include "ListRequestHandler.h"
#include "HttpSessionStoreManager.h" #include <QTextCodec> #include <QDebug>
#include <QDateTime>
//#define LOG qDebug()<<__FILE__<<__LINE__
//#define LOG qDebug()<<__FILE__<<__LINE__<<__FUNCTION__
//#define LOG qDebug()<<__FILE__<<__LINE__<<QThread()::currentThread()
//#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd")
#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz") using namespace stefanfrings; LoginRequestHandler::LoginRequestHandler(QObject *parent)
: HttpRequestHandler(parent)
{
// 返回文本(需要在浏览器上看,所以将Qt内部编码都转成GBK输出即可,不管他本身是哪个编码)
// WINDOWS: GBK GB2312
// LINUX : urf-8
// _pTextCodec = QTextCodec::codecForName("utf-8");
_pTextCodec = QTextCodec::codecForName("GBK");
} void LoginRequestHandler::service(HttpRequest &request, HttpResponse &response)
{ QString str; QString path = request.getPath();
LOG << path; // 这里获取到的param是直接获取的
QString userName = request.getParameter("username");
QString password = request.getParameter("password");
LOG << userName << password; // 注销,清空session
if(path == "/login/out")
{
HttpSession httpSession =
HttpSessionStoreManager::getInstance()->getHttpSessionStore()->getSession(request,response,true); HttpSessionStoreManager::getInstance()->getHttpSessionStore()->removeSession(httpSession);
} // 获取session(单例模式封装后的全局获取)
HttpSession httpSession =
HttpSessionStoreManager::getInstance()->getHttpSessionStore()->getSession(request,response,true); // 先判断是否session能获取到
if(!httpSession.contains("username"))
{
// 这里没有cookie,提交登录则是可以保存本地cookie
if(userName == "root" && password == "root123456")
{
httpSession.set("username" , userName);
httpSession.set("logindatetime", QDateTime::currentDateTime());
httpSession.set("timeout" , 60); QString username = httpSession.get("username").toString();
QDateTime logindatetime = httpSession.get("logindatetime").toDateTime();
int timeout = httpSession.get("timeout").toInt(); str = QString("<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>长沙红胖子Qt</title>"
"</head>"
"<body>"
" <a href=\"/login/out\"><input value='注销' type='reset'></a>"
" <p>登录账户:%1</p>"
" <p>登录时间:%2</p>"
" <p>超时时间:%3</p>"
"</body>")
.arg(username)
.arg(logindatetime.toString("yyyy-MM-dd hh:mm:ss:zzz"))
.arg(timeout); }else{
str = "<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>长沙红胖子Qt</title>"
"</head>"
"<body>"
" <a href=\"javascript:history.back(-1);\">返回上一页</a>"
" <form method='POST' action='/login'>"
" <p>Please login</p>"
" <p>Name: <input type='text' name='username'></p>"
" <p>Password: <input type='password' name='password'></p>"
" <p><input type='submit'></p>"
" </form>"
"</body>";
}
}else {
// 之前已经登录过了
QString username = httpSession.get("username").toString();
QDateTime logindatetime = httpSession.get("logindatetime").toDateTime();
int timeout = httpSession.get("timeout").toInt(); str = QString("<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>长沙红胖子Qt</title>"
"</head>"
"<body>"
" <a href=\"/login/out\"><input value='注销' type='reset'></a>"
" <p>登录账户:%1</p>"
" <p>登录时间:%2</p>"
" <p>超时时间:%3</p>"
"</body>")
.arg(username)
.arg(logindatetime.toString("yyyy-MM-dd hh:mm:ss:zzz"))
.arg(timeout);
} // 返回文本(需要在浏览器上看,所以将Qt内部编码都转成GBK输出即可,不管他本身是哪个编码)
// QByteArray byteArray = _pTextCodec->fromUnicode(str);
QByteArray byteArray = str.toUtf8();
response.write(byteArray);
}

工程模板v1.3.0

  

Qt+QtWebApp开发笔记(四):http服务器使用Session和Cookie实现用户密码登录和注销功能的更多相关文章

  1. Django开发笔记四

    Django开发笔记一 Django开发笔记二 Django开发笔记三 Django开发笔记四 Django开发笔记五 Django开发笔记六 1.邮箱激活 users app下,models.py: ...

  2. 05 . Go+Vue开发一个线上外卖应用(Session集成及修改用户头像到Fastdfs)

    用户头像上传 功能介绍 在用户中心中,允许用户更换自己的头像.因此,我们开发上传一张图片到服务器,并保存成为用户的头像. 接口解析 在用户模块的控制器MemberController中,解析头像上传的 ...

  3. 【转】服务器添加新用户用ssh-key 登录,并禁用root用户 密码登录

    [转]Linux最高权限用户root,默认可以直接登录sshd.为了提高服务器的安全度,需要对它进行禁止,使得攻击者无法通过暴力破解来获取root权限.  ps: 以下内容皆非原创,只是个人的一个实践 ...

  4. JSP学习笔记(三):Session和Cookie

    一.JSP Session HTTP是无状态协议,这意味着每次客户端检索网页时,都要单独打开一个服务器连接,因此服务器不会记录下先前客户端请求的任何信息.有三种方法来维持客户端与服务器的会话: 1.C ...

  5. Samba服务器搭建,匿名访问,用户密码访问

    环境 #服务端:centos7 客户端:centos7,windows10 配置yum源,使用光盘镜像安装Samba服务 #挂载光盘:mount  /dev/sr0  /mnt/cdrom #安装sa ...

  6. Qt+ECharts开发笔记(四):ECharts的饼图介绍、基础使用和Qt封装百分比图Demo

    前言   前一篇介绍了横向柱图图.本篇将介绍基础饼图使用,并将其封装一层Qt.  本篇的demo使用隐藏js代码的方式,实现了一个饼图的基本交互方式,并预留了Qt模块对外的基础接口.   Demo演示 ...

  7. Qt+ECharts开发笔记(三):ECharts的柱状图介绍、基础使用和Qt封装Demo

    前言   上一篇成功是EChart随着Qt窗口变化而变化,本篇将开始正式介绍柱状图介绍.基础使用,并将其封装一层Qt.  本篇的demo实现了隐藏js代码的方式,实现了一个条形图的基本交互方式,即Qt ...

  8. Qt+ECharts开发笔记(二):Qt窗口动态调整大小,使ECharts跟随Qt窗口大小变换而变换大小

    前言   上一篇将ECharts嵌入Qt中,在开始ECharts使用之前,还有一个很重要的功能,就是在窗口变换大小的时候,ECharts的图表尺寸也要跟随Qt窗口变换大小而变换大小.   Demo演示 ...

  9. 【大型软件开发】浅谈大型Qt软件开发(四)动态链接库的宏冲突问题、COM组件开发的常见问题

    最近工作的时候有一个链接库的对接工作,在对接时发生了一些小问题,这篇FAQ是办公室写这个库的工程师戴工写的,这里记录一下: 一.编译工程时报链接错误"不允许dllimport静态数据成员的定 ...

  10. Qt+MySql开发笔记:Qt5.9.3的msvc2017x64版本编译MySql8.0.16版本驱动并Demo连接数据库测试

    前言   mysql驱动版本msvc2015x32版本调好, mysql的mingw32版本的驱动上一个版本编译并测试好,有些三方库最低支持vs2017,所以只能使用msvc2017x64,基于Qt5 ...

随机推荐

  1. Java/Kotlin 使用Redis模拟发送验证码

    原文地址: Java/Kotlin 使用Redis模拟发送邮件验证码 - Stars-One的杂货小窝 Java中常用语连接Redis的库有lettuce和jredis,一般是推荐lettuce,其具 ...

  2. OPP前三次作业总结

    OPP前三次作业总结 目录 前言: 第一次OOP训练: 7-7 有重复的数据 设计与分析: 具体代码 踩坑心得 改进建议 7-8 从一个字符串中移除包含在另一个字符串中的字符 设计与分析: 具体代码 ...

  3. [nefu]算法设计与分析-锐格实验

    谈点个人感想:锐格这个题目和数据要是再不维护,估计直接就裂开了,跪求学校升级改进一下OJ系统和题目Orz 实验一 递归与分治 6104 #include<bits/stdc++.h> us ...

  4. [PKM] 服务器

    1 概述与基础常识 1.1 服务器的定义 定义: 服务器,英文名Server,指能提供某种服务的网络设备. 提供的主要服务包括:数据的接收和传递.数据的存储和数据的处理. 通俗点儿,我们可以把服务器比 ...

  5. 四月七号java基础学习

    1.数据类型分为基本数据类型以及引用数据类型 基本数据类型有整型.浮点型.字符型.布尔型 引用数据类型有类.数组以及接口 2.常量的声明需要用关键字final来标识 3.JAVA语言的变量名称由数字, ...

  6. C# System.lnvalidOperationException:"A second operation started on this context before a previousoperation completed. This is usually caused by different threads using the same instance ofDbContext...

    与上一篇问题一样,只是错误不一样,DbContext 不支持并发请求,每个数据库操作都使用await就可以了

  7. vue事件监听

    v-on <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF- ...

  8. 14-压缩css

    const { resolve } = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const M ...

  9. 今天能恢复我的Django吗——恢复了!

    今天能用两小时恢复我的Django吗 实在是累了,昨天和队友改bug的时候为了能在我的电脑上实现他的程序就在datagrip中删了我django建的表.没想到啊,这一删就全是报错!! 不说了,今天看看 ...

  10. 安装node并创建vue项目

    1.多版本管理工具 nvm https://github.com/coreybutler/nvm-windows/releases nvm-setup.zip 2. 打开nvm文件夹下的setting ...