標籤:style blog http color io os ar 使用 for
前面幾節我們討論了非阻塞IO的基本概念、Buffer的設計以及非阻塞connect的實現,現在我們使用它們來完成用戶端的編寫。
我們在http://www.cnblogs.com/inevermore/p/4049165.html中提出過,用戶端需要監聽stdin、stdout和sockfd。
這裡需要注意的是
只有緩衝區可寫的時候,才去監聽sockfd和stdin的讀事件。
過去在阻塞IO中,我們總是監聽sockfd的讀事件,因為每當sockfd可讀,我們就去調用使用者的回呼函數處理read事件,在回呼函數中需要使用者手工read緩衝區的資料。 換句話說,接收資料是使用者的責任,poll模型只需要提醒使用者去接收即可。
而在非阻塞IO中,因為poll採用的是水平觸發,如果緩衝區滿了,每次read等於無效操作,那麼資料始終堆積在核心中,poll會不停的被觸發。這在某種程度上等於輪詢。所以我們只在緩衝區可用的情況下監聽sockfd的讀事件。
只有緩衝區可讀的時候,才去監聽sockfd和stdout的寫事件。因為沒有資料可寫,監聽write事件除了不停的觸發poll之外,沒有實際意義。
所以每次執行poll之前,需要重新裝填poll的events數組。
完整的代碼如下:
#include "sysutil.h"#include "buffer.h"int main(int argc, char const *argv[]){ //建立client通訊端 int sockfd = tcp_client(8934); //調用非阻塞connect函數 int ret = nonblocking_connect(sockfd, "192.168.44.136", 9981, 5000); if(ret == -1) { fprintf(stderr, "Timeout .\n"); exit(EXIT_FAILURE); } //將三個fd設定為Non-Blocking activate_nonblock(sockfd); activate_nonblock(STDIN_FILENO); activate_nonblock(STDOUT_FILENO); buffer_t recvbuf; //sockfd -> Buffer -> stdout buffer_t sendbuf; //stdin -> Buffer -> sockfd //初始化緩衝區 buffer_init(&recvbuf); buffer_init(&sendbuf); struct pollfd pfd[10]; while(1) { //初始化 int ix; for(ix = 0; ix != 3; ++ix) { pfd[ix].fd = -1; pfd[ix].events = 0; } //重新裝填events數組 if(buffer_is_readable(&sendbuf)) { pfd[0].fd = sockfd; pfd[0].events |= kWriteEvent; } if(buffer_is_writeable(&sendbuf)) { pfd[1].fd = STDIN_FILENO; pfd[1].events |= kReadEvent; } if(buffer_is_readable(&recvbuf)) { pfd[2].fd = STDOUT_FILENO; pfd[2].events |= kWriteEvent; } if(buffer_is_writeable(&recvbuf)) { pfd[0].fd = sockfd; pfd[0].events |= kReadEvent; } //監聽fd數組 int nready = poll(pfd, 3, 5000); if(nready == -1) ERR_EXIT("poll"); else if(nready == 0) { printf("timeout\n"); continue; } else { int i; for(i = 0; i < 3; ++i) { int fd = pfd[i].fd; if(fd == sockfd && pfd[i].revents & kReadEvent) { //從sockfd接收資料到recvbuf if(buffer_read(&recvbuf, fd) == 0) { fprintf(stderr, "server close.\n"); exit(EXIT_SUCCESS); } } if(fd == sockfd && pfd[i].revents & kWriteEvent) buffer_write(&sendbuf, fd); //將sendbuf中的資料寫入sockfd if(fd == STDIN_FILENO && pfd[i].revents & kReadEvent) { //從stdin接收資料寫入sendbuf if(buffer_read(&sendbuf, fd) == 0) { fprintf(stderr, "exit.\n"); exit(EXIT_SUCCESS); } } if(fd == STDOUT_FILENO && pfd[i].revents & kWriteEvent) buffer_write(&recvbuf, fd); //將recvbuf中的資料輸出至stdout } } }}
從以上的代碼可以看出,大部分操作被封裝進了buffer的實現中。
測試伺服器,我暫時使用muduo庫編寫一個,代碼如下:
#include <muduo/net/TcpServer.h>#include <muduo/net/InetAddress.h>#include <muduo/net/TcpConnection.h>#include <muduo/base/Timestamp.h>#include <muduo/net/EventLoop.h>#include <muduo/base/Logging.h>using namespace muduo;using namespace muduo::net;void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp t){ string s(buf->retrieveAllAsString()); LOG_INFO << "recv msg : " << s.size() << " at: " << t.toFormattedString(); conn->send(s);}int main(int argc, char const *argv[]){ EventLoop loop; InetAddress addr("192.168.44.136", 9981); TcpServer server(&loop, addr, "EchoServer"); server.setMessageCallback(&onMessage); server.start(); loop.loop(); return 0;}
讀者如果使用上述的代碼需要安裝muduo網路程式庫。
採用以下命令編譯:
g++ server.cpp -lmuduo_net -lmuduo_base -lpthread -o server
下文用poll實現非阻塞的伺服器端。
Linux非阻塞IO(五)使用poll實現非阻塞的回射伺服器用戶端