网络编程(1)——同步读写api
2025-04-30 12:24:32 # C++ # 网络编程

一、day1

学习了服务器和客户端 socket 的建立、监听以及连接。

参考:

恋恋风辰官方博客

visual studio配置C++ boost库_哔哩哔哩_bilibili

1. socket的监听和连接

服务端
1)socket——创建socket对象。

2)bind——绑定本机ip+port。

3)listen——监听来电,若在监听到来电,则建立起连接。

4)accept——再创建一个socket对象给其收发消息。原因是现实中服务端都是面对多个客户端,那么为了区分各个客户端,则每个客户端都需再分配一个socket对象进行收发消息。

5)read、write——就是收发消息了。

对于客户端是这样的
客户端
1)socket——创建socket对象。

2)connect——根据服务端ip+port,发起连接请求。

3)write、read——建立连接后,就可发收消息了。

boost库网络编程的基本流程(阻塞)如下所示。

1.1 终端节点创建

终端节点代表一个网络通信的端点,由 ip 地址和端口号组成。该函数创建一个 TCP 客户端的终端节点。如果我们是客户端,我们可以通过对端的 ip 和端口构造一个 endpoint,用这个 endpoint和其通信。

客户端端点的建立:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在客户端创建一个TCP/IP的终端(endpoint),将IP地址和端口号组合成一个用于通信的终端
int client_end_point() {
std::string raw_ip_address = "127.4.8.1"; // 对端地址
unsigned short port_num = 3333; // 对端端口号
boost::system::error_code ec; // 错误码
// 使用 boost::asio 库的 from_string 方法,将字符串形式的 IP 地址转换为 asio 支持的 IP 地址格式。
// 如果转换失败,错误信息将存储在 ec 中。
boost::asio::ip::address ip_address = boost::asio::ip::address::from_string(raw_ip_address, ec);
if (ec.value() != 0) { // 转换失败
std::cout << "Failed to parse the IP address. Error code = " << ec.value() << " .Message is " << ec.message();
return ec.value();
}

// 创建一个 TCP 终端 ep,将 ip_address 和 port_num 绑定在一起。
boost::asio::ip::tcp::endpoint ep(ip_address, port_num);

return 0;
}

服务器端端点的建立:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在服务器端创建一个TCP/IP的终端(endpoint),使用IPv6地址的通配符::绑定本地的所有地址
int server_end_point() {
unsigned short port_num = 3333; // 定义服务器端使用的端口号 3333
/*
定义一个ip_address,使用boost::asio库的address_v6::any()方法创建一个IPv6地址的通配符,
这个地址可以绑定到本地的任何地址上。通配符地址 (:: in IPv6) 允许服务器绑定到所有的可用地址。
换句话说,服务器可以监听来自任何接口(有线连接、无线连接、虚拟接口等)的传入连接。
服务器不需要指定一个具体的 IP 地址来监听,而是监听所有可用的 IP 地址。
*/
boost::asio::ip::address ip_address = boost::asio::ip::address_v6::any();
// 创建一个 TCP/IP终端ep,将ip_address和port_num绑定在一起, 服务器将在所有的IPv6地址上监听端口3333的连接
boost::asio::ip::tcp::endpoint ep(ip_address, port_num);
return 0;
}

1.2 建立socket

客户端 socket_v4 的建立:

上下文 iocontext->选择协议->生成socket->打开socket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 客户端socket_v4的建立
int create_tcp_socket() {
boost::asio::io_context ioc; // 声明用于管理异步操作的I/O上下文
// 声明socket,使用前面定义的io_context(ioc)来管理其I/O操作
boost::asio::ip::tcp::socket sock(ioc);

boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v4(); // 声明ipv4的协议
boost::system::error_code ec; // 错误码
sock.open(protocol, ec); // 判断是否创建成功
if (ec.value() != 0) { // ec不为0时创建失败
std::cout << "Failed to parse the IP address. Error code = "
<< ec.value() << " .Message is " << ec.message();
return ec.value();
}

return 0;
}

上述 socket 只是通信的 socket,需在服务端建立 acceptor 的 socket,用于接收新的连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建一个服务器用于接受客户端连接的TCP监听器
int create_acceptor_socket() {
boost::asio::io_context ios;
boost::asio::ip::tcp::acceptor acceptor(ios); // 新版本截止到此已经成功,老版本还需下面的操作

boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v4(); // 声明ipv4的协议
boost::system::error_code ec; // 错误码
acceptor.open(protocol, ec);
if (ec.value() != 0) { // ec不为0时创建失败
std::cout << "Failed to parse the IP address. Error code = "
<< ec.value() << " .Message is " << ec.message();
return ec.value();
}

return 0;

// 生成一个acceptor,指定tcpv4协议,并接收所有发向3333端口的信息(新版本)
// 创建 acceptor 并绑定它到一个特定的端点(IP 地址和端口)
// boost::asio::ip::tcp::acceptor a(ios, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 3333));
}

1.3 绑定socket

对于acceptor类型的socket,服务器要将其绑定到指定的端点,所有连接这个端点的连接都可以被接收到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建一个服务器TCP连接接收器(acceptor),将其绑定到一个指定的端点(IP 地址和端口)
int bind_acceptor_socket() {
unsigned short port_num = 3333;
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address_v4::any(), port_num);
boost::asio::io_context ios;
boost::asio::ip::tcp::acceptor acceptor(ios, ep.protocol());
boost::system::error_code ec; // 将接收器绑定到之前定义的端点 ep(包括通配符 IP 地址和端口 3333)
acceptor.bind(ep, ec);
if (ec.value() != 0) { // ec不为0时创建失败
std::cout << "Failed to parse the IP address. Error code = "
<< ec.value() << " .Message is " << ec.message();
return ec.value();
}
return 0;
}

1.4 连接指定的端点

作为客户端可以连接服务器指定的端点

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
// 创建TCP客户端socket,并尝试连接到指定的服务器端点(服务器的 IP 地址和端口)
int connect_to_end() {
std::string raw_ip_address = "192.168.1.124"; // 服务器ip
unsigned short port_num = 3333; // 服务器端口号
try {
// 生成一个端点,在客户端是连接,在服务器端是绑定
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
boost::asio::io_context ios;
// socket对象连接到指定的服务器端点
boost::asio::ip::tcp::socket sock(ios, ep.protocol());
// 这里客户端是连接到指定端点,但在服务器中是绑定
sock.connect(ep);
}
catch (boost::system::system_error& e) {
std::cout << "Error occured! Error code = " << e.code()
<< ". Message: " << e.what();
return e.code().value();
}

return 0;
}

// 通过域名解析将主机名(服务器域名)转换为 IP 地址,然后尝试使用 TCP 套接字与服务器建立连接
int dns_connect_to_end() {
std::string host = "llfc.club"; // 服务器域名
std::string port_num = "3333"; // 服务器端口号
boost::asio::io_context ios; // 上下文服务
/*
boost::asio::ip::tcp::resolver::query 用于指定 DNS 解析的查询参数。
resolver_query 是一个查询对象,包含了主机名(host)和端口号(port_num)。
boost::asio::ip::tcp::resolver::query::numeric_service 指定端口号是一个数字,
而不是服务名称(例如,"http"、"ftp"等
*/
boost::asio::ip::tcp::resolver::query resolver_query(host, port_num,
boost::asio::ip::tcp::resolver::query::numeric_service); // 创建域名查询对象
/*
boost::asio::ip::tcp::resolver 是一个 DNS 解析器类,用于将域名解析为 IP 地址。
resolver 是一个解析器对象,使用 io_context(ios)进行初始化。
该解析器将用于执行 DNS 查询并获取服务器的 IP 地址。
*/
boost::asio::ip::tcp::resolver resolver(ios); // 创建解析器
try {
// 使用resolver.resolve(resolver_query)解析域名,返回一个迭代器it,
// 指向与主机名和端口号匹配的端点列表
boost::asio::ip::tcp::resolver::iterator it = resolver.resolve(resolver_query);
boost::asio::ip::tcp::socket sock(ios);
// 使用解析到的端点列表连接到服务器。它会自动尝试列表中的每一个端点,
// 直到成功连接或所有端点都尝试完毕
boost::asio::connect(sock, it);
}
catch(boost::system::system_error& e){
std::cout << "Error occured! Error code = " << e.code()
<< ". Message: " << e.what();
return e.code().value();
}
return 0;
}

1.5 服务器接收连接

当有客户端连接时,服务器需要接收连接

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
/ 创建一个 TCP 服务器端的接收器(acceptor),绑定到指定的 IP 地址和端口,并等待客户端连接。
// 一旦有新的客户端连接请求到达,服务器将接受这个连接
int accept_new_connection() {
const int BACKLOG_SIZE = 30; // 监听队列的大小,服务器来不急监听时将链接放入队列
unsigned short port_num = 3333;
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address_v4::any(), port_num); // 端点
boost::asio::io_context ios;
try {
boost::asio::ip::tcp::acceptor acceptor(ios, ep.protocol());
acceptor.bind(ep);
acceptor.listen(BACKLOG_SIZE);
// 模拟创建了一个 TCP socket 对象,用于表示与客户端的连接
boost::asio::ip::tcp::socket sock(ios);
// 阻塞式地等待并接受一个新的连接请求,并将新的连接绑定到 sock。一旦有新的客户端连接请求,
// acceptor 会接受它,并将新连接绑定到 sock
acceptor.accept(sock);
}
catch(boost::system::system_error& e){
std::cout << "Error occured! Error code = " << e.code()
<< ". Message: " << e.what();

return e.code().value();
}
return 0;
}

第一日总结:

1.网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。利用三元组(ip地址,协议,端口)就可以标识网络的进程,网络中的进程通信可以利用这个标志与其它进程进行交互。

2.什么是socket? socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。

3.acceptor和socket有什么区别,分别有什么作用?

1)acceptor

作用: acceptor 是一个服务器端的对象,用于接收来自客户端的连接请求。

功能:

  • 创建: acceptor 对象通常在服务器端创建,它的作用是等待并接收客户端的连接请求。
  • 绑定: 通过 acceptor.bind(endpoint) 将其绑定到特定的 IP 地址和端口号,指定服务器要监听的地址。
  • 监听: 通过 acceptor.listen(backlog_size) 启动监听,backlog_size 是一个整数,表示连接请求队列的大小。
  • 接受连接: 通过 acceptor.accept(socket) 接受一个连接请求,并将连接绑定到一个新的 socket 对象上,socket 用于后续的数据传输。原因是现实中服务端都是面对多个客户端,那么为了区分各个客户端,则每个客户端都需再分配一个socket对象进行收发消息
1
2
3
4
5
boost::asio::ip::tcp::acceptor acceptor(io_context, endpoint);
acceptor.bind(endpoint);
acceptor.listen(backlog_size);
boost::asio::ip::tcp::socket socket(io_context);
acceptor.accept(socket);

2)socket

作用: socket 是用于实际数据传输的对象。它代表了一个网络连接的端点。

功能:

  • 创建: socket 对象在客户端和服务器端都可以创建。客户端用来发起连接,服务器端用来处理接收到的连接。
  • 连接: 在客户端,socket 通过 socket.connect(endpoint) 连接到服务器端的 acceptor
  • 接收和发送数据: 一旦建立连接,socket 可以用来发送和接收数据,使用方法如 socket.send()socket.receive()
  • 关闭连接: 通过 socket.close() 关闭连接。
1
2
3
boost::asio::ip::tcp::socket socket(io_context);
socket.connect(endpoint);
boost::asio::write(socket, boost::asio::buffer("Hello, World!"));

二、day2

2.1 buffer

任何网络库都有提供buffer的数据结构,所谓buffer就是接收和发送数据时缓存数据的结构。

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

我们可以理解为MutableBufferSequence的数据结构为std::vector<asio::mutable_buffer>

img

buffer的结构

每个 vector 存储的都是 mutable_buffer的地址,每个mutable_buffer的第一个字节表示数据的长度,后面跟着数据内容。

这么复杂的结构交给用户使用并不合适,所以asio提出了buffer()函数,该函数接收多种形式的*字节流*,该函数返回asio::mutable_buffers_1 或者asio::const_buffers_1结构的对象。如果传递给buffer()的参数是一个只读类型,则函数返回asio::const_buffers_1 类型对象。如果传递给buffer()的参数是一个可写类型,则返回asio::mutable_buffers_1 类型对象。

asio::const_buffers_1asio::mutable_buffers_1asio::mutable_bufferasio::const_buffer的适配器,提供了符合MutableBufferSequenceConstBufferSequence概念的接口,所以他们可以作为boost::asio的api函数的参数使用。

简单概括一下,我们可以用buffer()函数生成我们要用的缓存存储数据。比如boost的发送接口send要求的参数为ConstBufferSequence类型

1
2
template<typename ConstBufferSequence>
std::size_t send(const ConstBufferSequence & buffers);

如果将“Hello World”转换成该类型:

1
2
3
4
5
6
7
8
9
10
// ConstBufferSequence类型构造函数1
void use_const_buffer() {
std::string buf = "Hello World!";
// 首先将buf转换为c风格字符串并获取首地址,然后将长度传至asio_buf函数,构造const_buffer
boost::asio::const_buffer asio_buf(buf.c_str(), buf.length());
// 构造ConstBufferSequence类型
std::vector< boost::asio::const_buffer> buffers_sequence;
// 将构造的const_buffer放入const_buffer容器中
buffers_sequence.push_back(asio_buf);
}

实际中我们使用并没有这么复杂,简化上述函数,用buffer函数将其转化为send需要的参数类型,output_buf可以直接传递给send接口使用,充当ConstBufferSequence类型:

1
2
3
4
5
// ConstBufferSequence类型构造函数2
void use_buffer_str() {
// buffer函数返回asio::mutable_buffers_1 o或者asio::const_buffers_1结构的对象
boost::asio::const_buffers_1 optput_buf = boost::asio::buffer("hello world");
}

也可以将数组转换为send需要的类型(ConstBufferSequence):

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用动态分配的字符数组作为缓冲区,并使用Boost.Asio库将其包装成一个可用于异步 
// I / O 操作的缓冲区对象
void use_buffer_array() {
// 使用无符号整数类型size_t作为缓冲区大小
const size_t BUF_SIZE_BYTES = 20;
// std::unique_ptr<char[]> 是一个智能指针,用于管理动态分配的数组的生命周期。它在不再需要时自动释放内存,
// 避免了手动 delete[] 的需要
std::unique_ptr<char[]> buf(new char[BUF_SIZE_BYTES]);
// boost::asio::buffer 函数将一个原始指针(在此例中是一个指向字符数组的指针)和其大小(以字节为单位)包装
// 成一个可用于异步 I/O 操作的 boost::asio::mutable_buffer 对象
// buf.get() 返回指向智能指针 buf 所管理的字符数组的原始指针,并将其转换为void*类型
auto input_buf = boost::asio::buffer(static_cast<void*>(buf.get()), BUF_SIZE_BYTES);
}

对于流式操作,我们可以用streambuf,将输入输出流和streambuf绑定,可以实现流式输入和输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void use_stream_buffer() {
asio::streambuf buf;
std::ostream output(&buf);
// Writing the message to the stream-based buffer.
output << "Message1\nMessage2";
// Now we want to read all data from a streambuf
// until '\n' delimiter.
// Instantiate an input stream which uses our
// stream buffer.
std::istream input(&buf);
// We'll read data into this string.
std::string message1;
std::getline(input, message1);
// Now message1 string contains 'Message1'.
}

2.2 同步写write_some

boost::asio提供了几种同步写的api,write_some可以每次向指定的空间(socket)写入固定的字节数,如果写缓冲区满了,就只写一部分,返回写入的字节数。举个栗子,用户buffer发送缓冲区长度为5,TCP发送缓冲区长度为12,虽然下面的write_some一开始希望发送buf.length() - total_bytes_written = 12 - 0=12个长度,但是用户buffer只有5个,所以只能先发送5个,剩下的7个循环继续发送:

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
// 写函数
void write_to_socket(boost::asio::ip::tcp::socket& sock) {
std::string buf = "Hello World!";
std::size_t total_bytes_wtitten = 0; // 已发送的字节数
// 循环发送
// write_some 返回每次写入的字节数, 该函数的输入类型是ConstBufferSequence
/*
持续写入:使用 while 循环持续尝试写入数据,直到所有数据都被写入完毕。
部分写入:sock.write_some 尝试将部分数据写入到 sock 中,这个方法不是一次性写入所有数据,
而是可能写入一部分,因此需要用 total_bytes_written 变量来跟踪已写入的字节数。
循环写入:每次写入后,更新 total_bytes_written,使写入指针向前移动,直到所有数据被写入。
*/
while (total_bytes_wtitten != buf.length()) {
// 举个栗子,用户buffer发送缓冲区长度为5,TCP发送缓冲区长度为12,虽然下面的write_some一开始希望
// 发送buf.length() - total_bytes_written = 12 - 0=12个长度,但是用户buffer只有5个,所以只能
// 先发送5个,剩下的7个循环继续发送
total_bytes_wtitten += sock.write_some(boost::asio::buffer(buf.c_str() + total_bytes_wtitten,
buf.length() - total_bytes_wtitten));
}
}

// 客户端发送函数
int send_data_by_write_some() {
std::string raw_ip_address = "127.0.0.1";
unsigned short port_num = 3333;
try {
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
//boost::asio::io_service ios
boost::asio::io_context ioc;
boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
sock.connect(ep);
// 发送信息至ep端点
write_to_socket(sock);
}
catch(boost::system::system_error& e){
std::cout << "Error occured! Error code = " << e.code()
<< ". Message: " << e.what();

return e.code().value();
}
}

2.3 同步写send

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

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
int send_data_by_send() {
std::string raw_ip_address = "127.0.0.1";
unsigned short port_num = 3333;
std::string buf = "Hello World!";
try {
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
//boost::asio::io_service ios
boost::asio::io_context ioc;
boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
sock.connect(ep);
// 不使用write_to_socket(sock)函数,使用sock的send函数
// 将buf长度为length的内容发送至socket,返回size_t类型,如果成功发送,
// bytes_sent 可能等于 buf.length(),即所有数据都成功发送;如果返回值小于
// buf.length(),表示只有部分数据被发送;如果发送失败,send 将抛出一个
// boost::system::system_error 异常。
// send 是一个同步操作,所以它会阻塞直到所有数据被成功发送或发生错误,
// 这意味着在 send 操作完成之前,程序将不会继续执行后面的代码。
int send_length = sock.send(boost::asio::buffer(buf.c_str(), buf.length()));
// send_length < 0:出现系统错误
// send_length == 0:对端关闭
// send_length = buf.length() > 0:发送成功
if (send_length <= 0) // 发送失败
return 0;
}
catch (boost::system::system_error& e) {
std::cout << "Error occured! Error code = " << e.code()
<< ". Message: " << e.what();

return e.code().value();
}
}

2.4 同步写write

类似send方法,asio还提供了一个write函数,可以一次性将所有数据发送给对端,如果发送缓冲区满了则阻塞,直到发送缓冲区可用,将数据发送完成。

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
int send_data_by_write() {
std::string raw_ip_address = "127.0.0.1";
unsigned short port_num = 3333;
std::string buf = "Hello World!";
try {
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
//boost::asio::io_service ios
boost::asio::io_context ioc;
boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
sock.connect(ep);
// 和sock.send()函数类似
int send_length = boost::asio::write(sock, boost::asio::buffer(buf.c_str(), buf.length()));
// send_length < 0:出现系统错误
// send_length == 0:对端关闭
// send_length = buf.length() > 0:发送成功
if (send_length <= 0) // 发送失败
return 0;
}
catch (boost::system::system_error& e) {
std::cout << "Error occured! Error code = " << e.code()
<< ". Message: " << e.what();

return e.code().value();
}
}

2.5 同步读read_some

同步读和同步写类似,提供了读取指定字节数的接口read_some,但read_some可能会被多次调用,比较麻烦。

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
// 使用 Boost.Asio 库从 TCP 套接字 sock 读取固定大小(MESSAGE_SIZE)的数据,
// 并返回一个 std::string 对象,表示读取到的数据
std::string read_from_socket(boost::asio::ip::tcp::socket& sock) {
const unsigned char MESSAGE_SIZE = 7;
char buf[MESSAGE_SIZE];
std::size_t total_byter_read = 0;
while (total_byter_read != MESSAGE_SIZE) {
total_byter_read += sock.read_some(boost::asio::buffer(buf + total_byter_read, MESSAGE_SIZE - total_byter_read));
}

return std::string(buf, total_byter_read);
}

// 从一个指定的 TCP 服务器(在 127.0.0.1:3333 上)建立连接,并调用 read_from_socket
// 函数来同步读取数据
int read_data_by_read_some() {
std::string raw_ip_address = "127.0.0.1";
unsigned short port_num = 3333;
try {
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
boost::asio::io_context ioc;
boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
sock.connect(ep);
// 调用同步读函数,读取来自连接套接字的数据
read_from_socket(sock); // 同步读
}
catch (boost::system::system_error& e) {
std::cout << "Error occured! Error code = " << e.code()
<< ". Message: " << e.what();

return e.code().value();
}
}

2.6 同步读receive

可以一次性同步接收对方发送的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int read_data_by_receive() {
std::string raw_ip_address = "127.0.0.1";
unsigned short port_num = 3333;
try {
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
boost::asio::io_context ioc;
boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
sock.connect(ep);
const unsigned char BUFF_SIZE = 7;
char buffer_receive[BUFF_SIZE];
// 接收数据,并获取接收到的字节数
int receive_length = sock.receive(boost::asio::buffer(buffer_receive, BUFF_SIZE));
if (receive_length <= 0) {
std::cout << "receive failed " << std::endl;
}

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

return e.code().value();
}
}

2.7 同步读read

可以一次性同步读取对方发送的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int read_data_by_read() {
std::string raw_ip_address = "127.0.0.1";
unsigned short port_num = 3333;
try {
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
boost::asio::io_context ioc;
boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
sock.connect(ep);
const unsigned char BUFF_SIZE = 7;
char buffer_receive[BUFF_SIZE];
int receive_length = boost::asio::read(sock, boost::asio::buffer(buffer_receive, BUFF_SIZE));
if (receive_length <= 0) {
std::cout << "receive failed " << std::endl;
}

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

return e.code().value();
}
}

2.8 读取直到指定字符

我们可以一直读取,直到读取指定字符结束:

1
2
3
4
5
6
7
8
9
10
11
12
13
std::string  read_data_by_until(boost::asio::ip::tcp::socket& sock) {
boost::asio::streambuf buf;
// Synchronously read data from the socket until
// '\n' symbol is encountered.
boost::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;
}

总结:

1.c++中,sock.receive和boost::asio::read有什么区别?

同:sock.receiveboost::asio::read 都用于网络编程中的数据接收操作

不同:1)sock.receive 相对比较简单,只是接收数据,并返回接收的数据长度或错误代码。它不具备高级功能,如超时处理、数据分片重组、异步操作等。boost::asio::read 提供了更高层次的功能封装。例如,它可以在接收到特定数量的字节后才返回结果,还可以支持异步操作 (async_read),使得程序能够同时处理多个 I/O 操作。2)sock.receive 本身是一个阻塞调用,如果需要非阻塞行为,需要使用特定的套接字选项来配置。boost::asio::read 支持同步和异步两种方式,通过 boost::asio::io_context 及回调机制,可以轻松实现异步 I/O 操作。