使用TCP实现daytime协议
同步的daytime客户端
利用asio实现一个TCP的客户端应用。
引入头文件
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
这个应用程序需要访问daytime服务器,所以需要指定服务器。
using boost::asio::ip::tcp;
int main(int argc,char *argv[]) {
try {
if(argc != 2) {
std::cerr << "Usage: client <host>" << std::endl;
return 1;
}
}
}
使用命令行的目的地址,进行解析后再建立连接。
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
tcp::resolver::results_type endpoints =
resolver.resolve(argv[1], "daytime");
tcp::socket socket(io_context);
//会自动遍历并尝试连接,直到成功
boost::asio::connect(socket, endpoints);
连接建立后,就可以从daytime服务器读取响应信息。
我们使用boost::array
来保存接收的数据。boost::asio::buffer()
函数自动确定数组的大小以帮助防止缓冲区溢出。我们可以使用 char []
或 std::vector
来代替 boost::array
。
for (;;)
{
boost::array<char, 128> buf;
boost::system::error_code error;
size_t len = socket.read_some(boost::asio::buffer(buf), error);
当服务器关闭连接时,ip::tcp::socket::read_some()
函数将退出并出现 boost::asio::error::eof
错误,这就是退出循环的方式。
if (error == boost::asio::error::eof)
break; // Connection closed cleanly by peer.
else if (error)
throw boost::system::system_error(error); // Some other error.
std::cout.write(buf.data(), len);
}
最后,处理抛出来的异常。
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
完整代码如下:
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: client <host>" << std::endl;
return 1;
}
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
tcp::resolver::results_type endpoints =
resolver.resolve(argv[1], "daytime");
tcp::socket socket(io_context);
boost::asio::connect(socket, endpoints);
for (;;)
{
boost::array<char, 128> buf;
boost::system::error_code error;
size_t len = socket.read_some(boost::asio::buffer(buf), error);
if (error == boost::asio::error::eof)
break; // Connection closed cleanly by peer.
else if (error)
throw boost::system::system_error(error); // Some other error.
std::cout.write(buf.data(), len);
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
同步的daytime服务端
本节显示了如何使用asio实现一个TCP服务器应用。
首先引入头文件
#include <ctime>
#include <iostream>
#include <string>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
我们定义了函数make_daytime_string()
来生成返回给客户端的字符串。该函数可以被我们所有的daytime服务器应用重用。
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
int main()
{
try
{
boost::asio::io_context io_context;
创建一个acceptor对象来监听连接。接受连接后,将时间字符串发送到客户端。
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 13));
for (;;)
{
tcp::socket socket(io_context);
acceptor.accept(socket);
//访问的时间
std::string message = make_daytime_string();
//发送到客户端
boost::system::error_code ignored_error;
boost::asio::write(socket, boost::asio::buffer(message), ignored_error);
}
}
最后,处理异常
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
完整代码如下:
#include <ctime>
#include <iostream>
#include <string>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
int main()
{
try
{
boost::asio::io_context io_context;
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 13));
for (;;)
{
tcp::socket socket(io_context);
acceptor.accept(socket);
std::string message = make_daytime_string();
boost::system::error_code ignored_error;
boost::asio::write(socket, boost::asio::buffer(message), ignored_error);
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
异步的daytime服务端
我们创建一个服务器对象来接受传入的客户端连接。
在主函数中只需要启动服务器即可。
int main() {
try{
boost::asio::io_context ioc;
tcp_server server(ioc);
ioc.run();
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
关键在于tcp_server服务器类的实现,作为服务器,一定要有一个接收连接的接收器,然后还有对接收的连接的处理方法。
构造函数在TCP的端口13初始化接收器。
class tcp_server
{
public:
tcp_server(boost::asio::io_context& io_context)
: io_context_(io_context),
acceptor_(io_context, tcp::endpoint(tcp::v4(), 13))
{
start_accept();
}
private:
start_accept()
创建一个套接字并使用一个异步accept操作等待一个新连接。
void start_accept()
{
tcp_connection::pointer new_connection =
tcp_connection::create(io_context_);
acceptor_.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
当由 start_accept()
发起的异步接受操作完成时,将调用函数 handle_accept()
。 它为客户端请求提供服务,然后调用 start_accept()
以启动下一个接受操作。
void handle_accept(tcp_connection::pointer new_connection,
const boost::system::error_code& error)
{
if (!error)
{
new_connection->start();
}
start_accept();
}
tcp_connection类
我们将使用 shared_ptr
和 enable_shared_from_this
因为我们想让 tcp_connection
对象保持活动状态,只要有操作引用它。
class tcp_connection
: public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_context& io_context)
{
return pointer(new tcp_connection(io_context));
}
tcp::socket& socket()
{
return socket_;
}
tcp_connection对象就是我们用来封装对连接的操作的。
在函数 start() 中,我们调用 boost::asio::async_write()
将数据提供给客户端。 请注意,我们使用的是 boost::asio::async_write()
,而不是 ip::tcp::socket::async_write_some()
,以确保发送整个数据块。
void start()
{
message_ = make_daytime_string();
boost::asio::async_write(socket_, boost::asio::buffer(message_),
boost::bind(&tcp_connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
此客户端连接的任何进一步操作现在由 handle_write() 负责。
}
private:
tcp_connection(boost::asio::io_context& io_context)
: socket_(io_context)
{
}
void handle_write(const boost::system::error_code& /*error*/,
size_t /*bytes_transferred*/)
{
}
tcp::socket socket_;
std::string message_;
};
这里使用了智能指针,意在让TcpConnection对象执行完后自动释放,对于异步操作是有等待时间的,故为了让连接不会在等待时间内释放,那么我们就要将智能指针同样传递给回调参数。
完整代码如下:
#include <ctime>
#include <iostream>
#include <string>
#include <boost/bind/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
class tcp_connection
: public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_context& io_context)
{
return pointer(new tcp_connection(io_context));
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
message_ = make_daytime_string();
boost::asio::async_write(socket_, boost::asio::buffer(message_),
boost::bind(&tcp_connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
tcp_connection(boost::asio::io_context& io_context)
: socket_(io_context)
{
}
void handle_write(const boost::system::error_code& /*error*/,
size_t /*bytes_transferred*/)
{
}
tcp::socket socket_;
std::string message_;
};
class tcp_server
{
public:
tcp_server(boost::asio::io_context& io_context)
: io_context_(io_context),
acceptor_(io_context, tcp::endpoint(tcp::v4(), 13))
{
start_accept();
}
private:
void start_accept()
{
tcp_connection::pointer new_connection =
tcp_connection::create(io_context_);
acceptor_.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
void handle_accept(tcp_connection::pointer new_connection,
const boost::system::error_code& error)
{
if (!error)
{
new_connection->start();
}
start_accept();
}
boost::asio::io_context& io_context_;
tcp::acceptor acceptor_;
};
int main()
{
try
{
boost::asio::io_context io_context;
tcp_server server(io_context);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
使用UDP实现
同步的daytime客户端
我们使用 ip::udp::resolver
对象根据主机名和服务名找到要使用的正确远程端点。 该查询被ip::udp::v4()
参数限制为仅返回 IPv4 端点。
udp::resolver resolver(io_context);
udp::endpoint receiver_endpoint =
*resolver.resolve(udp::v4(), argv[1], "daytime").begin();
如果 ip::udp::resolver::resolve()
函数没有失败,它保证至少返回列表中的一个端点。 这意味着直接解引用的返回值是安全的。
由于 UDP 是面向数据报的,因此我们不会使用流套接字。 创建一个 ip::udp::socket 并启动与远程端点的联系。
udp::socket socket(io_context);
socket.open(udp::v4());
boost::array<char, 1> send_buf = {{ 0 }};
socket.send_to(boost::asio::buffer(send_buf), receiver_endpoint);
与TCP不同的是,我们UDP客户端必须要先发送数据过去,服务端才能收到客户端的信息。
现在我们需要准备好接受服务器发回给我们的任何内容。 我们这边接收服务器响应的端点将由 ip::udp::socket::receive_from()
初始化。
boost::array<char, 128> recv_buf;
udp::endpoint sender_endpoint;
size_t len = socket.receive_from(
boost::asio::buffer(recv_buf), sender_endpoint);
std::cout.write(recv_buf.data(), len);
}
完整代码如下:
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::udp;
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: client <host>" << std::endl;
return 1;
}
boost::asio::io_context io_context;
udp::resolver resolver(io_context);
udp::endpoint receiver_endpoint =
*resolver.resolve(udp::v4(), argv[1], "daytime").begin();
udp::socket socket(io_context);
socket.open(udp::v4());
boost::array<char, 1> send_buf = {{ 0 }};
socket.send_to(boost::asio::buffer(send_buf), receiver_endpoint);
boost::array<char, 128> recv_buf;
udp::endpoint sender_endpoint;
size_t len = socket.receive_from(
boost::asio::buffer(recv_buf), sender_endpoint);
std::cout.write(recv_buf.data(), len);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
同步的daytime服务器
创建一个UDP的套接字对象用来接收UDP端口13上的请求。
每接收到一个数据报就意味着客户端的一次请求,我们要将响应发送过去。
完整代码如下:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/array.hpp>
using boost::asio::ip::udp;
int main(int argc, char* argv[]) {
try {
if(argc != 2) {
std::cerr << "Usage client <host> " << std::endl;
return 1;
}
boost::asio::io_context ioc;
udp::resolver resolver(ioc);
//解析出端点,我们只使用一个ipv4的
udp::endpoint endpoint = *resolver.resolve(udp::v4(),argv[1],"daytime").begin();
//创建UDP套接字
udp::socket socket(ioc);
socket.open(udp::v4());
//向服务端发送数据,然后接受响应并打印出来
boost::array<char,1> send_buf = {0};
socket.send_to(boost::asio::buffer(send_buf),endpoint);
boost::array<char,128> recv_buf;
udp::endpoint send_endpoint;
size_t len = socket.receive_from(boost::asio::buffer(recv_buf),send_endpoint);
std::cout.write(recv_buf.data(),len);
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
异步的daytime服务器
通过一个服务器对象来接收客户端请求
int main() {
try {
boost::asio::io_context ioc;
UdpServer server(ioc);
ioc.run();
} catch(const std::exception& e) {
std::cerr << e.what() << '
';
}
return 0;
}
服务器中启动接收数据
void start_receive() {
boost::array<char,1> recv_buf;
socket_.async_receive_from(boost::asio::buffer(recv_buf),
remote_endpoit_,
boost::bind(&UdpServer::handle_receive,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
接收完毕后,开始向对端发送响应,处理结束后等待下一个请求。
void handle_receive(const boost::system::error_code& error,std::size_t len) {
std::string message = make_daytime_string();
socket_.async_send_to(boost::asio::buffer(message),
remote_endpoit_,
boost::bind(&UdpServer::handle_send,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
start_receive();
}
完整代码如下:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <ctime>
#include <string>
using boost::asio::ip::udp;
std::string make_daytime_string() {
time_t now = time(0);
return ctime(&now);
}
class UdpServer {
public:
UdpServer(boost::asio::io_context& ioc) : socket_(ioc,udp::endpoint(udp::v4(),13)) {
start_receive();
}
private:
void start_receive() {
boost::array<char,1> recv_buf;
socket_.async_receive_from(boost::asio::buffer(recv_buf),
remote_endpoit_,
boost::bind(&UdpServer::handle_receive,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_receive(const boost::system::error_code& error,std::size_t len) {
std::string message = make_daytime_string();
socket_.async_send_to(boost::asio::buffer(message),
remote_endpoit_,
boost::bind(&UdpServer::handle_send,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
start_receive();
}
void handle_send(const boost::system::error_code& ,std::size_t len) {
}
udp::socket socket_;
udp::endpoint remote_endpoit_;
};
int main() {
try {
boost::asio::io_context ioc;
UdpServer server(ioc);
ioc.run();
} catch(const std::exception& e) {
std::cerr << e.what() << '
';
}
return 0;
}
TCP/UDP结合的异步服务器
我们将刚才的两个TCP和UDP异步服务器结合到一个服务器应用中。
其实就是在主函数中启动两个服务器而已,其他代码都不需要更改,直接就可以使用了。
int main()
{
try
{
boost::asio::io_context io_context;
tcp_server server1(io_context);
udp_server server2(io_context);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}