C++ asio网络编程(4)同步读写的客户端和服务器示例

目录

简介

客户端设计

服务器设计

session函数

server函数

同步读写的优劣


简介

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

:ip地址"127.0.0.1"是一个特殊的IP地址,它代表本地回环地址,通常用于在同一台计算机上进行网络通信。在网络编程中,如果将IP地址设置为"127.0.0.1",则表示将数据发送到本地机器上。这个地址通常用于测试和开发环境中,可以用来模拟客户端和服务器之间的通信,而不需要实际的网络连接。我们一会会用到这个特殊的ip地址。

客户端设计

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

#include <iostream>
#include <boost/asio.hpp>
using namespace boost::asio::ip;
using namespace std;
const int MAX_LENGTH = 1024;

int main()
{
	try{
		//创建上下文服务
		boost::asio::io_context ioc;
		
		//构造endpoint
		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);
		//连接对端的ip的一个端点,并且把这个错误作为参数传递进去,连接失败的话会通过这个错误作为参数返回
		if (error) {
			cout 
				<< "connect failed,code is" << error.value() 
				<< "error msg is" << error.what() 
				<< endl;
			return 0;
		}

		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));
		//这段代码的作用是从已连接的套接字 sock 中读取数据,并将其存储到 reply 缓冲区中,
		// 直到达到指定的最大长度为 request_length。读取的字节数会存储在 reply_length 变量中。
		
		cout << "Reply is:";
		cout.write(reply, reply_length);
		cout << "\n";
	}
	catch (std::exception& e){
		std::cerr << "Exception:" << e.what() << endl;
	}

}

服务器设计

session函数

创建session函数,该函数为服务器处理客户端请求,每当我们获取客户端连接后就调用该函数。在session函数里里进行echo方式的读写,所谓echo就是应答式的处理

#include <iostream>
#include <boost/asio.hpp>
#include <set>
using boost::asio::ip::tcp;
const int max_length = 1024;
typedef std::shared_ptr<tcp::socket> socket_ptr;
std::set<std::shared_ptr<std::thread>> thread_set;
using namespace std;


//连接会话
void session(socket_ptr sock){
	try{
		for (;;) {
			char data[max_length];
			
			memset(data, '\0', max_length);
			//安全起见,把chat数组里的内容清空成0

			/*boost::system::error_code error;
			size_t length = boost::asio::read(sock, 
				boost::asio::buffer(data, max_length), 
				error);*/
			//这样等到缓冲区满了(到了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) {
				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 (const std::exception& e){
		std::cerr << "Exception in thread:" << e.what() << "\n" << std::endl;
	}
}


void server(boost::asio::io_context& io_context, unsigned short port) {
	tcp::acceptor a(io_context, tcp::endpoint(tcp::v4(), port));
	for (;;) {
		socket_ptr socket(new tcp::socket(io_context));
		a.accept(*socket);
		auto t = std::make_shared<std::thread>(session, socket);
		
		//session(socket);
	
		//  ↑  ↑  ↑  假如直接调用session,必须要等对方的客户端发送完数据处理好之后,
		// acceptor才能进入下一次循环,会导致程序卡在这里
		thread_set.insert(t);
	}
}



int main()
{
	try{
		boost::asio::io_context ioc;
		server(ioc, 10086);
		for (auto& t : thread_set) {
			t->join();
		}
	}
	catch (const std::exception& e){

	}

	return 0;
}

server函数

server函数根据服务器ip和端口创建服务器acceptor用来接收数据,用socket接收新的连接,然后为这个socket创建session。

void server(boost::asio::io_context& io_context, unsigned short port) {
	tcp::acceptor a(io_context, tcp::endpoint(tcp::v4(), port));
	for (;;) {
		socket_ptr socket(new tcp::socket(io_context));
		a.accept(*socket);
		auto t = std::make_shared<std::thread>(session, socket);
		
		//session(socket);
	
		//  ↑  ↑  ↑  假如直接调用session,必须要等对方的客户端发送完数据处理好之后,
		// acceptor才能进入下一次循环,会导致程序卡在这里
		thread_set.insert(t);
	}
}

这里创建线程调用session函数可以分配独立的线程用于socket的读写,保证acceptor不会因为socket的读写而阻塞。

同步读写的优劣

1 同步读写的缺陷在于读写是阻塞的,如果客户端对端不发送数据服务器的read操作是阻塞的,这将导致服务器处于阻塞等待状态。
2 可以通过开辟新的线程为新生成的连接处理读写,但是一个进程开辟的线程是有限的,约为2048个线程,在Linux环境可以通过unlimit增加一个进程开辟的线程数,但是线程过多也会导致切换消耗的时间片较多。
3 该服务器和客户端为应答式,实际场景为全双工通信模式,发送和接收要独立分开。
4 该服务器和客户端未考虑粘包处理。
综上所述,是我们这个服务器和客户端存在的问题,为解决上述问题,我们在接下里的文章里做不断完善和改进,主要以异步读写改进上述方案。
当然同步读写的方式也有其优点,比如客户端连接数不多,而且服务器并发性不高的场景,可以使用同步读写的方式。使用同步读写能简化编码难度。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值