三、day3
将前面学习的boost::asio同步读写的api函数串联起来,做一个客户端和服务器,客户端和服务器采用阻塞的同步读写方式完成通信。
参考:
恋恋风辰官方博客
visual studio配置C++ boost库_哔哩哔哩_bilibili
客户端设计基本思路是根据服务器对端的ip和端口创建一个endpoint,然后创建socket连接这个endpoint,之后就可以用同步读写的方式发送和接收数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <boost/asio.hpp> #include <iostream> using namespace boost::asio::ip; using std::cout; using std::endl; const int MAX_LENGTH = 1024;
int main() { try { boost::asio::io_context ioc; tcp::endpoint remote_ep(address::from_string("127.0.0.1"), 10086); tcp::socket sock(ioc); boost::system::error_code error = boost::asio::error::host_not_found; sock.connect(remote_ep, error); if (error) { cout << "connect failed, code is " << error.value() << " error msg is " << error.message() << endl;; } cout << "Enter message: "; char request[MAX_LENGTH]; std::cin.getline(request, MAX_LENGTH); size_t request_length = strlen(request); boost::asio::write(sock, boost::asio::buffer(request, request_length));
char reply[MAX_LENGTH]; size_t reply_length = boost::asio::read(sock, boost::asio::buffer(reply, request_length)); cout << "Reply is: "; cout.write(reply, reply_length); cout << "\n";
} catch (std::exception& e) { std::cerr << "Exception: " << e.what() << endl; } return 0; }
|
2)服务器设计
1. session设计
创建session函数,该函数为服务器处理客户端请求,每当我们获取客户端连接后就调用该函数。在session函数里里进行echo方式的读写,所谓echo就是应答式的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| void session(socket_ptr sock) { try {
for (;;) { char data[MAX_LENGTH]; memset(data, '\0', MAX_LENGTH); boost::system::error_code error; size_t length = sock->read_some(boost::asio::buffer(data, MAX_LENGTH), error); if (error == boost::asio::error::eof) { std::cout << "connection closed by peer" << endl; break; } else if (error) throw boost::system::system_error(error); cout << "receive from " << sock->remote_endpoint().address().to_string() << endl; cout << "receive message is " << data << endl; boost::asio::write(*sock, boost::asio::buffer(data, length)); } } catch (std::exception& e) { cout << "Exception in thread: " << e.what() << "\n" << endl; } }
|
2. server设计
server函数根据服务器ip和端口创建服务器acceptor用来接收客户端的请求,然后创建一个新的socket去处理这个请求,并为此开辟一个线程,新的socket对客户端在该线程中执行session任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void server(boost::asio::io_context& io_context, unsigned short port) { tcp::acceptor a(io_context, tcp::endpoint(tcp::v4(), port)); while (1) { socket_ptr socket(new tcp::socket(io_context)); a.accept(*socket); auto t = std::make_shared<std::thread>(session, socket); thread_set.insert(t); } }
|
3. 整体结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| #include <iostream> #include <boost/asio.hpp> #include <set> #include <memory>
using boost::asio::ip::tcp; using std::cout; using std::endl; const int MAX_LENGTH = 1024; typedef std::shared_ptr<tcp::socket> socket_ptr; std::set<std::shared_ptr<std::thread>> thread_set;
void session(socket_ptr sock); void server(boost::asio::io_context& io_context, unsigned short port);
int main() { try { cout << "服务器已启动!\n"; boost::asio::io_context ioc; server(ioc, 10086); for (auto& t : thread_set) { if (t->joinable()) t->join(); } } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << endl; }
return 0; }
void session(socket_ptr sock) { try { for (;;) { char data[MAX_LENGTH]; memset(data, '\0', MAX_LENGTH); boost::system::error_code error; size_t length = sock->read_some(boost::asio::buffer(data, MAX_LENGTH), error); if (error == boost::asio::error::eof) { std::cout << "connection closed by peer" << endl; break; } else if (error) throw boost::system::system_error(error); cout << "receive from " << sock->remote_endpoint().address().to_string() << endl; cout << "receive message is " << data << endl; boost::asio::write(*sock, boost::asio::buffer(data, length)); } } catch (std::exception& e) { cout << "Exception in thread: " << e.what() << "\n" << endl; } }
void server(boost::asio::io_context& io_context, unsigned short port) { tcp::acceptor a(io_context, tcp::endpoint(tcp::v4(), port)); while (1) { socket_ptr socket(new tcp::socket(io_context)); a.accept(*socket); auto t = std::make_shared<std::thread>(session, socket); thread_set.insert(t); } }
|
客户端操作过程:
- 绑定服务器ip和端口号为端点->
- 创建socket->
- socket尝试连接服务器端点->
- 如果连接成功,通过
boost::asio::write(sock,buffer)
函数进行数据的发送,第一个参数是客户端创建的与服务器端点连接成功的socket,第二个参数是客户端要发送的数据,通过buffer函数进行类型转换->
- 发送后,等待回传消息,使用
boost::asio::read(sock,buffer)
,进行读取服务器传回的信息,第一个参数是与服务器端点连接成功的socket,第二个参数buffer,buffer中有一个容器,用于存储服务器传回的信息。
服务器操作过程:
1)进入server函数,首先生成一个用于接收客户端连接的acceptor,将服务器地址用ipv4的方式与自定义端口号绑定,然后进入循环,创建一个元素成员类型为tcp::socket
的智能指针容器(该容器可以不断的生成socket类型,相当于生成多个服务员应对不同的线程请求),如果acceptor接收到客户端请求,用智能指针容器中的socket去处理该请求,并为此创建一个元素成员类型是线程的智能指针容器,该容器中的第一个元素(线程)是第一个socket以及socket的任务session;最后,将该容器插入至全局变量集中,管理其声明周期。
2)socket在新线程中处理客户端的请求,首先,用这个socket读取客户端发送的信息,并将其保存至自定义的容器(缓冲区)中,并对该数据进行处理;然后,boost::asio::write
(sock,buffer)函数进行数据回传,第一个参数就是服务员一开始的socket,第二个参数是想要回传的数据。
3)为了防止子线程的内容还未结束,主程序就停止,使用t->join()
保证子线程的程序结束后,才会进行下一行命令
总结:
make_shared
为什么可以管理其声明周期,防止线程对象被提前销毁?
1)工作原理:
std::shared_ptr
是一种引用计数的智能指针。当你使用 std::make_shared
创建一个对象时,它不仅会分配内存并构造该对象,还会创建一个控制块(control block),这个控制块包含:
- 一个指向实际对象的指针。
- 一个引用计数(reference count),用于跟踪有多少个
std::shared_ptr
指向这个对象。
- 一个弱引用计数(weak reference count),用于跟踪有多少个
std::weak_ptr
指向这个对象。
- 每当创建一个新的
std::shared_ptr
指向同一个对象时,引用计数会增加;每当一个 std::shared_ptr
被销毁或重置(reset)时,引用计数会减少。当引用计数变为零时(即没有任何 std::shared_ptr
指向该对象时),对象会自动被销毁,其占用的内存也会被释放
2)与new的比较
- 内存效率:
std::make_shared
在一个单一的内存分配中同时分配对象和控制块。这通常比使用 new
和随后创建一个 std::shared_ptr
更加高效,因为后者可能需要两次内存分配(一次为对象,一次为控制块)。
- *简化代码,防止内存泄漏***:
- 使用
new
时,如果在创建对象和赋值给智能指针之间发生异常,可能导致内存泄漏。std::make_shared
避免了这个问题,因为它是一个原子操作,要么成功创建对象并管理它,要么什么都不做。