聊天项目(14) 登录功能和状态服务

客户端登录功能

登录界面新增err_tip,用来提示用户登陆结果。至于密码输入框大家可以根据注册界面的逻辑实现隐藏和显示的功能。这里留给大家自己实现。

https://cdn.llfc.club/1713228480446.jpg

点击登录需要发送http 请求到GateServer,GateServer先验证登录密码,再调用grpc请求给StatusServer,获取聊天服务器ip信息和token信息反馈给客户端。

结构图如下

https://cdn.llfc.club/1713230325540.jpg

我们先实现客户端登录,为登录按钮添加槽函数响应

  1. void LoginDialog::on_login_btn_clicked()
  2. {
  3. qDebug()<<"login btn clicked";
  4. if(checkUserValid() == false){
  5. return;
  6. }
  7. if(checkPwdValid() == false){
  8. return ;
  9. }
  10. auto user = ui->user_edit->text();
  11. auto pwd = ui->pass_edit->text();
  12. //发送http请求登录
  13. QJsonObject json_obj;
  14. json_obj["user"] = user;
  15. json_obj["passwd"] = xorString(pwd);
  16. HttpMgr::GetInstance()->PostHttpReq(QUrl(gate_url_prefix+"/user_login"),
  17. json_obj, ReqId::ID_LOGIN_USER,Modules::LOGINMOD);
  18. }

增加检测函数

  1. bool LoginDialog::checkUserValid(){
  2. auto user = ui->user_edit->text();
  3. if(user.isEmpty()){
  4. qDebug() << "User empty " ;
  5. return false;
  6. }
  7. return true;
  8. }
  9. bool LoginDialog::checkPwdValid(){
  10. auto pwd = ui->pass_edit->text();
  11. if(pwd.length() < 6 || pwd.length() > 15){
  12. qDebug() << "Pass length invalid";
  13. return false;
  14. }
  15. return true;
  16. }

在HttpMgr中添加sig_login_mod_finish信号,收到http请求完成回包的槽函数中添加登录模块的响应,将登录模块的消息发送到登录界面

  1. void HttpMgr::slot_http_finish(ReqId id, QString res, ErrorCodes err, Modules mod)
  2. {
  3. if(mod == Modules::REGISTERMOD){
  4. //发送信号通知指定模块http响应结束
  5. emit sig_reg_mod_finish(id, res, err);
  6. }
  7. if(mod == Modules::RESETMOD){
  8. //发送信号通知指定模块http响应结束
  9. emit sig_reset_mod_finish(id, res, err);
  10. }
  11. if(mod == Modules::LOGINMOD){
  12. emit sig_login_mod_finish(id, res, err);
  13. }
  14. }

在LoginDialog的构造函数中添加消息对应的槽函数连接

  1. LoginDialog::LoginDialog(QWidget *parent) :
  2. QDialog(parent),
  3. ui(new Ui::LoginDialog)
  4. {
  5. ui->setupUi(this);
  6. connect(ui->reg_btn, &QPushButton::clicked, this, &LoginDialog::switchRegister);
  7. ui->forget_label->SetState("normal","hover","","selected","selected_hover","");
  8. ui->forget_label->setCursor(Qt::PointingHandCursor);
  9. connect(ui->forget_label, &ClickedLabel::clicked, this, &LoginDialog::slot_forget_pwd);
  10. initHttpHandlers();
  11. //连接登录回包信号
  12. connect(HttpMgr::GetInstance().get(), &HttpMgr::sig_login_mod_finish, this,
  13. &LoginDialog::slot_login_mod_finish);
  14. }

initHttpHandlers为初始化http回调逻辑, 并添加_handlers成员

  1. void LoginDialog::initHttpHandlers()
  2. {
  3. //注册获取登录回包逻辑
  4. _handlers.insert(ReqId::ID_LOGIN_USER, [this](QJsonObject jsonObj){
  5. int error = jsonObj["error"].toInt();
  6. if(error != ErrorCodes::SUCCESS){
  7. showTip(tr("参数错误"),false);
  8. return;
  9. }
  10. auto user = jsonObj["user"].toString();
  11. showTip(tr("登录成功"), true);
  12. qDebug()<< "user is " << user ;
  13. });
  14. }

在LoginDialog中添加槽函数slot_login_mod_finish

  1. void LoginDialog::slot_login_mod_finish(ReqId id, QString res, ErrorCodes err)
  2. {
  3. if(err != ErrorCodes::SUCCESS){
  4. showTip(tr("网络请求错误"),false);
  5. return;
  6. }
  7. // 解析 JSON 字符串,res需转化为QByteArray
  8. QJsonDocument jsonDoc = QJsonDocument::fromJson(res.toUtf8());
  9. //json解析错误
  10. if(jsonDoc.isNull()){
  11. showTip(tr("json解析错误"),false);
  12. return;
  13. }
  14. //json解析错误
  15. if(!jsonDoc.isObject()){
  16. showTip(tr("json解析错误"),false);
  17. return;
  18. }
  19. //调用对应的逻辑,根据id回调。
  20. _handlers[id](jsonDoc.object());
  21. return;
  22. }

到此客户端登陆请求发送的模块封装完了

GateServer完善登陆逻辑

在LogicSystem的构造函数中添加登陆请求的注册。

  1. //用户登录逻辑
  2. RegPost("/user_login", [](std::shared_ptr<HttpConnection> connection) {
  3. auto body_str = boost::beast::buffers_to_string(connection->_request.body().data());
  4. std::cout << "receive body is " << body_str << std::endl;
  5. connection->_response.set(http::field::content_type, "text/json");
  6. Json::Value root;
  7. Json::Reader reader;
  8. Json::Value src_root;
  9. bool parse_success = reader.parse(body_str, src_root);
  10. if (!parse_success) {
  11. std::cout << "Failed to parse JSON data!" << std::endl;
  12. root["error"] = ErrorCodes::Error_Json;
  13. std::string jsonstr = root.toStyledString();
  14. beast::ostream(connection->_response.body()) << jsonstr;
  15. return true;
  16. }
  17. auto name = src_root["user"].asString();
  18. auto pwd = src_root["passwd"].asString();
  19. UserInfo userInfo;
  20. //查询数据库判断用户名和密码是否匹配
  21. bool pwd_valid = MysqlMgr::GetInstance()->CheckPwd(name, pwd, userInfo);
  22. if (!pwd_valid) {
  23. std::cout << " user pwd not match" << std::endl;
  24. root["error"] = ErrorCodes::PasswdInvalid;
  25. std::string jsonstr = root.toStyledString();
  26. beast::ostream(connection->_response.body()) << jsonstr;
  27. return true;
  28. }
  29. //查询StatusServer找到合适的连接
  30. auto reply = StatusGrpcClient::GetInstance()->GetChatServer(userInfo.uid);
  31. if (reply.error()) {
  32. std::cout << " grpc get chat server failed, error is " << reply.error()<< std::endl;
  33. root["error"] = ErrorCodes::RPCGetFailed;
  34. std::string jsonstr = root.toStyledString();
  35. beast::ostream(connection->_response.body()) << jsonstr;
  36. return true;
  37. }
  38. std::cout << "succeed to load userinfo uid is " << userInfo.uid << std::endl;
  39. root["error"] = 0;
  40. root["user"] = name;
  41. root["uid"] = userInfo.uid;
  42. root["token"] = reply.token();
  43. root["host"] = reply.host();
  44. std::string jsonstr = root.toStyledString();
  45. beast::ostream(connection->_response.body()) << jsonstr;
  46. return true;
  47. });

在MysqlMgr中添加CheckPwd函数

  1. bool MysqlMgr::CheckPwd(const std::string& name, const std::string& pwd, UserInfo& userInfo) {
  2. return _dao.CheckPwd(name, pwd, userInfo);
  3. }

在DAO层添加根据用户名查询sql逻辑,并且判断pwd是否匹配。

  1. bool MysqlDao::CheckPwd(const std::string& name, const std::string& pwd, UserInfo& userInfo) {
  2. auto con = pool_->getConnection();
  3. Defer defer([this, &con]() {
  4. pool_->returnConnection(std::move(con));
  5. });
  6. try {
  7. if (con == nullptr) {
  8. return false;
  9. }
  10. // 准备SQL语句
  11. std::unique_ptr<sql::PreparedStatement> pstmt(con->prepareStatement("SELECT * FROM user WHERE name = ?"));
  12. pstmt->setString(1, name); // 将username替换为你要查询的用户名
  13. // 执行查询
  14. std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());
  15. std::string origin_pwd = "";
  16. // 遍历结果集
  17. while (res->next()) {
  18. origin_pwd = res->getString("pwd");
  19. // 输出查询到的密码
  20. std::cout << "Password: " << origin_pwd << std::endl;
  21. break;
  22. }
  23. if (pwd != origin_pwd) {
  24. return false;
  25. }
  26. userInfo.name = name;
  27. userInfo.email = res->getString("email");
  28. userInfo.uid = res->getInt("uid");
  29. userInfo.pwd = origin_pwd;
  30. return true;
  31. }
  32. catch (sql::SQLException& e) {
  33. std::cerr << "SQLException: " << e.what();
  34. std::cerr << " (MySQL error code: " << e.getErrorCode();
  35. std::cerr << ", SQLState: " << e.getSQLState() << " )" << std::endl;
  36. return false;
  37. }
  38. }

因为要调用grpc访问StatusServer,所以我们这里先完善协议proto文件

  1. syntax = "proto3";
  2. package message;
  3. service VarifyService {
  4. rpc GetVarifyCode (GetVarifyReq) returns (GetVarifyRsp) {}
  5. }
  6. message GetVarifyReq {
  7. string email = 1;
  8. }
  9. message GetVarifyRsp {
  10. int32 error = 1;
  11. string email = 2;
  12. string code = 3;
  13. }
  14. message GetChatServerReq {
  15. int32 uid = 1;
  16. }
  17. message GetChatServerRsp {
  18. int32 error = 1;
  19. string host = 2;
  20. string port = 3;
  21. string token = 4;
  22. }
  23. service StatusService {
  24. rpc GetChatServer (GetChatServerReq) returns (GetChatServerRsp) {}
  25. }

我们用下面两条命令重新生成pb.h和grpc.pb.h

  1. D:\cppsoft\grpc\visualpro\third_party\protobuf\Debug\protoc.exe --cpp_out=. "message.proto"

生成grpc.pb.h

  1. D:\cppsoft\grpc\visualpro\third_party\protobuf\Debug\protoc.exe -I="." --grpc_out="." --plugin=protoc-gen-grpc="D:\cppsoft\grpc\visualpro\Debug\grpc_cpp_plugin.exe" "message.proto"

这俩命令执行完成后总计生成四个文件

https://cdn.llfc.club/1713239066360.jpg

实现StatusGrpcClient

  1. #include "const.h"
  2. #include "Singleton.h"
  3. #include "ConfigMgr.h"
  4. using grpc::Channel;
  5. using grpc::Status;
  6. using grpc::ClientContext;
  7. using message::GetChatServerReq;
  8. using message::GetChatServerRsp;
  9. using message::StatusService;
  10. class StatusGrpcClient :public Singleton<StatusGrpcClient>
  11. {
  12. friend class Singleton<StatusGrpcClient>;
  13. public:
  14. ~StatusGrpcClient() {
  15. }
  16. GetChatServerRsp GetChatServer(int uid);
  17. private:
  18. StatusGrpcClient();
  19. std::unique_ptr<StatusConPool> pool_;
  20. };

具体实现

  1. #include "StatusGrpcClient.h"
  2. GetChatServerRsp StatusGrpcClient::GetChatServer(int uid)
  3. {
  4. ClientContext context;
  5. GetChatServerRsp reply;
  6. GetChatServerReq request;
  7. request.set_uid(uid);
  8. auto stub = pool_->getConnection();
  9. Status status = stub->GetChatServer(&context, request, &reply);
  10. Defer defer([&stub, this]() {
  11. pool_->returnConnection(std::move(stub));
  12. });
  13. if (status.ok()) {
  14. return reply;
  15. }
  16. else {
  17. reply.set_error(ErrorCodes::RPCFailed);
  18. return reply;
  19. }
  20. }
  21. StatusGrpcClient::StatusGrpcClient()
  22. {
  23. auto& gCfgMgr = ConfigMgr::Inst();
  24. std::string host = gCfgMgr["StatusServer"]["Host"];
  25. std::string port = gCfgMgr["StatusServer"]["Port"];
  26. pool_.reset(new StatusConPool(5, host, port));
  27. }

当然GateServer的config.ini文件也要做更新

  1. [GateServer]
  2. Port = 8080
  3. [VarifyServer]
  4. Host = 127.0.0.1
  5. Port = 50051
  6. [StatusServer]
  7. Host = 127.0.0.1
  8. Port = 50052
  9. [Mysql]
  10. Host = 81.68.86.146
  11. Port = 3308
  12. User = root
  13. Passwd = 123456
  14. Schema = llfc
  15. [Redis]
  16. Host = 81.68.86.146
  17. Port = 6380
  18. Passwd = 123456

StatusGrpcClient用到了StatusConPool, 将其实现放在StatusGrpcClient类之上

  1. class StatusConPool {
  2. public:
  3. StatusConPool(size_t poolSize, std::string host, std::string port)
  4. : poolSize_(poolSize), host_(host), port_(port), b_stop_(false) {
  5. for (size_t i = 0; i < poolSize_; ++i) {
  6. std::shared_ptr<Channel> channel = grpc::CreateChannel(host + ":" + port,
  7. grpc::InsecureChannelCredentials());
  8. connections_.push(StatusService::NewStub(channel));
  9. }
  10. }
  11. ~StatusConPool() {
  12. std::lock_guard<std::mutex> lock(mutex_);
  13. Close();
  14. while (!connections_.empty()) {
  15. connections_.pop();
  16. }
  17. }
  18. std::unique_ptr<StatusService::Stub> getConnection() {
  19. std::unique_lock<std::mutex> lock(mutex_);
  20. cond_.wait(lock, [this] {
  21. if (b_stop_) {
  22. return true;
  23. }
  24. return !connections_.empty();
  25. });
  26. //如果停止则直接返回空指针
  27. if (b_stop_) {
  28. return nullptr;
  29. }
  30. auto context = std::move(connections_.front());
  31. connections_.pop();
  32. return context;
  33. }
  34. void returnConnection(std::unique_ptr<StatusService::Stub> context) {
  35. std::lock_guard<std::mutex> lock(mutex_);
  36. if (b_stop_) {
  37. return;
  38. }
  39. connections_.push(std::move(context));
  40. cond_.notify_one();
  41. }
  42. void Close() {
  43. b_stop_ = true;
  44. cond_.notify_all();
  45. }
  46. private:
  47. atomic<bool> b_stop_;
  48. size_t poolSize_;
  49. std::string host_;
  50. std::string port_;
  51. std::queue<std::unique_ptr<StatusService::Stub>> connections_;
  52. std::mutex mutex_;
  53. std::condition_variable cond_;
  54. };

StatusServer状态服务

我们要实现状态服务,主要是用来监听其他服务器的查询请求, 用visual studio创建项目,名字为StatusServer.

在主函数所在文件StatusServer.cpp中实现如下逻辑

  1. #include <iostream>
  2. #include <json/json.h>
  3. #include <json/value.h>
  4. #include <json/reader.h>
  5. #include "const.h"
  6. #include "ConfigMgr.h"
  7. #include "hiredis.h"
  8. #include "RedisMgr.h"
  9. #include "MysqlMgr.h"
  10. #include "AsioIOServicePool.h"
  11. #include <iostream>
  12. #include <memory>
  13. #include <string>
  14. #include <thread>
  15. #include <boost/asio.hpp>
  16. #include "StatusServiceImpl.h"
  17. void RunServer() {
  18. auto & cfg = ConfigMgr::Inst();
  19. std::string server_address(cfg["StatusServer"]["Host"]+":"+ cfg["StatusServer"]["Port"]);
  20. StatusServiceImpl service;
  21. grpc::ServerBuilder builder;
  22. // 监听端口和添加服务
  23. builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
  24. builder.RegisterService(&service);
  25. // 构建并启动gRPC服务器
  26. std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
  27. std::cout << "Server listening on " << server_address << std::endl;
  28. // 创建Boost.Asio的io_context
  29. boost::asio::io_context io_context;
  30. // 创建signal_set用于捕获SIGINT
  31. boost::asio::signal_set signals(io_context, SIGINT, SIGTERM);
  32. // 设置异步等待SIGINT信号
  33. signals.async_wait([&server](const boost::system::error_code& error, int signal_number) {
  34. if (!error) {
  35. std::cout << "Shutting down server..." << std::endl;
  36. server->Shutdown(); // 优雅地关闭服务器
  37. }
  38. });
  39. // 在单独的线程中运行io_context
  40. std::thread([&io_context]() { io_context.run(); }).detach();
  41. // 等待服务器关闭
  42. server->Wait();
  43. io_context.stop(); // 停止io_context
  44. }
  45. int main(int argc, char** argv) {
  46. try {
  47. RunServer();
  48. }
  49. catch (std::exception const& e) {
  50. std::cerr << "Error: " << e.what() << std::endl;
  51. return EXIT_FAILURE;
  52. }
  53. return 0;
  54. }

在开始逻辑之前,我们需要先更新下config.ini文件

  1. [StatusServer]
  2. Port = 50052
  3. Host = 0.0.0.0
  4. [Mysql]
  5. Host = 81.68.86.146
  6. Port = 3308
  7. User = root
  8. Passwd = 123456
  9. Schema = llfc
  10. [Redis]
  11. Host = 81.68.86.146
  12. Port = 6380
  13. Passwd = 123456
  14. [ChatServer1]
  15. Host = 127.0.0.1
  16. Port = 8090
  17. [ChatServer2]
  18. Host = 127.0.0.1
  19. Port = 8091

然后我们将GateServer之前生成的pb文件和proto文件拷贝到StatusServer中。并且加入到项目中。

我们在项目中添加一个新的类StatusServiceImpl,该类主要继承自StatusService::Service。

  1. #include <grpcpp/grpcpp.h>
  2. #include "message.grpc.pb.h"
  3. using grpc::Server;
  4. using grpc::ServerBuilder;
  5. using grpc::ServerContext;
  6. using grpc::Status;
  7. using message::GetChatServerReq;
  8. using message::GetChatServerRsp;
  9. using message::StatusService;
  10. struct ChatServer {
  11. std::string host;
  12. std::string port;
  13. };
  14. class StatusServiceImpl final : public StatusService::Service
  15. {
  16. public:
  17. StatusServiceImpl();
  18. Status GetChatServer(ServerContext* context, const GetChatServerReq* request,
  19. GetChatServerRsp* reply) override;
  20. std::vector<ChatServer> _servers;
  21. int _server_index;
  22. };

具体实现

  1. #include "StatusServiceImpl.h"
  2. #include "ConfigMgr.h"
  3. #include "const.h"
  4. std::string generate_unique_string() {
  5. // 创建UUID对象
  6. boost::uuids::uuid uuid = boost::uuids::random_generator()();
  7. // 将UUID转换为字符串
  8. std::string unique_string = to_string(uuid);
  9. return unique_string;
  10. }
  11. Status StatusServiceImpl::GetChatServer(ServerContext* context, const GetChatServerReq* request, GetChatServerRsp* reply)
  12. {
  13. std::string prefix("llfc status server has received : ");
  14. _server_index = (_server_index++) % (_servers.size());
  15. auto &server = _servers[_server_index];
  16. reply->set_host(server.host);
  17. reply->set_port(server.port);
  18. reply->set_error(ErrorCodes::Success);
  19. reply->set_token(generate_unique_string());
  20. return Status::OK;
  21. }
  22. StatusServiceImpl::StatusServiceImpl():_server_index(0)
  23. {
  24. auto& cfg = ConfigMgr::Inst();
  25. ChatServer server;
  26. server.port = cfg["ChatServer1"]["Port"];
  27. server.host = cfg["ChatServer1"]["Host"];
  28. _servers.push_back(server);
  29. server.port = cfg["ChatServer2"]["Port"];
  30. server.host = cfg["ChatServer2"]["Host"];
  31. _servers.push_back(server);
  32. }

其余的文件为了保持复用,我们不重复开发,将GateServer中的RedisMgr,MysqlMgr,Singleton,IOSerivePool等统统拷贝过来并添加到项目中。

联调测试

我们启动StatusServer,GateServer以及QT客户端,输入密码和用户名,点击登陆,会看到前端收到登陆成功的消息

https://cdn.llfc.club/1713248019373.jpg

热门评论

热门文章

  1. vscode搭建windows C++开发环境

    喜欢(596) 浏览(82988)
  2. 聊天项目(28) 分布式服务通知好友申请

    喜欢(507) 浏览(6079)
  3. 使用hexo搭建个人博客

    喜欢(533) 浏览(11838)
  4. Linux环境搭建和编码

    喜欢(594) 浏览(13490)
  5. Qt环境搭建

    喜欢(517) 浏览(24581)

最新评论

  1. 聊天项目(7) visualstudio配置grpc diablorrr:cmake文件得改一下 find_package(Boost REQUIRED COMPONENTS system filesystem),要加上filesystem。在target_link_libraries中也同样加上
  2. 堆排序 secondtonone1:堆排序非常实用,定时器就是这个原理制作的。
  3. 创建项目和编译 secondtonone1:谢谢支持
  4. 再谈单例模式 secondtonone1:是的,C++11以后返回局部static变量对象能保证线程安全了。
  5. 类和对象 陈宇航:支持!!!!
  6. 聊天项目(15) 客户端实现TCP管理者 lkx:已经在&QTcpSocket::readyRead 回调函数中做了处理了的。
  7. 答疑汇总(thread,async源码分析) Yagus:如果引用计数为0,则会执行 future 的析构进而等待任务执行完成,那么看到的输出将是 这边应该不对吧,std::future析构只在这三种情况都满足的时候才回block: 1.共享状态是std::async 创造的(类型是_Task_async_state) 2.共享状态没有ready 3.这个future是共享状态的最后一个引用 这边共享状态类型是“_Package_state”,引用计数即使为0也不应该block啊
  8. 处理网络粘包问题 zyouth: //消息的长度小于头部规定的长度,说明数据未收全,则先将部分消息放到接收节点里 if (bytes_transferred < data_len) { memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, bytes_transferred); _recv_msg_node->_cur_len += bytes_transferred; ::memset(_data, 0, MAX_LENGTH); _socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, shared_self)); //头部处理完成 _b_head_parse = true; return; } 把_b_head_parse = true;放在_socket.async_read_some前面是不是更好
  9. 利用栅栏实现同步 Dzher:作者你好!我觉得 std::thread a(write_x); std::thread b(write_y); std::thread c(read_x_then_y); std::thread d(read_y_then_x); 这个例子中的assert fail并不会发生,原子变量设定了非relaxed内存序后一个线程的原子变量被写入,那么之后的读取一定会被同步的,c和d线程中只可能同时发生一个z++未执行的情况,最终z不是1就是2了,我测试了很多次都没有assert,请问我这个观点有什么错误,谢谢!
  10. 聊天项目(9) redis服务搭建 pro_lin:redis线程池的析构函数,除了pop出队列,还要free掉redis连接把
  11. Qt MVC结构之QItemDelegate介绍 胡歌-此生不换:gpt, google
  12. protobuf配置和使用 熊二:你可以把dll放到系统目录,也可以配置环境变量,还能把dll丢到lib里
  13. boost::asio之socket的创建和连接 项空月:发现一些错别字 :每隔vector存储  是不是是每个. asio::mutable_buffers_1 o或者    是不是多打了个o
  14. 无锁并发队列 TenThousandOne:_head  和 _tail  替换为原子变量。那里pop的逻辑,val = _data[h] 可以移到循环外面吗
  15. 网络编程学习方法和图书推荐 Corleone:啥程度可以找工作
  16. visual studio配置boost库 一giao里我离giaogiao:请问是修改成这样吗:.\b2.exe toolset=MinGW
  17. 构造函数 secondtonone1:构造函数是类的基础知识,要着重掌握
  18. 解决博客回复区被脚本注入的问题 secondtonone1:走到现在我忽然明白一个道理,无论工作也好生活也罢,最重要的是开心,即使一份安稳的工作不能给我带来事业上的积累也要合理的舍弃,所以我还是想去做喜欢的方向。
  19. interface应用 secondtonone1:interface是万能类型,但是使用时要转换为实际类型来使用。interface丰富了go的多态特性,也降低了传统面向对象语言的耦合性。
  20. string类 WangQi888888:确实错了,应该是!isspace(sind[index]). 否则不进入循环,还是原来的字符串“some string”
  21. Qt 对话框 Spade2077:QDialog w(); //这里是不是不需要带括号
  22. 面试题汇总(一) secondtonone1:看到网络上经常提问的go的问题,做了一下汇总,结合自己的经验给出的答案,如有纰漏,望指正批评。
  23. C++ 并发三剑客future, promise和async Yunfei:大佬您好,如果这个线程池中加入的异步任务的形参如果有右值引用,这个commit中的返回类型推导和bind绑定就会出现问题,请问实际工程中,是不是不会用到这种任务,如果用到了,应该怎么解决?

个人公众号

个人微信