boost::asio库支持TCP、UDP和ICMP通信协议,在名字空间boost::asio::ip中提供大量网络通信方面的函数和类。
class | context |
---|---|
tcp | ip::tcp是asio主要用于TCP协议的类,内部类型endpoint、socket、acceptor和resolver是TCP通信最核心的一组类,封装了socket的连接、断开和数据收发等功能 |
address | ip::address表示IP地址,独立于TCP、UDP等通信协议,可以同时支持ipv4和ipv6,其静态成员from_string()是一个工工厂函数,可以从字符串产生ip地址,地址版本可以用is_v4()和is_v6()来检测,to_string()可以把ip地址转换为字符串 |
endpoint | ip::tcp::endpoint用来表示端口号,通过构造函数创建一个可用于Socket通信的端点对象,端点的地址和端口号可以用address()和port()获得 |
socket | TCP通信的基本类,可以在构造时就指定使用的协议和endpoint,或调用成员函数connect();连接成功后可以使用local_endpoint()和remote_endpoint()获得两端点信息;用available()获取可读取的字节数;用receive()/read_some()和send()/write_some()读写数据,参数是buffer类型,用buffer()函数进行包装;当操作完成后使用close()关闭socket |
acceptor | 构造时传入endpoint开始侦听,调用accept()接受新的连接 |
resolver | ip::tcp::resolver通过域名获得可用的IP地址,可以实现与IP版本无关的网址解析 |
io_service | asio::io_service类提供核心的I/O操作函数,提供了任务队列和任务分发功能,在异步编程中显式调用了io_service.run(),程序中的异步操作会添加至任务队列,由run()循环执行直到全部执行完毕。io_service是完全线程安全的队列。以上类大都需要io_service作为构造参数。 |
一、同步or异步
同步:所有操作都是顺序执行,如从socket中读取(请求),然后写入(回应)到socket中,每一个操作都是阻塞的。一般情况下会采取多线程的方式来进行socket的io操作。
异步:事件驱动,启动操作时会提供一个回调(感觉与C#的委托事件类似),操作结束后会自动返回结果。在异步编程中只需要一个线程。
1. 连接过程
客户端client
a.创建io_server实例(在Boost1.66后的版本为io_context)
boost::asio::io_service io_service;
b.创建连接的地址IP和端口port,在Boost::Asio中提供endpoint(ip,port)进行两者的绑定
unsigned short port = 8080;
auto const address = boost::asio::ip::address_v4::from_string("192.168.0.123");
boost::asio::ip::tcp::endpoint endpoint(address,port);
c.创建socket实例,并建立到endpoint的socket连接
boost::asio::ip::tcp::socket socket(io_service);
socket.connect(endpoint);//同步
socket.async_connect(endpoint,[](){});//异步,需要回调函数,这里使用的是lambda表达式
服务器端server
a.创建io_server实例(在Boost1.66后的版本为io_context)
boost::asio::io_service io_service;
b.设置要接受连接的端口和协议类型
unsigned short port = 8080;
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(),port);//ipv4,侦听端口8080
c.创建acceptor实例,并开始侦听Socket连接
boost::asio::ip::tcp::socket socket(io_service);
acceptor.accept(socket)//同步,运行到此处时会阻塞线程知道侦听到client的连接请求
socket.async_accept(socket,[](){});//异步,需要回调函数,这里使用的是lambda表达式
d.对Socket进行读写操作,客户端相同
boost::asio::write(socket,boost::asio::buffer("hello world!")); //向socket中写入字符
std::cout<<socket.available()<<std::endl; //获取可读取的字节数
std::vector<char> str(socket.available()+1,0); //定义一个vector缓冲区 socket.receive(boost::asio::buffer(str)); //使用buffer()包装缓冲区并接收数据
std::cout<<"client received: "<<&str[0]<<std::endl;//输出接收到的字符串
*在boost::asio中进行网络连接的具体表达方式有多种,但是连接过程基本如上。
2. 同步编程实例
客户端client
//**************************tcpClient.h***************************
#include <boost/asio.hpp>
using tcp=boost::asio::ip::tcp;
class tcpClient {
private:
boost::asio::io_service io_service;
tcp::endpoint endpoint;
tcp::socket socket;
public:
tcpClient(const tcp::endpoint &point);
~tcpClient();
private:
void conn();
void ioAction();
};
//**************************tcpClient.cpp***************************
#include <iostream>
#include "tcpClient.h"
#include <vector>
#include <boost/bind.hpp>
tcpClient::tcpClient(const tcp::endpoint &point):
io_service(),
endpoint(point),
socket(io_service){
conn();
}
void tcpClient::conn() {
socket.connect(endpoint); //Socket连接到端点
std::cout<<"client connected "<<socket.remote_endpoint().address()<<std::endl;
ioAction();
}
void tcpClient::ioAction() {
while(true){
boost::asio::write(socket,boost::asio::buffer("hello server"));//向Socket中写入数据
if(socket.available()){
std::cout<<socket.available()<<std::endl; //获取可读取的字节数
std::vector<char> str(socket.available()+1,0); //定义一个vector缓冲区
socket.receive(boost::asio::buffer(str)); //使用buffer()包装缓冲区接收数据
std::cout<<"client received: "<<&str[0]<<std::endl; //输出接收到的字符串
std::string string= &str[0];
if(string == "ok"){
return; //收到返回值后结束
}
}
}
}
tcpClient::~tcpClient() {
}
服务器端server
//**************************tcpServer.h***************************
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
using tcp=boost::asio::ip::tcp;
class tcpServer {
typedef boost::shared_ptr<tcp::socket> socket_ptr;
private:
boost::asio::io_service io_service;
tcp::endpoint endpoint;
tcp::acceptor acceptor;
public:
tcpServer();
~tcpServer();
private:
void accept();
void do_conn(socket_ptr sock);
};
//**************************tcpServer.cpp***************************
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include "tcpServer.h"
tcpServer::tcpServer():
endpoint(tcp::v4(),2001),io_service(), acceptor(io_service,endpoint){
accept();
}
void tcpServer::accept() {
socket_ptr sock(new tcp::socket(io_service));
std::cout<<"server has opened! and accept is sync"<<std::endl;
acceptor.accept(*sock);//阻塞
std::cout<<"server accepted, client: "<<sock->remote_endpoint().address()<<std::endl;
sock->send(boost::asio::buffer("hello client!")); //发送数据
do_conn(sock);
// boost::thread thread(boost::bind(&tcpServer::do_conn,this,sock));
}
void tcpServer::do_conn(socket_ptr sock) {
try{
while(true){
char data[512];
size_t len = sock->read_some(boost::asio::buffer(data));
std::cout<<"server connected to client! and loop to read data"<<std::endl;
if (len>0){
write(*sock,boost::asio::buffer("ok",2));
std::cout<<"server recall ok"<<std::endl;
}
}
}catch(const std::exception &e){
std::cerr<<"Server Exception:"<<e.what()<<std::endl;
}
// accept();
}
tcpServer::~tcpServer() {
}
main.cpp
//**************************client***************************
#include <iostream>
#include "tcpClient.h"
#include <boost/thread.hpp>
void openClient(const tcp::endpoint &point);
int main(int argc, char* argv[]) {
if (argc!=2){
std::cerr<<"Usage: "<<argv[0]<<" <port>"<<std::endl;
return 1;
}
unsigned short port = std:: atoi(argv[1]);
auto const address = boost::asio::ip::address_v4::from_string("192.168.0.123");
tcp::endpoint point(address,port);
std::cout<<"start"<<std::endl;
openClient(point);
std::cout<<"end"<<std::endl;
return 0;
}
void openClient(const tcp::endpoint &point) {
tcpClient client(point);
// async_TcpClient async_client(point);
}
//**************************Server***************************
#include <iostream>
//#include "tcpClient.h"
#include "tcpServer.h"
#include <boost/thread.hpp>
void openClient(const tcp::endpoint &point);
bool openServer();
int main(int argc, char* argv[]) {
if (argc!=2){
std::cerr<<"Usage: "<<argv[0]<<" <port>"<<std::endl;
return 1;
}
unsigned short port = std:: atoi(argv[1]);
auto const address = boost::asio::ip::address_v4::from_string("192.168.0.123");
tcp::endpoint point(address,port);
std::cout<<"start"<<std::endl;
// boost::thread thread([=](){
// std::cout<<"this is server thread"<<std::endl;
// openServer();
// }); //服务器线程,应当保证服务器监听线程先打开
// openClient(point);
openServer();
std::cout<<"end"<<std::endl;
return 0;
}
void openClient(const tcp::endpoint &point) {
// tcpClient client(point);
}
bool openServer(){
// tcpServer server;
ansyc_tcpServer ansyc_server;
return true;
}
3. 异步编程实例
客户端client
//**************************async_TcpClient.h***************************
#include <boost/asio.hpp>
using tcp=boost::asio::ip::tcp;
class async_TcpClient{
private:
boost::asio::io_service io_service;
tcp::endpoint endpoint;
tcp::socket socket;
public:
async_TcpClient(const tcp::endpoint &point);
~async_TcpClient();
private:
void connect();
void connect_handler(const boost::system::error_code &ec);
};
//**************************async_TcpClient.cpp***************************
#include <iostream>
#include "tcpClient.h"
#include <vector>
#include <boost/bind.hpp>
async_TcpClient::async_TcpClient(const tcp::endpoint &point):
io_service(),endpoint(point),socket(io_service){
connect();
io_service.run();
std::cout<<"io_service.run() is end!"<<std::endl;
}
void async_TcpClient::connect() {
socket.async_connect(endpoint,[this](const boost::system::error_code &ec){
connect_handler(ec);
});
std::cout<<"connect()"<<std::endl;
}
void async_TcpClient::connect_handler(const boost::system::error_code &ec) {
std::cout<<"connected!"<<ec.message()<<std::endl;
boost::asio::write(socket,boost::asio::buffer("hello world!",1024));
// std::cout<<"start another accept()"<<std::endl;
// connect();
}
async_TcpClient::~async_TcpClient() {
}
服务器端server
//**************************async_tcpServer.h***************************
class ansyc_tcpServer{
typedef boost::shared_ptr<tcp::socket> socket_ptr;
private:
boost::asio::io_service io_service;
tcp::endpoint endpoint;
tcp::acceptor acceptor;
socket_ptr sock;
unsigned char str[1024];
public:
ansyc_tcpServer();
~ansyc_tcpServer();
private:
void accept();
void handle_accept(const boost::system::error_code &ec);
void read();
void write();
};
//**************************async_tcpServer.cpp***************************
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include "tcpServer.h"
ansyc_tcpServer::ansyc_tcpServer() :
io_service(),endpoint(tcp::v4(),2001), acceptor(io_service,endpoint),sock(new tcp::socket(io_service)){
accept();
io_service.run();
std::cout<<"io_service.run() is end"<<std::endl;
}
ansyc_tcpServer::~ansyc_tcpServer() {
}
void ansyc_tcpServer::accept() {
acceptor.async_accept(*sock,[this](const boost::system::error_code &ec){
handle_accept(ec);
});
std::cout<<"accept()"<<std::endl;
}
void ansyc_tcpServer::handle_accept(const boost::system::error_code &ec) {
if(ec){
return;
}
read();
std::cout<<"start another accept()"<<std::endl;
accept();
}
void ansyc_tcpServer::read() {
boost::asio::async_read(*sock,boost::asio::buffer(str,1024),[this](boost::system::error_code ec, std::size_t){
if(!ec){
write();
std::cout<<"server received: "<<str<<std::endl; //输出接收到的字符串
}
read();
});
}
void ansyc_tcpServer::write() {
boost::asio::async_write(*sock,boost::asio::buffer("ok",2),[this](boost::system::error_code ec, std::size_t){
if(!ec){
std::cout<<"server recalled: "<<str<<" ok"<<std::endl; //输出接收到的字符串
}
});
}
main.cpp 同上
*代码逻辑比较简单。最初想在多线程中实现,不过在linux终端上进程太快结束,而且必须保证服务器先开启,不方便调试就分成两个进程。代码测试选取的同步客户端和异步服务器进行测试,可根据需要进行修改。
4. 异常处理&错误码
Boost.Asio允许同时使用异常处理或者错误码,所有的异步函数都有抛出错误和返回错误码两种方式的重载。
异常处理:
try{
sock.connect(endpoint);//抛出异常错误的重载
}catch(boost::system::system_error e){
std::cout << e.code() << std::endl;
}
错误码:
boost::system::error_code err;
sock.connect(endpoint, err);//返回错误码的重载
if(err)
std::cout << err << std::endl;
当使用异步函数时,可以在回调函数中检查返回的错误码
二、Tcp短连接&长连接
Tcp进行通信是首先需要建立一个Socket连接(三次握手),当完成读写操作后需要释放连接(四次挥手)。
1. 短连接
在每次需要进行通信时才在client和server间建立连接,每次读写操作完成后就关闭连接(有四次挥手)。这种方式管理简单,存在的连接都是有用的连接,不需要额外的控制手段。
2. 长连接
连接的建立与断开仅一次(不考虑意外中断),进行数据传输时不再需要进行额外的连接操作。但是为了避免长时间连接时意外中断,需要设计保活机制如心跳和断线重连。心跳机制一般利用定时器以设定的时间间隔向连接另一端发送心跳包,根据发送是否成功或者另一端的回复内容来判断连接是否中断,如果连接中断则关闭Socket再重新进行侦听或发起连接请求。
心跳与断线重连
void async_TcpClient::heartbeat() {
std::cout<<"heartbeat!"<<std::endl;
sendheart();
timercontrol(true);
}
void async_TcpClient::sendheart() {
//发送心跳
boost::asio::async_write(socket,boost::asio::buffer("heart beating!"),
[this](boost::system::error_code ec, std::size_t)
if(!ec){
std::cout<<"发送心跳"<<std::endl;
readheart();
} else{
//失败时则进行断线重连
reconnect();
}
});
}
void async_TcpClient::readheart() {
//读取回复
boost::asio::async_read(socket,boost::asio::buffer(&str[0],2),
[this](boost::system::error_code ec, std::size_t){
if(!ec){
if(!ec){//这里偷了个懒,实际上是要验证通信协议中心跳包的服务器响应数据
std::cout<<"读取回复1"<<std::endl;
// write();
}else{
std::cout<<"读取回复2"<<std::endl;
//内容不符合则进行断线重连
reconnect();
}
} else{
std::cout<<"读取回复3"<<std::endl;
//失败时则进行断线重连
reconnect();
}
});
}
void async_TcpClient::reconnect() {
socket.close();
socket.async_connect(endpoint,[this](const boost::system::error_code ec){
if(!ec){
std::cout<<"reconnected!"<<std::endl;
}else{
std::cout<<"reconnect failed!"<<std::endl;
}
});
}
void async_TcpClient::timercontrol(const bool isInit) {
if(isInit){
async_timer.expires_from_now(std::chrono::seconds(10));
async_timer.async_wait([this](const boost::system::error_code& ec){
//这里就是用来被取消的,不需要任何进行操作
});
}
//每次io操作时重置定时器计时,重置时应该保证async_wait已存在
if(async_timer.expires_from_now(std::chrono::seconds(10))>0){
async_timer.async_wait([this](const boost::system::error_code& ec){
if (ec != boost::asio::error::operation_aborted)
{
heartbeat();
} else{
std::cout<<"another async_timer error2 "<<ec.message()<<std::endl;
}
});
} else{
std::cout<<"1async_timer error "<<std::endl;
}
}
这里的设计思路是每次进行IO操作时重置定时器,如果超过定时器设置的时间仍未进行下一次IO操作,则发送心跳包来测试网络连接。心跳包发送失败则进行断线重连,发送成功则判断服务器端返回的数据是否符合通信协议,符合则重新进行IO操作 ,否则则判断网络还未恢复并进行重连。
三、Websocket和Soket
Socket:通常指TCP/IP网络中的两个连接端,是位于应用层和传输控制层之间的一组接口。此外也是一种进程间通信方式。
Websocket:一种应用层(浏览器)的双向通信协议,建立在TCP协议之上,类似于HTTP,不过HTTP采用的短连接方式,而Websocket采用TCP长连接。
两者的关系类似于Java和JavaScript~~~~
参考文档:
Boost库之asio io_service以及run、run_one、poll、poll_one区别
一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等
学习资源(PDF):
代码下载:
github&gitee