现在处于刚刚学了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,则读取到指定长度或者读完了就不会阻塞住。