本篇应该是最后一篇了。给出一个完整的可以用于产品环境的代码。
现在修改一下代码逻辑,让server变成一个echo服务器。收到16字节长度后,再接受字符串。然后返回16字节长度给客户端,再返回字符串。
16字节长度类型是unsigned short,在网络传输的时候采用big-endian 字节顺序。字符串采用utf-8编码格式。
先来看一下我的newLISP客户端模拟程序:
(define (quit-for-error)
((println (net-error)) (exit)))
(define (send-test)
(set 'socket (net-connect "localhost" 8889))
(set 'hello (utf8 (unicode "hello, my name is 陈抒")))
(set 'size (pack ">u" (length hello)))
(if (net-send socket size) (println "send size succeeded") (quit-for-error))
(if (net-send socket hello) (println "send string succeeded") (quit-for-error))
(if (net-receive socket size-buffer 2) (println "receive size succeeded") (quit-for-error))
(set 'size2 ((unpack ">u" size-buffer) 0))
(println "received size is: " size2)
(if (net-receive socket str-buffer size2) (println "receive string succeeded, str: " str-buffer) (quit-for-error))
(exit))
(dotimes (i 2000)
(spawn 'ri (send-test)))
(until (sync 1000))
(exit)
说明:
1. 如果对使用newLISTP进行TCP通信感兴趣,可以参考我的另一篇文章:http://blog.csdn.net/sheismylife/article/details/8521748
2. dotimes是一个循环,i取值范围是:[0, 2000) (左闭右开区间),不断的创建进程,每个进程都运行函数send-test。
现在看一下改动后的server代码:
1.为了在多线程环境下输出一些信息,cout由于不是线程安全,所以不适用。这里引入了booster::log,可以参考我的文章:
http://blog.csdn.net/sheismylife/article/details/8248663
2.为了证明线程池在asio中的作用,日志将记录线程id
3.因为涉及到big-endian,所以自己实现了相关算法。
先看一下src/CMakeLists.txt文件, 里面加入了booster库。
cmake_minimum_required(VERSION 2.8)
set(CMAKE_BUILD_TYPE Debug)
set(PROJECT_INCLUDE_DIR ../include)
find_package(Boost COMPONENTS system filesystem thread REQUIRED)
include_directories(${Boost_INCLUDE_DIR} ${PROJECT_INCLUDE_DIR})
AUX_SOURCE_DIRECTORY(${CMAKE_SOURCE_DIR}/src CPP_LIST1)
AUX_SOURCE_DIRECTORY(${CMAKE_SOURCE_DIR}/src/core CPP_LIST2)
AUX_SOURCE_DIRECTORY(${CMAKE_SOURCE_DIR}/src/business CPP_LIST3)
add_executable(service ${CPP_LIST1} ${CPP_LIST2} ${CPP_LIST3})
target_link_libraries(service ${Boost_LIBRARIES} booster)
add_definitions(-Wall)
#include <iostream>
#include "core/server.h"
#include "business/client.h"
#include <booster/log.h>
#include <booster/shared_ptr.h>
using namespace std;
void init_log() {
booster::shared_ptr<booster::log::sinks::file> f(new booster::log::sinks::file());
f->append();
f->max_files(10);
f->open("/opt/tcp_template.log");
booster::log::logger::instance().add_sink(f);
booster::log::logger::instance().set_default_level(booster::log::debug);
}
int main(int argc,char ** argv) {
try {
init_log();
io_service iosev;
tcp::endpoint listen_endpoint(tcp::v4(), 8889);
Server<Client> server(iosev, listen_endpoint, 10);
server.Run();
} catch(std::exception const& ex) {
BOOSTER_ERROR("main") << "thread id: " << this_thread::get_id() << "Caught an exception: " << ex.what();
}
}
现在看一下server.h文件,里面也使用了log:
#ifndef CORE_SERVER_H_
#define CORE_SERVER_H_
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <booster/log.h>
#include <boost/thread/thread.hpp>
#include <vector>
using namespace std;
using namespace boost;
using boost::system::error_code;
using namespace boost::asio;
using ip::tcp;
// Crate a thread pool for io_service.
// Run the io_service to accept new incoming TCP connection and handle the I/O events
// You should provide your class as template argument here
// Your class must inherit from Connection class.
template<class T>
class Server {
public:
typedef T ClientType;
Server(io_service& s, tcp::endpoint const& listen_endpoint, size_t threads_number)
: io_(s),
signals_(s),
acceptor_(io_, listen_endpoint),
thread_pool_size_(threads_number) {
signals_.add(SIGINT);
signals_.add(SIGTERM);
#if defined(SIGQUIT)
signals_.add(SIGQUIT);
#endif
signals_.async_wait(bind(&Server::Stop, this));
shared_ptr<ClientType> c(new ClientType(io_));
acceptor_.async_accept(c->socket, bind(&Server::AfterAccept, this, c, _1));
}
void AfterAccept(shared_ptr<ClientType>& c, error_code const& ec) {
// Check whether the server was stopped by a signal before this completion
// handler had a chance to run.
if (!acceptor_.is_open()) {
cout << "acceptor is closed" << endl;
return;
}
if (!ec) {
c->StartJob();
shared_ptr<ClientType> c2(new ClientType(io_));
acceptor_.async_accept(c2->socket, bind(&Server::AfterAccept, this, c2, _1));
}
}
// Create a thread pool for io_service
// Launch io_service
void Run() {
// Create a pool of threads to run all of the io_services.
vector<shared_ptr<thread> > threads;
for (size_t i = 0; i < thread_pool_size_; ++i) {
shared_ptr<thread> t(new thread(bind(&io_service::run, &io_)));
threads.push_back(t);
}
// Wait for all threads in the pool to exit.
for (std::size_t i = 0; i < threads.size(); ++i) {
threads[i]->join();
}
}
private:
void Stop() {
BOOSTER_INFO("Server") << "thread id: " << this_thread::get_id() << "stopping" << endl;
acceptor_.close();
io_.stop();
}
private:
io_service& io_;
boost::asio::signal_set signals_;
tcp::acceptor acceptor_;
size_t thread_pool_size_;
};
#endif
connection.h文件也有改变,添加了日志,并且拦截了关闭socket的异常。
#ifndef CORE_CONNECTION_H_
#define CORE_CONNECTION_H_
#include <boost/asio.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <booster/log.h>
#include <boost/thread/thread.hpp>
using namespace boost::asio;
using ip::tcp;
using boost::system::error_code;
using namespace boost;
using namespace std;
template<class T>
class Connection: public boost::enable_shared_from_this<T> {
public:
Connection(io_service& s)
: socket(s), strand_(s) {
}
~Connection() {
}
// You must override it yourself
// Default implementation closes the socket using shutdonw&cloes methods
// You could override it if want change it
// Or resue it with Connection::CloseSocket() format
void CloseSocket() {
try {
socket.shutdown(tcp::socket::shutdown_both);
socket.close();
} catch (std::exception& e) {
BOOSTER_INFO("Connection") << "thread id: " << this_thread::get_id() << e.what() << endl;
}
}
// You must override it yourself
virtual void StartJob() = 0;
tcp::socket socket;
// Strand to ensure the connection's handlers are not called concurrently.
boost::asio::io_service::strand strand_;
};
#endif
好,现在看一下新的util/endian.h文件,里面是关于big-endian的算法:
#ifndef UTIL_ENDIAN_H_
#define UTIL_ENDIAN_H_
#include <boost/cstdint.hpp>
#include <vector>
#include <sstream>
using namespace std;
// Get the bit value specified by the index
// index starts with 0
template<class T>
int Bit_Value(T value, uint8_t index) {
return (value & (1 << index)) == 0 ? 0 : 1;
}
// T must be one of integer type
template<class T>
string PrintIntAsBinaryString(T v) {
stringstream stream;
int i = sizeof(T) * 8 - 1;
while (i >= 0) {
stream << Bit_Value(v, i);
--i;
}
return stream.str();
}
bool IsLittleEndian() {
short int x = 0x00ff;
char* p = (char*)&x;
return (short int)p[0] == -1;
}
static union {
char c[4];
unsigned char l;
} endian_test = {{'l','?','?','b'}};
#define IsLittleEndian2() (endian_test.l == 'l')
// Convert the following integer values to big-endian if necessary
template<class T>
T Int16ToBigEndian(T value) {
if (IsLittleEndian2()) {
uint8_t* p = reinterpret_cast<uint8_t*> (&value);
T v1 = static_cast<T> (p[0]);
T v2 = static_cast<T> (p[1]);
return (v1 << 8) | v2;
} else {
return value;
}
}
template<class T>
T Int32ToBigEndian(T value) {
if (IsLittleEndian2()) {
uint8_t* p = reinterpret_cast<uint8_t*> (&value);
T v1 = static_cast<T> (p[0]);
T v2 = static_cast<T> (p[1]);
T v3 = static_cast<T> (p[2]);
T v4 = static_cast<T> (p[3]);
return (v1 << 24) | (v2 << 16) << (v3 << 8) | v4;
} else {
return value;
}
}
// The following functions convert the byte arrays
// that has big-endian into integers on local platform
template<class T>
T BigEndianBytesToInt16(vector<uint8_t> const& value) {
if (IsLittleEndian2()) {
T h = static_cast<T> (value[0]);
T l = static_cast<T> (value[1]);
return (h << 8) | l;
} else {
T tmp = 0;
memcpy(&tmp, &value[0], 2);
return tmp;
}
}
template<class T>
T BigEndianBytesToInt32(uint8_t value[4]) {
if (IsLittleEndian2()) {
T a = static_cast<T> (value[0]);
T b = static_cast<T> (value[1]);
T c = static_cast<T> (value[2]);
T d = static_cast<T> (value[3]);
return (a << 24) | (b << 16) | (c << 8) | d;
} else {
T tmp = 0;
memcpy(&tmp, &value[0], 4);
return tmp;
}
}
#endif
最后啊看一下Client.cc文件的代码,真正实现了echo服务器的业务:
#include "business/client.h"
#include <boost/bind.hpp>
#include "util/endian.h"
#include <booster/log.h>
using namespace boost;
Client::Client(io_service& s):
Connection(s), size_buffer_(2, 0), string_buffer_(100, 0) {
}
void Client::StartJob() {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << " start job" << endl;
async_read(socket, buffer(size_buffer_),
strand_.wrap(bind(&Client::AfterReadSize, shared_from_this(), _1)));
}
void Client::CloseSocket() {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << " close socket" << endl;
Connection::CloseSocket();
}
Client::~Client() {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << " ~client" << endl;
CloseSocket();
}
void Client::AfterReadString(error_code const& ec, uint16_t size) {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << " enter AfterReadString" << endl;
if (ec) {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << ec.message() << endl;
return;
}
string str(string_buffer_.begin(), string_buffer_.begin() + size);
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << " str:" << str << endl;
size_buffer_.assign(2, 0);
uint16_t s = Int16ToBigEndian(size);
memcpy(&size_buffer_[0], &s, 2);
async_write(socket, buffer(size_buffer_),
strand_.wrap(bind(&Client::AfterSendSize, shared_from_this(), _1, s)));
}
void Client::AfterReadSize(error_code const& ec) {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << " enter AfterReadSize" << endl;
if (ec) {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << ec.message() << endl;
return;
}
uint16_t size = BigEndianBytesToInt16<uint16_t>(size_buffer_);
if (size > 0) {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << " correct size received, size:" << size << endl;
string_buffer_.assign(100, 0);
async_read(socket, buffer(string_buffer_, size),
strand_.wrap(bind(&Client::AfterReadString, shared_from_this(), _1, size)));
} else {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << " wrong size received, size:" << size << endl;
CloseSocket();
}
}
void Client::AfterSendSize(error_code const& ec, uint16_t size) {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << " enter AfterSendSize" << endl;
if (ec) {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << ec.message() << endl;
return;
}
async_write(socket, buffer(string_buffer_, size),
strand_.wrap(bind(&Client::AfterSendString, shared_from_this(), _1, size)));
}
void Client::AfterSendString(error_code const& ec, uint16_t size) {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << " enter AfterSendString" << endl;
if (ec) {
BOOSTER_INFO("Client") << "thread id: " << this_thread::get_id() << ec.message() << endl;
return;
}
size_buffer_.assign(2, 0);
async_read(socket, buffer(size_buffer_),
strand_.wrap(bind(&Client::AfterReadSize, shared_from_this(), _1)));
}
为完整起见,client.h文件代码也贴一下:
#ifndef BUSINESS_CLIENT_H_
#define BUSINESS_CLIENT_H_
#include "core/connection.h"
#include <vector>
using namespace std;
class Client: public Connection<Client> {
public:
Client(io_service& s);
~Client();
void StartJob();
void CloseSocket();
void AfterReadSize(error_code const& ec);
void AfterReadString(error_code const& ec, uint16_t size);
void AfterSendSize(error_code const& ec, uint16_t size);
void AfterSendString(error_code const& ec, uint16_t size);
private:
vector<uint8_t> size_buffer_;
vector<uint8_t> string_buffer_;
};
#endif
运行结果,没有失败,并发测试结果证明server是可靠的。