This article should be the last one. Provide a complete code that can be used in the product environment.
Modify the code logic to make the server an echo server. After receiving the 16-byte length, the string is accepted. Then return the 16-byte length to the client, and then return the string.
The 16-byte length type is unsigned short, which adopts the big-Endian byte sequence during network transmission. The string is encoded in UTF-8 format.
Let's take a look at my newlisp client simulation program:
(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 Chen Jing "))) (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)
Note:
1. If you are interested in using newlistp for TCP communication, you can refer to my another article: http://blog.csdn.net/sheismylife/article/details/8521748
2. dotimes is a loop. The value range of I is [0, 2000) (Left closed and right open). A process is continuously created and each process runs the send-test function.
Now let's take a look at the changed server code:
1. to output some information in a multi-threaded environment, cout is not applicable because it is not thread-safe. Booster: log is introduced here. For details, refer to my article:
Http://blog.csdn.net/sheismylife/article/details/8248663
2. To prove the role of the thread pool in ASIO, the log records the thread ID.
3. Because big-Endian is involved, related algorithms are implemented by yourself.
Let's take a look at the src/cmakelists.txt file, which contains the booster library.
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)
Then let's take a look at main. CC, which uses log
#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(); }}
Now let's take a look at the server. h file, which also uses 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
The connection. h file is also changed. logs are added and exceptions of socket closure are blocked.
#ifndef CORE_CONNECTION_H_#defineCORE_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
Now let's take a look at the new util/endian. h file, which contains the big-Endian algorithm:
#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 0template<class T>int Bit_Value(T value, uint8_t index) { return (value & (1 << index)) == 0 ? 0 : 1;}// T must be one of integer typetemplate<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 necessarytemplate<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 platformtemplate<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
Finally, let's take a look at the code of the client. CC file to truly implement the business of the echo server:
#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))); }
For completeness, the client. h file code is also pasted:
#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
The running result does not fail. The concurrent test result proves that the server is reliable.