Boost.asio学习之Tcp异步通信Demo

    现在处于刚刚学了C++基础语法,看别人的代码缕逻辑缕的慢,按自己的逻辑写代码构思不太清楚漏洞百出。虽然boost asio编程已经学了一周,逻辑也差不多清楚,不过亲手写一个Demo会发现很多平时注意不到的小细节,往往这些小细节就是以后耽误时间的主要原因。所以强行逼自己写了这个Demo,写了1个小时,改bug改了好几天,终于在周末前把程序调通了,只是简单的服务端接收客户端消息,就这么艰难,看来未来的路还很长。现在将这个代码记录在这里。

下面是这个代码的主要流程与自己觉得需要注意的地方:

一、服务端

1.服务器自己创建io_service,endpoint,设定ip与port。

2.创建两个类,一个接受连接,其中acceptor使用io_service与endpoint两个参数初始化,类成员变量io_service需要用引用。

      开始async_accept后会阻塞在这里,其中socket参数由session类getSocket获取,当有客户端连接后启用回调函数,回调函数判断是否连接成功,成功则开始处理会话部分。是否成功都会继续调用async_accept,循环接受连接。

3.另一个处理连接后的会话,继承自基类boost::enable_shared_from_this<class>,成员变量socket由io_service初始化,并将新的客户端存入Server的列表中,如果断开了连接,则根据Session里记录的编号从List中删除。开始async_read读取客户端发来的消息,这里如果buffer设定的长度小于等于消息长度会读取设定长度的消息,否则会一直阻塞在这里,这个解决方法有待进一步学习。关闭socket后执行析构。

4.接受分为两部分,第一部分读取报头,固定长度5字节,里面存放即将发来的数据报长度,第二部分根据报头提供的长度信息读取指定长度的报文。这里注意,async_read只会在读完或者出错是才返回,如果设定buffer内的长度大于报文,将一直阻塞,我改用了async_read_some,这样读完或者读取到指定的长度后即返回。

5.服务端总结:服务端分为两个部分,一部分负责建立服务端,另一部分负责数据接受与发送。

二、客户端

1.创建io_service。

2.socket由io_service初始化,创建resolver与query,query请求服务端地址和ip,resolver将请求到的query解析成endpoint_iterator格式,以建立与服务端的连接。通过socket与endpoint_iterator利用async_connect与服务端建立连接,并调用回调函数。

3.连接成功后,异步发送报头与报文。


代码如下:

头文件

// TODO:  在此处引用程序需要的其他头文件
#include <boost\asio.hpp>
#include <boost\shared_ptr.hpp>
#include <boost\bind.hpp>
#include <boost\lexical_cast.hpp>
#include <boost\enable_shared_from_this.hpp>
#include <iostream>

#define	Asio	boost::asio
#define Tcp	boost::asio::ip::tcp

MyServer.h

#pragma once
#include <unordered_map>

class CMySession;

typedef boost::shared_ptr<CMySession> session_ptr;

class CMyServer
{
public:
	CMyServer(Asio::io_service &io_service,Tcp::endpoint &ep)
		:m_io_service(io_service), m_acceptor(io_service, ep), m_nSessionCount(0)
	{
		StartServer();
	}
	~CMyServer();
/* 公有成员函数 */
public:
	void StartServer();
	
/* 私有成员函数 */
private:
	void handle_accept(session_ptr new_session,const boost::system::error_code &error);
/* 私有成员变量 */
private:
	Asio::io_service &m_io_service;
	Tcp::acceptor m_acceptor;

	int m_nSessionCount;
	std::unordered_map<int, session_ptr> m_mapSessionList;

};

class CMySession
	:public boost::enable_shared_from_this<CMySession>
{
public:
	/* 构造函数中,io_service需要传引用 */
	CMySession(Asio::io_service &io_service, std::unordered_map<int, session_ptr> &mapSessionList,int nCount)
		:m_socket(io_service), m_mapSessionList(mapSessionList), m_nCount(nCount)
	{

	}
	~CMySession();
public:
	Tcp::socket& getSocket();
	void start();
private:
	void handle_read_head(const boost::system::error_code &error);
	void handle_read_body(const boost::system::error_code &error);
private:
	char m_cHead[5];
	char m_cReciv[512];
	Tcp::socket m_socket;
	std::unordered_map<int, session_ptr> &m_mapSessionList;
	int m_nCount;
};

MyServer.cpp

#include "stdafx.h"
#include "MyServer.h"

CMyServer::~CMyServer()
{
}

void CMyServer::StartServer()
{
	/* 将表的引用传递给Session,存储信息 */
	session_ptr new_session(new CMySession(m_io_service, m_mapSessionList, m_nSessionCount));
	++m_nSessionCount;
	m_acceptor.async_accept(new_session->getSocket(),
		boost::bind(&CMyServer::handle_accept, this, new_session, Asio::placeholders::error
		));
}

void CMyServer::handle_accept(session_ptr session,const boost::system::error_code &error)
{
	if (!error)
	{
		session->start();
	}
	/* 此处不管是否成功,都会继续监听 */
	StartServer();
}



CMySession::~CMySession()
{
// 	auto it = m_mapSessionList.find(m_nCount);
// 	m_mapSessionList.erase(it);
	m_mapSessionList;
	std::cout << m_nCount << ":~SessionComplete" << std::endl;
}

Tcp::socket& CMySession::getSocket()
{
	return m_socket;
}

void CMySession::start()
{
	/* 成功建立连接 加入List */
	m_mapSessionList.insert(std::make_pair(m_nCount, shared_from_this()));
	Asio::async_read(m_socket, Asio::buffer(m_cHead, 5), boost::bind(&CMySession::handle_read_head, shared_from_this(), Asio::placeholders::error));
}



void CMySession::handle_read_head(const boost::system::error_code &error)
{
	if (!error)
	{
		/* 获得数据报长度 */
		int nLenth = atoi(m_cHead);
		memset(m_cHead, 0, 5);
		//std::cout << nLenth << std::endl;
		//Asio::async_read(m_socket, Asio::buffer(m_cReciv, 20), boost::bind(&CMySession::handle_read_body, shared_from_this(), Asio::placeholders::error));
		m_socket.async_read_some(Asio::buffer(m_cReciv, nLenth + 1), boost::bind(&CMySession::handle_read_body, shared_from_this(), Asio::placeholders::error));
	}
	else
	{
		auto it = m_mapSessionList.find(m_nCount);
		m_mapSessionList.erase(it);
		m_socket.shutdown(Tcp::socket::shutdown_both);
		m_socket.close();
	}
		
}
void CMySession::handle_read_body(const boost::system::error_code &error)
{
	if (!error)
	{
		std::cout << m_cReciv << std::endl;
		//Asio::async_read(m_socket, Asio::buffer(m_cHead, 5), boost::bind(&CMySession::handle_read_head, shared_from_this(), Asio::placeholders::error));
		m_socket.async_read_some(Asio::buffer(m_cHead, 5), boost::bind(&CMySession::handle_read_head, shared_from_this(), Asio::placeholders::error));
	}
	else
	{
		auto it = m_mapSessionList.find(m_nCount);
		m_mapSessionList.erase(it);
		m_socket.shutdown(Tcp::socket::shutdown_both);
		m_socket.close();
	}
}

服务端main函数

// Server.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "MyServer.h"
int _tmain(int argc, _TCHAR* argv[])
{
	Asio::io_service io_service;
	Tcp::endpoint ep(Tcp::v4(), 8899);

	CMyServer a(io_service, ep);

	io_service.run();
	system("pause");
	return 0;
}

头文件

#include <boost\asio.hpp>
#include <boost\shared_ptr.hpp>
#include <boost\bind.hpp>
#include <boost\enable_shared_from_this.hpp>
#include <iostream>

#define ASIO	boost::asio
#define TCP		boost::asio::ip::tcp

Client.h

#pragma once

class CClient:
	public boost::enable_shared_from_this<CClient>
{
public:
	CClient(ASIO::io_service&io_service)
		:m_io_service(io_service), m_socket(io_service)
	{}
	~CClient();
public:
	void connectServer(std::string strIP,int nPort);

private:
	void handle_connect(const boost::system::error_code &error);
	void handle_write(const boost::system::error_code &error);
	void handle_write_stop(const boost::system::error_code &error);

private:
	ASIO::io_service &m_io_service;
	TCP::resolver::iterator m_it_ep;
	TCP::socket	m_socket;
};

Client.cpp

#include "stdafx.h"
#include "Client.h"
#define testStringB	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
#pragma disable(error:4996)
CClient::~CClient()
{
}

void CClient::connectServer(std::string strIP, int nPort)
{
	

	TCP::resolver cResolver(m_io_service);
	TCP::resolver::query cQuery(strIP, std::to_string(nPort));	/* 接受两个string类型参数 */
	m_it_ep = cResolver.resolve(cQuery);

	ASIO::async_connect(m_socket, m_it_ep, boost::bind(&CClient::handle_connect, shared_from_this(), ASIO::placeholders::error));
}

void CClient::handle_connect(const boost::system::error_code &error)
{
	if (!error)
	{
		int n = strlen(testStringB);
		char tmpHead[5 + 1] = "";
		/* 将长度信息由int转换为字符串,作为报头发送给服务端 */
		sprintf_s(tmpHead, "%5d", n);

		ASIO::async_write(m_socket, ASIO::buffer(tmpHead,5), boost::bind(&CClient::handle_write, shared_from_this(), ASIO::placeholders::error));
	}
	
}

void CClient::handle_write(const boost::system::error_code &error)
{
	if (!error)
	{		
		std::cout << "报头发送成功!" << std::endl;
		char ctmp[] = testStringB;
		int nLenth = strlen(ctmp);

		ASIO::async_write(m_socket, ASIO::buffer(ctmp,nLenth), boost::bind(&CClient::handle_write_stop, shared_from_this(), ASIO::placeholders::error));
	}

}

void CClient::handle_write_stop(const boost::system::error_code &error)
{
	return;
}

客户端main函数

//Practice0530.cpp : 定义控制台应用程序的入口点。


#include "stdafx.h"
#include "Client.h"

int _tmain(int argc, _TCHAR* argv[])
{
	ASIO::io_service io_service;
	ASIO::io_service::work work(io_service);
 	int i = 0;
	boost::shared_ptr<CClient> a(new CClient(io_service));
	a->connectServer("127.0.0.1", 8899);

	io_service.run(); 
	return 0;
}

下午的新发现

今天下午写另外一个程序不小心点错了 把这个程序与上一个程序进行了通讯,结果发现:

客户端连续两次ansyc_write,其实是一个数据流过去的,这就说明服务端那边接收的时候,如果指定的read长度小于整个流的长度,那么可以读指定长度,如果大于则会阻塞住,相当于没读完,不会向下走。而用ansyc_read_some,则读取到指定长度或者读完了就不会阻塞住。


猜你喜欢

转载自blog.csdn.net/lasuerte/article/details/80526347