asio编写多线程异步服务器(C++asio网络库相关)

可以在服务器代码中加入以下方法开启多个线程:

std::vector<std::thread> threadGroup;
for(int i = 0; i < 5; ++i) {
	threadGroup.emplace_back([&io_service, i]{
		std::cout << i << " name is " << std::this_thread::get_id() << std::endl;
		io_service.run();});
}

使用asio网络库开启多线程时有以下几点需要注意的地方:

1、通过在heandleMessage下打印 std::cout << "i’m in " << std::this_thread::get_id() << " time "线程ID可知道具体回调是在哪个线程下处理的
2、io_service会随机分配合适线程去执行回调
3、当同个客户端瞬间发送多个消息时IO_service可能会分配同个线程进行处理,因为服务器一次接收了一个客户端的多个消息
4、如果一个客户端间隔一段时间发送多个消息可以分配到服务器不同线程进行处理
5、作为回调是安全的,但不能保证回调所做的事情是不是安全,导致io_service不是线程安全的

在原先聊天室系统中可以用以下测试方式观察用io_service处理多线程是否可行:

通过以下代码可以构造多个客户端给服务器发消息,方便测试:

for(int i = 0; i < 10; ++i) {
	clientGroup.emplace_back(std::make_unique<chat_client>(
		io_service, endpoint_iterator));
}

system_clock:钟表时间,可以调时间
stread_clock:固定时间不能调整

当服务器处理回调消息在handler_message方法下增加std::this_thread::sleep_for(std::chrono::milliseconds(3));后就会出错
其中std::chrono::duration_caststd::chrono::milliseconds(n).count() 将精度转换成毫秒打印消息处理间隔
在这里插入图片描述
用GDB调试发现问题:
在这里插入图片描述
singnal sigesgv 表示指针是野指针或试图写入或读取一段不存在内存地址的数据

用info thread命令获取线程信息:
在这里插入图片描述
再运行一次发现错误原因也改变了:
在这里插入图片描述
用bt查看堆栈信息:
在这里插入图片描述
double free or corruption:内存free了两次或者内存已经不存在
在这里插入图片描述
在这里插入图片描述
在makefile文件里加入参数-g可以输出一些debug信息,更容易定位到错误
在这里插入图片描述
在这里插入图片描述
通过debug信息可以定位出错的原因在于:STL的container并不是线程安全的,所以在deque中push_back的时候会出错
ps:在多线程服务器开发中stl容器的应用是最需要注意的,用得不好就会造成内存泄漏或访问野指针等等问题,也是最不容易排查的,即使像本系统这样简单的程序也是通过程序运行多次最终定位,如果在一个大型游戏项目中就更不好排查了。

因为加入等待时间所有线程会一起执行接下来的语句

 if (read_msg_.type() == MT_BIND_NAME) {
		PBindName bindName;
		if(fillProtobuf(&bindName))
			m_name = bindName.name();
    } else if (read_msg_.type() == MT_CHAT_INFO) {
		PChat chat;
		if(!fillProtobuf(&chat)) return;
		m_chatInformation = chat.information();
}
auto rinfo = buildRoomInfo();
chat_message msg;
msg.setMessage(MT_ROOM_INFO, rinfo);
room_.deliver(msg);

之后的语句中局部变量不会有问题,m_chatInformation每个客户端单独的消息过来不可能两个线程处理同个客户端消息所以也不会有问题,只有room_deliver(msg)会出问题,有可能6个客户端同时调用该语句,但STL的container并不是线程安全的,所以在deque中push_back的时候会出错

总结:io_service作为回调是安全的,回调所做的事情并不能保证安全导致io_service不是线程安全的

解决方法:

1、可以在chat_room的join,leave,deliver方法下加mutex线程锁,但锁的力度太大,lock(m_mutex)是同步执行的别的线程要等待

2、也可以用boost::asio::io_service::strand m_strand;并在构造函数中加入chat_room(boost::asio::io_service& io_service) : m_strand(io_service) {}进行初始化,只针对io_service做一个线程安全的处理

本系统可以通过strand作以下几点的优化:
1、在join,leave,deliver方法下加入m_strand.post([this,participant]{},m_strand是异步执行的放入队列,lock(m_mutex)是同步执行的别的线程要等待,从同步保护变成了异步保护

2、读数据do_read_header,do_read_body和写数据do_write的时候用m_strand.wrap对回调函数进行保护

3、chat_session类里同样加入m_strand,并用chat_session()m_strand(socket.get_io_service(){}在构造函数中进行初始化

4、m_strand具体所做的事情可以看官方文档主要有post,dispatch,wrap三个接口,wrap最终也是调用post,同一线程用wrap或post包裹顺序是不会乱的

以下是用asio编写的多线程异步服务器设计的聊天室系统完整代码:

protobuf协议部分:

//Protocal.proto
syntax = 'proto3';
message PBindName {
	string name = 1;
}
message PChat {
	string information = 1;
}
message PRoomInformation {
	string name = 1;
	string information = 2;
}

数据包传送部分:

//chat_message.h
#ifndef CHAT_MESSAGE_HPP
#define CHAT_MESSAGE_HPP

#include "structHeader.h"
#include "SerilizationObject.h"

#include <iostream>

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>
// s -> c   c -> s message { header, body } // header length

class chat_message {
public:
  enum { header_length = sizeof(Header) };
  enum { max_body_length = 512 };

  chat_message(){}

  const char *data() const { return data_; }

  char *data() { return data_; }

  std::size_t length() const { return header_length + m_header.bodySize; }

  const char *body() const { return data_ + header_length; }

  char *body() { return data_ + header_length; }
  int type() const { return m_header.type; }

  std::size_t body_length() const { return m_header.bodySize; }
  void setMessage(int messageType, const void *buffer, size_t bufferSize) {
    assert(bufferSize <= max_body_length);
		m_header.bodySize = bufferSize;
		m_header.type = messageType;
		std::memcpy(body(), buffer, bufferSize);
		std::memcpy(data(), &m_header, sizeof(m_header));
  }
	void setMessage(int messageType, const std::string& buffer) {
		setMessage(messageType, buffer.data(), buffer.size());
	}

  bool decode_header() {
    std::memcpy(&m_header, data(), header_length);
    if (m_header.bodySize > max_body_length) {
      std::cout << "body size " << m_header.bodySize << " " << m_header.type
                << std::endl;
      return false;
		}
    return true;
  }

private:
  char data_[header_length + max_body_length];
	Header m_header;
};

#endif // CHAT_MESSAGE_HPP

处理protobuf协议部分:

//structHeader.h
#ifndef FND_STRUCT_HEADER_H
#define FND_STRUCT_HEADER_H
#include <string>
struct Header {
	int bodySize;
	int type;
};

enum MessageType {
	MT_BIND_NAME = 1, // {"name" : "abc"}
	MT_CHAT_INFO = 2, // {"information" "what i say"}
	MT_ROOM_INFO = 3, // {"name" : "abc", "information" : "what i say"}
};

// client send
struct BindName {
	char name[32];
	int nameLen;
};

// client send
struct ChatInformation {
	char information[256];
	int infoLen;
};


// serversend
struct RoomInformation {
	BindName name;
	ChatInformation chat;
};

bool parseMessage(const std::string &input, int *type, std::string &outbuffer);
bool parseMessage2(const std::string &input, int *type, std::string &outbuffer);
bool parseMessage3(const std::string &input, int *type, std::string &outbuffer);
bool parseMessage4(const std::string &input, int *type, std::string &outbuffer);
#endif

//structHeader.cpp
#include "structHeader.h"
#include "SerilizationObject.h"
#include "JsonObject.h"
#include "Protocal.pb.h"
#include <cstdlib>
#include <cstring>
#include <iostream>
template <typename T> std::string seriliaze(const T &obj) {
  std::stringstream ss;
  boost::archive::text_oarchive oa(ss);
  oa & obj;
  return ss.str();
}

bool parseMessage4(const std::string &input, int *type,
                   std::string &outbuffer) {
  auto pos = input.find_first_of(" ");
  if (pos == std::string::npos)
    return false;
  if (pos == 0)
    return false;
	// "BindName ok" -> substr -> BindName
  auto command = input.substr(0, pos);
  if (command == "BindName") {
    // we try to bind name
    std::string name = input.substr(pos + 1);
    if (name.size() > 32)
      return false;
    if (type)
      *type = MT_BIND_NAME;
		PBindName bindName;
		bindName.set_name(name);
		//auto oldname = bindName.name();
		auto ok = bindName.SerializeToString(&outbuffer);
    return ok;
  } else if (command == "Chat") {
    // we try to chat
    std::string chat = input.substr(pos + 1);
    if (chat.size() > 256)
      return false;
		PChat pchat;
		pchat.set_information(chat);
		auto ok = pchat.SerializeToString(&outbuffer);
    if (type)
      *type = MT_CHAT_INFO;
    return ok;
  }
  return false;
}



bool parseMessage3(const std::string &input, int *type,
                   std::string &outbuffer) {
  auto pos = input.find_first_of(" ");
  if (pos == std::string::npos)
    return false;
  if (pos == 0)
    return false;
	// "BindName ok" -> substr -> BindName
  auto command = input.substr(0, pos);
  if (command == "BindName") {
    // we try to bind name
    std::string name = input.substr(pos + 1);
    if (name.size() > 32)
      return false;
    if (type)
      *type = MT_BIND_NAME;
		ptree tree;
		tree.put("name", name);
		outbuffer = ptreeToJsonString(tree);
    //outbuffer = seriliaze(SBindName(std::move(name)));
    return true;
  } else if (command == "Chat") {
    // we try to chat
    std::string chat = input.substr(pos + 1);
    if (chat.size() > 256)
      return false;
		ptree tree;
		tree.put("information", chat);
		outbuffer = ptreeToJsonString(tree);
		//outbuffer = seriliaze(SChatInfo(std::move(chat)));
    if (type)
      *type = MT_CHAT_INFO;
    return true;
  }
  return false;
}

bool parseMessage2(const std::string &input, int *type,
                   std::string &outbuffer) {
  auto pos = input.find_first_of(" ");
  if (pos == std::string::npos)
    return false;
  if (pos == 0)
    return false;
	// "BindName ok" -> substr -> BindName
  auto command = input.substr(0, pos);
  if (command == "BindName") {
    // we try to bind name
    std::string name = input.substr(pos + 1);
    if (name.size() > 32)
      return false;
    if (type)
      *type = MT_BIND_NAME;
    //SBindName bindInfo(std::move(name));
    outbuffer = seriliaze(SBindName(std::move(name)));
    return true;
  } else if (command == "Chat") {
    // we try to chat
    std::string chat = input.substr(pos + 1);
    if (chat.size() > 256)
      return false;
		outbuffer = seriliaze(SChatInfo(std::move(chat)));
//    ChatInformation info;
//    info.infoLen = chat.size();
//    std::memcpy(&(info.information), chat.data(), chat.size());
//    auto buffer = reinterpret_cast<const char *>(&info);
//    outbuffer.assign(buffer, buffer + sizeof(info));
    if (type)
      *type = MT_CHAT_INFO;
    return true;
  }
  return false;
}
// cmd messagebody
bool parseMessage(const std::string &input, int *type, std::string &outbuffer) {
  // input should be cmd body
  auto pos = input.find_first_of(" ");
  if (pos == std::string::npos)
    return false;
  if (pos == 0)
    return false;
	// "BindName ok" -> substr -> BindName
  auto command = input.substr(0, pos);
  if (command == "BindName") {
    // we try to bind name
    std::string name = input.substr(pos + 1);
    if (name.size() > 32)
      return false;
    if (type)
      *type = MT_BIND_NAME;
    BindName bindInfo;
    bindInfo.nameLen = name.size();
    std::memcpy(&(bindInfo.name), name.data(), name.size());
    auto buffer = reinterpret_cast<const char *>(&bindInfo);
    outbuffer.assign(buffer, buffer + sizeof(bindInfo));
    return true;
  } else if (command == "Chat") {
    // we try to chat
    std::string chat = input.substr(pos + 1);
    if (chat.size() > 256)
      return false;
    ChatInformation info;
    info.infoLen = chat.size();
    std::memcpy(&(info.information), chat.data(), chat.size());
    auto buffer = reinterpret_cast<const char *>(&info);
    outbuffer.assign(buffer, buffer + sizeof(info));
    if (type)
      *type = MT_CHAT_INFO;
    return true;
  }
  return false;
}

服务器代码部分:

//main.cpp
#include "chat_message.h"
#include "JsonObject.h"
#include "Protocal.pb.h"

#include <boost/asio.hpp>

#include <chrono>
#include <deque>
#include <iostream>
#include <list>
#include <memory>
#include <set>
#include <thread>
#include <utility>
#include <mutex>

#include <cstdlib>

using boost::asio::ip::tcp;

//----------------------------------------------------------------------

using chat_message_queue = std::deque<chat_message>;
using chat_message_queue2 = std::list<chat_message>;
//----------------------------------------------------------------------

// stread_clock
std::chrono::system_clock::time_point base;

//----------------------------------------------------------------------

class chat_session;
using chat_session_ptr = std::shared_ptr<chat_session>;
class chat_room {
public:
	chat_room(boost::asio::io_service& io_service) : m_strand(io_service) {}
public:
	void join(chat_session_ptr);
	void leave(chat_session_ptr);
	void deliver(const chat_message&);
private:
	boost::asio::io_service::strand m_strand;
	//std::mutex m_mutex;
  std::set<chat_session_ptr> participants_;
  enum { max_recent_msgs = 100 };
  chat_message_queue recent_msgs_;
};
//----------------------------------------------------------------------

class chat_session : public std::enable_shared_from_this<chat_session> {
public:
  chat_session(tcp::socket socket, chat_room &room)
      : socket_(std::move(socket)), room_(room),
			m_strand(socket_.get_io_service()) {}

  void start() {
    room_.join(shared_from_this());
    do_read_header();
  }

  void deliver(const chat_message &msg) {
		// first false
		m_strand.post([this, msg]{
    bool write_in_progress = !write_msgs_.empty();
    write_msgs_.push_back(msg);
    if (!write_in_progress) {
			// first
      do_write();
    }});
  }

private:
  void do_read_header() {
    auto self(shared_from_this());
    boost::asio::async_read(
        socket_,
        boost::asio::buffer(read_msg_.data(), chat_message::header_length),
				m_strand.wrap(
        [this, self](boost::system::error_code ec, std::size_t /*length*/) {
          if (!ec && read_msg_.decode_header()) {
            do_read_body();
          } else {
            std::cout << "Player leave the room\n";
            room_.leave(shared_from_this());
          }
        }));
  }

  void do_read_body() {
    auto self(shared_from_this());
    boost::asio::async_read(
        socket_, boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
				m_strand.wrap(
        [this, self](boost::system::error_code ec, std::size_t /*length*/) {
          if (!ec) {
            //room_.deliver(read_msg_);
						handleMessage();
            do_read_header();
          } else {
            room_.leave(shared_from_this());
          }
        }));
  }

  template <typename T> T toObject() {
    T obj;
    std::stringstream ss(std::string(
        read_msg_.body(), read_msg_.body() + read_msg_.body_length()));
    boost::archive::text_iarchive oa(ss);
    oa &obj;
    return obj;
  }

  bool fillProtobuf(::google::protobuf::Message* msg) {
    std::string ss(
        read_msg_.body(), read_msg_.body() + read_msg_.body_length());
		auto ok = msg->ParseFromString(ss);
    return ok;
  }

	ptree toPtree() {
		ptree obj;
		std::stringstream ss(
				std::string(read_msg_.body(),
					read_msg_.body() + read_msg_.body_length()));
		boost::property_tree::read_json(ss, obj);
		return obj;
	}

  void handleMessage() {
		auto n = std::chrono::system_clock::now() - base;
		std::cout << "i'm in " << std::this_thread::get_id() << " time "
			<< std::chrono::duration_cast<std::chrono::milliseconds>(n).count() << std::endl;
		std::this_thread::sleep_for(std::chrono::milliseconds(3));
    if (read_msg_.type() == MT_BIND_NAME) {
			PBindName bindName;
			if(fillProtobuf(&bindName))
				m_name = bindName.name();
    } else if (read_msg_.type() == MT_CHAT_INFO) {
			PChat chat;
			if(!fillProtobuf(&chat)) return;
			m_chatInformation = chat.information();

      auto rinfo = buildRoomInfo();
      chat_message msg;
      msg.setMessage(MT_ROOM_INFO, rinfo);
      room_.deliver(msg);

    } else {
      // not valid msg do nothing
    }
  }

  void do_write() {
    auto self(shared_from_this());
    boost::asio::async_write(
        socket_, boost::asio::buffer(write_msgs_.front().data(),
                                     write_msgs_.front().length()),
				m_strand.wrap(
        [this, self](boost::system::error_code ec, std::size_t /*length*/) {
          if (!ec) {
            write_msgs_.pop_front();
            if (!write_msgs_.empty()) {
              do_write();
            }
          } else {
            room_.leave(shared_from_this());
          }
        }));
  }

  tcp::socket socket_;
  chat_room &room_;
  chat_message read_msg_;
  chat_message_queue write_msgs_;
	std::string m_name;
	std::string m_chatInformation;
	boost::asio::io_service::strand m_strand;
	std::string buildRoomInfo() const {
		PRoomInformation roomInfo;
		roomInfo.set_name(m_name);
		roomInfo.set_information(m_chatInformation);
		std::string out;
		auto ok = roomInfo.SerializeToString(&out);
		assert(ok);
		return out;
	}
//	RoomInformation buildRoomInfo() const {
//		RoomInformation info;
//		info.name.nameLen = m_name.size();
//		std::memcpy(info.name.name, m_name.data(), m_name.size());
//		info.chat.infoLen = m_chatInformation.size();
//		std::memcpy(info.chat.information, m_chatInformation.data(),
//				m_chatInformation.size());
//		return info;
//	}
};


  void chat_room::join(chat_session_ptr participant) {
		//std::lock_guard<std::mutex> lock(m_mutex);
		m_strand.post([this, participant]{
				participants_.insert(participant);
				for (const auto& msg : recent_msgs_)
				participant->deliver(msg);
				});
  }

  void chat_room::leave(chat_session_ptr participant) {
		//std::lock_guard<std::mutex> lock(m_mutex);
		m_strand.post([this,participant]{
    participants_.erase(participant);});
  }

  void chat_room::deliver(const chat_message &msg) {
		//std::lock_guard<std::mutex> lock(m_mutex);
		m_strand.post([this, msg]{
    recent_msgs_.push_back(msg);
    while (recent_msgs_.size() > max_recent_msgs)
      recent_msgs_.pop_front();

    for (auto& participant : participants_)
      participant->deliver(msg);
		});
  }


//----------------------------------------------------------------------

class chat_server {
public:
  chat_server(boost::asio::io_service &io_service,
              const tcp::endpoint &endpoint)
      : acceptor_(io_service, endpoint), socket_(io_service), room_(io_service) {
    do_accept();
  }

private:
  void do_accept() {
    acceptor_.async_accept(socket_, [this](boost::system::error_code ec) {
      if (!ec) {
        auto session =
            std::make_shared<chat_session>(std::move(socket_), room_);
        session->start();
      }

      do_accept();
    });
  }

  tcp::acceptor acceptor_;
  tcp::socket socket_;
  chat_room room_;
};

//----------------------------------------------------------------------

int main(int argc, char *argv[]) {
  try {
		GOOGLE_PROTOBUF_VERIFY_VERSION;
    if (argc < 2) {
      std::cerr << "Usage: chat_server <port> [<port> ...]\n";
      return 1;
    }
		base = std::chrono::system_clock::now();

    boost::asio::io_service io_service;

    std::list<chat_server> servers;
    for (int i = 1; i < argc; ++i) {
      tcp::endpoint endpoint(tcp::v4(), std::atoi(argv[i]));
      servers.emplace_back(io_service, endpoint);
    }

		std::vector<std::thread> threadGroup;
		for(int i = 0; i < 5; ++i) {
			threadGroup.emplace_back([&io_service, i]{
					std::cout << i << " name is " << std::this_thread::get_id() << std::endl;
					io_service.run();});
		}

		std::cout << "main thread name is " << std::this_thread::get_id() << std::endl;
		io_service.run();
		for(auto& v : threadGroup) v.join();
  } catch (std::exception &e) {
    std::cerr << "Exception: " << e.what() << "\n";
  }

	google::protobuf::ShutdownProtobufLibrary();
  return 0;
}

客户端代码部分:

//client.cpp
#include "chat_message.h"
#include "structHeader.h"
#include "JsonObject.h"
#include "SerilizationObject.h"
#include "Protocal.pb.h"

#include <boost/asio.hpp>

#include <chrono>
#include <deque>
#include <iostream>
#include <memory>
#include <thread>
#include <vector>

#include <cstdlib>
#include <cassert>

using boost::asio::ip::tcp;

using chat_message_queue = std::deque<chat_message>;


class chat_client {
public:
  chat_client(boost::asio::io_service &io_service,
              tcp::resolver::iterator endpoint_iterator)
      : io_service_(io_service), socket_(io_service) {
    do_connect(endpoint_iterator);
  }

  void write(const chat_message &msg) {
    io_service_.post([this, msg]() {
      bool write_in_progress = !write_msgs_.empty();
      write_msgs_.push_back(msg);
      if (!write_in_progress) {
        do_write();
      }
    });
  }

  void close() {
    io_service_.post([this]() { socket_.close(); });
  }

private:
  void do_connect(tcp::resolver::iterator endpoint_iterator) {
    boost::asio::async_connect(
        socket_, endpoint_iterator,
        [this](boost::system::error_code ec, tcp::resolver::iterator) {
          if (!ec) {
            do_read_header();
          }
        });
  }

  void do_read_header() {
    boost::asio::async_read(
        socket_,
        boost::asio::buffer(read_msg_.data(), chat_message::header_length),
        [this](boost::system::error_code ec, std::size_t /*length*/) {
          if (!ec && read_msg_.decode_header()) {
            do_read_body();
          } else {
            socket_.close();
          }
        });
  }

  void do_read_body() {
    boost::asio::async_read(
        socket_, boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
        [this](boost::system::error_code ec, std::size_t /*length*/) {
          if (!ec) {
            if (read_msg_.type() == MT_ROOM_INFO) {
              //SRoomInfo info;
						std::string buffer(read_msg_.body(),
                              read_msg_.body() + read_msg_.body_length());

						PRoomInformation roomInfo;
						auto ok = roomInfo.ParseFromString(buffer);
						//if(!ok) throw std::runtime_error("not valid message");
              //std::stringstream ss(buffer);
							//ptree tree;
							//boost::property_tree::read_json(ss, tree);
						if (ok) {
						std::cout << "client: '";
						std::cout << roomInfo.name();
						std::cout << "' says '";
						std::cout
						<< roomInfo.information();
						std::cout << "'\n";
						}
//              boost::archive::text_iarchive ia(ss);
//              ia & info;
//              std::cout << "client: '";
//              std::cout << info.name();
//              std::cout << "' says '";
//              std::cout << info.information();
//              std::cout << "'\n";
            }
            do_read_header();
          } else {
            socket_.close();
          }
        });
  }

  void do_write() {
    boost::asio::async_write(
        socket_, boost::asio::buffer(write_msgs_.front().data(),
                                     write_msgs_.front().length()),
        [this](boost::system::error_code ec, std::size_t /*length*/) {
          if (!ec) {
            write_msgs_.pop_front();
            if (!write_msgs_.empty()) {
              do_write();
            }
          } else {
            socket_.close();
          }
        });
  }

private:
  boost::asio::io_service &io_service_;
  tcp::socket socket_;
  chat_message read_msg_;
  chat_message_queue write_msgs_;
};

int main(int argc, char *argv[]) {
  try {
		GOOGLE_PROTOBUF_VERIFY_VERSION;
    if (argc != 3) {
      std::cerr << "Usage: chat_client <host> <port>\n";
      return 1;
    }

    boost::asio::io_service io_service;
		std::vector<std::unique_ptr<chat_client>> clientGroup;

    tcp::resolver resolver(io_service);
    auto endpoint_iterator = resolver.resolve({argv[1], argv[2]});
		for(int i = 0; i < 10; ++i) {
			clientGroup.emplace_back(std::make_unique<chat_client>(
						io_service, endpoint_iterator));
		}

    std::thread t([&io_service]() { io_service.run(); });

    char line[chat_message::max_body_length + 1];
		// ctrl-d
    while (std::cin.getline(line, chat_message::max_body_length + 1)) {
      chat_message msg;
			auto type = 0;
			std::string input(line, line + std::strlen(line));
			std::string output;
			if(parseMessage4(input, &type, output)) {
				msg.setMessage(type, output.data(), output.size());
				for(auto& v : clientGroup)
					v->write(msg);
				std::cout << "write message for server " << output.size() << std::endl;
			}
    }

		for(auto& v: clientGroup)
			v->close();
    t.join();
  } catch (std::exception &e) {
    std::cerr << "Exception: " << e.what() << "\n";
  }

	google::protobuf::ShutdownProtobufLibrary();
  return 0;
}
发布了147 篇原创文章 · 获赞 35 · 访问量 5570

猜你喜欢

转载自blog.csdn.net/qq_39885372/article/details/104085172