성능과 사용 편의성이 모두있는 C++ NIO 네트워크 라이브러리입니다. C++14 이상을 지원하고 3 개의 주요 주류 플랫폼에 걸쳐 있습니다.
기본 IO 모델은 Muduo Network 라이브러리의 one loop per thread 빌려서 스레드-안전 API 캡슐화를보다 효율적이고 간단하게 만듭니다.
상단 계층이 제공하는 API 인터페이스는 Bytedance의 오픈 소스 Go Language Nio Network Library Netpoll에서 차용하고 Listener 와 Dialer 추상화하여 마침내 EventLoop 통해 서비스를 제공합니다.
이 라이브러리 사용 방법은 빠른 시작을 확인할 수 있습니다.
특정 사용 예는 예제를 참조하십시오.
이미 지원 :
windows/linux/macos 플랫폼은 플랫폼의 최고 성능 멀티플렉싱 구현 (IOCP에서 구현 된 epoll/epoll/kqueue)을 사용하여 구현됩니다.EventLoop 결합하여 비동기 호출을 구현합니다. 운영 체제가 sendfile 통화를 지원하는 경우 C 표준 라이브러리를 호출하는 대신 제로 카피 호출이 호출됩니다.echo 서버를 작성하려면 다음 코드 만 필요합니다. struct server
{
NETPOLL_TCP_MESSAGE (conn, buffer){conn-> send (buffer-> readAll ());}
};
int main ()
{
auto loop = netpoll::NewEventLoop ();
auto listener = netpoll::tcp::Listener::New ({ 6666 });
listener. bind <server>();
loop. serve (listener);
}100^8s 타이밍 100*8byte 만 필요할 수 있음). 자세한 내용은 블로그 : 자세한 소개를 확인하십시오.one loop per thread 와 매우 일치합니다 .LockFree_Queuefuture 반환합니다. netpoll::SignalTask::Register ({SIGINT,SIGTERM});
netpoll::SignalTask::Add ([](){
// do something
});앞으로 그것을 지원할 것입니다 .
성능은 매우 높으며 ASIO (C ++)/Netty (Java)/NetPoll (GO Language)을 일시적으로 테스트했습니다.
Windows 시스템에서 Asio/Netty를 테스트했으며 다음 차트 테스트는 Linux를 기반으로하며 Linux에 Java 프로그램을 배포하는 데별로 적합하지 않으므로 다음 차트에는 Netty의 성능이 없습니다.
다른 동시성 상황에서 단일 연결 에코 서비스의 평균 대기 시간은 다음과 같습니다 (AVG).
서버 측의 기본 모델 아키텍처 :
Listener 캡슐화는 TcpServer 의 사용을 단순화하며 모든 통화는 다음과 같습니다.
namespace netpoll ::tcp{
class Listener {
template < typename T>
void bind (Args &&...args); // 用于绑定任意类的对应方法到回调
template < typename T>
std::shared_ptr<T> instance () // 返回内部帮你创建的实例
static Listener New( const InetAddress &address,
const StringView &name = " tcp_listener " ,
bool reUseAddr = true , bool reUsePort = true ); // 建立Listener实例
void enableKickoffIdle ( size_t timeout); // 用于开启剔除空闲连接
}
} Dialer 캡슐화는 TcpClient 의 사용을 단순화하며 사용 된 통화는 다음과 같습니다.
namespace netpoll ::tcp{
class Dialer {
template < typename T>
void bind (Args &&...args); // 用于绑定任意类的对应方法到回调
template < typename T>
std::shared_ptr<T> instance () // 返回内部帮你创建的实例
static Dialer New( const InetAddress &address,
const StringView &name = " tcp_dialer " ); // 建立Listener实例
void enableRetry (); // 在连接失败后重试
// 以下调用方均是为了在没有C++17的if constexpr情况下的替代品,否则应该直接使用bind
template < typename T, typename ... Args>
static std::shared_ptr<T> Register (Args &&...args);
void onMessage (netpoll::RecvMessageCallback const &cb);
void onConnection (netpoll::ConnectionCallback const &cb);
void onConnectionError (netpoll::ConnectionErrorCallback const &cb);
void onWriteComplete (netpoll::WriteCompleteCallback const &cb);
}
} EventLoop 의로드 밸런싱 전략에는 별도의 설정이 없으며 모두 Round Robin 사용합니다.
EventLoop와 관련된 API는 다음과 같습니다.
NewEventLoop(size_t threadNum=2,const netpoll::StringView&name="eventloop") : eventLoop 인스턴스를 만들고 eventLoop의 스레드 수를 설정합니다.
serve 방법 : 다이얼러 또는 리스너를 새로 종료 한 후에는이 방법을 통해 서비스를 제공 할 수 있습니다.
serveAsDaemon Method : 효과는 Serve 메소드와 동일하지만 서빙 할 새 스레드를 열면 현재 스레드가 차단되지 않습니다.
enableQuit 방법 : 활성 호출로 종료 방법을 루프 할 수 있습니다. 기본적으로 모든 루프 스레드를 적극적으로 종료 할 수 없으며 QuitAllEventLoop 메소드와 함께 사용됩니다.
QuitAllEventLoop 메소드 : 모든 루프를 종료하십시오.
MessageBuffer는 커널 버퍼 데이터를 읽고 쓰는 중간 캐시입니다. 실제로 가변 버퍼이며 구현하기가 매우 간단합니다. 다양한 유형의 버퍼 구현에 대해서는 내 기사를 확인할 수 있습니다 : 가변 길이 및 불변 길이 버퍼 구현
나는 여기에 다양한 전화를 설명하지 않을 것입니다. 알고 싶다면 해당 헤더 파일 인 NetPoll/Util/Message_Buffer.h를 직접 볼 수 있습니다.
이 버퍼 구현의 하이라이트에 대해 간단히 이야기하겠습니다.
확장 할 때 크기 조정의 부작용을 피하면 메모리 개방 및 복사 작업을 단순화 할 수도 있습니다.
void MessageBuffer::ensureWritableBytes ( size_t len)
{
if ( writableBytes () >= len) return ;
// move readable bytes
if (m_head + writableBytes () >= (len + kBufferOffset ))
{
std::copy ( begin () + m_head, begin () + m_tail, begin () + kBufferOffset );
m_tail = kBufferOffset + (m_tail - m_head);
m_head = kBufferOffset ;
return ;
}
// create new buffer
size_t newLen;
if ((m_buffer. size () * 2 ) > ( kBufferOffset + readableBytes () + len))
newLen = m_buffer. size () * 2 ;
else newLen = kBufferOffset + readableBytes () + len;
// Avoid the inefficiency of using resize
MessageBuffer newbuffer (newLen);
newbuffer. pushBack (* this );
swap (newbuffer);
}해당 FD 읽기 버퍼의 데이터를 메시지 버퍼에 읽는 readfd 메소드를 제공합니다. 내용은 매번 읽습니다. 예를 들어, MessageBuffer Writable 영역이 8KB 미만인 경우 8KB 대체 읽기 캐시가 활성화됩니다.
ssize_t MessageBuffer::readFd ( int fd, int *retErrno)
{
char extBuffer[ 8192 ];
struct iovec vec[ 2 ];
size_t writable = writableBytes ();
vec[ 0 ]. iov_base = begin () + m_tail;
vec[ 0 ]. iov_len = static_cast < int >(writable);
vec[ 1 ]. iov_base = extBuffer;
vec[ 1 ]. iov_len = sizeof (extBuffer);
const int iovcnt = (writable < sizeof extBuffer) ? 2 : 1 ;
ssize_t n = :: readv (fd, vec, iovcnt);
if (n < 0 ) { *retErrno = errno; }
else if ( static_cast < size_t >(n) <= writable) { m_tail += n; }
else
{
m_tail = m_buffer. size ();
pushBack ({extBuffer, n - writable});
}
return n;
}TCPConnection 클래스는 사용시 스마트 포인터를 통해 사용되는 추상 클래스입니다.
이 인터페이스는 다음 기능을 지정합니다.
데이터를 보내십시오 (문자열/버퍼/파일/스트림 포함).
/* *
* @brief Send some data to the peer.
*
* @param msg
* @param len
*/
virtual void send (StringView const &msg) = 0;
virtual void send ( const MessageBuffer &buffer) = 0;
virtual void send (MessageBuffer &&buffer) = 0;
virtual void send ( const std::shared_ptr<MessageBuffer> &msgPtr) = 0;
/* *
* @brief Send a file to the peer.
*
* @param fileName in UTF-8
* @param offset
* @param length
*/
virtual void sendFile (StringView const &fileName, size_t offset = 0 ,
size_t length = 0 ) = 0;
/* *
* @brief Send a stream to the peer.
*
* @param callback function to retrieve the stream data (stream ends when a
* zero size is returned) the callback will be called with nullptr when the
* send is finished/interrupted, so that it cleans up any internal data (ex:
* close file).
* @warning The buffer size should be >= 10 to allow http chunked-encoding
* data stream
*/
// (buffer, buffer size) -> size
// of data put in buffer
virtual void sendStream (
std::function<std:: size_t ( char *, std:: size_t )> callback) = 0;주소 정보 또는 연결 상태 또는 데이터를 수신하는 버퍼와 같은 연결 정보를 가져옵니다.
/* *
* @brief New the local address of the connection.
*
* @return const InetAddress&
*/
virtual const InetAddress & localAddr () const = 0;
/* *
* @brief New the remote address of the connection.
*
* @return const InetAddress&
*/
virtual const InetAddress & peerAddr () const = 0;
/* *
* @brief Return true if the connection is established.
*
* @return true
* @return false
*/
virtual bool connected () const = 0;
/* *
* @brief Return false if the connection is established.
*
* @return true
* @return false
*/
virtual bool disconnected () const = 0;
/* *
* @brief New the buffer in which the received data stored.
*
* @return MsgBuffer*
*/
virtual MessageBuffer * getRecvBuffer () = 0;연결 콜백 또는 상태를 설정합니다 (TCPNODELAY/KEEPALIVE를 분리 또는 설정).
/* *
* @brief Set the high water mark callback
*
* @param cb The callback is called when the data in sending buffer is
* larger than the water mark.
* @param markLen The water mark in bytes.
*/
virtual void setHighWaterMarkCallback ( const HighWaterMarkCallback &cb,
size_t markLen) = 0;
/* *
* @brief Set the TCP_NODELAY option to the socket.
*
* @param on
*/
virtual void setTcpNoDelay ( bool on) = 0;
/* *
* @brief Shutdown the connection.
* @note This method only closes the writing direction.
*/
virtual void shutdown () = 0;
/* *
* @brief Close the connection forcefully.
*
*/
virtual void forceClose () = 0;
/* *
* @brief Call this method to avoid being kicked off by TcpServer, refer to
* the kickoffIdleConnections method in the TcpServer class.
*
*/
virtual void keepAlive () = 0;
/* *
* @brief Return true if the keepAlive() method is called.
*
* @return true
* @return false
*/
virtual bool isKeepAlive () = 0;연결에 대한 전용 비즈니스 로직을 처리하기 위해 연결 컨텍스트를 설정합니다.
/* *
* @brief Set the custom data on the connection.
*
* @param context
*/
void setContext ( const Any &context) { m_context = context; }
void setContext (Any &&context) { m_context = std::move (context); }
/* *
* @brief New mutable context
*
* @return Any
*/
Any & getContextRefMut () { return m_context; }
/* *
* @brief New unmutable context
*
* @return Any
*/
Any const & getContextRef () const { return m_context; }
/* *
* @brief Return true if the custom data is set by user.
*
* @return true
* @return false
*/
bool hasContext () const
{
# if __cplusplus >= 201703L
return m_context. has_value ();
# else
return m_context. empty ();
# endif
}
/* *
* @brief Clear the custom data.
*
*/
void clearContext ()
{
# if __cplusplus >= 201703L
m_context. reset ();
# else
m_context. clear ();
# endif
}연결은 데이터가 보내고 수신 한 데이터의 양을 축적합니다.
/* *
* @brief Return the number of bytes sent
*
* @return size_t
*/
virtual size_t bytesSent () const = 0;
/* *
* @brief Return the number of bytes received.
*
* @return size_t
*/
virtual size_t bytesReceived () const = 0;이 연결에 해당 루프를 가져옵니다.
/* *
* @brief New the event loop in which the connection I/O is handled.
*
* @return EventLoop*
*/
virtual EventLoop * getLoop () = 0;다음 두 가지 사용 사례가 일시적으로 제공됩니다.