在這一章。咱們將會實現一個小的client/服務端應用。這可能會是你寫過的最簡單的client/服務端應用。回顯應用就是一個把client發過來的不論什麼內容回顯給其自己,而後關閉鏈接的的服務端。這個服務端可以處理不論什麼數量的client。每個client鏈接以後發送一個消息,服務端接收到完畢消息後把它發送回去。在那以後。服務端關閉鏈接。css
所以。每個回顯client鏈接到服務端,發送一個消息,而後讀取服務端返回的結果。確保這是它發送給服務端的消息就結束和服務端的會話。緩存
咱們首先實現一個同步應用。而後實現一個異步應用,以便你可以很easy對照他們:ruby
爲了節省空間,如下的代碼有一些被裁剪掉了。你可以在附加在這本書的代碼中看到所有的代碼。markdown
對於TCP而言。咱們需要一個額外的保證;每個消息以換行符結束(‘\n’)。編寫一個同步回顯服務端/client很簡單。異步
咱們會展現編碼內容,比方同步client,同步服務端。異步client和異步服務端。socket
在大多數有價值的樣例中。client一般比服務端編碼要簡單(因爲服務端需要處理多個client請求)。async
如下的代碼展現了不符合這條規則的一個例外:tcp
函數
size_t read_complete(char * buf, const error_code & err, size_t bytes)
{
if ( err) return 0;
bool found = std::find(buf, buf + bytes, '\n') < buf + bytes;
// 咱們一個一個讀取直到讀到回車,不緩存
return found ?0 : 1; } void sync_echo(std::string msg) { msg += "\n」; ip::tcp::socket sock(service); sock.connect(ep); sock.write_some(buffer(msg)); char buf[1024]; int bytes = read(sock, buffer(buf), boost::bind(read_complete,buf,_1,_2)); std::string copy(buf, bytes - 1); msg = msg.substr(0, msg.size() - 1); std::cout << "server echoed our " << msg << ": "<< (copy == msg ? "OK" : "FAIL") << std::endl; sock.close(); } int main(int argc, char* argv[]) { char* messages[] = { "John says hi", "so does James", "Lucy just got home", "Boost.Asio is Fun!", 0 }; boost::thread_group threads; for ( char ** message = messages; *message; ++message) { threads.create_thread( boost::bind(sync_echo, *message)); boost::this_thread::sleep( boost::posix_time::millisec(100)); } threads.join_all(); }
核心功能sync_echo。工具
它包括了鏈接到服務端。發送信息而後等待回顯的所有邏輯。
你會發現,在讀取時,我使用了自由函數read(),因爲我想要讀’\n’以前的所有內容。sock.read_some()方法知足不了這個要求,因爲它僅僅會讀可用的,而不是所有的消息。
read()方法的第三個參數是完畢處理句柄。當讀取到完整消息時,它返回0。不然,它會返回我下一步(直到讀取結束)能都到的最大的緩衝區大小。
在咱們的樣例中。返回結果始終是1,因爲我永遠不想讀的消息比咱們需要的不少其它。
在main()中,咱們建立了幾個線程;每個線程負責把消息發送到client,而後等待操做結束。
假設你執行這個程序,你會看到如下的輸出:
server echoed our John says hi: OK
server echoed our so does James: OK
server echoed our Lucy just got home: OK
server echoed our Boost.Asio is Fun!: OK
注意:因爲咱們是同步的,因此不需要調用service.run()。
回顯同步服務端的編寫很easy。參考例如如下的代碼片斷:
io_service service;
size_t read_complete(char * buff, const error_code & err, size_t bytes) {
if ( err) return 0;
bool found = std::find(buff, buff + bytes, '\n') < buff + bytes;
// 咱們一個一個讀取直到讀到回車,不緩存
return found ? 0 : 1;
}
void handle_connections() {
ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(),8001));
char buff[1024];
while ( true) {
ip::tcp::socket sock(service);
acceptor.accept(sock);
int bytes = read(sock, buffer(buff), boost::bind(read_complete,buff,_1,_2));
std::string msg(buff, bytes);
sock.write_some(buffer(msg));
sock.close();
}
}
int main(int argc, char* argv[]) {
handle_connections();
}
服務端的邏輯主要在handle_connections()。因爲是單線程。咱們接受一個client請求。讀取它發送給咱們的消息,而後回顯。而後等待下一個鏈接。可以肯定。當兩個client同一時候鏈接時,第二個client需要等待服務端處理完第一個client的請求。
仍是要注意因爲咱們是同步的,因此不需要調用service.run()。
當咱們開始異步時。編碼會變得略微有點複雜。
咱們會構建在第二章 保持活動中展現的connection類。
觀察這個章節中接下來的代碼,你會發現每個異步操做啓動了新的異步操做。以保持service.run()一直工做。
首先,核心功能例如如下:
#define MEM_FN(x) boost::bind(&self_type::x, shared_from_this())
#define MEM_FN1(x,y) boost::bind(&self_type::x, shared_from_this(),y)
#define MEM_FN2(x,y,z) boost::bind(&self_type::x, shared_from_this(),y,z)
class talk_to_svr : public boost::enable_shared_from_this<talk_to_svr> , boost::noncopyable {
typedef talk_to_svr self_type;
talk_to_svr(const std::string & message) : sock_(service), started_(true), message_(message) {}
void start(ip::tcp::endpoint ep) {
sock_.async_connect(ep, MEM_FN1(on_connect,_1));
}
public:
typedef boost::system::error_code error_code;
typedef boost::shared_ptr<talk_to_svr> ptr;
static ptr start(ip::tcp::endpoint ep, const std::string &message) {
ptr new_(new talk_to_svr(message));
new_->start(ep);
return new_;
}
void stop() {
if ( !started_) return;
started_ = false;
sock_.close();
}
bool started() { return started_; }
...
private:
ip::tcp::socket sock_;
enum { max_msg = 1024 };
char read_buffer_[max_msg]; char write_buffer_[max_msg]; bool started_; std::string message_; };
咱們需要一直使用指向talk_to_svr的智能指針。這種話當在tack_to_svr的實例上有異步操做時,那個實例是一直活動的。
爲了不錯誤。比方在棧上構建一個talk_to_svr對象的實例時,我把構造方法設置成了私有而且不一樣意拷貝構造(繼承自boost::noncopyable)。
咱們有了核心方法,比方start(),stop()和started(),它們所作的事情也正如它們名字表達的同樣。假設需要創建鏈接,調用talk_to_svr::start(endpoint, message)就能夠。咱們同一時候另外一個read緩衝區和一個write緩衝區。(read_buufer_和write_buffer_)。
MEM_FN 是一個方便使用的宏,它們經過*shared_ptr_from_this()方法強制使用一個指向* this 的智能指針。
如下的幾行代碼和以前的解釋很不一樣:
//等同於 "sock_.async_connect(ep, MEM_FN1(on_connect,_1));"
sock_.async_connect(ep,boost::bind(&talk_to_svr::on_connect,shared_ptr_from_this(),_1));
sock_.async_connect(ep, boost::bind(&talk_to_svr::on_connect,this,_1));
在上述樣例中。咱們正確的建立了async_connect的完畢處理句柄;在調用完畢處理句柄以前它會保留一個指向talk_to_server實例的智能指針,從而保證當其發生時talk_to_server實例仍是保持活動的。
在接下來的樣例中,咱們錯誤地建立了完畢處理句柄。當它被調用時。talk_to_server實例極可能已經被釋放了。
從socket讀取或寫入時,你使用例如如下的代碼片斷:
void do_read() {
async_read(sock_, buffer(read_buffer_), MEM_FN2(read_complete,_1,_2), MEM_FN2(on_read,_1,_2));
}
void do_write(const std::string & msg) {
if ( !started() ) return;
std::copy(msg.begin(), msg.end(), write_buffer_);
sock_.async_write_some( buffer(write_buffer_, msg.size()), MEM_FN2(on_write,_1,_2));
}
size_t read_complete(const boost::system::error_code & err, size_t bytes) {
// 和TCPclient中的類似
}
do_read()方法會保證當on_read()被調用的時候,咱們從服務端讀取一行。do_write()方法會先把信息複製到緩衝區(考慮到當async_write發生時msg可能已經超出範圍被釋放),而後保證明際的寫入操做發生時on_write()被調用。
而後是最重要的方法,這種方法包括了類的主要邏輯:
void on_connect(const error_code & err) {
if ( !err) do_write(message_ + "\n");
else stop();
}
void on_read(const error_code & err, size_t bytes) {
if ( !err) {
std::string copy(read_buffer_, bytes - 1);
std::cout << "server echoed our " << message_ << ": " << (copy == message_ ? "OK" : "FAIL") << std::endl;
}
stop();
}
void on_write(const error_code & err, size_t bytes) {
do_read();
}
當鏈接成功以後。咱們發送消息到服務端,do_write()。當write操做結束時,on_write()被調用,它初始化了一個do_read()方法。當do_read()完畢時。on_read()被調用;這裏,咱們簡單的檢查一下返回的信息是不是服務端的回顯。而後退出服務。
咱們會發送三個消息到服務端讓它變得更有趣一點:
int main(int argc, char* argv[]) {
ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001);
char* messages[] = { "John says hi", "so does James", "Lucy got home", 0 };
for ( char ** message = messages; *message; ++message) {
talk_to_svr::start( ep, *message);
boost::this_thread::sleep( boost::posix_time::millisec(100));
}
service.run();
}
上述的代碼會生成例如如下的輸出:
server echoed our John says hi: OK
server echoed our so does James: OK
server echoed our Lucy just got home: OK
核心功能和同步服務端的功能類似,例如如下:
class talk_to_client : public boost::enable_shared_from_this<talk_to_
client>, boost::noncopyable {
typedef talk_to_client self_type;
talk_to_client() : sock_(service), started_(false) {}
public:
typedef boost::system::error_code error_code;
typedef boost::shared_ptr<talk_to_client> ptr;
void start() {
started_ = true;
do_read();
}
static ptr new_() {
ptr new_(new talk_to_client);
return new_;
}
void stop() {
if ( !started_) return;
started_ = false;
sock_.close();
}
ip::tcp::socket & sock() { return sock_;}
...
private:
ip::tcp::socket sock_;
enum { max_msg = 1024 };
char read_buffer_[max_msg]; char write_buffer_[max_msg]; bool started_; };
因爲咱們是很簡單的回顯服務,這裏不需要is_started()方法。對每個client。僅僅讀取它的消息,回顯,而後關閉它。
do_read(),do_write()和read_complete()方法和TCP同步服務端的全然一致。
基本的邏輯相同是在on_read()和on_write()方法中:
void on_read(const error_code & err, size_t bytes) {
if ( !err) {
std::string msg(read_buffer_, bytes);
do_write(msg + "\n");
}
stop();
}
void on_write(const error_code & err, size_t bytes) {
do_read();
}
對client的處理例如如下:
ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(),8001));
void handle_accept(talk_to_client::ptr client, const error_code & err)
{
client->start();
talk_to_client::ptr new_client = talk_to_client::new_();
acceptor.async_accept(new_client->sock(), boost::bind(handle_accept,new_client,_1));
}
int main(int argc, char* argv[]) { talk_to_client::ptr client = talk_to_client::new_(); acceptor.async_accept(client->sock(), boost::bind(handle_accept,client,_1)); service.run(); }
每一次client鏈接到服務時,handle_accept被調用,它會異步地從client讀取。而後相同異步地等待一個新的client。
你會在這本書對應的代碼中獲得所有4個應用(TCP回顯同步client,TCP回顯同步服務端,TCP回顯異步client,TCP回顯異步服務端)。
當測試時。你可以使用隨意client/服務端組合(比方,一個異步client和一個同步服務端)。
因爲UDP不能保證所有信息都抵達接收者。咱們不能保證「信息以回車結尾」。
沒收到消息,咱們僅僅是回顯,但是沒有socket去關閉(在服務端)。因爲咱們是UDP。
UDP回顯client比TCP回顯client要簡單:
ip::udp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001);
void sync_echo(std::string msg) {
ip::udp::socket sock(service, ip::udp::endpoint(ip::udp::v4(), 0));
sock.send_to(buffer(msg), ep);
char buff[1024];
ip::udp::endpoint sender_ep;
int bytes = sock.receive_from(buffer(buff), sender_ep);
std::string copy(buff, bytes);
std::cout << "server echoed our " << msg << ": " << (copy == msg ?"OK" : "FAIL") << std::endl; sock.close(); } int main(int argc, char* argv[]) { char* messages[] = { "John says hi", "so does James", "Lucy got home", 0 }; boost::thread_group threads; for ( char ** message = messages; *message; ++message) { threads.create_thread( boost::bind(sync_echo, *message)); boost::this_thread::sleep( boost::posix_time::millisec(100)); } threads.join_all(); }
所有的邏輯都在synch_echo()中。鏈接到服務端。發送消息,接收服務端的回顯,而後關閉鏈接。
UDP回顯服務端會是你寫過的最簡單的服務端:
io_service service;
void handle_connections() {
char buff[1024];
ip::udp::socket sock(service, ip::udp::endpoint(ip::udp::v4(), 8001));
while ( true) {
ip::udp::endpoint sender_ep;
int bytes = sock.receive_from(buffer(buff), sender_ep);
std::string msg(buff, bytes);
sock.send_to(buffer(msg), sender_ep);
}
}
int main(int argc, char* argv[]) {
handle_connections();
}
它很簡單。而且能很好的自釋。
我把異步UDPclient和服務端留給讀者看成一個練習。
咱們已經寫了完整的應用。終於讓Boost.Asio得以工做。
回顯應用是開始學習一個庫時很好的工具。你可以經常學習和執行這個章節所展現的代碼,這樣你就可以很easy地記住這個庫的基礎。
在下一章。咱們會創建更復雜的client/服務端應用,咱們要確保避免低級錯誤,比方內存泄漏,死鎖等等。