In this guide, we will build a server that echo "Hello, kingpin!" to the client and then close the connection. Just several codes.
Implement a IOHandler, this could tell the thread what to do when handling a connection. We just echo the string and close the connection when write completes.
template<typename _Data>
class SimpleHandler : public IOHandlerForServer<_Data> {
public:
SimpleHandler(_Data *d) : IOHandlerForServer<_Data>(d) {}
void onConnect(int conn) { this->writeToBuffer(conn, "Hello, kingpin!"); }
void onWriteComplete(int conn) { ::close(conn); }
};
The template _Data refers to the data shared between threads. This template is essential for the IOHandler.
Define the data shared between threads. Since no EXTRA DATA to be shared in the simple demo, just derive from the base class (or use the base class).
class SharedData : public TPSharedDataForServer {};
Use 8 threads to handle the connnections, and listen in 8888 port.
int main() {
SharedData data;
data._port = 8888;
EpollTPServer<SimpleHandler, SharedData> server(8, &data);
server.run();
return 0;
}
Since as long as the server alives, the thread pool alives, so the data shared between TP shall have the same life cycle as the server. Usually we place them in the main function's stack.
Use netcat to connect.
$ nc localhost 8888
Hello, kingpin!
Ncat: Broken pipe.
Now let's use the client to build a crawler just send a simple HTTP request.
This step is always essential for you thread pool. We just print the page we crawled.
template<typename _Data>
class CrawlerHandler : public IOHandlerForClient<_Data> {
public:
CrawlerHandler(_Data *d) : IOHandlerForClient<_Data>(d) {}
void _print(int conn) {
if (0 == this->_rbh[conn]->_offset) return;
INFO << "Host of socket " << conn << ": " << this->_conn_info[conn].first
<< "\nMessage of socket " << conn << ":\n"
<< this->_rbh[conn]->_buffer << END;
this->_rbh[conn]->clear();
}
void onMessage(int conn) { _print(conn); }
void onPassivelyClosed(int conn) { _print(conn); }
};
In this simple crawler, we just use the default structure for client.
Since our client will not just support crawler but also concurrency test. Our connection pool is identified by a tuple (ip, port, init_message). We could use the raw_add to add our data to the pool shared by crawler.Then Use 2 threads to handle the connnections.
int main() {
TPSharedDataForClient data;
EpollTPClient<CrawlerHandler, TPSharedDataForClient> crawler(2, &data);
vector<string> hosts = { "www.taobao.com", "www.bytedance.com", "www.baidu.com" };
for (auto &h : hosts) {
string ip = getHostIp(h.c_str());
const char *request = "GET /TEST HTTP/1.1\r\n\r\n";
data.raw_add(ip, 80, request);
}
crawler.run();
return 0;
}
Override 5 functions for IOHandlerForServer:
-
void onEpollLoop() : Epoll loop starts
-
void onConnect(int conn) : Socket accepted
-
void onMessage(int conn) : Message arrives, has been read automatically to read buffer
-
void onWriteComplete(int conn) : Async write finished
-
void onPassivelyClosed(int conn) : Oppo closed socket or crash
Override 6 functions for IOHandlerForClient:
-
void onEpollLoop() : Epoll loop starts
-
void onConnect(int conn) : Connection established
-
void onConnectFailed(int conn) : Connect to host failed
-
void onMessage(int conn) : Message arrives, has been read automatically to read buffer
-
void onWriteComplete(int conn) : Async write finished
-
void onPassivelyClosed(int conn) : Oppo closed socket or crash