您的位置:首页 > 科技 > 能源 > web前端毕业设计新颖题目_ui设计软件官网_万网注册域名查询官方网站_郑州seo关键词排名优化

web前端毕业设计新颖题目_ui设计软件官网_万网注册域名查询官方网站_郑州seo关键词排名优化

2024/9/30 8:27:08 来源:https://blog.csdn.net/m0_63086198/article/details/142577678  浏览:    关键词:web前端毕业设计新颖题目_ui设计软件官网_万网注册域名查询官方网站_郑州seo关键词排名优化
web前端毕业设计新颖题目_ui设计软件官网_万网注册域名查询官方网站_郑州seo关键词排名优化

三、day3

将前面学习的boost::asio同步读写的api函数串联起来,做一个客户端和服务器,客户端和服务器采用阻塞的同步读写方式完成通信。

1)客户端设计

客户端设计基本思路是根据服务器对端的ip和端口创建一个endpoint,然后创建socket连接这个endpoint,之后就可以用同步读写的方式发送和接收数据

#include <boost/asio.hpp>
#include <iostream>
using namespace boost::asio::ip;
using std::cout;
using std::endl;
const int MAX_LENGTH = 1024; // 发送和接收的长度为1024字节int main()
{try {boost::asio::io_context ioc; // 创建上下文服务// 127.0.0.1是本机的回路地址,也就是服务器和客户端在一个机器上tcp::endpoint remote_ep(address::from_string("127.0.0.1"), 10086); // 构造endpointtcp::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就是应答式的处理。

void session(socket_ptr sock) {try {for (;;) {char data[MAX_LENGTH]; // 缓存收到的数据memset(data, '\0', MAX_LENGTH); // 将char数组中的数据全部设为\0boost::system::error_code error;// 服务器可能不会一次读完,而用read或receive可能会造成堵塞// 因为这里是独立的线程而不是主程序,所以这里可以用read,一直等,但直到读到最大长度才会返回//size_t length = boost::asio::read(sock, boost::asio::buffer(data, MAX_LENGTH), error);// read_some实际只会读收到的长度,在没有粘包的情况下用read_some比较好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任务。

void server(boost::asio::io_context& io_context, unsigned short port) {// 生成一个用于接收客户端连接的acceptor,绑定的端点是服务器的地址(用ipv4的方式绑定)以及自定义的端口tcp::acceptor a(io_context, tcp::endpoint(tcp::v4(), port));while (1) {// 生成一个socket来处理客户端的请求,相当于服务员socket_ptr socket(new tcp::socket(io_context));// accept接收到请求后,用socket对请求进行处理a.accept(*socket);// 生成一个线程,相当于服务员将客户带到包间,大厅仍进行客人的招待// 线程中也就是包间中,用session作为招待服务,session就是服务器的读和写// 如果不创建线程,而是直接session,那么如果对端不发送数据,程序就会阻塞一直不运行下一行,其他的连接也会无法接收// 之所以将线程存储到智能指针中,是为了管理其生命周期,防止线程对象被提前销毁auto t = std::make_shared<std::thread>(session, socket);// thread_set是全局变量而不是局部变量,当server函数结束后,thread_set仍会保留thread_set.insert(t);}
}

3. 整体结构

#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; // 发送和接收的长度为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.joinable()检查t(即当前遍历到的std::thread对象)是否可以被连接t->join(); // 调用join()的线程将阻塞,直到t所代表的线程执行完毕才会继续下一行}}catch (std::exception& e) {std::cerr << "Exception: " << e.what() << endl;}return 0;
}void session(socket_ptr sock) {try {// 该段代码是循环的,当第一次接收到数据后,程序走到最后又重回第一行,如果第二次没有// 读到任何信息,那么length==0,此时error未eof,相当于客户端不在发送信息,连接中止for (;;) {char data[MAX_LENGTH]; // 缓存收到的数据memset(data, '\0', MAX_LENGTH); // 将char数组中的数据全部设为\0boost::system::error_code error;// 服务器可能不会一次读完,而用read或receive可能会造成堵塞// 因为这里是独立的线程而不是主程序,所以这里可以用read,一直等,但直到读到最大长度才会返回//size_t length = boost::asio::read(sock, boost::asio::buffer(data, MAX_LENGTH), error);// read_some实际只会读收到的长度,在没有粘包的情况下用read_some比较好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) {// 生成一个用于接收客户端连接的acceptor,绑定的端点是服务器的地址(用ipv4的方式绑定)以及自定义的端口tcp::acceptor a(io_context, tcp::endpoint(tcp::v4(), port));while (1) {// 生成一个socket来处理客户端的请求,相当于服务员socket_ptr socket(new tcp::socket(io_context));// accept接收到请求后,用socket对请求进行处理a.accept(*socket);// 生成一个线程,相当于服务员将客户带到包间,大厅仍进行客人的招待// 线程中也就是包间中,用session作为招待服务,session就是服务器的读和写// 如果不创建线程,而是直接session,那么如果对端不发送数据,程序就会阻塞一直不运行下一行,其他的连接也会无法接收// 之所以将线程存储到智能指针中,是为了管理其生命周期,防止线程对象被提前销毁auto t = std::make_shared<std::thread>(session, socket);// thread_set是全局变量而不是局部变量,当server函数结束后,thread_set仍会保留thread_set.insert(t);}
}

客户端操作过程:

  1. 绑定服务器ip和端口号为端点->
  2. 创建socket->
  3. socket尝试连接服务器端点->
  4. 如果连接成功,通过boost::asio::write(sock,buffer)函数进行数据的发送,第一个参数是客户端创建的与服务器端点连接成功的socket,第二个参数是客户端要发送的数据,通过buffer函数进行类型转换->
  5. 发送后,等待回传消息,使用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()保证子线程的程序结束后,才会进行下一行命令

总结:

  1. 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 避免了这个问题,因为它是一个原子操作,要么成功创建对象并管理它,要么什么都不做。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com