非boost下的standalone模式下asio使用,最近项目需要,学习了下asio库,发现网上给出的基本上都是基于boost的asio或者二次封装后的,对于原生的asio的讲解比较少,最近学习后,总结了一些资料,可供参考,主要是tcp,udp模式下的同步异步读写功能。以下时总结的一些内容,主要是源码(基于vs2017开发的,复制后可以直接编译通过),废话不多说,直接上源码(新手上路,如有疏漏,请见谅)。
- asio网络库TCP同步(sync)模式
1.1.服务端(server)
a.通过tcp::acceptor类创建一个tcp server对象,并绑定端口(也可以不在构造器中自动绑定,而通过bind函数手动绑定);
b.通过accept函数获取远端连接;
c.通过远端连接的write_some函数将数据发往客户端
服务端demo
首先要将asio-1.12.2源码复制到源文件的文件夹中,如果用的是vs2017开发,在属性-VC++中,添加asio的include路径,./asio-1.12.2/include;如果用的是Qt Creator开发,那么在pro文件中添加,INCLUDEPATH += asio-1.12.2/include DEFINES+=ASIO_STANDALONE,以下demo是vs2017下开发的,源码如下:
#include <iostream>
#include <chrono>
#ifdef _WIN32
#define _WIN32_WINNT 0x0A00
#endif // _WIN32
#define ASIO_STANDALONE
#include <asio.hpp>
int main(int argc, char* argv[])
{
asio::io_service iosev;
asio::ip::tcp::acceptor acceptor(iosev, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 1000));
for (;;)
{
// 创建socket对象
asio::ip::tcp::socket socket(iosev);
// 等待,直到用客户端连接进来
acceptor.accept(socket);
// 显示连接进来的客户端
std::cout << socket.remote_endpoint().address() << std::endl;
// 向客户端发送hello world!
asio::error_code ec;
std::string str = "hello world!";
socket.write_some(asio::buffer(str,str.size()), ec);
// 如果出错,打印出错信息,ec=0代表写入成功了,其他代表失败
if (ec)
{
std::cout << ec.message() << std::endl;
break;
}
else
{
std::cout << "jin ru else" << std::endl;
//using namespace std::chrono_literals;
//std::this_thread::sleep_for(2000ms);
socket.wait(socket.wait_read);
std::vector<char> vBuffer;
vBuffer.resize(128);
asio::error_code error;
socket.read_some(asio::buffer(vBuffer.data(), vBuffer.size()), error);
if (error == asio::error::eof)
{
break;
}
else if (error)
{
std::cout << error.message().data() << std::endl;
}
else
{
std::cout << "123 == " << vBuffer.data() << std::endl;
}
}
// 与当前客户交互完成后循环继续等待下一客户连接
}
return 0;
}
-
- 客户端(client)
- 通过tcp::socket类定义一个tcp client对象socket;
- 通过connect函数连接服务器,打开socket连接;
- 通过read_some函数来读数据,另外,还可以通过write_some来写数据,通过close来关闭socket连接(这里是通过释放socket对象隐式释放连接)。
源码demo如下:
#include <iostream>
#ifdef _WIN32
#define _WIN32_WINNT 0x0A00
#endif // _WIN32
#define ASIO_STANDALONE
#include <asio.hpp>
using namespace std;
using asio::ip::tcp;
int main(int argc, char* argv[])
{
try
{
// 通过tcp::socket类定义一个tcp client对象socket
asio::io_service io;
tcp::socket socket(io);
// 通过connect函数连接服务器,打开socket连接
tcp::endpoint end_point(asio::ip::address::from_string("127.0.0.1"), 1000);
socket.connect(end_point);
for (;;)
{
std::vector<char> vBuffer;
vBuffer.resize(128);
asio::error_code error;
// 通过read_some函数来读数据
size_t len = socket.read_some(asio::buffer(vBuffer), error);
if (error == asio::error::eof)
{
break;
}
else if (error)
{
std::cout << error.message().data() << std::endl;
}
else
{
// 回复给服务端一个,hello too
std::string str = "hello too!";
socket.write_some(asio::buffer(str, str.size()), error);
}
cout << vBuffer.data() << endl;
}
}
catch (const std::exception& e)
{
cout << e.what() << endl;
}
return 0;
}
- asio网络库TCP异步(async)模式
2.1.服务端(server)
异步服务器的编写,最核心的一个函数就是acceptor(asio::ip::tcp::acceptor),监听来自客户端的socket连接,如果监听到socket连接,就调用read(socket.async_read_some)读取socket中的数据,接收完毕数据后,可以通过write(asio::async_write)函数来给客户端回复需要的数据。整个异步过程,最核心的就是回调函数,在read,write中可以通过匿名函数,或者传入函数指针的方式进行数据的读写。具体代码如下:
Session.h:服务器的读写操作都在这里
AsyncServer.h:acceptor的实现
asyncServerMain.cpp:测试代码
依赖关系:AsyncServeMain.cpp依赖AsyncServer.h,AsyncServer.h依赖Session.h.
#include <iostream>
#include "AsyncServer.h"
int main(int argc, char* argv[])
{
try
{
asio::io_context io_context;
AsyncServer s(io_context, 1000);
io_context.run();
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl;
}
return 0;
}
AsyncServer.h的源码:
#pragma once
#include "Session.h"
class AsyncServer
{
public:
AsyncServer(asio::io_context& io_context, short port):acceptor_(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(),port))
{
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(
[this](std::error_code ec, asio::ip::tcp::socket socket)
{
if (!ec)
{
std::make_shared<Session>(std::move(socket))->start();
std::cout << "accept success" << std::endl;
}
do_accept();
});
}
private:
asio::ip::tcp::acceptor acceptor_;
};
Session.h的源码:
#pragma once
#include <cstdlib>
#include <iostream>
#include <memory>
#define ASIO_STANDALONE
#include <asio.hpp>
using asio::ip::tcp;
class Session : public std::enable_shared_from_this<Session>
{
public:
Session(tcp::socket socket) : socket_(std::move(socket))
{
std::cout << "Session " << std::endl;
}
void start()
{
do_read();
}
~Session()
{
std::cout << "~Session " << std::endl;
}
private:
void do_read()
{
auto self(shared_from_this());
// 异步监听,当有信息发送过来,就调用lamda
socket_.async_read_some(asio::buffer(data_, max_length),
[this, self](std::error_code ec, std::size_t length)
{
if (!ec)
{
std::cout << "self.use_count():" << self.use_count() << std::endl;
std::cout << data_ << std::endl;
do_write(length);
}
else
{
std::cout << "async_read_some else" << std::endl;
}
});
}
void do_write(std::size_t length)
{
auto self(shared_from_this());
// 发送指定字节的数据
asio::async_write(socket_, asio::buffer(data_, length),
[this, self](std::error_code ec,std::size_t length)
{
if (!ec)
{
do_read();
}
});
}
private:
tcp::socket socket_;
enum {max_length = 1024};
char data_[max_length];
};
2.2.客户端(client)
客户端主要通过connect函数来连接服务端,支持域名解析,同时采用心跳保活模式,定时发送一条数据给服务端,这里只展示了如何发送心跳数据,业务数据发送流程和这个一样,如果想要保证数据的顺序性,可以使用一个queue来存放要发送的数据,这样就可以避免多次调用write函数而导致的数据乱序问题。具体参考客户端代码:
client.h:包含客户端的连接,读写等操作
asyncClient.cpp:测试函数
依赖关系:asyncClient.cpp依赖client.h
client.h的源码:
#pragma once
#include <functional>
#include <iostream>
#include <string>
#ifdef _WIN32
#define _WIN32_WINNT 0x0A00
#endif // _WIN32
#define ASIO_STANDALONE
#include <asio.hpp>
#include <asio/buffer.hpp>
#include <asio/io_context.hpp>
#include <asio/ip/tcp.hpp>
#include <asio/read_until.hpp>
#include <asio/steady_timer.hpp>
#include <asio/write.hpp>
using asio::steady_timer;
using asio::ip::tcp;
using std::placeholders::_1;
using std::placeholders::_2;
class client
{
public:
client(asio::io_context& io_context) : socket_(io_context),
deadline_(io_context),
heartbeat_timer_(io_context)
{
}
void start(tcp::resolver::results_type endpoints)
{
endpoints_ = endpoints;
start_connect(endpoints_.begin());
deadline_.async_wait(std::bind(&client::check_deadline, this));
}
void stop()
{
stopped_ = true;
std::error_code ignored_error;
socket_.close(ignored_error);
deadline_.cancel();
heartbeat_timer_.cancel();
}
private:
void start_connect(tcp::resolver::results_type::iterator endpoint_iter)
{
if (endpoint_iter != endpoints_.end())
{
std::cout << "Trying " << endpoint_iter->endpoint() << "...\n" << std::endl;
deadline_.expires_after(std::chrono::seconds(60));
socket_.async_connect(endpoint_iter->endpoint(),
std::bind(&client::handle_connect,this,_1,endpoint_iter));
}
else
{
stop();
}
}
void handle_connect(const std::error_code& error, tcp::resolver::results_type::iterator endpoint_iter)
{
if (stopped_)
{
return;
}
if (!socket_.is_open())
{
std::cout << "Connect timed out\n";
start_connect(++endpoint_iter);
}
else if (error)
{
std::cout << "Connect error:" << error.message() << "\n";
socket_.close();
start_connect(++endpoint_iter);
}
else
{
std::cout << "Connect to " << endpoint_iter->endpoint() << "\n";
start_read();
start_write();
}
}
void start_read()
{
deadline_.expires_after(std::chrono::seconds(30));
asio::async_read_until(socket_, asio::dynamic_buffer(input_buffer_), '\n',
std::bind(&client::handle_read, this, _1, _2));
}
void handle_read(const std::error_code& error,std::size_t n)
{
if (stopped_)
{
return;
}
if (!error)
{
std::string line(input_buffer_.substr(0, n - 1));
input_buffer_.erase(0, n);
if (!line.empty())
{
std::cout << "Received: " << line << "\n";
}
start_read();
}
else
{
std::cout << "Error on receive:" << error.message() << "\n";
stop();
}
}
void start_write()
{
if (stopped_)
{
return;
}
asio::async_write(socket_, asio::buffer("hello async server\n", 20), std::bind(&client::handle_write, this, _1));
}
void handle_write(const std::error_code& error)
{
if (stopped_)
{
return;
}
if (!error)
{
heartbeat_timer_.expires_after(std::chrono::seconds(10));
heartbeat_timer_.async_wait(std::bind(&client::start_write, this));
}
else
{
std::cout << "Error on heartbeat:" << error.message() << "\n";
stop();
}
}
void check_deadline()
{
if (stopped_)
{
return;
}
if (deadline_.expiry() <= steady_timer::clock_type::now())
{
socket_.close();
deadline_.expires_at(steady_timer::time_point::max());
}
deadline_.async_wait(std::bind(&client::check_deadline,this));
}
private:
bool stopped_ = false;
tcp::resolver::results_type endpoints_;
tcp::socket socket_;
std::string input_buffer_;
steady_timer deadline_;
steady_timer heartbeat_timer_;
};
asyncClient.cpp的源码:
#include <iostream>
#include "client.h"
int main(int argc, char* argv[])
{
try
{
asio::io_context io_context;
asio::ip::tcp::resolver r(io_context);
client c(io_context);
c.start(r.resolve("127.0.0.1", "1000"));
io_context.run();
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
- asio网络库UDP同步(sync)模式
3.1.服务端(server)
Udp的服务的很简单,只需要开启一个接收socket,通过receive_from函数读取接收到客户端发送到指定断开的数据即可。
源码如下:
#include <iostream>
#include <cstdlib>
#ifdef _WIN32
#define _WIN32_WINNT 0x0A00
#endif // _WIN32
#define ASIO_STANDALONE
#include <asio.hpp>
using asio::ip::udp;
enum {max_length = 1024};
void server(asio::io_context& io_context, unsigned short port)
{
udp::socket sock(io_context, udp::endpoint(udp::v4(), port));
for (;;)
{
char data[max_length];
udp::endpoint sender_endpoint;
size_t length = sock.receive_from(asio::buffer(data, max_length), sender_endpoint);
sock.send_to(asio::buffer(data, length), sender_endpoint);
if (length > 0)
{
std::cout.write(data, length);
std::cout << "\n";
}
}
}
int main(int argc, char* argv[])
{
try
{
asio::io_context io_context;
server(io_context, 32100);
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
3.2.客户端(client)
源码如下:
#include <iostream>
#include <cstring>
#include <cstdlib>
#ifdef _WIN32
#define _WIN32_WINNT 0x0A00
#endif // _WIN32
#define ASIO_STANDALONE
#include <asio.hpp>
using asio::ip::udp;
enum {max_length = 1024};
int main(int argc, char* argv[])
{
try
{
asio::io_context io_context;
udp::socket s(io_context, udp::endpoint(udp::v4(), 0));
udp::resolver resolver(io_context);
udp::resolver::results_type endpoints = resolver.resolve(udp::v4(), "127.0.0.1", "32100");
std::cout << endpoints.begin()->endpoint() << "\n";
std::cout << "Enter message:";
char request[max_length];
std::cin.getline(request, max_length);
size_t request_length = std::strlen(request);
s.send_to(asio::buffer(request, request_length), *endpoints.begin());
char reply[max_length];
udp::endpoint sender_endpoint;
size_t reply_length = s.receive_from(asio::buffer(reply, max_length), sender_endpoint);
std::cout << "Reply port is:" << sender_endpoint << " Reply is: ";
std::cout.write(reply, reply_length);
std::cout << "\n";
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
- asio网络库UDP异步(async)模式
4.1.服务端(server)
udp的服务端异步操作和同步几乎一样,只不过调用的时带有async_前缀的函数,往指定端口发送数据即可,代码如下:
#include <iostream>
#include <cstdlib>
#ifdef _WIN32
#define _WIN32_WINNT 0x0A00
#endif // _WIN32
#define ASIO_STANDALONE
#include <asio.hpp>
using asio::ip::udp;
class server
{
public:
server(asio::io_context& io_context, short port)
: socket_(io_context, udp::endpoint(udp::v4(), port))
{
do_receive();
}
void do_receive()
{
socket_.async_receive_from(asio::buffer(data_,max_length),sender_endpoint_,
[this](std::error_code ec, std::size_t bytes_recvd)
{
if (!ec && bytes_recvd > 0)
{
do_send(bytes_recvd);
}
else
{
do_receive();
}
});
}
void do_send(std::size_t length)
{
socket_.async_send_to(asio::buffer(data_,length),sender_endpoint_,
[this](std::error_code ec, std::size_t bytes_send)
{
do_receive();
});
}
private:
udp::socket socket_;
udp::endpoint sender_endpoint_;
enum {max_length = 1024};
char data_[max_length];
};
int main(int argc, char* argv[])
{
try
{
asio::io_context io_context;
server(io_context,6668);
io_context.run();
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
4.2.客户端(client)
客户端代码如下:
asio_timer.h源码如下(事件循环,服务端循环写数据到指定端口):
#pragma once
#ifndef ASIO_TIMER_ASIO_TIMER_H_
#define ASIO_TIMER_ASIO_TIMER_H_
#include <chrono>
#include <functional>
#define ASIO_STANDALONE
#include "asio.hpp"
#include "asio/steady_timer.hpp"
namespace asiotimer {
template <typename TimeUnit>
class TimerManager {
public:
struct TimerItem {
TimerItem(asio::io_service & io_service, int seconds,
const std::function<void()> & f) : timer(io_service),
duration(seconds),
func(f) {}
asio::steady_timer timer;
TimeUnit duration;
std::function<void()> func;
};
TimerManager(asio::io_service & io_service) : io_service_(io_service) {}
TimerManager(TimerManager &) = delete;
TimerManager & operator = (const TimerManager &) = delete;
template <typename T>
void Add(T * obj, const unsigned int & duration, void (T::* mem_func)()) {
std::function<void()> func = std::bind(mem_func, obj);
this->items_.push_back(std::make_shared<TimerItem>(
this->io_service_,
duration,
func));
}
void Run() {
for (auto & item : this->items_) {
asio::steady_timer & timer = item->timer;
const TimeUnit & duration = item->duration;
const std::function<void()> & func = item->func;
TimerLoop(timer, duration, func);
}
}
protected:
void TimerLoop(asio::steady_timer & timer,
const TimeUnit & duration,
const std::function<void()> & func) {
timer.expires_from_now(duration);
timer.async_wait(
[this, &timer, duration, func](const asio::error_code &) {
func();
TimerLoop(timer, duration, func);
});
}
private:
asio::io_service & io_service_;
std::vector< std::shared_ptr<TimerItem> > items_;
};
} // namespace asiotimer
#endif // ASIO_TIMER_ASIO_TIMER_H_
asyncClient.cpp的源码如下:
#include <iostream>
#include <cstdlib>
#include <functional>
#include <memory>
#include <thread>
#ifdef _WIN32
#define _WIN32_WINNT 0x0A00
#endif // _WIN32
#define ASIO_STANDALONE
#include <asio.hpp>
#include <asio/buffer.hpp>
#include <asio/io_context.hpp>
#include <asio/ip/udp.hpp>
#include "asio_timer.h"
using asio::ip::udp;
using std::placeholders::_1;
using std::placeholders::_2;
using namespace std;
asio::ip::udp::endpoint ep(asio::ip::address::from_string("127.0.0.1"), 6668);
class noncopyable {
protected:
constexpr noncopyable() = default;
~noncopyable() = default;
noncopyable(const noncopyable &) = delete;
noncopyable &operator= (const noncopyable &) = delete;
};
class client : public enable_shared_from_this<client>,noncopyable
{
public:
client(asio::io_service& io_context, const std::string & message)
: sock_(io_context, asio::ip::udp::endpoint(asio::ip::udp::v4(), 6668)), started_(true), message_(message),
timer_manager_(io_context)
{
timer_manager_.Add(this, 1, &client::Timer1Sec);
timer_manager_.Run();
start();
}
typedef shared_ptr<client> ptr;
void start() {
do_write(message_);
}
bool started() { return started_; }
private:
void on_read(const error_code & err, size_t bytes) {
if (!err) {
std::string copy(read_buffer_, bytes);
std::cout << "server echoed us " << copy << std::endl;
//<< (copy == message_ ? "OK" : "FAIL") << std::endl;
}
start();
}
void on_write(const error_code & err, size_t bytes) {
//printf("client write result:%d, bytes:%d \n", err.value(), bytes);
do_read();
}
void do_read() {
sock_.async_receive_from(asio::buffer(read_buffer_), sender_ep,
bind(&client::on_read,
this,
std::placeholders::_1,
std::placeholders::_2));
std::cout << sender_ep << std::endl;
}
void do_write(const std::string & msg) {
std::copy(msg.begin(), msg.end(), write_buffer_);
sock_.async_send_to(asio::buffer(write_buffer_, msg.size()), ep,
bind(&client::on_write,
this,
std::placeholders::_1,
std::placeholders::_2));
}
void Timer1Sec() {
start();
std::cout << "Timer1Sec." << std::endl;
}
private:
asiotimer::TimerManager<std::chrono::seconds> timer_manager_;
private:
asio::ip::udp::socket sock_;
asio::ip::udp::endpoint sender_ep;
enum { max_msg = 1024 };
char read_buffer_[max_msg];
char write_buffer_[max_msg];
bool started_;
std::string message_;
};
int main(int argc, char* argv[])
{
try
{
asio::io_service io_service;
client c(io_service, "hello world");
io_service.run();
system("pause");
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}