هذه مكتبة شبكة C++ NIO مع كل من الأداء وسهولة الاستخدام. وهو يدعم C++14 وما فوق ويمتد ثلاثة منصات رئيسية رئيسية.
يستعير طراز IO الأساسي one loop per thread لمكتبة شبكة Muduo ، مما يجعل تغليف واجهة برمجة تطبيقات آمن مؤشر الترابط أكثر كفاءة وبسيطة.
تتساقط واجهة API التي توفرها الطبقة العليا من مكتبة شبكة GO Language Open Source Open من Bytedance ، وملخصات Listener و Dialer لتقديم الخدمات أخيرًا من خلال EventLoop .
لكيفية استخدام هذه المكتبة ، يمكنك التحقق من البداية السريعة.
للحصول على أمثلة محددة للاستخدام ، يرجى الاطلاع على الأمثلة.
مدعوم بالفعل :
windows/linux/macos الكاملة باستخدام أعلى تطبيق تعدد الأداء للنظام الأساسي (Epoll/Epoll/Kqueue الذي تم تنفيذه بواسطة IOCP).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 مؤشر ترابط بشكل جيد للغاية.future للمزامنة ، والتي قد تكون مفيدة في برمجة العميل. netpoll::SignalTask::Register ({SIGINT,SIGTERM});
netpoll::SignalTask::Add ([](){
// do something
});سيدعمه في المستقبل :
الأداء مرتفع للغاية ، وقد اختبرت مؤقتًا ASIO (C ++)/Netty (Java)/NetPoll (GO Language).
لقد اختبرت ASIO/Netty على نظام Windows ، ويستند اختبار الرسم البياني التالي على Linux ، وأنا لست جيدًا في نشر برامج Java على Linux ، وبالتالي فإن الرسم البياني التالي لا يتمتع بأداء 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 هي كما يلي:
NewEventLoop(size_t threadNum=2,const netpoll::StringView&name="eventloop") : إنشاء مثيل eventLoop وقم بتعيين عدد مؤشرات الترابط لـ EventLoop.
طريقة serve : بعد خروجك حديثًا أو مستمعًا ، يمكنك تقديم خدمات لهم من خلال هذه الطريقة.
طريقة serveAsDaemon : التأثير هو نفس طريقة الخدمة ، ولكن فتح مؤشر ترابط جديد لتقديمه لن يمنع الخيط الحالي.
طريقة enableQuit : يسمح بطرق الخروج النشطة لمكالمة. بشكل افتراضي ، لا يمكنه الخروج بنشاط من جميع مؤشرات ترابط الحلقة ويتم استخدامه بالتزامن مع طريقة QuitAllEventLoop .
طريقة QuitAllEventLoop : الخروج من جميع الحلقات.
MessageBuffer هو ذاكرة التخزين المؤقت الوسيطة لقراءة وكتابة بيانات المخزن المؤقت kernel. إنه في الواقع مخزن مؤقت متغير ، كما أنه سهل التنفيذ. بالنسبة لأنواع مختلفة من تطبيقات المخزن المؤقت ، يمكنك الاطلاع على مقالتي: تنفيذ الطول المتغير ومخزن المؤقت
لن أصف المكالمات المختلفة هنا. إذا كنت تريد أن تعرف ، يمكنك النظر مباشرة إلى ملف الرأس المقابل: 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);
}قم بتوفير طريقة ReadFD ، التي تقرأ بيانات المخزن المؤقت لـ FD المقابل إلى MessageBuffer. يقرأ المحتوى في كل مرة يكون كبيرًا بدرجة كافية. على سبيل المثال ، إذا كانت منطقة MessageBuffer القابلة للكتابة أقل من 8 كيلو بايت ، فسيتم تمكين ذاكرة التخزين المؤقت القراءة البديلة 8 كيلو بايت.
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;يتم توفير حالتين للاستخدام التاليين مؤقتًا: