我正在编写点对点(它不应该有服务器 - 这是一个任务)程序来交换文本消息。这是一个非常小的聊天。就是简单的消息,没有别的。这是我第一次练习 Boost::Asio,因此我有一些问题。
我的聊天应该是点对点的,正如我所说,并且应该使用 udp 协议。我认为,最好的方法是使用广播。第一个问题:我如何了解新的联系?
另一个问题是发送消息:我通过广播地址发送消息,然后它传播到本地网络中的所有计算机。是吗?
此代码发送消息并接收其返回。就像回声一样。是吗?
#include <iostream>
#include <boost/asio.hpp>
#include <boost/array.hpp>
int main()
{
try
{
namespace ip = boost::asio::ip;
boost::asio::io_service io_service;
ip::udp::socket socket(io_service,
ip::udp::endpoint(ip::udp::v4(), 1555));
socket.set_option(boost::asio::socket_base::broadcast(true));
ip::udp::endpoint broadcast_endpoint(ip::address_v4::broadcast(), 1555);
boost::array<char, 4> buffer1;
socket.send_to(boost::asio::buffer(buffer1), broadcast_endpoint);
ip::udp::endpoint sender_endpoint;
boost::array<char, 4> buffer2;
std::size_t bytes_transferred =
socket.receive_from(boost::asio::buffer(buffer2), sender_endpoint);
std::cout << "got " << bytes_transferred << " bytes." << std::endl;
}
catch (std::exception &e)
{
std::cerr << e.what();
}
system("PAUSE");
return 0;
}
在 Ubuntu 20.04.3 LTS 和 Boost.Asio 1.71 上测试。
通常这种任务是通过使用组播来完成的。广播会给网络带来太多负载。
基于 sender 和 receiver 示例,同时结合它们,您应该在多播地址上打开套接字,该地址代表“聊天室”,同时订阅该多播组以接收发送的消息来自其他聊天参与者。
#include <iostream>
#include <string>
#include <boost/asio.hpp>
constexpr std::uint16_t multicast_port = 30001;
class Peer {
public:
Peer(boost::asio::io_context& io_context,
const boost::asio::ip::address& chat_room,
const std::string& nickname)
: socket_(io_context)
, multicast_endpoint_(chat_room, multicast_port)
, nickname_(nickname)
{
boost::asio::ip::udp::endpoint listen_endpoint(chat_room, multicast_port);
socket_.open(listen_endpoint.protocol());
socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));
socket_.bind(listen_endpoint);
请注意,我们使用了reuse_address选项,因此您可以在本地测试此示例。
如果您想接收发送到多播组的消息,您必须订阅该多播组:
socket_.set_option(boost::asio::ip::multicast::join_group(chat_room));
当你问是否想了解新连接(虽然UDP是无连接协议)时,你可以发送多播欢迎消息:
auto welcome_message = std::string(nickname_ + " connected to the chat\n");
socket_.async_send_to(boost::asio::buffer(welcome_message), multicast_endpoint_,
[this](const boost::system::error_code& error_code, std::size_t bytes_sent){
if (!error_code.failed()){
std::cout << "Entered chat room successfully" << std::endl;
}
});
因此,现在我们必须建立两个循环:第一个循环将等待本地用户的输入,将其发送到多播组,然后等待另一个用户输入,而另一个循环将侦听套接字上传入的 UDP 数据报,打印数据报的接收到的每个数据报上的内容,然后返回到套接字侦听:
void do_receive(){
socket_.async_receive_from(boost::asio::buffer(receiving_buffer_), remote_endpoint_,
[this](const boost::system::error_code& error_code, std::size_t bytes_received){
if (!error_code.failed() && bytes_received > 0){
auto received_message_string = std::string(receiving_buffer_.begin(), receiving_buffer_.begin() + bytes_received);
// We don't want to receive the messages we produce
if (received_message_string.find(name_) != 0){
std::cout.write(receiving_buffer_.data(), bytes_received);
std::cout << std::flush;
}
do_receive();
}
});
}
void do_send(){
std::string nickname = nickname_;
std::string message;
std::getline(std::cin, message);
std::string buffer = name.append(": " + message);
socket_.async_send_to(boost::asio::buffer(buffer, maximum_message_size_), multicast_endpoint_,
[this, message](const boost::system::error_code& /*error_code*/, std::size_t bytes_sent){
std::cout << "You: " << message << std::endl;
do_send();
});
}
我们还在每个完成处理程序中调用相同的 IO 函数来实现看起来仍然像递归的循环效果。
现在,我们要做的就是在单独的线程中发布每个函数调用,因为 io_context.run() 调用会阻塞,否则我们的一个循环将阻塞另一个循环,因此我们在每个线程:
int main(int argc, char* argv[])
{
boost::asio::thread_pool thread_pool(2);
if(argc != 3){
std::cerr << "Usage: ./peer <your_nickname> <multicast_address>" << std::endl;
std::exit(1);
}
boost::asio::io_context io_context;
boost::asio::ip::address chat_room(boost::asio::ip::make_address(argv[2]));
Peer peer(io_context, chat_room, argv[1]);
boost::asio::post(thread_pool, [&]{
peer.do_receive();
io_context.run();
});
boost::asio::post(thread_pool, [&]{
peer.do_send();
io_context.run();
});
thread_pool.join();
return 0;
}
现在,如果您运行两个 Linux 终端,在每个终端中运行此程序,提供相同的多播地址和不同的昵称,您将能够在实例之间进行通信。
完整源代码可在此处获得。