Esta es una biblioteca de red C++ NIO con rendimiento y facilidad de uso. Admite C++14 y superior y abarca tres plataformas principales principales.
El modelo IO subyacente toma prestado one loop per thread de la biblioteca de la red Muduo, que hace que la encapsulación API segura de hilos sea más eficiente y simple.
La interfaz API proporcionada por la capa superior toma prestada del lenguaje de código abierto GO de Bytedance NIO NUTA Network Library NetPoll, y abstrae Listener y Dialer para finalmente proporcionar servicios a través de EventLoop .
Para cómo se usa esta biblioteca, puede verificar el inicio rápido.
Para ejemplos de uso específicos, consulte ejemplos.
Ya compatible :
windows/linux/macos se implementan utilizando la implementación de multiplexación de más alto rendimiento de la plataforma (Epoll/Epoll/Kqueue implementada por IOCP).EventLoop para implementar llamadas asíncronas. Si el sistema operativo admite llamadas sendfile , se llamará a la llamada de copia cero en lugar de llamar a la biblioteca estándar C.echo , solo necesita el siguiente código: 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 , que solo puede requerir 100*8byte ). Para más detalles, puede consultar el blog: Introducción detallada.one loop per threadfuture para la sincronización, que puede ser útil en la programación de clientes. netpoll::SignalTask::Register ({SIGINT,SIGTERM});
netpoll::SignalTask::Add ([](){
// do something
});Lo apoyará en el futuro :
El rendimiento es extremadamente alto, y probé temporalmente ASIO (C ++)/Netty (Java)/NetPoll (Idioma GO).
Probé ASIO/Netty en el sistema Windows, y la siguiente prueba de gráfico se basa en Linux, y no soy muy bueno para implementar programas Java en Linux, por lo que el siguiente cuadro no tiene el rendimiento de Netty.
La latencia promedio de un solo servicio de eco conectado bajo diferentes situaciones de concurrencia es la siguiente (AVG):
La arquitectura del modelo subyacente del lado del servidor:
La encapsulación Listener simplifica el uso de TcpServer , todas las llamadas son las siguientes:
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); // 用于开启剔除空闲连接
}
} La encapsulación Dialer simplifica el uso de TcpClient , y las llamadas utilizadas son las siguientes:
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);
}
} La estrategia de equilibrio de carga de EventLoop no tiene configuraciones separadas, y todos usan Round Robin .
Las API relacionadas con EventLoop son las siguientes:
NewEventLoop(size_t threadNum=2,const netpoll::StringView&name="eventloop") : cree una instancia de eventloop y establezca el número de hilos para eventloop.
Método serve : después de su recién salida o oyente de salida, puede proporcionarles servicios a través de este método.
Método serveAsDaemon : el efecto es el mismo que el método de servicio, pero abrir un nuevo hilo para servir no bloqueará el hilo actual.
Método enableQuit : Permite los métodos activos de salida de llamada al bucle. Por defecto, no puede salir activamente de todos los hilos de bucle y se usa junto con el método QuitAllEventLoop .
Método QuitAllEventLoop : Salga de todos los bucles.
MessageBuffer es un caché intermedio para leer y escribir datos de búfer del kernel. En realidad es un búfer variable, y también es muy simple de implementar. Para diferentes tipos de implementaciones de búfer, puede consultar mi artículo: Implementación de la longitud variable y el amortiguador de longitud inmutable
No describiré varias llamadas aquí. Si desea saber, puede mirar directamente el archivo de encabezado correspondiente: NetPoll/Util/Message_Buffer.h.
Permítanme hablar brevemente sobre los aspectos más destacados de implementar este búfer:
Al expandirse, evitando los efectos secundarios del cambio de tamaño, también puede simplificar las operaciones de apertura y copia de la memoria.
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);
}Proporcione el método ReadFD, que lee los datos del búfer FD Read correspondiente al MessageBuffer. El contenido se lee cada vez que es lo suficientemente grande. Por ejemplo, si el área de escritura MessageBuffer es inferior a 8 kb, entonces el caché de lectura alternativo de 8 kb está habilitado.
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;
}La clase TCPConnection es una clase abstracta que se usa a través de punteros inteligentes cuando se usa.
Esta interfaz especifica las siguientes funciones:
Enviar datos (incluido String/Buffer/File/Stream).
/* *
* @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;Obtenga información de conexión, como información de dirección o estado de conexión o un búfer que recibe datos.
/* *
* @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;Establezca la devolución o estado de llamada de conexión (desconecte o establezca 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;Establece el contexto de la conexión para manejar la lógica de negocios dedicada para la conexión.
/* *
* @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
}La conexión acumula la cantidad de datos enviados y recibidos por los datos.
/* *
* @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;Obtenga el bucle correspondiente para esta conexión.
/* *
* @brief New the event loop in which the connection I/O is handled.
*
* @return EventLoop*
*/
virtual EventLoop * getLoop () = 0;Los siguientes dos casos de uso se proporcionan temporalmente: