一、同步or异步
1. 连接过程
客户端client
服务器端server
2. 同步编程实例
3. 异步编程实例
4. 异常处理&错误码
二、Tcp短连接&长连接
1. 短连接
2. 长连接
三、Websocket和Soket
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作为构造参数。 |
同步:所有操作都是顺序执行,如从socket中读取(请求),然后写入(回应)到socket中,每一个操作都是阻塞的。一般情况下会采取多线程的方式来进行socket的io操作。
异步:事件驱动,启动操作时会提供一个回调(感觉与C#的委托事件类似),操作结束后会自动返回结果。在异步编程中只需要一个线程。
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表达式
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< str(socket.available()+1,0); //定义一个vector缓冲区 socket.receive(boost::asio::buffer(str)); //使用buffer()包装缓冲区并接收数据
std::cout<<"client received: "<<&str[0]<
*在boost::asio中进行网络连接的具体表达方式有多种,但是连接过程基本如上。
客户端client
//**************************tcpClient.h***************************
#include
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
#include "tcpClient.h"
#include
#include
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 "< str(socket.available()+1,0); //定义一个vector缓冲区
socket.receive(boost::asio::buffer(str)); //使用buffer()包装缓冲区接收数据
std::cout<<"client received: "<<&str[0]<
服务器端server
//**************************tcpServer.h***************************
#include
#include
using tcp=boost::asio::ip::tcp;
class tcpServer {
typedef boost::shared_ptr 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
#include
#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"<remote_endpoint().address()<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"<0){
write(*sock,boost::asio::buffer("ok",2));
std::cout<<"server recall ok"<
main.cpp
//**************************client***************************
#include
#include "tcpClient.h"
#include
void openClient(const tcp::endpoint &point);
int main(int argc, char* argv[]) {
if (argc!=2){
std::cerr<<"Usage: "<"<
//#include "tcpClient.h"
#include "tcpServer.h"
#include
void openClient(const tcp::endpoint &point);
bool openServer();
int main(int argc, char* argv[]) {
if (argc!=2){
std::cerr<<"Usage: "<"<
客户端client
//**************************async_TcpClient.h***************************
#include
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
#include "tcpClient.h"
#include
#include
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!"<
服务器端server
//**************************async_tcpServer.h***************************
class ansyc_tcpServer{
typedef boost::shared_ptr 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
#include
#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"<
main.cpp 同上
*代码逻辑比较简单。最初想在多线程中实现,不过在linux终端上进程太快结束,而且必须保证服务器先开启,不方便调试就分成两个进程。代码测试选取的同步客户端和异步服务器进行测试,可根据需要进行修改。
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进行通信是首先需要建立一个Socket连接(三次握手),当完成读写操作后需要释放连接(四次挥手)。
在每次需要进行通信时才在client和server间建立连接,每次读写操作完成后就关闭连接(有四次挥手)。这种方式管理简单,存在的连接都是有用的连接,不需要额外的控制手段。
连接的建立与断开仅一次(不考虑意外中断),进行数据传输时不再需要进行额外的连接操作。但是为了避免长时间连接时意外中断,需要设计保活机制如心跳和断线重连。心跳机制一般利用定时器以设定的时间间隔向连接另一端发送心跳包,根据发送是否成功或者另一端的回复内容来判断连接是否中断,如果连接中断则关闭Socket再重新进行侦听或发起连接请求。
心跳与断线重连
void async_TcpClient::heartbeat() {
std::cout<<"heartbeat!"<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 "<
这里的设计思路是每次进行IO操作时重置定时器,如果超过定时器设置的时间仍未进行下一次IO操作,则发送心跳包来测试网络连接。心跳包发送失败则进行断线重连,发送成功则判断服务器端返回的数据是否符合通信协议,符合则重新进行IO操作 ,否则则判断网络还未恢复并进行重连。
Socket:通常指TCP/IP网络中的两个连接端,是位于应用层和传输控制层之间的一组接口。此外也是一种进程间通信方式。
Websocket:一种应用层(浏览器)的双向通信协议,建立在TCP协议之上,类似于HTTP,不过HTTP采用的短连接方式,而Websocket采用TCP长连接。
两者的关系类似于Java和JavaScript~~~~
参考文档:
Boost.Asio C++ 网络编程
基于 Asio 的 C++ 网络编程
boost::asio::ip::tcp实现网络通信
Boost库之asio io_service以及run、run_one、poll、poll_one区别
一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等
boost1.58定时器steady_timer 官方文档
学习资源(PDF):
boost库学习书籍
代码下载:
github&gitee