muduo網路庫學習之EventLoop(三):Socket、Acceptor、TcpServer、TcpConnection(連線建立,接收訊息)
1、Socket 操作封裝
Endian.h
封裝了位元組序轉換函式(全域性函式,位於muduo::net::sockets名稱空間中)。
SocketsOps.h/ SocketsOps.cc
封裝了socket相關係統呼叫(全域性函式,位於muduo::net::sockets名稱空間中)。
Socket.h/Socket.cc(Socket類)
用RAII方法封裝socket file descriptor
InetAddress.h/InetAddress.cc(InetAddress類)
網際地址sockaddr_in封裝
2、Acceptor
Acceptor的資料成員包括acceptSocket_、acceptChannel_,Acceptor的acceptSocket_是listening socket(即server socket)。
acceptChannel_用於觀察acceptSocket_的readable事件,可讀事件發生,Channel::handleEvent()中回撥Acceptor::handleRead(),
後者呼叫accept(2)來接受新連線,並回撥使用者callback,注意callback 中傳入的第一個引數是accept返回的connfd。
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
void Acceptor::handleRead()
{ loop_->assertInLoopThread(); InetAddress peerAddr(0); //FIXME loop until no more int connfd = acceptSocket_.accept(&peerAddr); if (connfd >= 0) { // string hostport = peerAddr.toIpPort(); // LOG_TRACE << "Accepts of " << hostport; if (newConnectionCallback_) { newConnectionCallback_(connfd, peerAddr); } else { sockets::close(connfd); } } else { // Read the section named "The special problem of // accept()ing when you can't" in libev's doc. // By Marc Lehmann, author of libev. if (errno == EMFILE) { ::close(idleFd_); idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL); ::close(idleFd_); idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC); } } } |
在建構函式中:
C++ Code
1
2 |
acceptChannel_.setReadCallback(
boost::bind(&Acceptor::handleRead, this)); |
設定使用者回撥函式:
C++ Code
1
2 3 4 5 6 7 8 |
// 傳入connfd
typedef boost::function < void (int sockfd, const InetAddress &) > NewConnectionCallback; void setNewConnectionCallback(const NewConnectionCallback &cb) { newConnectionCallback_ = cb; } |
開始監聽:
C++ Code
1
2 3 4 5 6 7 |
void Acceptor::listen()
{ loop_->assertInLoopThread(); listenning_ = true; acceptSocket_.listen(); acceptChannel_.enableReading(); } |
測試程式碼:
simba@ubuntu:~$ telnet 127.0.0.1 8888
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#include <muduo/net/Acceptor.h>
#include <muduo/net/EventLoop.h> #include <muduo/net/InetAddress.h> #include <muduo/net/SocketsOps.h> #include <stdio.h> using namespace muduo; using namespace muduo::net; void newConnection(int sockfd, const InetAddress &peerAddr) { printf("newConnection(): accepted a new connection from %s\n", peerAddr.toIpPort().c_str()); ::write(sockfd, "How are you?\n", 13); sockets::close(sockfd); } int main() { printf("main(): pid = %d\n", getpid()); InetAddress listenAddr(8888); EventLoop loop; Acceptor acceptor(&loop, listenAddr); acceptor.setNewConnectionCallback(newConnection); acceptor.listen(); loop.loop(); } |
使用telnet 連線伺服器,伺服器輸出如下:
simba@ubuntu:~/Documents/build/debug/bin$ ./reactor_test07
20131108 07:22:30.560145Z 3960 TRACE IgnoreSigPipe Ignore SIGPIPE - EventLoop.cc:51
main(): pid = 3960
20131108 07:22:30.675116Z 3960 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20131108 07:22:30.675684Z 3960 TRACE EventLoop EventLoop created 0xBFED7324 in thread 3960 - EventLoop.cc:76
20131108 07:22:30.676073Z 3960 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
20131108 07:22:30.676577Z 3960 TRACE updateChannel fd = 6 events = 3 - EPollPoller.cc:104
20131108 07:22:30.676988Z 3960 TRACE loop EventLoop 0xBFED7324 start looping - EventLoop.cc:108
20131108 07:22:40.687957Z 3960 TRACE poll nothing happended - EPollPoller.cc:74
20131108 07:22:41.606525Z 3960 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 07:22:41.607053Z 3960 TRACE printActiveChannels {6: IN} - EventLoop.cc:271
newConnection(): accepted a new connection from 127.0.0.1:56409
20131108 07:22:51.617500Z 3960 TRACE poll nothing happended - EPollPoller.cc:74
20131108 07:22:30.560145Z 3960 TRACE IgnoreSigPipe Ignore SIGPIPE - EventLoop.cc:51
main(): pid = 3960
20131108 07:22:30.675116Z 3960 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20131108 07:22:30.675684Z 3960 TRACE EventLoop EventLoop created 0xBFED7324 in thread 3960 - EventLoop.cc:76
20131108 07:22:30.676073Z 3960 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
20131108 07:22:30.676577Z 3960 TRACE updateChannel fd = 6 events = 3 - EPollPoller.cc:104
20131108 07:22:30.676988Z 3960 TRACE loop EventLoop 0xBFED7324 start looping - EventLoop.cc:108
20131108 07:22:40.687957Z 3960 TRACE poll nothing happended - EPollPoller.cc:74
20131108 07:22:41.606525Z 3960 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 07:22:41.607053Z 3960 TRACE printActiveChannels {6: IN} - EventLoop.cc:271
newConnection(): accepted a new connection from 127.0.0.1:56409
20131108 07:22:51.617500Z 3960 TRACE poll nothing happended - EPollPoller.cc:74
telnet 端輸出如下:
simba@ubuntu:~$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
How are you?
Connection closed by foreign host.
simba@ubuntu:~$
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
How are you?
Connection closed by foreign host.
simba@ubuntu:~$
從輸出可以看出,acceptSocket_.sockfd_ = 6,客戶端連線上來,監聽套接字發生可讀事件,呼叫accept() 接收連線後呼叫使用者回撥函式newConnection()。
3、TcpServer/TcpConnection
Acceptor類的主要功能是socket、bind、listen
一般來說,在上層應用程式中,我們不直接使用Acceptor,而是把它作為TcpServer的成員
C++ Code
1
|
boost::scoped_ptr<Acceptor> acceptor_; // avoid revealing Acceptor
|
TcpServer還包含了一個TcpConnection列表
C++ Code
1
2 3 |
typedef boost::shared_ptr<TcpConnection> TcpConnectionPtr;
typedef std::map<string, TcpConnectionPtr> ConnectionMap; ConnectionMap connections_; // 連線列表 |
此外,還有一個IO執行緒池物件和一個acceptor Eventloop*, 通過setThreadNum()設定IO執行緒池的執行緒個數(不包括main Reactor)
關於EventLoopThread, EventLoopThreadPool 類參見這裡。
C++ Code
1
|
boost::scoped_ptr<EventLoopThreadPool> threadPool_;
EventLoop* loop_; // the acceptor loop
|
C++ Code
1
2 3 4 5 |
void TcpServer::setThreadNum(int numThreads)
{ assert(0 <= numThreads); threadPool_->setThreadNum(numThreads); } |
TcpConnection與Acceptor類似,有兩個重要的資料成員,Socket(connfd)與Channel
C++ Code
1
2 |
boost::scoped_ptr<Socket> socket_;
boost::scoped_ptr<Channel> channel_; |
時序圖分析:
在TcpServer 建構函式中先初始化acceptor_成員,acceptor_(new Acceptor(loop, listenAddr)),在建構函式體內:
C++ Code
1
2 3 4 |
// Acceptor::handleRead函式中會回撥用TcpServer::newConnection
// _1對應的是socket檔案描述符,_2對應的是對等方的地址(InetAddress) acceptor_->setNewConnectionCallback( boost::bind(&TcpServer::newConnection, this, _1, _2)); |
呼叫TcpServer::start(),開始Acceptor::listen(), 已連線佇列不為空,TcpServer::acceptor_.acceptChannel_ 可讀,poll返回,呼叫
Channel::handleEvent()處理活動通道,呼叫Acceptor::handleRead(),函式中呼叫accept(2)來接受新連線,並回撥TcpServer::newConnection(), 函式中先建立一個TcpConnectionPtr
物件,在TcpConnection 建構函式體中:
C++ Code
1
2 3 |
// 通道可讀事件到來的時候,回撥TcpConnection::handleRead,_1是事件發生時間
channel_->setReadCallback( boost::bind(&TcpConnection::handleRead, this, _1)); |
新增進TcpServer::connections_, 設定連線回撥函式和訊息到來回撥函式,如下:
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 |
//傳入connfd
void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr) { ...... TcpConnectionPtr conn(new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr)); connections_[connName] = conn; // conn 是TcpConnectionPtr 物件 conn->setConnectionCallback(connectionCallback_); conn->setMessageCallback(messageCallback_); conn->connectEstablished(); } |
最後呼叫TcpConnection::connectEstablished()
C++ Code
1
2 3 4 5 6 |
void TcpConnection::connectEstablished()
{ channel_->enableReading(); // TcpConnection所對應的通道加入到Poller關注 connectionCallback_(shared_from_this()); } |
現在已經建立了一個新連線,對等方傳送資料到connfd,核心接收緩衝區不為空,TcpConnection::channel_ 可讀事件發生,poll返回,呼叫Channel::handleEvent()處理活動通道,呼叫TcpConnection::handleRead()
C++ Code
1
2 3 4 5 |
void TcpConnection::handleRead(Timestamp receiveTime)
{ ssize_t n = ::read(channel_->fd(), buf, sizeof buf); messageCallback_(shared_from_this(), buf, n); } |
shared_from_this() 會用當前物件的裸指標構造一個臨時智慧指標物件,引用計數加1,但馬上會被析構,又減1,故無論呼叫多少次,對引用計數都沒有影響。
測試程式:
simba@ubuntu:~$ telnet 127.0.0.1 8888
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h> #include <muduo/net/InetAddress.h> #include <stdio.h> using namespace muduo; using namespace muduo::net; void onConnection(const TcpConnectionPtr &conn) { if (conn->connected()) { printf("onConnection(): new connection [%s] from %s\n", conn->name().c_str(), conn->peerAddress().toIpPort().c_str()); } else { printf("onConnection(): connection [%s] is down\n", conn->name().c_str()); } } void onMessage(const TcpConnectionPtr &conn, const char *data, ssize_t len) { printf("onMessage(): received %zd bytes from connection [%s]\n", len, conn->name().c_str()); } int main() { printf("main(): pid = %d\n", getpid()); InetAddress listenAddr(8888); EventLoop loop; TcpServer server(&loop, listenAddr, "TestServer"); server.setConnectionCallback(onConnection); server.setMessageCallback(onMessage); server.start(); loop.loop(); } |
simba@ubuntu:~/Documents/build/debug/bin$ ./reactor_test08
main(): pid = 7557
20131108 09:37:51.098888Z 7557 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20131108 09:37:51.099825Z 7557 TRACE EventLoop EventLoop created 0xBFAD3D08 in thread 7557 - EventLoop.cc:62
20131108 09:37:51.100692Z 7557 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
20131108 09:37:51.101548Z 7557 TRACE updateChannel fd = 6 events = 3 - EPollPoller.cc:104
20131108 09:37:51.102063Z 7557 TRACE loop EventLoop 0xBFAD3D08 start looping - EventLoop.cc:94
20131108 09:38:01.116672Z 7557 TRACE poll nothing happended - EPollPoller.cc:74
20131108 09:38:10.616161Z 7557 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 09:38:10.616774Z 7557 TRACE printActiveChannels {6: IN} - EventLoop.cc:257
20131108 09:38:10.616894Z 7557 INFO TcpServer::newConnection [TestServer] - new connection [TestServer:0.0.0.0:8888#1] from 127.0.0.1:56410 - TcpServer.cc:93
20131108 09:38:10.617007Z 7557 DEBUG TcpConnection TcpConnection::ctor[TestServer:0.0.0.0:8888#1] at 0x827D7F8 fd=8 - TcpConnection.cc:62
20131108 09:38:10.617103Z 7557 TRACE newConnection [1] usecount=1 - TcpServer.cc:111
20131108 09:38:10.617152Z 7557 TRACE newConnection [2] usecount=2 - TcpServer.cc:113
20131108 09:38:10.617166Z 7557 TRACE connectEstablished [3] usecount=6 - TcpConnection.cc:78
20131108 09:38:10.617174Z 7557 TRACE updateChannel fd = 8 events = 3 - EPollPoller.cc:104
onConnection(): new connection [TestServer:0.0.0.0:8888#1] from 127.0.0.1:56410
20131108 09:38:10.617266Z 7557 TRACE connectEstablished [4] usecount=6 - TcpConnection.cc:83
20131108 09:38:10.617275Z 7557 TRACE newConnection [5] usecount=2 - TcpServer.cc:122
20131108 09:38:20.627567Z 7557 TRACE poll nothing happended - EPollPoller.cc:74
20131108 09:38:30.638037Z 7557 TRACE poll nothing happended - EPollPoller.cc:74
20131108 09:38:40.648523Z 7557 TRACE poll nothing happended - EPollPoller.cc:74
20131108 09:38:46.891543Z 7557 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 09:38:46.891599Z 7557 TRACE printActiveChannels {8: IN } - EventLoop.cc:257
20131108 09:38:46.891611Z 7557 TRACE handleEvent [6] usecount=2 - Channel.cc:67
onMessage(): received 6 bytes from connection [TestServer:0.0.0.0:8888#1]
20131108 09:38:46.891744Z 7557 TRACE handleEvent [12] usecount=2 - Channel.cc:69
20131108 09:38:56.901306Z 7557 TRACE poll nothing happended - EPollPoller.cc:74
main(): pid = 7557
20131108 09:37:51.098888Z 7557 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20131108 09:37:51.099825Z 7557 TRACE EventLoop EventLoop created 0xBFAD3D08 in thread 7557 - EventLoop.cc:62
20131108 09:37:51.100692Z 7557 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
20131108 09:37:51.101548Z 7557 TRACE updateChannel fd = 6 events = 3 - EPollPoller.cc:104
20131108 09:37:51.102063Z 7557 TRACE loop EventLoop 0xBFAD3D08 start looping - EventLoop.cc:94
20131108 09:38:01.116672Z 7557 TRACE poll nothing happended - EPollPoller.cc:74
20131108 09:38:10.616161Z 7557 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 09:38:10.616774Z 7557 TRACE printActiveChannels {6: IN} - EventLoop.cc:257
20131108 09:38:10.616894Z 7557 INFO TcpServer::newConnection [TestServer] - new connection [TestServer:0.0.0.0:8888#1] from 127.0.0.1:56410 - TcpServer.cc:93
20131108 09:38:10.617007Z 7557 DEBUG TcpConnection TcpConnection::ctor[TestServer:0.0.0.0:8888#1] at 0x827D7F8 fd=8 - TcpConnection.cc:62
20131108 09:38:10.617103Z 7557 TRACE newConnection [1] usecount=1 - TcpServer.cc:111
20131108 09:38:10.617152Z 7557 TRACE newConnection [2] usecount=2 - TcpServer.cc:113
20131108 09:38:10.617166Z 7557 TRACE connectEstablished [3] usecount=6 - TcpConnection.cc:78
20131108 09:38:10.617174Z 7557 TRACE updateChannel fd = 8 events = 3 - EPollPoller.cc:104
onConnection(): new connection [TestServer:0.0.0.0:8888#1] from 127.0.0.1:56410
20131108 09:38:10.617266Z 7557 TRACE connectEstablished [4] usecount=6 - TcpConnection.cc:83
20131108 09:38:10.617275Z 7557 TRACE newConnection [5] usecount=2 - TcpServer.cc:122
20131108 09:38:20.627567Z 7557 TRACE poll nothing happended - EPollPoller.cc:74
20131108 09:38:30.638037Z 7557 TRACE poll nothing happended - EPollPoller.cc:74
20131108 09:38:40.648523Z 7557 TRACE poll nothing happended - EPollPoller.cc:74
20131108 09:38:46.891543Z 7557 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 09:38:46.891599Z 7557 TRACE printActiveChannels {8: IN } - EventLoop.cc:257
20131108 09:38:46.891611Z 7557 TRACE handleEvent [6] usecount=2 - Channel.cc:67
onMessage(): received 6 bytes from connection [TestServer:0.0.0.0:8888#1]
20131108 09:38:46.891744Z 7557 TRACE handleEvent [12] usecount=2 - Channel.cc:69
20131108 09:38:56.901306Z 7557 TRACE poll nothing happended - EPollPoller.cc:74
可以看到,fd = 6 是監聽套接字,fd = 8是返回來的已連線套接字,那麼fd = 7去哪了呢?其實是被acceptor的 idleFd_ 佔據了。
連線建立的時候回撥onConnection(),我們在telnet 上輸入aaaa,伺服器端訊息到來,fd=8可讀事件發生,回撥onMessage(),加上\r\n 所以收到6個位元組資料。
參考:
《UNP》
muduo manual.pdf
《linux 多執行緒伺服器程式設計:使用muduo c++網路庫》
相關文章
- muduo網路庫Timestamp類
- HC-25 連線HC web網路端測試tcpserverWebTCPServer
- 網路核心之TCP是如何傳送和接收訊息的TCP
- muduo網路庫Exception異常類Exception
- muduo網路庫編譯安裝編譯
- muduo網路庫AtomicIntegerT原子整數類
- 網路學習筆記(一):TCP連線的建立與關閉筆記TCP
- 菜鳥學網路之 —— 長連線和短連線
- python中socket建立客戶連線Python
- RabbitMQ學習(三)之 “訊息佇列高階使用”MQ佇列
- Android Socket連線,使用Socket進行通訊(Android)Android
- 全連線神經網路學習筆記神經網路筆記
- 深度學習與圖神經網路學習分享:訊息傳遞模式深度學習神經網路模式
- 怎麼建立網站連線資料庫網站資料庫
- 網路協議之:socket協議詳解之Socket和Stream Socket協議
- Socket連線和Http連線HTTP
- QNX學習 -- API之訊息傳遞API
- Dubbo原始碼解析之服務端接收訊息原始碼服務端
- Android 基於Netty的訊息推送方案之字串的接收和傳送(三)AndroidNetty字串
- Linux網路連線的三種方式Linux
- 滲透測試學習之探測和攻擊無線網路三
- Go Socket 連線Go
- 網路協議之:WebSocket的訊息格式協議Web
- 網路協議之:socket協議詳解之Datagram Socket協議
- netty建立數萬客戶端連線,並主動發訊息Netty客戶端
- 音視訊學習路線
- 達夢資料庫關於[-70028]:建立SOCKET連線失敗的錯誤原因資料庫
- 【中秋國慶不斷更】HarmonyOS網路管理開發—Socket連線
- Linux學習/TCP Socket通訊LinuxTCP
- socket通訊的建立
- 網路協議之:socket協議詳解之Unix domain Socket協議AI
- C/C++利用Boost::Asio網路庫建立自己的Socket伺服器C++伺服器
- Docker學習之搭建ActiveMQ訊息服務DockerMQ
- Socket網路程式設計基礎與實踐:建立高效的網路通訊程式設計
- Service Worker學習與實踐(三)——訊息推送
- Java微信公眾平臺開發(三)--接收訊息的分類及實體的建立Java
- Qt學習第三篇(訊號槽函式的連線)QT函式
- 資料庫學習線路圖資料庫
- Android連線網路資料庫的方式Android資料庫