buffer结构、同步读写API与简单的同步读写服务器

一、buffer数据结构

        在Boost的asio里,发送数据需要用一个结构去承载,这个结构就是buffer。任何网络库都有提供buffer的数据结构,所谓buffer就是接收和发送数据时缓存数据的结构。(通俗一点说就是,buffer就是包装你要发送或接收的数据的一种结构。发送或者接收一段数据时要转换为buffer这种数据结构去接收或者发送)

boost::asio提供了两种内存缓冲区结构:

        asio::mutable_buffer,用于写服务

        asio::const_buffer,用于读服务

        他们是一段连续的空间,首字节存储了后续数据的长度。但是这两个结构都没有被asio的api直接使用。
对于api的buffer参数,asio提出了MutableBufferSequence和ConstBufferSequence概念,他们是由多个asio::mutable_buffer和asio::const_buffer组成的。也就是说boost::asio为了节省空间,将一部分连续的空间组合起来,作为参数交给api使用。
 

二、asio::const_buffer

将数据转换为const_buffer对象有两种方法:通过构造函数包装

const_buffer(const void* data, std::size_t size) noexcept
  : data_(data),
    size_(size)
{
}
void use_const_buffer() {
	std::string s = "hello world";
	asio::const_buffer asio_buf(s.c_str(), s.length());  //asio::const_buffer的构造函数
	std::vector<asio::const_buffer> buffers_sequence;  //使用 std::vector<asio::const_buffer> 可以方便地将多个缓冲区合并成一个发送操作。
	//虽然你当前只想发送一个字符串,但未来可能会有更多的数据需要发送

	buffers_sequence.push_back(asio_buf);

}

通过asio::buffer()函数将数据转换为const_buffer结构

void use_buffer_str() {
	asio::const_buffers_1 output_buf = asio::buffer("hello world!");   //asio::const_buffers_1 主要用于表示只有一个 const_buffer 的情况, class const_buffers_1: public const_buffer
	//通过调用 asio::buffer(),不需要手动指定字符串的内容和大小。ASIO 会自动处理这些细节并返回一个 const_buffer,这个步骤简化了use_const_buffer()中的步骤。
	//我们也可以将其存到数组中
	std::vector<asio::const_buffer> buffers_sequence;
	buffers_sequence.push_back(output_buf);
	//还可以
	buffers_sequence.push_back(asio::buffer("你好,世界"));

}

对于数组这种类型数据,怎样转换为buffer结构

void use_buffer_array() {
	const size_t BUF_SIZE_BYTES = 20;
	std::unique_ptr<char[]> buf(new char[BUF_SIZE_BYTES]);
	auto input_but = asio::buffer(static_cast<void*>(buf.get()), BUF_SIZE_BYTES);  //取出buf的原始指针,转化为void*类型的指针,并指明大小,
	//asio::buffer()就知道你想把buf这个智能指针转换为buffer结构
}

三、同步读写

同步读写就是进行IO操作是程序会被阻塞,直到完成后才会继续往下执行

asio中提供了几种同步写的api:

同步写write_some

write_some可以每次向指定的空间写入固定的字节数,如果写缓冲区满了,就只写一部分,返回写入的字节数。

void write_to_socket(asio::ip::tcp::socket& sock) {
	std::string buf = "hello world";
	std::size_t total_bytes_written = 0;
	//循环发送
	//write_som返回每次写入的字节数
	while (total_bytes_written != buf.size()) {
		total_bytes_written += sock.write_some(asio::buffer(buf.c_str() + total_bytes_written, buf.size() - total_bytes_written));
	}
}

同步写send

write_some使用起来比较麻烦,在传递一段数据时可能需要循环多次调用,(因为缓冲区大小有限,可能一次发不完)。asio中提供了send函数,send函数会一次性的将buffer中的内容全部发送给对端,如果有部分字节因为发送缓冲区满了无法发送,则阻塞等待,直到发送缓冲区可用,则继续发送完成。

int send_data_by_send() {
	std::string raw_ip_address = "192.168.3.11";
	unsigned short port_num = 3333;
	try {
		asio::ip::tcp::endpoint ep(asio::ip::address::from_string(raw_ip_address), port_num);
		asio::io_context ioc;
		asio::ip::tcp::socket sock(ioc, ep.protocol());//可以省略协议选择的那步,因为endpoint可以隐式推出协议
		sock.connect(ep);
		std::string buf = "Hello world!";
		int send_length = sock.send(asio::buffer(buf.c_str(), buf.length()));
		//send_length有三种情况,为0,表示对端关闭
		//小于0说明发生系统级错误
		//大于0时,则一定等于buf的长度
		if (send_length <= 0) {
			std::cout << "send failed" << std::endl;
			return 0;
		}
	}
	catch (system::system_error& er) {
		std::cout << "Error occured! Error code= " << er.code() << " .Message: " << er.what() << std::endl;
		return er.code().value();
	}
	return 0;
}

同步写write

将建立连接后,新创建的socket作为参数传入

void send_data_by_wirte(asio::ip::tcp::socket& sock) {
	std::string buf = "Hello World!";
	int send_length = asio::write(sock, asio::buffer(buf.c_str(), buf.length()));
	//和send发送一样,都有三种
	if (send_length <= 0) {
		std::cout << "send failed" << std::endl;
		return;
	}
}

或者

int send_data_by_write() {
	std::string raw_ip_address = "192.168.3.11";
	unsigned short port_num = 3333;
	try {
		asio::ip::tcp::endpoint ep(asio::ip::address::from_string(raw_ip_address), port_num);
		asio::io_context ioc;
		asio::ip::tcp::socket sock(ioc, ep.protocol());//可以省略协议选择的那步,因为endpoint可以隐式推出协议
		sock.connect(ep);
		std::string buf = "Hello world!";
		int send_length = asio::write(sock,asio::buffer(buf.c_str(), buf.length()));
	
		if (send_length <= 0) {
			std::cout << "send failed" << std::endl;
			return 0;
		}
	}
	catch (system::system_error& er) {
		std::cout << "Error occured! Error code= " << er.code() << " .Message: " << er.what() << std::endl;
		return er.code().value();
	}
	return 0;
}

同步读read_some

std::string read_from_socket(asio::ip::tcp::socket& sock) {
	const unsigned int MESSAGE_SIZE = 7;
	char buf[MESSAGE_SIZE];
	std::size_t total_bytes_read = 0;
	while (total_bytes_read != MESSAGE_SIZE) {
		total_bytes_read += sock.read_some(asio::buffer(buf + total_bytes_read, MESSAGE_SIZE - total_bytes_read));
	}
	return std::string(buf, total_bytes_read);
}

int read_data_by_read_some() {
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	try {
		asio::ip::tcp::endpoint ep(
			asio::ip::address::from_string(raw_ip_address), port_num
		);
		asio::io_context ioc;
		asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);

		read_from_socket(sock);
	}
	catch (system::system_error& e) {
		std::cout<< "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();
		return e.code().value();
	}

	return 0;
}

同步读receive

int read_data_by_receive() {
    std::string raw_ip_address = "127.0.0.1";
    unsigned short port_num = 3333;
    try {
        asio::ip::tcp::endpoint
            ep(asio::ip::address::from_string(raw_ip_address),
                port_num);
        asio::io_service ios;
        asio::ip::tcp::socket sock(ios, ep.protocol());
        sock.connect(ep);
        const unsigned char BUFF_SIZE = 7;
         char buffer_receive[BUFF_SIZE];
        int receive_length =  sock.receive(asio::buffer(buffer_receive, BUFF_SIZE));
        if (receive_length <= 0) {
            cout << "receive failed" << endl;
        }
    }
    catch (system::system_error& e) {
        std::cout << "Error occured! Error code = " << e.code()
            << ". Message: " << e.what();
        return e.code().value();
    }
    return 0;
}

同步读read

int read_data_by_read() {
    std::string raw_ip_address = "127.0.0.1";
    unsigned short port_num = 3333;
    try {
        asio::ip::tcp::endpoint
            ep(asio::ip::address::from_string(raw_ip_address),
                port_num);
        asio::io_service ios;
        asio::ip::tcp::socket sock(ios, ep.protocol());
        sock.connect(ep);
        const unsigned char BUFF_SIZE = 7;
        char buffer_receive[BUFF_SIZE];
        int receive_length = asio::read(sock, asio::buffer(buffer_receive, BUFF_SIZE));
        if (receive_length <= 0) {
            cout << "receive failed" << endl;
        }
    }
    catch (system::system_error& e) {
        std::cout << "Error occured! Error code = " << e.code()
            << ". Message: " << e.what();
        return e.code().value();
    }
    return 0;
 }

读取直到指定字符

一直读,读到指定字符才停下来

std::string  read_data_by_until(asio::ip::tcp::socket& sock) {
    asio::streambuf buf;
    // Synchronously read data from the socket until
    // '\n' symbol is encountered.  
    asio::read_until(sock, buf, '\n');
    std::string message;
    // Because buffer 'buf' may contain some other data
    // after '\n' symbol, we have to parse the buffer and
    // extract only symbols before the delimiter. 
    std::istream input_stream(&buf);
    std::getline(input_stream, message);
    return message;
 }

四、同步读取demo

客户端

#include <iostream>
#include <boost/asio.hpp>
using namespace boost;
const int MASSAGE_SIZE = 1024;

int main() {
	std::string raw_ip_address = "127.0.0.1";  //本机的回路地址,服务器与本机在同一个地址
	unsigned short port_num = 10086;
	try {
		asio::io_context ioc;
		asio::ip::tcp::endpoint ep(asio::ip::address::from_string(raw_ip_address), port_num);
		asio::ip::tcp::socket sock(ioc, ep.protocol());
		system::error_code error=asio::error::host_not_found;  //默认的错误,如果没有错误,主机会重置
		sock.connect(ep,error);

		if (error) {
			std::cout << "connect failed , code is " <<error.value()<<"error message is"<< error.message() << std::endl;
			return 0;
		}

		/*std::cout << "Enter message:";
		char request[MASSAGE_SIZE];
		std::cin.getline(request,MASSAGE_SIZE);

		std::size_t length_r = strlen(request);
		asio::write(sock,asio::buffer(request, length_r));

		char reply[MASSAGE_SIZE];
		size_t reply_length = asio::read(sock,asio::buffer(reply, length_r));

		std::cout << "Reply is:";
		std::cout.write(reply, reply_length);
		std::cout << "\n";*/
		for (;;) {
			std::cout << "Enter message:";
			char request[MASSAGE_SIZE];
			std::cin.getline(request, MASSAGE_SIZE);

			std::size_t length_r = strlen(request);
			asio::write(sock, asio::buffer(request, length_r));

			char reply[MASSAGE_SIZE];
			size_t reply_length = asio::read(sock, asio::buffer(reply, length_r));

			std::cout << "Reply is:";
			std::cout.write(reply, reply_length);
			std::cout << "\n";
		}
	}
	catch (std::exception& e) {
        std::cout << "Exception: " << e.what() << std::endl;
	}

	return 0;
}

服务端

#include<iostream>
#include<boost/asio.hpp>
#include<set>
#include<memory>
using namespace boost;

const int max_length = 1024;
typedef std::shared_ptr<asio::ip::tcp::socket> socket_ptr;  //用于管理socket

std::set<std::shared_ptr<std::thread>> thread_set;  //用个集合便于管理线程


//会话
void session(socket_ptr sock) {

	try {
		for (;;) {
			char data[max_length];
			memset(data, '\0', max_length);
			boost::system::error_code error;

			//size_t length = asio::read(sock, asio::buffer(data, max_length), error);
			size_t length = sock->read_some( asio::buffer(data, max_length), error);
			//或者size_t length = (*sock).read_some(asio::buffer(data, max_length), error);
			
			if (error == boost::asio::error::eof) {  //对端关闭错误
				std::cout << "connection closed by peer" << std::endl;
			}
			else if (error) {
				throw boost::system::system_error(error);
			}

			std::cout << "receive from" << sock->remote_endpoint().address().to_string() << std::endl;
			std::cout << "receive message is:" << data << std::endl;

			//回传给对方
			boost::asio::write(*sock, boost::asio::buffer(data, length));
		}
	}
	catch (std::exception& e) {
		std::cout << "Exception in thread: " << e.what() << std::endl;
	}

}

//建立连接
void serve(boost::asio::io_context& ioc,unsigned short port) {
	asio::ip::tcp::acceptor a(ioc, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port));  //监听对应端口,接收,建立连接,相当于打开大门
	for (;;) {
		socket_ptr socket(new asio::ip::tcp::socket(ioc));  //创建服务人员socket,这里是利用shared_ptr只能直接初始化的语法
		a.accept(*socket); //把客户交给socket处理

		auto t = std::make_shared<std::thread>(session, socket);  //创建线程+专门管理为客户服务
		//因为可能这个线程来不及执行就进入下一个for循环了,但这个线程又是局部变量,所以就直接销毁了
		//这里利用智能指针的引用集合了做,让出了当前循环后,还能继续存在
		thread_set.insert(t);

	}
}


int main() {

	try {
		boost::asio::io_context ioc;
		serve(ioc, 10086);
		for (auto& t : thread_set) {  //防止因为主线程退出,导致子线程还在工作时强制退出造成的不良影响
			t->join();
		}
	}
	catch (std::exception& e) {
		std::cout << "Exception in thread: " << e.what() << std::endl;
	}
	
	return 0;
}

运行

总结

1.同步读写缺陷在于阻塞,如果客户端不发送数据会导致服务端一直阻塞在read操作,这将导致服务器处于阻塞等待状态。

2.在这个demo中,通过开辟新的线程为新生成的连接处理读写,但是一个进程开辟的线程是有限的,约为2048个线程,在Linux环境可以通过unlimit增加一个进程开辟的线程数,但是线程过多也会导致切换消耗的时间片较多。
3.该服务器和客户端为应答式,实际场景为全双工通信模式,发送和接收要独立分开。
4.该服务器和客户端未考虑粘包处理,关于粘包就是用于tcp的缓冲区积累了一些数据,或者收发端的频率不一致导致的。
当然同步读写的方式也有其优点,比如客户端连接数不多,而且服务器并发性不高的场景,可以使用同步读写的方式。使用同步读写能简化编码难度。后面会用异步处理,优化该服务器。

以上内容都是跟着博主学的,一个非常棒的博主!

补充知识

1.强转

1. C 风格的强制转换

double b = (double)(5);

这种括号包裹的方式实际上是 C 风格的强制类型转换,它可以将一个类型强制转换为另一个类型。在 C 和 C++ 中都适用,但在现代 C++ 编程中并不推荐使用,因为它可能导致意想不到的转换行为。C 风格的强制转换会尝试进行所有类型的转换,包括 static_castreinterpret_castconst_cast,因此可能难以追踪转换的具体性质。

2. static_cast

double b = static_cast<double>(5);

static_cast 是 C++ 中的一种显式类型转换,用于在类型安全的情况下执行转换。它可以用于以下情况:

  • 基本数据类型之间的转换(如 int 到 double
  • 指针和引用类型之间的转换(如基类指针到派生类指针,但在此情况下需要确保类型安全)。

使用 static_cast 明确表明了转换的意图,并且比 C 风格的转换更易于阅读和理解。

3. reinterpret_cast

reinterpret_cast 用于低级别的指针转换,通常涉及到不同类型之间完全不同的表示,例如将一个指针转换为另一个完全不同类型的指针。这种转换通常是非常危险的,因为它可能破坏对象的类型信息和内存布局。

4. const_cast

const_cast 主要用于去除或添加对象的 const 限定符。这对于某些 API 函数需要非常重要,尤其是在处理与 C 函数接口兼容的代码时。

5. 使用 static_cast<void*>

当你看到 static_cast<void*>(...) 这样的用法时,这是一种明确将某个特定类型的指针(例如 char*)转换为通用指针类型 void* 的方式。void* 可以指向任何类型的数据,因此在许多库(如 ASIO)中,要求传递的缓冲区是 void* 类型。

示例对比

std::unique_ptr<char[]> buffer(new char[10]); 
asio::const_buffer const_buf1 = asio::buffer(buffer.get(), 10); // 隐式转换 asio::const_buffer const_buf2 = asio::buffer(static_cast<void*>(buffer.get()), 10); // 显式转换

在这两个例子中,const_buf1const_buf2 实际上是等效的,但 const_buf1 更为简洁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值