hkc320 2024-09-10 16:12:38 +08:00
parent bac55f5902
commit 3fc451546d
103 changed files with 38480 additions and 168 deletions

2
.gitignore vendored
View File

@ -3,3 +3,5 @@
/appRelease/test.release /appRelease/test.release
/sdk/include/SQLiteCpp /sdk/include/SQLiteCpp
/appRelease/mysql/mysql-arm.tar /appRelease/mysql/mysql-arm.tar
/applications/ems_datahubs/.vs
/applications/ems_datahubs/obj

View File

@ -222,4 +222,111 @@ bool OpDatabase::queryUser(const std::string& user_id, const std::string& passwd
hloge("Failed to connect to database: %s", ss.str().c_str()); hloge("Failed to connect to database: %s", ss.str().c_str());
return false; return false;
} }
}
bool OpDatabase::queryInnerDevice(int dev_id, std::string& jsonResult)
{
try
{
#if 0
// 数据库连接配置
std::string server = "tcp://127.0.0.1:3306";
std::string dbuser = "root";
std::string password = "Hj57471000";
std::string database = "hjems";
// 创建连接
sql::mysql::MySQL_Driver* driver;
driver = sql::mysql::get_mysql_driver_instance();
//m_pDbConnection.reset(driver->connect(server, dbuser, password));
sql::Connection* pDbConnection = driver->connect(server, dbuser, password);
if( !pDbConnection )
{
hloge("Failed to connect to database.");
return false;
}
// 设置为使用指定数据库
pDbConnection->setSchema(database);
hloge("%s : %s", user_id.c_str(), passwd_md5.c_str());
#endif
// 准备SQL查询语句
std::string sql = "SELECT uid,uname, upasswd,usalt,email,mobile1,mobile2,memo FROM tbl_user WHERE uid = ?";
// 创建预编译的prepared statement
std::unique_ptr<sql::PreparedStatement> pstmt(m_pDbConnection->prepareStatement(sql));
// 绑定参数
pstmt->setString(1, user_id); // 替换为你要查询的用户名
// 执行查询
std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());
bool ret = false;
OpenJson json;
auto& nodeRoot = json["user"];
int i = 0;
// 处理结果集
while( res->next() )
{
auto& node = nodeRoot[i++];
node["valid"] = 0;
node["uid"] = res->getString("uid");
// 假设username和password都是字符串类型
std::string pass = res->getString("upasswd");
std::string salt = res->getString("usalt");
//计算passwd和salt之间的关系
//!passwd=md5(passwd_md5+salt+salt)
// passwd_md5 是大写的,需要确保一下
std::string tmp2(passwd_md5);
std::transform(tmp2.begin(), tmp2.end(), tmp2.begin(),
[](unsigned char c)
{
return std::toupper(c);
});
std::string tmp = tmp2 + salt + salt;
std::string smd5 = CalculateMD5(tmp);
if( pass == smd5 )
{
node["valid"] = 1;
node["uname"] = res->getString("uname");
node["email"] = res->getString("email");
node["mobile1"] = res->getString("mobile1");
node["mobile2"] = res->getString("mobile2");
node["memo"] = res->getString("memo");
ret = true;
break;
}
else
{
hloge("upass=[%s],calc=[%s],src=[%s]", pass.c_str(), smd5.c_str(), tmp.c_str());
}
}
jsonResult = json.encode();
//pDbConnection->close();
//delete pDbConnection;
return ret;
}
catch( sql::SQLException& e )
{
std::ostringstream ss;
ss << "SQLException: " << e.what();
ss << " (MySQL error code: " << e.getErrorCode();
ss << ", SQLState: " << e.getSQLState() << " )";
hloge("Failed to connect to database: %s", ss.str().c_str());
return false;
}
} }

View File

@ -21,6 +21,9 @@ public:
void InsertMessage(const std::string& ts, const std::string& msg_type, const std::string& fsu, const std::string& content, int topic, int dev_id); void InsertMessage(const std::string& ts, const std::string& msg_type, const std::string& fsu, const std::string& content, int topic, int dev_id);
bool queryUser(const std::string& user_id, const std::string& passwd_md5, std::string& jsonResult); bool queryUser(const std::string& user_id, const std::string& passwd_md5, std::string& jsonResult);
//查询设备,dev_id=0则返回全部
bool queryInnerDevice(int dev_id, std::string& jsonResult);
protected: protected:
//std::unique_ptr<sql::Connection> m_pDbConnection; //std::unique_ptr<sql::Connection> m_pDbConnection;
sql::Connection* m_pDbConnection; sql::Connection* m_pDbConnection;

View File

@ -0,0 +1,254 @@
INSTALLATION INSTRUCTIONS
These instructions refer to the package you are installing as
some-package.tar.gz or some-package.zip. The .zip file is intended for use
on Windows.
The directory you choose for the installation will be referred to as
your-install-dir.
Note to Qt Visual Studio Integration users: In the instructions below,
instead of building from command line with nmake, you can use the menu
command 'Qt->Open Solution from .pro file' on the .pro files in the
example and plugin directories, and then build from within Visual
Studio.
Unpacking and installation
--------------------------
1. Unpacking the archive (if you have not done so already).
On Unix and Mac OS X (in a terminal window):
cd your-install-dir
gunzip some-package.tar.gz
tar xvf some-package.tar
This creates the subdirectory some-package containing the files.
On Windows:
Unpack the .zip archive by right-clicking it in explorer and
choosing "Extract All...". If your version of Windows does not
have zip support, you can use the infozip tools available
from www.info-zip.org.
If you are using the infozip tools (in a command prompt window):
cd your-install-dir
unzip some-package.zip
2. Configuring the package.
The configure script is called "configure" on unix/mac and
"configure.bat" on Windows. It should be run from a command line
after cd'ing to the package directory.
You can choose whether you want to use the component by including
its source code directly into your project, or build the component
as a dynamic shared library (DLL) that is loaded into the
application at run-time. The latter may be preferable for
technical or licensing (LGPL) reasons. If you want to build a DLL,
run the configure script with the argument "-library". Also see
the note about usage below.
(Components that are Qt plugins, e.g. styles and image formats,
are by default built as a plugin DLL.)
The configure script will prompt you in some cases for further
information. Answer these questions and carefully read the license text
before accepting the license conditions. The package cannot be used if
you do not accept the license conditions.
3. Building the component and examples (when required).
If a DLL is to be built, or if you would like to build the
examples, next give the commands
qmake
make [or nmake if your are using Microsoft Visual C++]
The example program(s) can be found in the directory called
"examples" or "example".
Components that are Qt plugins, e.g. styles and image formats, are
ready to be used as soon as they are built, so the rest of this
installation instruction can be skipped.
4. Building the Qt Designer plugin (optional).
Some of the widget components are provided with plugins for Qt
Designer. To build and install the plugin, cd into the
some-package/plugin directory and give the commands
qmake
make [or nmake if your are using Microsoft Visual C++]
Restart Qt Designer to make it load the new widget plugin.
Note: If you are using the built-in Qt Designer from the Qt Visual
Studio Integration, you will need to manually copy the plugin DLL
file, i.e. copy
%QTDIR%\plugins\designer\some-component.dll
to the Qt Visual Studio Integration plugin path, typically:
C:\Program Files\Trolltech\Qt VS Integration\plugins
Note: If you for some reason are using a Qt Designer that is built
in debug mode, you will need to build the plugin in debug mode
also. Edit the file plugin.pro in the plugin directory, changing
'release' to 'debug' in the CONFIG line, before running qmake.
Solutions components are intended to be used directly from the package
directory during development, so there is no 'make install' procedure.
Using a component in your project
---------------------------------
To use this component in your project, add the following line to the
project's .pro file (or do the equivalent in your IDE):
include(your-install-dir/some-package/src/some-package.pri)
This adds the package's sources and headers to the SOURCES and HEADERS
project variables respectively (or, if the component has been
configured as a DLL, it adds that library to the LIBS variable), and
updates INCLUDEPATH to contain the package's src
directory. Additionally, the .pri file may include some dependencies
needed by the package.
To include a header file from the package in your sources, you can now
simply use:
#include <SomeClass>
or alternatively, in pre-Qt 4 style:
#include <some-class.h>
Refer to the documentation to see the classes and headers this
components provides.
Install documentation (optional)
--------------------------------
The HTML documentation for the package's classes is located in the
your-install-dir/some-package/doc/html/index.html. You can open this
file and read the documentation with any web browser.
To install the documentation into Qt Assistant (for Qt version 4.4 and
later):
1. In Assistant, open the Edit->Preferences dialog and choose the
Documentation tab. Click the Add... button and select the file
your-install-dir/some-package/doc/html/some-package.qch
For Qt versions prior to 4.4, do instead the following:
1. The directory your-install-dir/some-package/doc/html contains a
file called some-package.dcf. Execute the following commands in a
shell, command prompt or terminal window:
cd your-install-dir/some-package/doc/html/
assistant -addContentFile some-package.dcf
The next time you start Qt Assistant, you can access the package's
documentation.
Removing the documentation from assistant
-----------------------------------------
If you have installed the documentation into Qt Assistant, and want to uninstall it, do as follows, for Qt version 4.4 and later:
1. In Assistant, open the Edit->Preferences dialog and choose the
Documentation tab. In the list of Registered Documentation, select
the item com.nokia.qtsolutions.some-package_version, and click
the Remove button.
For Qt versions prior to 4.4, do instead the following:
1. The directory your-install-dir/some-package/doc/html contains a
file called some-package.dcf. Execute the following commands in a
shell, command prompt or terminal window:
cd your-install-dir/some-package/doc/html/
assistant -removeContentFile some-package.dcf
Using the component as a DLL
----------------------------
1. Normal components
The shared library (DLL) is built and placed in the
some-package/lib directory. It is intended to be used directly
from there during development. When appropriate, both debug and
release versions are built, since the run-time linker will in some
cases refuse to load a debug-built DLL into a release-built
application or vice versa.
The following steps are taken by default to help the dynamic
linker to locate the DLL at run-time (during development):
Unix: The some-package.pri file will add linker instructions to
add the some-package/lib directory to the rpath of the
executable. (When distributing, or if your system does not support
rpath, you can copy the shared library to another place that is
searched by the dynamic linker, e.g. the "lib" directory of your
Qt installation.)
Mac: The full path to the library is hardcoded into the library
itself, from where it is copied into the executable at link time,
and ready by the dynamic linker at run-time. (When distributing,
you will want to edit these hardcoded paths in the same way as for
the Qt DLLs. Refer to the document "Deploying an Application on
Mac OS X" in the Qt Reference Documentation.)
Windows: the .dll file(s) are copied into the "bin" directory of
your Qt installation. The Qt installation will already have set up
that directory to be searched by the dynamic linker.
2. Plugins
For Qt Solutions plugins (e.g. image formats), both debug and
release versions of the plugin are built by default when
appropriate, since in some cases the release Qt library will not
load a debug plugin, and vice versa. The plugins are automatically
copied into the plugins directory of your Qt installation when
built, so no further setup is required.
Plugins may also be built statically, i.e. as a library that will be
linked into your application executable, and so will not need to
be redistributed as a separate plugin DLL to end users. Static
building is required if Qt itself is built statically. To do it,
just add "static" to the CONFIG variable in the plugin/plugin.pro
file before building. Refer to the "Static Plugins" section in the
chapter "How to Create Qt Plugins" for explanation of how to use a
static plugin in your application. The source code of the example
program(s) will also typically contain the relevant instructions
as comments.
Uninstalling
------------
The following command will remove any fils that have been
automatically placed outside the package directory itself during
installation and building
make distclean [or nmake if your are using Microsoft Visual C++]
If Qt Assistant documentation or Qt Designer plugins have been
installed, they can be uninstalled manually, ref. above.
Enjoy! :)
- The Qt Solutions Team.

View File

@ -0,0 +1 @@
#include "qtlockedfile.h"

View File

@ -0,0 +1,33 @@
Qt Solutions Component: Single Application
The QtSingleApplication component provides support for
applications that can be only started once per user.
Version history:
2.0: - Version 1.3 ported to Qt 4.
2.1: - Fix compilation problem on Mac.
2.2: - Really fix the Mac compilation problem.
- Mac: fix crash due to wrong object releasing.
- Mac: Fix memory leak.
2.3: - Windows: Force creation of internal widget to make it work
with Qt 4.2.
2.4: - Fix the system for automatic window raising on message
reception. NOTE: minor API change.
2.5: - Mac: Fix isRunning() to work and report correctly.
2.6: - - initialize() is now obsolete, no longer necessary to call
it
- - Fixed race condition where multiple instances migth be started
- - QtSingleCoreApplication variant provided for non-GUI (console)
usage
- Complete reimplementation. Visible changes:
- LGPL release.

View File

@ -0,0 +1,205 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtlocalpeer.h"
#include <QCoreApplication>
#include <QTime>
#include <QDataStream>
#include <QRegularExpression>
#if defined(Q_OS_WIN)
#include <QLibrary>
#include <qt_windows.h>
typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*);
static PProcessIdToSessionId pProcessIdToSessionId = 0;
#endif
#if defined(Q_OS_UNIX)
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#endif
namespace QtLP_Private {
#include "qtlockedfile.cpp"
#if defined(Q_OS_WIN)
#include "qtlockedfile_win.cpp"
#else
#include "qtlockedfile_unix.cpp"
#endif
}
const char* QtLocalPeer::ack = "ack";
QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
: QObject(parent), id(appId)
{
QString prefix = id;
if (id.isEmpty()) {
id = QCoreApplication::applicationFilePath();
#if defined(Q_OS_WIN)
id = id.toLower();
#endif
prefix = id.section(QLatin1Char('/'), -1);
}
prefix.remove(QRegularExpression ("[^a-zA-Z]"));
prefix.truncate(6);
QByteArray idc = id.toUtf8();
quint16 idNum = qChecksum(idc.constData(), idc.size());
socketName = QLatin1String("qtsingleapp-") + prefix
+ QLatin1Char('-') + QString::number(idNum, 16);
#if defined(Q_OS_WIN)
if (!pProcessIdToSessionId) {
QLibrary lib("kernel32");
pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
}
if (pProcessIdToSessionId) {
DWORD sessionId = 0;
pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
socketName += QLatin1Char('-') + QString::number(sessionId, 16);
}
#else
socketName += QLatin1Char('-') + QString::number(::getuid(), 16);
#endif
server = new QLocalServer(this);
QString lockName = QDir(QDir::tempPath()).absolutePath()
+ QLatin1Char('/') + socketName
+ QLatin1String("-lockfile");
lockFile.setFileName(lockName);
lockFile.open(QIODevice::ReadWrite);
}
bool QtLocalPeer::isClient()
{
if (lockFile.isLocked())
return false;
if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false))
return true;
bool res = server->listen(socketName);
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0))
// ### Workaround
if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
res = server->listen(socketName);
}
#endif
if (!res)
qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection()));
return false;
}
bool QtLocalPeer::sendMessage(const QString &message, int timeout)
{
if (!isClient())
return false;
QLocalSocket socket;
bool connOk = false;
for(int i = 0; i < 2; i++) {
// Try twice, in case the other instance is just starting up
socket.connectToServer(socketName);
connOk = socket.waitForConnected(timeout/2);
if (connOk || i)
break;
int ms = 250;
#if defined(Q_OS_WIN)
Sleep(DWORD(ms));
#else
struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
nanosleep(&ts, NULL);
#endif
}
if (!connOk)
return false;
QByteArray uMsg(message.toUtf8());
QDataStream ds(&socket);
ds.writeBytes(uMsg.constData(), uMsg.size());
bool res = socket.waitForBytesWritten(timeout);
if (res) {
res &= socket.waitForReadyRead(timeout); // wait for ack
if (res)
res &= (socket.read(qstrlen(ack)) == ack);
}
return res;
}
void QtLocalPeer::receiveConnection()
{
QLocalSocket* socket = server->nextPendingConnection();
if (!socket)
return;
while (socket->bytesAvailable() < (int)sizeof(quint32))
socket->waitForReadyRead();
QDataStream ds(socket);
QByteArray uMsg;
quint32 remaining;
ds >> remaining;
uMsg.resize(remaining);
int got = 0;
char* uMsgBuf = uMsg.data();
do {
got = ds.readRawData(uMsgBuf, remaining);
remaining -= got;
uMsgBuf += got;
} while (remaining && got >= 0 && socket->waitForReadyRead(2000));
if (got < 0) {
qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData());
delete socket;
return;
}
QString message(QString::fromUtf8(uMsg));
socket->write(ack, qstrlen(ack));
socket->waitForBytesWritten(1000);
socket->waitForDisconnected(1000); // make sure client reads ack
delete socket;
emit messageReceived(message); //### (might take a long time to return)
}

View File

@ -0,0 +1,77 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTLOCALPEER_H
#define QTLOCALPEER_H
#include <QLocalServer>
#include <QLocalSocket>
#include <QDir>
#include "qtlockedfile.h"
class QtLocalPeer : public QObject
{
Q_OBJECT
public:
QtLocalPeer(QObject *parent = 0, const QString &appId = QString());
bool isClient();
bool sendMessage(const QString &message, int timeout);
QString applicationId() const
{ return id; }
Q_SIGNALS:
void messageReceived(const QString &message);
protected Q_SLOTS:
void receiveConnection();
protected:
QString id;
QString socketName;
QLocalServer* server;
QtLP_Private::QtLockedFile lockFile;
private:
static const char* ack;
};
#endif // QTLOCALPEER_H

View File

@ -0,0 +1,193 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtlockedfile.h"
/*!
\class QtLockedFile
\brief The QtLockedFile class extends QFile with advisory locking
functions.
A file may be locked in read or write mode. Multiple instances of
\e QtLockedFile, created in multiple processes running on the same
machine, may have a file locked in read mode. Exactly one instance
may have it locked in write mode. A read and a write lock cannot
exist simultaneously on the same file.
The file locks are advisory. This means that nothing prevents
another process from manipulating a locked file using QFile or
file system functions offered by the OS. Serialization is only
guaranteed if all processes that access the file use
QLockedFile. Also, while holding a lock on a file, a process
must not open the same file again (through any API), or locks
can be unexpectedly lost.
The lock provided by an instance of \e QtLockedFile is released
whenever the program terminates. This is true even when the
program crashes and no destructors are called.
*/
/*! \enum QtLockedFile::LockMode
This enum describes the available lock modes.
\value ReadLock A read lock.
\value WriteLock A write lock.
\value NoLock Neither a read lock nor a write lock.
*/
/*!
Constructs an unlocked \e QtLockedFile object. This constructor
behaves in the same way as \e QFile::QFile().
\sa QFile::QFile()
*/
QtLockedFile::QtLockedFile()
: QFile()
{
#ifdef Q_OS_WIN
wmutex = 0;
rmutex = 0;
#endif
m_lock_mode = NoLock;
}
/*!
Constructs an unlocked QtLockedFile object with file \a name. This
constructor behaves in the same way as \e QFile::QFile(const
QString&).
\sa QFile::QFile()
*/
QtLockedFile::QtLockedFile(const QString &name)
: QFile(name)
{
#ifdef Q_OS_WIN
wmutex = 0;
rmutex = 0;
#endif
m_lock_mode = NoLock;
}
/*!
Opens the file in OpenMode \a mode.
This is identical to QFile::open(), with the one exception that the
Truncate mode flag is disallowed. Truncation would conflict with the
advisory file locking, since the file would be modified before the
write lock is obtained. If truncation is required, use resize(0)
after obtaining the write lock.
Returns true if successful; otherwise false.
\sa QFile::open(), QFile::resize()
*/
bool QtLockedFile::open(OpenMode mode)
{
if (mode & QIODevice::Truncate) {
qWarning("QtLockedFile::open(): Truncate mode not allowed.");
return false;
}
return QFile::open(mode);
}
/*!
Returns \e true if this object has a in read or write lock;
otherwise returns \e false.
\sa lockMode()
*/
bool QtLockedFile::isLocked() const
{
return m_lock_mode != NoLock;
}
/*!
Returns the type of lock currently held by this object, or \e
QtLockedFile::NoLock.
\sa isLocked()
*/
QtLockedFile::LockMode QtLockedFile::lockMode() const
{
return m_lock_mode;
}
/*!
\fn bool QtLockedFile::lock(LockMode mode, bool block = true)
Obtains a lock of type \a mode. The file must be opened before it
can be locked.
If \a block is true, this function will block until the lock is
aquired. If \a block is false, this function returns \e false
immediately if the lock cannot be aquired.
If this object already has a lock of type \a mode, this function
returns \e true immediately. If this object has a lock of a
different type than \a mode, the lock is first released and then a
new lock is obtained.
This function returns \e true if, after it executes, the file is
locked by this object, and \e false otherwise.
\sa unlock(), isLocked(), lockMode()
*/
/*!
\fn bool QtLockedFile::unlock()
Releases a lock.
If the object has no lock, this function returns immediately.
This function returns \e true if, after it executes, the file is
not locked by this object, and \e false otherwise.
\sa lock(), isLocked(), lockMode()
*/
/*!
\fn QtLockedFile::~QtLockedFile()
Destroys the \e QtLockedFile object. If any locks were held, they
are released.
*/

View File

@ -0,0 +1,97 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTLOCKEDFILE_H
#define QTLOCKEDFILE_H
#include <QFile>
#ifdef Q_OS_WIN
#include <QVector>
#endif
#if defined(Q_OS_WIN)
# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT)
# define QT_QTLOCKEDFILE_EXPORT
# elif defined(QT_QTLOCKEDFILE_IMPORT)
# if defined(QT_QTLOCKEDFILE_EXPORT)
# undef QT_QTLOCKEDFILE_EXPORT
# endif
# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport)
# elif defined(QT_QTLOCKEDFILE_EXPORT)
# undef QT_QTLOCKEDFILE_EXPORT
# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport)
# endif
#else
# define QT_QTLOCKEDFILE_EXPORT
#endif
namespace QtLP_Private {
class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile
{
public:
enum LockMode { NoLock = 0, ReadLock, WriteLock };
QtLockedFile();
QtLockedFile(const QString &name);
~QtLockedFile();
bool open(OpenMode mode);
bool lock(LockMode mode, bool block = true);
bool unlock();
bool isLocked() const;
LockMode lockMode() const;
private:
#ifdef Q_OS_WIN
Qt::HANDLE wmutex;
Qt::HANDLE rmutex;
QVector<Qt::HANDLE> rmutexes;
QString mutexname;
Qt::HANDLE getMutexHandle(int idx, bool doCreate);
bool waitMutex(Qt::HANDLE mutex, bool doBlock);
#endif
LockMode m_lock_mode;
};
}
#endif

View File

@ -0,0 +1,115 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include "qtlockedfile.h"
bool QtLockedFile::lock(LockMode mode, bool block)
{
if (!isOpen()) {
qWarning("QtLockedFile::lock(): file is not opened");
return false;
}
if (mode == NoLock)
return unlock();
if (mode == m_lock_mode)
return true;
if (m_lock_mode != NoLock)
unlock();
struct flock fl;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK;
int cmd = block ? F_SETLKW : F_SETLK;
int ret = fcntl(handle(), cmd, &fl);
if (ret == -1) {
if (errno != EINTR && errno != EAGAIN)
qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
return false;
}
m_lock_mode = mode;
return true;
}
bool QtLockedFile::unlock()
{
if (!isOpen()) {
qWarning("QtLockedFile::unlock(): file is not opened");
return false;
}
if (!isLocked())
return true;
struct flock fl;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
fl.l_type = F_UNLCK;
int ret = fcntl(handle(), F_SETLKW, &fl);
if (ret == -1) {
qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
return false;
}
m_lock_mode = NoLock;
return true;
}
QtLockedFile::~QtLockedFile()
{
if (isOpen())
unlock();
}

View File

@ -0,0 +1,211 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtlockedfile.h"
#include <qt_windows.h>
#include <QFileInfo>
#define MUTEX_PREFIX "QtLockedFile mutex "
// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS
#define MAX_READERS MAXIMUM_WAIT_OBJECTS
#if QT_VERSION >= 0x050000
#define QT_WA(unicode, ansi) unicode
#endif
Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate)
{
if (mutexname.isEmpty()) {
QFileInfo fi(*this);
mutexname = QString::fromLatin1(MUTEX_PREFIX)
+ fi.absoluteFilePath().toLower();
}
QString mname(mutexname);
if (idx >= 0)
mname += QString::number(idx);
Qt::HANDLE mutex;
if (doCreate) {
QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); },
{ mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } );
if (!mutex) {
qErrnoWarning("QtLockedFile::lock(): CreateMutex failed");
return 0;
}
}
else {
QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); },
{ mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } );
if (!mutex) {
if (GetLastError() != ERROR_FILE_NOT_FOUND)
qErrnoWarning("QtLockedFile::lock(): OpenMutex failed");
return 0;
}
}
return mutex;
}
bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock)
{
Q_ASSERT(mutex);
DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0);
switch (res) {
case WAIT_OBJECT_0:
case WAIT_ABANDONED:
return true;
break;
case WAIT_TIMEOUT:
break;
default:
qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed");
}
return false;
}
bool QtLockedFile::lock(LockMode mode, bool block)
{
if (!isOpen()) {
qWarning("QtLockedFile::lock(): file is not opened");
return false;
}
if (mode == NoLock)
return unlock();
if (mode == m_lock_mode)
return true;
if (m_lock_mode != NoLock)
unlock();
if (!wmutex && !(wmutex = getMutexHandle(-1, true)))
return false;
if (!waitMutex(wmutex, block))
return false;
if (mode == ReadLock) {
int idx = 0;
for (; idx < MAX_READERS; idx++) {
rmutex = getMutexHandle(idx, false);
if (!rmutex || waitMutex(rmutex, false))
break;
CloseHandle(rmutex);
}
bool ok = true;
if (idx >= MAX_READERS) {
qWarning("QtLockedFile::lock(): too many readers");
rmutex = 0;
ok = false;
}
else if (!rmutex) {
rmutex = getMutexHandle(idx, true);
if (!rmutex || !waitMutex(rmutex, false))
ok = false;
}
if (!ok && rmutex) {
CloseHandle(rmutex);
rmutex = 0;
}
ReleaseMutex(wmutex);
if (!ok)
return false;
}
else {
Q_ASSERT(rmutexes.isEmpty());
for (int i = 0; i < MAX_READERS; i++) {
Qt::HANDLE mutex = getMutexHandle(i, false);
if (mutex)
rmutexes.append(mutex);
}
if (rmutexes.size()) {
DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(),
TRUE, block ? INFINITE : 0);
if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) {
if (res != WAIT_TIMEOUT)
qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed");
m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky
unlock();
return false;
}
}
}
m_lock_mode = mode;
return true;
}
bool QtLockedFile::unlock()
{
if (!isOpen()) {
qWarning("QtLockedFile::unlock(): file is not opened");
return false;
}
if (!isLocked())
return true;
if (m_lock_mode == ReadLock) {
ReleaseMutex(rmutex);
CloseHandle(rmutex);
rmutex = 0;
}
else {
foreach(Qt::HANDLE mutex, rmutexes) {
ReleaseMutex(mutex);
CloseHandle(mutex);
}
rmutexes.clear();
ReleaseMutex(wmutex);
}
m_lock_mode = QtLockedFile::NoLock;
return true;
}
QtLockedFile::~QtLockedFile()
{
if (isOpen())
unlock();
if (wmutex)
CloseHandle(wmutex);
}

View File

@ -0,0 +1,356 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtsingleapplication.h"
#include "qtlocalpeer.h"
#include <QWidget>
/*!
\class QtSingleApplication qtsingleapplication.h
\brief The QtSingleApplication class provides an API to detect and
communicate with running instances of an application.
This class allows you to create applications where only one
instance should be running at a time. I.e., if the user tries to
launch another instance, the already running instance will be
activated instead. Another usecase is a client-server system,
where the first started instance will assume the role of server,
and the later instances will act as clients of that server.
By default, the full path of the executable file is used to
determine whether two processes are instances of the same
application. You can also provide an explicit identifier string
that will be compared instead.
The application should create the QtSingleApplication object early
in the startup phase, and call isRunning() to find out if another
instance of this application is already running. If isRunning()
returns false, it means that no other instance is running, and
this instance has assumed the role as the running instance. In
this case, the application should continue with the initialization
of the application user interface before entering the event loop
with exec(), as normal.
The messageReceived() signal will be emitted when the running
application receives messages from another instance of the same
application. When a message is received it might be helpful to the
user to raise the application so that it becomes visible. To
facilitate this, QtSingleApplication provides the
setActivationWindow() function and the activateWindow() slot.
If isRunning() returns true, another instance is already
running. It may be alerted to the fact that another instance has
started by using the sendMessage() function. Also data such as
startup parameters (e.g. the name of the file the user wanted this
new instance to open) can be passed to the running instance with
this function. Then, the application should terminate (or enter
client mode).
If isRunning() returns true, but sendMessage() fails, that is an
indication that the running instance is frozen.
Here's an example that shows how to convert an existing
application to use QtSingleApplication. It is very simple and does
not make use of all QtSingleApplication's functionality (see the
examples for that).
\code
// Original
int main(int argc, char **argv)
{
QApplication app(argc, argv);
MyMainWidget mmw;
mmw.show();
return app.exec();
}
// Single instance
int main(int argc, char **argv)
{
QtSingleApplication app(argc, argv);
if (app.isRunning())
return !app.sendMessage(someDataString);
MyMainWidget mmw;
app.setActivationWindow(&mmw);
mmw.show();
return app.exec();
}
\endcode
Once this QtSingleApplication instance is destroyed (normally when
the process exits or crashes), when the user next attempts to run the
application this instance will not, of course, be encountered. The
next instance to call isRunning() or sendMessage() will assume the
role as the new running instance.
For console (non-GUI) applications, QtSingleCoreApplication may be
used instead of this class, to avoid the dependency on the QtGui
library.
\sa QtSingleCoreApplication
*/
void QtSingleApplication::sysInit(const QString &appId)
{
actWin = 0;
peer = new QtLocalPeer(this, appId);
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
}
/*!
Creates a QtSingleApplication object. The application identifier
will be QCoreApplication::applicationFilePath(). \a argc, \a
argv, and \a GUIenabled are passed on to the QAppliation constructor.
If you are creating a console application (i.e. setting \a
GUIenabled to false), you may consider using
QtSingleCoreApplication instead.
*/
QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled)
: QApplication(argc, argv, GUIenabled)
{
sysInit();
}
/*!
Creates a QtSingleApplication object with the application
identifier \a appId. \a argc and \a argv are passed on to the
QAppliation constructor.
*/
QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
: QApplication(argc, argv),
peer(nullptr)
{
sysInit(appId);
}
QtSingleApplication::~QtSingleApplication()
{
if (peer)
{
delete peer;
}
}
#if QT_VERSION < 0x050000
/*!
Creates a QtSingleApplication object. The application identifier
will be QCoreApplication::applicationFilePath(). \a argc, \a
argv, and \a type are passed on to the QAppliation constructor.
*/
QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type)
: QApplication(argc, argv, type)
{
sysInit();
}
# if defined(Q_WS_X11)
/*!
Special constructor for X11, ref. the documentation of
QApplication's corresponding constructor. The application identifier
will be QCoreApplication::applicationFilePath(). \a dpy, \a visual,
and \a cmap are passed on to the QApplication constructor.
*/
QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap)
: QApplication(dpy, visual, cmap)
{
sysInit();
}
/*!
Special constructor for X11, ref. the documentation of
QApplication's corresponding constructor. The application identifier
will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a
argv, \a visual, and \a cmap are passed on to the QApplication
constructor.
*/
QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
: QApplication(dpy, argc, argv, visual, cmap)
{
sysInit();
}
/*!
Special constructor for X11, ref. the documentation of
QApplication's corresponding constructor. The application identifier
will be \a appId. \a dpy, \a argc, \a
argv, \a visual, and \a cmap are passed on to the QApplication
constructor.
*/
QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
: QApplication(dpy, argc, argv, visual, cmap)
{
sysInit(appId);
}
# endif // Q_WS_X11
#endif // QT_VERSION < 0x050000
/*!
Returns true if another instance of this application is running;
otherwise false.
This function does not find instances of this application that are
being run by a different user (on Windows: that are running in
another session).
\sa sendMessage()
*/
bool QtSingleApplication::isRunning()
{
return peer->isClient();
}
/*!
Tries to send the text \a message to the currently running
instance. The QtSingleApplication object in the running instance
will emit the messageReceived() signal when it receives the
message.
This function returns true if the message has been sent to, and
processed by, the current instance. If there is no instance
currently running, or if the running instance fails to process the
message within \a timeout milliseconds, this function return false.
\sa isRunning(), messageReceived()
*/
bool QtSingleApplication::sendMessage(const QString &message, int timeout)
{
return peer->sendMessage(message, timeout);
}
/*!
Returns the application identifier. Two processes with the same
identifier will be regarded as instances of the same application.
*/
QString QtSingleApplication::id() const
{
return peer->applicationId();
}
/*!
Sets the activation window of this application to \a aw. The
activation window is the widget that will be activated by
activateWindow(). This is typically the application's main window.
If \a activateOnMessage is true (the default), the window will be
activated automatically every time a message is received, just prior
to the messageReceived() signal being emitted.
\sa activateWindow(), messageReceived()
*/
void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage)
{
actWin = aw;
if (activateOnMessage)
connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
else
disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
}
/*!
Returns the applications activation window if one has been set by
calling setActivationWindow(), otherwise returns 0.
\sa setActivationWindow()
*/
QWidget* QtSingleApplication::activationWindow() const
{
return actWin;
}
/*!
De-minimizes, raises, and activates this application's activation window.
This function does nothing if no activation window has been set.
This is a convenience function to show the user that this
application instance has been activated when he has tried to start
another instance.
This function should typically be called in response to the
messageReceived() signal. By default, that will happen
automatically, if an activation window has been set.
\sa setActivationWindow(), messageReceived(), initialize()
*/
void QtSingleApplication::activateWindow()
{
if (actWin) {
actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized);
actWin->raise();
actWin->activateWindow();
}
}
/*!
\fn void QtSingleApplication::messageReceived(const QString& message)
This signal is emitted when the current instance receives a \a
message from another instance of this application.
\sa sendMessage(), setActivationWindow(), activateWindow()
*/
/*!
\fn void QtSingleApplication::initialize(bool dummy = true)
\obsolete
*/

View File

@ -0,0 +1,107 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTSINGLEAPPLICATION_H
#define QTSINGLEAPPLICATION_H
#include <QApplication>
class QtLocalPeer;
#if defined(Q_OS_WIN)
# if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT)
# define QT_QTSINGLEAPPLICATION_EXPORT
# elif defined(QT_QTSINGLEAPPLICATION_IMPORT)
# if defined(QT_QTSINGLEAPPLICATION_EXPORT)
# undef QT_QTSINGLEAPPLICATION_EXPORT
# endif
# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport)
# elif defined(QT_QTSINGLEAPPLICATION_EXPORT)
# undef QT_QTSINGLEAPPLICATION_EXPORT
# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport)
# endif
#else
# define QT_QTSINGLEAPPLICATION_EXPORT
#endif
class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication
{
Q_OBJECT
public:
QtSingleApplication(int &argc, char **argv, bool GUIenabled = true);
QtSingleApplication(const QString &id, int &argc, char **argv);
virtual ~QtSingleApplication();
#if QT_VERSION < 0x050000
QtSingleApplication(int &argc, char **argv, Type type);
# if defined(Q_WS_X11)
QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0);
QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
# endif // Q_WS_X11
#endif // QT_VERSION < 0x050000
bool isRunning();
QString id() const;
void setActivationWindow(QWidget* aw, bool activateOnMessage = true);
QWidget* activationWindow() const;
// Obsolete:
void initialize(bool dummy = true)
{ isRunning(); Q_UNUSED(dummy) }
public Q_SLOTS:
bool sendMessage(const QString &message, int timeout = 5000);
void activateWindow();
Q_SIGNALS:
void messageReceived(const QString &message);
private:
void sysInit(const QString &appId = QString());
QtLocalPeer *peer;
QWidget *actWin;
};
#endif // QTSINGLEAPPLICATION_H

View File

@ -0,0 +1,17 @@
#include(../common.pri)
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD
QT *= network
greaterThan(QT_MAJOR_VERSION, 4): QT *= widgets
qtsingleapplication-uselib:!qtsingleapplication-buildlib {
LIBS += -L$$QTSINGLEAPPLICATION_LIBDIR -l$$QTSINGLEAPPLICATION_LIBNAME
} else {
SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp
HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h
}
win32 {
contains(TEMPLATE, lib):contains(CONFIG, shared):DEFINES += QT_QTSINGLEAPPLICATION_EXPORT
else:qtsingleapplication-uselib:DEFINES += QT_QTSINGLEAPPLICATION_IMPORT
}

View File

@ -0,0 +1,149 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtsinglecoreapplication.h"
#include "qtlocalpeer.h"
/*!
\class QtSingleCoreApplication qtsinglecoreapplication.h
\brief A variant of the QtSingleApplication class for non-GUI applications.
This class is a variant of QtSingleApplication suited for use in
console (non-GUI) applications. It is an extension of
QCoreApplication (instead of QApplication). It does not require
the QtGui library.
The API and usage is identical to QtSingleApplication, except that
functions relating to the "activation window" are not present, for
obvious reasons. Please refer to the QtSingleApplication
documentation for explanation of the usage.
A QtSingleCoreApplication instance can communicate to a
QtSingleApplication instance if they share the same application
id. Hence, this class can be used to create a light-weight
command-line tool that sends commands to a GUI application.
\sa QtSingleApplication
*/
/*!
Creates a QtSingleCoreApplication object. The application identifier
will be QCoreApplication::applicationFilePath(). \a argc and \a
argv are passed on to the QCoreAppliation constructor.
*/
QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv)
: QCoreApplication(argc, argv)
{
peer = new QtLocalPeer(this);
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
}
/*!
Creates a QtSingleCoreApplication object with the application
identifier \a appId. \a argc and \a argv are passed on to the
QCoreAppliation constructor.
*/
QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv)
: QCoreApplication(argc, argv)
{
peer = new QtLocalPeer(this, appId);
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
}
/*!
Returns true if another instance of this application is running;
otherwise false.
This function does not find instances of this application that are
being run by a different user (on Windows: that are running in
another session).
\sa sendMessage()
*/
bool QtSingleCoreApplication::isRunning()
{
return peer->isClient();
}
/*!
Tries to send the text \a message to the currently running
instance. The QtSingleCoreApplication object in the running instance
will emit the messageReceived() signal when it receives the
message.
This function returns true if the message has been sent to, and
processed by, the current instance. If there is no instance
currently running, or if the running instance fails to process the
message within \a timeout milliseconds, this function return false.
\sa isRunning(), messageReceived()
*/
bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout)
{
return peer->sendMessage(message, timeout);
}
/*!
Returns the application identifier. Two processes with the same
identifier will be regarded as instances of the same application.
*/
QString QtSingleCoreApplication::id() const
{
return peer->applicationId();
}
/*!
\fn void QtSingleCoreApplication::messageReceived(const QString& message)
This signal is emitted when the current instance receives a \a
message from another instance of this application.
\sa sendMessage()
*/

View File

@ -0,0 +1,71 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTSINGLECOREAPPLICATION_H
#define QTSINGLECOREAPPLICATION_H
#include <QCoreApplication>
class QtLocalPeer;
class QtSingleCoreApplication : public QCoreApplication
{
Q_OBJECT
public:
QtSingleCoreApplication(int &argc, char **argv);
QtSingleCoreApplication(const QString &id, int &argc, char **argv);
bool isRunning();
QString id() const;
public Q_SLOTS:
bool sendMessage(const QString &message, int timeout = 5000);
Q_SIGNALS:
void messageReceived(const QString &message);
private:
QtLocalPeer* peer;
};
#endif // QTSINGLECOREAPPLICATION_H

View File

@ -0,0 +1,10 @@
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD
HEADERS += $$PWD/qtsinglecoreapplication.h $$PWD/qtlocalpeer.h
SOURCES += $$PWD/qtsinglecoreapplication.cpp $$PWD/qtlocalpeer.cpp
QT *= network
win32:contains(TEMPLATE, lib):contains(CONFIG, shared) {
DEFINES += QT_QTSINGLECOREAPPLICATION_EXPORT=__declspec(dllexport)
}

View File

@ -29,7 +29,7 @@ win32:LIBS += Ws2_32.lib
win32:LIBS += $$PWD/..\..\SDK\lib\libdes.lib win32:LIBS += $$PWD/..\..\SDK\lib\libdes.lib
win32:LIBS += $$PWD/..\..\SDK\lib\OpenSSL_VC\libcrypto64MD.lib win32:LIBS += $$PWD/..\..\SDK\lib\OpenSSL_VC\libcrypto64MD.lib
win32:LIBS += $$PWD/..\..\SDK\lib\OpenSSL_VC\libssl64MD.lib win32:LIBS += $$PWD/..\..\SDK\lib\OpenSSL_VC\libssl64MD.lib
win32:LIBS += $$PWD/..\..\SDK\lib\hv.lib win32:LIBS += $$PWD/..\..\SDK\lib\hvd.lib
win32:LIBS += Ws2_32.lib win32:LIBS += Ws2_32.lib
} }
@ -40,12 +40,10 @@ win32:LIBS += Ws2_32.lib
SOURCES += \ SOURCES += \
main.cpp \ main.cpp \
mainwindow.cpp \ mainwindow.cpp
cloggermaganer.cpp
HEADERS += \ HEADERS += \
mainwindow.h \ mainwindow.h
cloggermaganer.h
FORMS += \ FORMS += \
mainwindow.ui mainwindow.ui
@ -54,3 +52,6 @@ FORMS += \
qnx: target.path = /tmp/$${TARGET}/bin qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target !isEmpty(target.path): INSTALLS += target
RESOURCES += \
emscfgres.qrc

View File

@ -0,0 +1,7 @@
<RCC>
<qresource prefix="/">
<file>images/emscfg-main.ico</file>
<file>images/hj-net.png</file>
<file>images/icon.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -17,7 +17,6 @@
#include <hv/hsocket.h> #include <hv/hsocket.h>
#include <hv/hssl.h> #include <hv/hssl.h>
#include "cloggermaganer.h"
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
@ -36,11 +35,9 @@ int main(int argc, char *argv[])
std::string logFilePath = (appDir + QString::fromStdString("/emsConfigurer.log")).toStdString(); std::string logFilePath = (appDir + QString::fromStdString("/emsConfigurer.log")).toStdString();
hlog_set_file(logFilePath.c_str()); hlog_set_file(logFilePath.c_str());
hlogi("=========--- Welcome to the Earth ---=========\n"); hlogi("=========--- Welcome to the Earth ---=========");
hlogi("%s version: %s\n", argv[0], "1.1.0"); hlogi("%s version: %s", argv[0], "1.1.0");
hlog_fsync(); hlog_fsync();
hlogi("%s versionversionversionversionversionversion: %s\n", argv[0], "1.1.0");
QTranslator translator; QTranslator translator;
const QStringList uiLanguages = QLocale::system().uiLanguages(); const QStringList uiLanguages = QLocale::system().uiLanguages();
@ -55,6 +52,13 @@ int main(int argc, char *argv[])
} }
MainWindow w; MainWindow w;
//隐藏(不显示)最大化最小化按钮
w.setWindowFlags(w.windowFlags()&~Qt::WindowMinMaxButtonsHint);
//w.setWindowFlags(w.windowFlags() | Qt::WindowStaysOnTopHint);
w.setStyleSheet("{border-radius: 4px;}"); // 定制圆角
// ".QLabel{background: gray;}.QTextEdit{background: white;}");
//获取窗口尺寸并居中 //获取窗口尺寸并居中
QScreen *scr = app.primaryScreen(); QScreen *scr = app.primaryScreen();
int scr_w = scr->size().width(); int scr_w = scr->size().width();
@ -62,5 +66,7 @@ int main(int argc, char *argv[])
w.move((scr_w - w.width()) / 2, (scr_h - w.height()) / 2); w.move((scr_w - w.width()) / 2, (scr_h - w.height()) / 2);
w.show(); w.show();
return app.exec(); int ret = app.exec();
hlogi("=========--- I'll be back! ---=========");
return ret;
} }

View File

@ -1,11 +1,29 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "ui_mainwindow.h" #include "ui_mainwindow.h"
#include <QPixmap>
#include <QMessageBox>
#include <QRegularExpression>
#include <QRegularExpressionValidator>
MainWindow::MainWindow(QWidget *parent) MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) : QMainWindow(parent)
, ui(new Ui::MainWindow) , ui(new Ui::MainWindow)
{ {
ui->setupUi(this); ui->setupUi(this);
this->setWindowIcon(QIcon(":/images/icon.png"));
QPixmap pixmap(":/images/hj-net.png");
pixmap = pixmap.scaled(250, 75, Qt::KeepAspectRatio, Qt::SmoothTransformation); // 按比例缩放
ui->label_logo->setPixmap(pixmap);
QString qsLineEditStyle("QLineEdit { min-height: 20px; min-width: 120px; }");
ui->userToken->setStyleSheet(qsLineEditStyle);
ui->serverIp->setStyleSheet(qsLineEditStyle);
QRegularExpression rx("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$");
QRegularExpressionValidator* ipValidator = new QRegularExpressionValidator(rx, this);
ui->serverIp->setValidator(ipValidator);
setIp("127.0.0.1");
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()
@ -13,3 +31,7 @@ MainWindow::~MainWindow()
delete ui; delete ui;
} }
void MainWindow::setIp(const QString &ip)
{
ui->serverIp->setText(ip);
}

View File

@ -1,4 +1,4 @@
#ifndef MAINWINDOW_H #ifndef MAINWINDOW_H
#define MAINWINDOW_H #define MAINWINDOW_H
#include <QMainWindow> #include <QMainWindow>
@ -15,6 +15,9 @@ public:
MainWindow(QWidget *parent = nullptr); MainWindow(QWidget *parent = nullptr);
~MainWindow(); ~MainWindow();
protected:
void setIp(const QString &ip);
private: private:
Ui::MainWindow *ui; Ui::MainWindow *ui;
}; };

View File

@ -2,20 +2,217 @@
<ui version="4.0"> <ui version="4.0">
<class>MainWindow</class> <class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow"> <widget class="QMainWindow" name="MainWindow">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>551</width>
<height>600</height> <height>352</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="contextMenuPolicy">
<string>MainWindow</string> <enum>Qt::NoContextMenu</enum>
</property> </property>
<widget class="QWidget" name="centralwidget"/> <property name="windowTitle">
<widget class="QMenuBar" name="menubar"/> <string>EMS Configurer</string>
<widget class="QStatusBar" name="statusbar"/> </property>
<widget class="QWidget" name="centralwidget">
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>130</x>
<y>176</y>
<width>61</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Host IP</string>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>140</x>
<y>60</y>
<width>231</width>
<height>71</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>26</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Configurer</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QLineEdit" name="serverIp">
<property name="geometry">
<rect>
<x>220</x>
<y>173</y>
<width>124</width>
<height>23</height>
</rect>
</property>
<property name="inputMask">
<string/>
</property>
</widget>
<widget class="QLabel" name="label_6">
<property name="geometry">
<rect>
<x>370</x>
<y>88</y>
<width>161</width>
<height>41</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>for EMU Host</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QPushButton" name="pb_Test">
<property name="geometry">
<rect>
<x>350</x>
<y>173</y>
<width>51</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Test</string>
</property>
</widget>
<widget class="QLabel" name="label_logo">
<property name="geometry">
<rect>
<x>7</x>
<y>7</y>
<width>261</width>
<height>61</height>
</rect>
</property>
<property name="text">
<string>WWW.HJ-NET.COM</string>
</property>
</widget>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>140</x>
<y>280</y>
<width>251</width>
<height>31</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="pb_Logon">
<property name="text">
<string>Logon</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pb_Close">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QLineEdit" name="userToken">
<property name="geometry">
<rect>
<x>220</x>
<y>233</y>
<width>181</width>
<height>23</height>
</rect>
</property>
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="inputMethodHints">
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoEditMenu|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData</set>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>130</x>
<y>200</y>
<width>68</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>Login name</string>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>130</x>
<y>233</y>
<width>56</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Password</string>
</property>
</widget>
<widget class="QLineEdit" name="userName">
<property name="geometry">
<rect>
<x>220</x>
<y>203</y>
<width>181</width>
<height>23</height>
</rect>
</property>
</widget>
</widget>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -12,6 +12,8 @@
#include "openjson.h" #include "openjson.h"
#include "iconv-utils.h" #include "iconv-utils.h"
#include "kutilities.h" #include "kutilities.h"
#include "opmysql.h"
#include "mqtt_msg.h"
EventHandler::EventHandler() EventHandler::EventHandler()
{ {
@ -58,165 +60,190 @@ void EventHandler::onRecvHandler(hio_t* io, void* buf, int readbytes)
if( Frame_DeviceData_Request == pReadFrame->frame_type ) if( Frame_DeviceData_Request == pReadFrame->frame_type )
{ {
hlogi("<=== reveive device data request"); handleGatherData(io, buf, readbytes);
}
else
{
assert(false);
}
}
OpenJson json;
CODING buf_code = GetCoding((unsigned char*)pReadFrame->frame_content, pReadFrame->frame_len); //判断是否是utf-8 void EventHandler::handleGatherData(hio_t* io, void* buf, int readbytes)
{
assert(buf);
hlogi("<=== recieve buffer coding is [%s]", buf_code== GBK ? "GBK": (buf_code == UTF8 ? "UTF8" : "UNKNOWN CODING")); __USING_NAMESPACE_HJ__;
//这里将帧内帧的内容转换为字符串符合json格式的字符串详见mqtt_msg.h的MessageData结构定义 MessageFrame respFrame;
std::string msg((char*)pReadFrame->frame_content + 8, pReadFrame->frame_len - 8); MessageFrame* pReadFrame = (MessageFrame*)buf;
hlogi("<=== reveive device data request");
if( buf_code == CODING::GBK OpenJson json;
|| buf_code == CODING::UNKOWN )
{
std::string str_result;
//转换为UTF8
if( !GBKToUTF8(msg, str_result) )
{
hloge("Failed to transfer code from GBK to UTF-8");
respFrame.setErrorFrame(ERR_INVALID_UTF8);
hio_write(io, (void*)&respFrame, sizeof respFrame);
return;
}
hlogi("Successfuly transfer code from GBK to UTF-8!"); CODING buf_code = GetCoding((unsigned char*)pReadFrame->frame_content, pReadFrame->frame_len); //判断是否是utf-8
msg = str_result;
} hlogi("<=== recieve buffer coding is [%s]", buf_code == GBK ? "GBK" : (buf_code == UTF8 ? "UTF8" : "UNKNOWN CODING"));
MessageData* pData = (MessageData*)pReadFrame->frame_content;
#ifdef _DEBUG #ifdef _DEBUG
hlogd("<=== recieve !!VALID!! mqtt pack len =[%d] data=[%s]", msg.length(), msg.c_str()); //这里还是好的 hlogd("<=== MessageData structure [\n%s\n]", printHex(pData, pReadFrame->frame_len).c_str());
#endif #endif
unsigned int len = msg.length(); //这里将帧内帧的内容转换为字符串符合json格式的字符串详见mqtt_msg.h的MessageData结构定义
char* pTmp = new char[len]; std::string msg((char*)pReadFrame->frame_content + MSG_HEADER_LENGTH, pReadFrame->frame_len - MSG_HEADER_LENGTH);
memcpy(pTmp, msg.c_str(), len);
std::shared_ptr<char> ptr; //放个智能指针省得忘记删除 #ifdef _DEBUG
ptr.reset(pTmp); hlogd("<=== json content [\n%s\n]", msg.c_str());
#endif
//hlogi("<=== decode OK, msg=[%s]", msg.c_str()); if( buf_code == CODING::GBK
|| buf_code == CODING::UNKOWN )
{
std::string str_result;
//转换为UTF8
if( !GBKToUTF8(msg, str_result) )
{
hloge("Failed to transfer code from GBK to UTF-8");
respFrame.setErrorFrame(ERR_INVALID_UTF8);
hio_write(io, (void*)&respFrame, sizeof respFrame);
return;
}
hlogi("Successfuly transfer code from GBK to UTF-8!");
msg = str_result;
}
#ifdef _DEBUG
hlogd("<=== recieve !!VALID!! mqtt pack len =[%d] data=[%s]", msg.length(), msg.c_str()); //这里还是好的
#endif
unsigned int len = msg.length();
char* pTmp = new char[len];
memcpy(pTmp, msg.c_str(), len);
std::shared_ptr<char> ptr; //放个智能指针省得忘记删除
ptr.reset(pTmp);
//hlogi("<=== decode OK, msg=[%s]", msg.c_str());
#if 0 #if 0
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 17).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 17).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 18).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 18).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 19).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 19).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 20).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 20).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 21).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 21).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 22).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 22).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 23).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 23).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 24).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 24).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 25).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 25).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 26).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 26).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 27).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 27).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 28).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 28).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 29).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 29).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 30).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 30).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 31).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 31).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 32).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 32).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 33).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 33).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 34).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 34).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 61).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 61).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 62).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 62).c_str());
hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 63).c_str()); hlogi("<=== decode OK [\n%s\n]", printHex(pTmp, 63).c_str());
#endif #endif
//hlogd("<=== decode OK [\n%s\n]", printHex(pTmp, len).c_str()); //hlogd("<=== decode OK [\n%s\n]", printHex(pTmp, len).c_str());
try try
{
if( !json.decode(msg) )
{ {
if( !json.decode(msg) ) hloge("Failed to decode json string pack , length=%d", readbytes);
{
hloge("Failed to decode json string pack , length=%d", readbytes);
respFrame.setErrorFrame(ERR_INVALID_JSON_FMT);
hio_write(io, (void*)&respFrame, sizeof respFrame);
return;
}
std::string fsucode = json["FsuCode"].s();
std::string msg_type = json["type"].s();
std::string timestamp = get_current_timestamp(); // json["TimeStamp"].s();
if( fsucode.length() == 0 )
{
//delete[] pTmp;
hlogw("!!empty fsucode recieved!");
respFrame.setErrorFrame(ERR_INVALID_FSUCODE);
hio_write(io, (void*)&respFrame, sizeof respFrame);
return;
}
hlogi("<=== decode OK, recieve fsucode=[%s] type=[%s] ts=[%s]", fsucode.c_str(), msg_type.c_str(), timestamp.c_str());
hio_write(io, (void*)&respFrame, sizeof respFrame);
#ifdef _DEBUG
hlogd("<<<<Check Mem after decode here>>>> \n[\n%s\n]\n", printHex(pTmp, len).c_str());
#endif
std::string out_compress;
int zip_ret = 0;
if( (zip_ret = CompressString(pTmp, len, out_compress, Z_DEFAULT_COMPRESSION)) != Z_OK )
{
hloge("Failed to compress source data, zip return value %d", zip_ret);
return;
}
hlogd("<<<<Compress result: string size from original [%d] to [%d]", len, out_compress.size());
#ifdef _DEBUG
hlogd("<<<<Compressed string here>>>> \n[\n%s\n]\n", printHex(out_compress.c_str(), out_compress.size()).c_str());
#endif
//std::string msg2(pTmp, len);
if( msg_type == "gateway-data"
|| msg_type == "gateway-alarmdata"
|| msg_type == "gateway-writedata"
|| msg_type == "gateway-readdata"
|| msg_type == "web-write"
|| msg_type == "web-alarm" )
{
auto& IdCodeContent = json["IdCodeContent"];
if( IdCodeContent.size() <= 0 )
{
//delete[] pTmp;
hloge("invalid IdCodeContent's size: %d", IdCodeContent.size());
return;
}
auto& pNode = IdCodeContent[0]; //这是只解析第一个节点
std::string oid = pNode["OID"].s();
//OpDatabase::getInstance()->InsertMessage(timestamp, msg_type, fsucode, out_compress, (int)pData->mqtt_topic, (int)pData->device_id);
}
if( msg_type == "web-read" )
{
auto& IdCodeContent = json["IdCodes"];
if( IdCodeContent.size() <= 0 )
{
hloge("invalid IdCodes's size: %d", IdCodeContent.size());
//delete[] pTmp;
return;
}
auto& pNode = IdCodeContent[0]; //这是只解析第一个节点
std::string oid = pNode.s();
//OpDatabase::getInstance()->InsertMessage(timestamp, msg_type, fsucode, out_compress, (int)pData->mqtt_topic, (int)pData->device_id);
}
//delete[] pTmp;
}
catch( const char* errMsg )
{
hloge("Failed to decode json string pack , catch error:%s", errMsg);
respFrame.setErrorFrame(ERR_INVALID_JSON_FMT); respFrame.setErrorFrame(ERR_INVALID_JSON_FMT);
hio_write(io, (void*)&respFrame, sizeof respFrame); hio_write(io, (void*)&respFrame, sizeof respFrame);
return; return;
} }
std::string fsucode = json["FsuCode"].s();
std::string msg_type = json["type"].s();
std::string timestamp = get_current_timestamp(); // json["TimeStamp"].s();
if( fsucode.length() == 0 )
{
//delete[] pTmp;
hlogw("!!empty fsucode recieved!");
respFrame.setErrorFrame(ERR_INVALID_FSUCODE);
hio_write(io, (void*)&respFrame, sizeof respFrame);
return;
}
hlogi("<=== decode OK, recieve fsucode=[%s] type=[%s] ts=[%s]", fsucode.c_str(), msg_type.c_str(), timestamp.c_str());
hio_write(io, (void*)&respFrame, sizeof respFrame);
#ifdef _DEBUG
hlogd("<<<<Check Mem after decode here>>>> \n[\n%s\n]\n", printHex(pTmp, len).c_str());
#endif
std::string out_compress;
int zip_ret = 0;
if( (zip_ret = CompressString(pTmp, len, out_compress, Z_DEFAULT_COMPRESSION)) != Z_OK )
{
hloge("Failed to compress source data, zip return value %d", zip_ret);
return;
}
hlogd("<<<<Compress result: string size from original [%d] to [%d]", len, out_compress.size());
#ifdef _DEBUG
hlogd("<<<<Compressed string here>>>> \n[\n%s\n]\n", printHex(out_compress.c_str(), out_compress.size()).c_str());
#endif
//std::string msg2(pTmp, len);
if( msg_type == "gateway-data"
|| msg_type == "gateway-alarmdata"
|| msg_type == "gateway-writedata"
|| msg_type == "gateway-readdata"
|| msg_type == "web-write"
|| msg_type == "web-alarm" )
{
auto& IdCodeContent = json["IdCodeContent"];
if( IdCodeContent.size() <= 0 )
{
//delete[] pTmp;
hloge("invalid IdCodeContent's size: %d", IdCodeContent.size());
return;
}
auto& pNode = IdCodeContent[0]; //这是只解析第一个节点
std::string oid = pNode["OID"].s();
OpDatabase::getInstance()->InsertMessage(timestamp, msg_type, fsucode, out_compress, (int)pData->mqtt_topic, (int)pData->device_id);
}
if( msg_type == "web-read" )
{
auto& IdCodeContent = json["IdCodes"];
if( IdCodeContent.size() <= 0 )
{
hloge("invalid IdCodes's size: %d", IdCodeContent.size());
//delete[] pTmp;
return;
}
auto& pNode = IdCodeContent[0]; //这是只解析第一个节点
std::string oid = pNode.s();
OpDatabase::getInstance()->InsertMessage(timestamp, msg_type, fsucode, out_compress, (int)pData->mqtt_topic, (int)pData->device_id);
}
//delete[] pTmp;
}
catch( const char* errMsg )
{
hloge("Failed to decode json string pack , catch error:%s", errMsg);
respFrame.setErrorFrame(ERR_INVALID_JSON_FMT);
hio_write(io, (void*)&respFrame, sizeof respFrame);
return;
} }
} }

View File

@ -10,5 +10,9 @@ public:
public: public:
static void onRecvHandler(hio_t* io, void* buf, int readbytes); static void onRecvHandler(hio_t* io, void* buf, int readbytes);
protected:
//处理采集程序上传的数据以JSON数据上报
static void handleGatherData(hio_t* io, void* buf, int readbytes);
}; };

View File

@ -5,6 +5,9 @@
__NAMESPACE_BEGIN__(HJ) __NAMESPACE_BEGIN__(HJ)
#define FRAME_HEADER_LENGTH (5)
#define FRAME_TAILE_LENGTH (4)
typedef enum tagErrorCode : unsigned char typedef enum tagErrorCode : unsigned char
{ {
ERR_OK = 0X00, ERR_OK = 0X00,

View File

@ -6,14 +6,6 @@
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
#include "kdefine.h"
#include "frame_define.h"
#include "kutilities.h"
#include "mqtt_msg.h"
#include "opmysql.h"
#include "iconv-utils.h"
#include "openjson.h"
#include <hv/hv.h> #include <hv/hv.h>
#include <hv/hmain.h> #include <hv/hmain.h>
#include <hv/iniparser.h> #include <hv/iniparser.h>
@ -21,6 +13,8 @@
#include <hv/hsocket.h> #include <hv/hsocket.h>
#include <hv/hssl.h> #include <hv/hssl.h>
#include "kdefine.h"
#include "opmysql.h"
#include "eventhandler.h" #include "eventhandler.h"
#ifndef TEST_UNPACK #ifndef TEST_UNPACK

View File

@ -1,11 +1,17 @@
#pragma once #pragma once
#include "kdefine.h"
#pragma pack(1) #pragma pack(1)
__NAMESPACE_BEGIN__(HJ)
#define MSG_HEADER_LENGTH (8)
typedef enum tagTopic : char typedef enum tagTopic : char
{ {
GateWayPublicTopic_Server = 0, GateWayPublicTopic_Server = 0,
ServerPublicTopic_GateWay = 1, ServerPublicTopic_GateWay = 1,
}FrameType, MQTT_Topic; } MQTT_Topic;
typedef enum tagDataType : char typedef enum tagDataType : char
{ {
@ -19,7 +25,7 @@ typedef enum tagDataType : char
DT_GATEWAY_WRITE = 7, DT_GATEWAY_WRITE = 7,
FRAME_REQUEST = 8, FRAME_REQUEST = 8,
FRAME_RESPONSE = 9, FRAME_RESPONSE = 9,
}FrameDataType,MQTT_DataType; }MQTT_DataType;
/* /*
{ {
@ -50,6 +56,6 @@ typedef struct tagMsgData
char content_data[1]; //MQTT包字符串的内容 char content_data[1]; //MQTT包字符串的内容
}MessageData; }MessageData;
__NAMESPACE_END__(HJ)
#pragma pack() #pragma pack()

View File

@ -0,0 +1,164 @@
#ifndef HV_ASYNC_HTTP_CLIENT_H_
#define HV_ASYNC_HTTP_CLIENT_H_
#include <map>
#include <list>
#include "EventLoopThread.h"
#include "Channel.h"
#include "HttpMessage.h"
#include "HttpParser.h"
namespace hv {
template<typename Conn>
class ConnPool {
public:
int size() {
return conns_.size();
}
bool get(Conn& conn) {
if (conns_.empty()) return false;
conn = conns_.front();
conns_.pop_front();
return true;
}
bool add(const Conn& conn) {
conns_.push_back(conn);
return true;
}
bool remove(const Conn& conn) {
auto iter = conns_.begin();
while (iter != conns_.end()) {
if (*iter == conn) {
iter = conns_.erase(iter);
return true;
} else {
++iter;
}
}
return false;
}
private:
std::list<Conn> conns_;
};
struct HttpClientTask {
HttpRequestPtr req;
HttpResponseCallback cb;
uint64_t start_time;
};
typedef std::shared_ptr<HttpClientTask> HttpClientTaskPtr;
struct HttpClientContext {
HttpClientTaskPtr task;
HttpResponsePtr resp;
HttpParserPtr parser;
TimerID timerID;
HttpClientContext() {
timerID = INVALID_TIMER_ID;
}
~HttpClientContext() {
cancelTimer();
}
void cancelTimer() {
if (timerID != INVALID_TIMER_ID) {
killTimer(timerID);
timerID = INVALID_TIMER_ID;
}
}
void cancelTask() {
cancelTimer();
task = NULL;
}
void callback() {
cancelTimer();
if (task && task->cb) {
task->cb(resp);
}
// NOTE: task done
task = NULL;
}
void successCallback() {
callback();
resp = NULL;
}
void errorCallback() {
resp = NULL;
callback();
}
};
class HV_EXPORT AsyncHttpClient : private EventLoopThread {
public:
AsyncHttpClient(EventLoopPtr loop = NULL) : EventLoopThread(loop) {
if (loop == NULL) {
EventLoopThread::start(true);
}
}
~AsyncHttpClient() {
EventLoopThread::stop(true);
}
// thread-safe
int send(const HttpRequestPtr& req, HttpResponseCallback resp_cb);
int send(const HttpClientTaskPtr& task) {
EventLoopThread::loop()->queueInLoop(std::bind(&AsyncHttpClient::sendInLoop, this, task));
return 0;
}
protected:
void sendInLoop(HttpClientTaskPtr task) {
int err = doTask(task);
if (err != 0 && task->cb) {
task->cb(NULL);
}
}
int doTask(const HttpClientTaskPtr& task);
static int sendRequest(const SocketChannelPtr& channel);
// channel
const SocketChannelPtr& getChannel(int fd) {
return channels[fd];
// return fd < channels.capacity() ? channels[fd] : NULL;
}
const SocketChannelPtr& addChannel(hio_t* io) {
auto channel = std::make_shared<SocketChannel>(io);
channel->newContext<HttpClientContext>();
int fd = channel->fd();
channels[fd] = channel;
return channels[fd];
}
void removeChannel(const SocketChannelPtr& channel) {
channel->deleteContext<HttpClientContext>();
int fd = channel->fd();
channels.erase(fd);
}
private:
// NOTE: just one loop thread, no need mutex.
// fd => SocketChannelPtr
std::map<int, SocketChannelPtr> channels;
// peeraddr => ConnPool
std::map<std::string, ConnPool<int>> conn_pools;
};
}
#endif // HV_ASYNC_HTTP_CLIENT_H_

View File

@ -0,0 +1,15 @@
#ifndef HV_BUFFER_HPP_
#define HV_BUFFER_HPP_
#include <memory>
#include "hbuf.h"
namespace hv {
typedef HBuf Buffer;
typedef std::shared_ptr<Buffer> BufferPtr;
}
#endif // HV_BUFFER_HPP_

View File

@ -0,0 +1,377 @@
#ifndef HV_CHANNEL_HPP_
#define HV_CHANNEL_HPP_
#include <string>
#include <functional>
#include <memory>
#include <atomic>
#include "hloop.h"
#include "hsocket.h"
#include "Buffer.h"
namespace hv {
class Channel {
public:
Channel(hio_t* io = NULL) {
io_ = io;
fd_ = -1;
id_ = 0;
ctx_ = NULL;
status = CLOSED;
if (io) {
fd_ = hio_fd(io);
id_ = hio_id(io);
ctx_ = hio_context(io);
hio_set_context(io, this);
if (hio_is_opened(io)) {
status = OPENED;
}
if (hio_getcb_read(io) == NULL) {
hio_setcb_read(io_, on_read);
}
if (hio_getcb_write(io) == NULL) {
hio_setcb_write(io_, on_write);
}
if (hio_getcb_close(io) == NULL) {
hio_setcb_close(io_, on_close);
}
}
}
virtual ~Channel() {
if (isOpened()) {
close();
// NOTE: Detach after destructor to avoid triggering onclose
if (io_ && id_ == hio_id(io_)) {
hio_set_context(io_, NULL);
}
}
}
hio_t* io() { return io_; }
int fd() { return fd_; }
uint32_t id() { return id_; }
int error() { return hio_error(io_); }
// context
void* context() {
return ctx_;
}
void setContext(void* ctx) {
ctx_ = ctx;
}
template<class T>
T* newContext() {
ctx_ = new T;
return (T*)ctx_;
}
template<class T>
T* getContext() {
return (T*)ctx_;
}
template<class T>
void deleteContext() {
if (ctx_) {
delete (T*)ctx_;
ctx_ = NULL;
}
}
// contextPtr
std::shared_ptr<void> contextPtr() {
return contextPtr_;
}
void setContextPtr(const std::shared_ptr<void>& ctx) {
contextPtr_ = ctx;
}
void setContextPtr(std::shared_ptr<void>&& ctx) {
contextPtr_ = std::move(ctx);
}
template<class T>
std::shared_ptr<T> newContextPtr() {
contextPtr_ = std::make_shared<T>();
return std::static_pointer_cast<T>(contextPtr_);
}
template<class T>
std::shared_ptr<T> getContextPtr() {
return std::static_pointer_cast<T>(contextPtr_);
}
void deleteContextPtr() {
contextPtr_.reset();
}
bool isOpened() {
if (io_ == NULL || status >= DISCONNECTED) return false;
return id_ == hio_id(io_) && hio_is_opened(io_);
}
bool isClosed() {
return !isOpened();
}
int startRead() {
if (!isOpened()) return -1;
return hio_read_start(io_);
}
int stopRead() {
if (!isOpened()) return -1;
return hio_read_stop(io_);
}
int readOnce() {
if (!isOpened()) return -1;
return hio_read_once(io_);
}
int readString() {
if (!isOpened()) return -1;
return hio_readstring(io_);
}
int readLine() {
if (!isOpened()) return -1;
return hio_readline(io_);
}
int readBytes(int len) {
if (!isOpened() || len <= 0) return -1;
return hio_readbytes(io_, len);
}
// write thread-safe
int write(const void* data, int size) {
if (!isOpened()) return -1;
return hio_write(io_, data, size);
}
int write(Buffer* buf) {
return write(buf->data(), buf->size());
}
int write(const std::string& str) {
return write(str.data(), str.size());
}
// iobuf setting
void setReadBuf(void* buf, size_t len) {
if (io_ == NULL) return;
hio_set_readbuf(io_, buf, len);
}
void setMaxReadBufsize(uint32_t size) {
if (io_ == NULL) return;
hio_set_max_read_bufsize(io_, size);
}
void setMaxWriteBufsize(uint32_t size) {
if (io_ == NULL) return;
hio_set_max_write_bufsize(io_, size);
}
size_t writeBufsize() {
if (io_ == NULL) return 0;
return hio_write_bufsize(io_);
}
bool isWriteComplete() {
return writeBufsize() == 0;
}
// close thread-safe
int close(bool async = false) {
if (isClosed()) return -1;
status = CLOSED;
return async ? hio_close_async(io_) : hio_close(io_);
}
public:
hio_t* io_;
int fd_;
uint32_t id_;
void* ctx_;
enum Status {
OPENED,
CONNECTING,
CONNECTED,
DISCONNECTED,
CLOSED,
};
std::atomic<Status> status;
std::function<void(Buffer*)> onread;
// NOTE: Use Channel::isWriteComplete in onwrite callback to determine whether all data has been written.
std::function<void(Buffer*)> onwrite;
std::function<void()> onclose;
std::shared_ptr<void> contextPtr_;
private:
static void on_read(hio_t* io, void* data, int readbytes) {
Channel* channel = (Channel*)hio_context(io);
if (channel && channel->onread) {
Buffer buf(data, readbytes);
channel->onread(&buf);
}
}
static void on_write(hio_t* io, const void* data, int writebytes) {
Channel* channel = (Channel*)hio_context(io);
if (channel && channel->onwrite) {
Buffer buf((void*)data, writebytes);
channel->onwrite(&buf);
}
}
static void on_close(hio_t* io) {
Channel* channel = (Channel*)hio_context(io);
if (channel) {
channel->status = CLOSED;
if (channel->onclose) {
channel->onclose();
}
}
}
};
class SocketChannel : public Channel {
public:
std::function<void()> onconnect; // only for TcpClient
std::function<void()> heartbeat;
SocketChannel(hio_t* io) : Channel(io) {
}
virtual ~SocketChannel() {}
// SSL/TLS
int enableSSL() {
if (io_ == NULL) return -1;
return hio_enable_ssl(io_);
}
bool isSSL() {
if (io_ == NULL) return false;
return hio_is_ssl(io_);
}
int setSSL(hssl_t ssl) {
if (io_ == NULL) return -1;
return hio_set_ssl(io_, ssl);
}
int setSslCtx(hssl_ctx_t ssl_ctx) {
if (io_ == NULL) return -1;
return hio_set_ssl_ctx(io_, ssl_ctx);
}
int newSslCtx(hssl_ctx_opt_t* opt) {
if (io_ == NULL) return -1;
return hio_new_ssl_ctx(io_, opt);
}
// for hssl_set_sni_hostname
int setHostname(const std::string& hostname) {
if (io_ == NULL) return -1;
return hio_set_hostname(io_, hostname.c_str());
}
// timeout
void setConnectTimeout(int timeout_ms) {
if (io_ == NULL) return;
hio_set_connect_timeout(io_, timeout_ms);
}
void setCloseTimeout(int timeout_ms) {
if (io_ == NULL) return;
hio_set_close_timeout(io_, timeout_ms);
}
void setReadTimeout(int timeout_ms) {
if (io_ == NULL) return;
hio_set_read_timeout(io_, timeout_ms);
}
void setWriteTimeout(int timeout_ms) {
if (io_ == NULL) return;
hio_set_write_timeout(io_, timeout_ms);
}
void setKeepaliveTimeout(int timeout_ms) {
if (io_ == NULL) return;
hio_set_keepalive_timeout(io_, timeout_ms);
}
// heartbeat
// NOTE: Beware of circular reference problems caused by passing SocketChannelPtr by value.
void setHeartbeat(int interval_ms, std::function<void()> fn) {
if (io_ == NULL) return;
heartbeat = std::move(fn);
hio_set_heartbeat(io_, interval_ms, send_heartbeat);
}
/*
* unpack
*
* NOTE: unpack_setting_t of multiple IOs of the same function also are same,
* so only the pointer of unpack_setting_t is stored in hio_t,
* the life time of unpack_setting_t shoud be guaranteed by caller.
*/
void setUnpack(unpack_setting_t* setting) {
if (io_ == NULL) return;
hio_set_unpack(io_, setting);
}
int startConnect(int port, const char* host = "127.0.0.1") {
sockaddr_u peeraddr;
memset(&peeraddr, 0, sizeof(peeraddr));
int ret = sockaddr_set_ipport(&peeraddr, host, port);
if (ret != 0) {
// hloge("unknown host %s", host);
return ret;
}
return startConnect(&peeraddr.sa);
}
int startConnect(struct sockaddr* peeraddr) {
if (io_ == NULL) return -1;
hio_set_peeraddr(io_, peeraddr, SOCKADDR_LEN(peeraddr));
return startConnect();
}
int startConnect() {
if (io_ == NULL) return -1;
status = CONNECTING;
hio_setcb_connect(io_, on_connect);
return hio_connect(io_);
}
bool isConnected() {
return status == CONNECTED && isOpened();
}
std::string localaddr() {
if (io_ == NULL) return "";
struct sockaddr* addr = hio_localaddr(io_);
char buf[SOCKADDR_STRLEN] = {0};
return SOCKADDR_STR(addr, buf);
}
std::string peeraddr() {
if (io_ == NULL) return "";
struct sockaddr* addr = hio_peeraddr(io_);
char buf[SOCKADDR_STRLEN] = {0};
return SOCKADDR_STR(addr, buf);
}
private:
static void on_connect(hio_t* io) {
SocketChannel* channel = (SocketChannel*)hio_context(io);
if (channel) {
channel->status = CONNECTED;
if (channel->onconnect) {
channel->onconnect();
}
}
}
static void send_heartbeat(hio_t* io) {
SocketChannel* channel = (SocketChannel*)hio_context(io);
if (channel && channel->heartbeat) {
channel->heartbeat();
}
}
};
typedef std::shared_ptr<Channel> ChannelPtr;
typedef std::shared_ptr<SocketChannel> SocketChannelPtr;
}
#endif // HV_CHANNEL_HPP_

View File

@ -0,0 +1,47 @@
#ifndef HV_EVENT_HPP_
#define HV_EVENT_HPP_
#include <functional>
#include <memory>
#include "hloop.h"
namespace hv {
struct Event;
struct Timer;
typedef uint64_t TimerID;
#define INVALID_TIMER_ID ((hv::TimerID)-1)
typedef std::function<void(Event*)> EventCallback;
typedef std::function<void(TimerID)> TimerCallback;
struct Event {
hevent_t event;
EventCallback cb;
Event(EventCallback cb = NULL) {
memset(&event, 0, sizeof(hevent_t));
this->cb = std::move(cb);
}
};
struct Timer {
htimer_t* timer;
TimerCallback cb;
uint32_t repeat;
Timer(htimer_t* timer = NULL, TimerCallback cb = NULL, uint32_t repeat = INFINITE) {
this->timer = timer;
this->cb = std::move(cb);
this->repeat = repeat;
}
};
typedef std::shared_ptr<Event> EventPtr;
typedef std::shared_ptr<Timer> TimerPtr;
}
#endif // HV_EVENT_HPP_

View File

@ -0,0 +1,277 @@
#ifndef HV_EVENT_LOOP_HPP_
#define HV_EVENT_LOOP_HPP_
#include <functional>
#include <queue>
#include <map>
#include <mutex>
#include "hloop.h"
#include "hthread.h"
#include "Status.h"
#include "Event.h"
#include "ThreadLocalStorage.h"
namespace hv {
class EventLoop : public Status {
public:
typedef std::function<void()> Functor;
// New an EventLoop using an existing hloop_t object,
// so we can embed an EventLoop object into the old application based on hloop.
// NOTE: Be careful to deal with destroy of hloop_t.
EventLoop(hloop_t* loop = NULL) {
setStatus(kInitializing);
if (loop) {
loop_ = loop;
is_loop_owner = false;
} else {
loop_ = hloop_new(HLOOP_FLAG_AUTO_FREE);
is_loop_owner = true;
}
connectionNum = 0;
nextTimerID = 0;
setStatus(kInitialized);
}
~EventLoop() {
stop();
}
hloop_t* loop() {
return loop_;
}
// @brief Run loop forever
void run() {
if (loop_ == NULL) return;
if (status() == kRunning) return;
ThreadLocalStorage::set(ThreadLocalStorage::EVENT_LOOP, this);
setStatus(kRunning);
hloop_run(loop_);
setStatus(kStopped);
}
// stop thread-safe
void stop() {
if (loop_ == NULL) return;
if (status() < kRunning) {
if (is_loop_owner) {
hloop_free(&loop_);
}
loop_ = NULL;
return;
}
setStatus(kStopping);
hloop_stop(loop_);
loop_ = NULL;
}
void pause() {
if (loop_ == NULL) return;
if (isRunning()) {
hloop_pause(loop_);
setStatus(kPause);
}
}
void resume() {
if (loop_ == NULL) return;
if (isPause()) {
hloop_resume(loop_);
setStatus(kRunning);
}
}
// Timer interfaces: setTimer, killTimer, resetTimer
TimerID setTimer(int timeout_ms, TimerCallback cb, uint32_t repeat = INFINITE, TimerID timerID = INVALID_TIMER_ID) {
if (loop_ == NULL) return INVALID_TIMER_ID;
assertInLoopThread();
htimer_t* htimer = htimer_add(loop_, onTimer, timeout_ms, repeat);
assert(htimer != NULL);
if (timerID == INVALID_TIMER_ID) {
timerID = generateTimerID();
}
hevent_set_id(htimer, timerID);
hevent_set_userdata(htimer, this);
timers[timerID] = std::make_shared<Timer>(htimer, cb, repeat);
return timerID;
}
// setTimerInLoop thread-safe
TimerID setTimerInLoop(int timeout_ms, TimerCallback cb, uint32_t repeat = INFINITE, TimerID timerID = INVALID_TIMER_ID) {
if (timerID == INVALID_TIMER_ID) {
timerID = generateTimerID();
}
runInLoop(std::bind(&EventLoop::setTimer, this, timeout_ms, cb, repeat, timerID));
return timerID;
}
// alias javascript setTimeout, setInterval
// setTimeout thread-safe
TimerID setTimeout(int timeout_ms, TimerCallback cb) {
return setTimerInLoop(timeout_ms, cb, 1);
}
// setInterval thread-safe
TimerID setInterval(int interval_ms, TimerCallback cb) {
return setTimerInLoop(interval_ms, cb, INFINITE);
}
// killTimer thread-safe
void killTimer(TimerID timerID) {
runInLoop([timerID, this](){
auto iter = timers.find(timerID);
if (iter != timers.end()) {
htimer_del(iter->second->timer);
timers.erase(iter);
}
});
}
// resetTimer thread-safe
void resetTimer(TimerID timerID, int timeout_ms = 0) {
runInLoop([timerID, timeout_ms, this](){
auto iter = timers.find(timerID);
if (iter != timers.end()) {
htimer_reset(iter->second->timer, timeout_ms);
if (iter->second->repeat == 0) {
iter->second->repeat = 1;
}
}
});
}
long tid() {
if (loop_ == NULL) return hv_gettid();
return hloop_tid(loop_);
}
bool isInLoopThread() {
if (loop_ == NULL) return false;
return hv_gettid() == hloop_tid(loop_);
}
void assertInLoopThread() {
assert(isInLoopThread());
}
void runInLoop(Functor fn) {
if (isRunning() && isInLoopThread()) {
if (fn) fn();
} else {
queueInLoop(std::move(fn));
}
}
void queueInLoop(Functor fn) {
postEvent([fn](Event* ev) {
if (fn) fn();
});
}
void postEvent(EventCallback cb) {
if (loop_ == NULL) return;
EventPtr ev = std::make_shared<Event>(cb);
hevent_set_userdata(&ev->event, this);
ev->event.cb = onCustomEvent;
mutex_.lock();
customEvents.push(ev);
mutex_.unlock();
hloop_post_event(loop_, &ev->event);
}
private:
TimerID generateTimerID() {
return (((TimerID)tid() & 0xFFFFFFFF) << 32) | ++nextTimerID;
}
static void onTimer(htimer_t* htimer) {
EventLoop* loop = (EventLoop*)hevent_userdata(htimer);
TimerID timerID = hevent_id(htimer);
TimerPtr timer = NULL;
auto iter = loop->timers.find(timerID);
if (iter != loop->timers.end()) {
timer = iter->second;
if (timer->repeat != INFINITE) --timer->repeat;
}
if (timer) {
if (timer->cb) timer->cb(timerID);
if (timer->repeat == 0) {
// htimer_t alloc and free by hloop, but timers[timerID] managed by EventLoop.
loop->timers.erase(timerID);
}
}
}
static void onCustomEvent(hevent_t* hev) {
EventLoop* loop = (EventLoop*)hevent_userdata(hev);
loop->mutex_.lock();
EventPtr ev = loop->customEvents.front();
loop->customEvents.pop();
loop->mutex_.unlock();
if (ev && ev->cb) ev->cb(ev.get());
}
public:
std::atomic<uint32_t> connectionNum; // for LB_LeastConnections
private:
hloop_t* loop_;
bool is_loop_owner;
std::mutex mutex_;
std::queue<EventPtr> customEvents; // GUAREDE_BY(mutex_)
std::map<TimerID, TimerPtr> timers;
std::atomic<TimerID> nextTimerID;
};
typedef std::shared_ptr<EventLoop> EventLoopPtr;
// ThreadLocalStorage
static inline EventLoop* tlsEventLoop() {
return (EventLoop*)ThreadLocalStorage::get(ThreadLocalStorage::EVENT_LOOP);
}
#define currentThreadEventLoop ::hv::tlsEventLoop()
static inline TimerID setTimer(int timeout_ms, TimerCallback cb, uint32_t repeat = INFINITE) {
EventLoop* loop = tlsEventLoop();
assert(loop != NULL);
if (loop == NULL) return INVALID_TIMER_ID;
return loop->setTimer(timeout_ms, cb, repeat);
}
static inline void killTimer(TimerID timerID) {
EventLoop* loop = tlsEventLoop();
assert(loop != NULL);
if (loop == NULL) return;
loop->killTimer(timerID);
}
static inline void resetTimer(TimerID timerID, int timeout_ms) {
EventLoop* loop = tlsEventLoop();
assert(loop != NULL);
if (loop == NULL) return;
loop->resetTimer(timerID, timeout_ms);
}
static inline TimerID setTimeout(int timeout_ms, TimerCallback cb) {
return setTimer(timeout_ms, cb, 1);
}
static inline TimerID setInterval(int interval_ms, TimerCallback cb) {
return setTimer(interval_ms, cb, INFINITE);
}
}
#endif // HV_EVENT_LOOP_HPP_

View File

@ -0,0 +1,116 @@
#ifndef HV_EVENT_LOOP_THREAD_HPP_
#define HV_EVENT_LOOP_THREAD_HPP_
#include <thread>
#include "hlog.h"
#include "EventLoop.h"
namespace hv {
class EventLoopThread : public Status {
public:
// Return 0 means OK, other failed.
typedef std::function<int()> Functor;
EventLoopThread(EventLoopPtr loop = NULL) {
setStatus(kInitializing);
loop_ = loop ? loop : std::make_shared<EventLoop>();
setStatus(kInitialized);
}
~EventLoopThread() {
stop();
join();
}
const EventLoopPtr& loop() {
return loop_;
}
hloop_t* hloop() {
return loop_->loop();
}
bool isRunning() {
return loop_->isRunning();
}
// @param wait_thread_started: if ture this method will block until loop_thread started.
// @param pre: This functor will be executed when loop_thread started.
// @param post:This Functor will be executed when loop_thread stopped.
void start(bool wait_thread_started = true,
Functor pre = Functor(),
Functor post = Functor()) {
if (status() >= kStarting && status() < kStopped) return;
if (isRunning()) return;
setStatus(kStarting);
thread_ = std::make_shared<std::thread>(&EventLoopThread::loop_thread, this, pre, post);
if (wait_thread_started) {
while (loop_->status() < kRunning) {
hv_delay(1);
}
}
}
// @param wait_thread_started: if ture this method will block until loop_thread stopped.
// stop thread-safe
void stop(bool wait_thread_stopped = false) {
if (status() < kStarting || status() >= kStopping) return;
setStatus(kStopping);
long loop_tid = loop_->tid();
loop_->stop();
if (wait_thread_stopped) {
if (hv_gettid() == loop_tid) return;
join();
}
}
// @brief join loop_thread
// @note destructor will join loop_thread if you forget to call this method.
void join() {
if (thread_ && thread_->joinable()) {
thread_->join();
thread_ = NULL;
}
}
private:
void loop_thread(const Functor& pre, const Functor& post) {
hlogi("EventLoopThread started, tid=%ld", hv_gettid());
setStatus(kStarted);
if (pre) {
loop_->queueInLoop([this, pre]{
if (pre() != 0) {
loop_->stop();
}
});
}
loop_->run();
assert(loop_->isStopped());
if (post) {
post();
}
setStatus(kStopped);
hlogi("EventLoopThread stopped, tid=%ld", hv_gettid());
}
private:
EventLoopPtr loop_;
std::shared_ptr<std::thread> thread_;
};
typedef std::shared_ptr<EventLoopThread> EventLoopThreadPtr;
}
#endif // HV_EVENT_LOOP_THREAD_HPP_

View File

@ -0,0 +1,138 @@
#ifndef HV_EVENT_LOOP_THREAD_POOL_HPP_
#define HV_EVENT_LOOP_THREAD_POOL_HPP_
#include "EventLoopThread.h"
#include "hbase.h"
namespace hv {
class EventLoopThreadPool : public Status {
public:
EventLoopThreadPool(int thread_num = std::thread::hardware_concurrency()) {
setStatus(kInitializing);
thread_num_ = thread_num;
next_loop_idx_ = 0;
setStatus(kInitialized);
}
~EventLoopThreadPool() {
stop();
join();
}
int threadNum() {
return thread_num_;
}
void setThreadNum(int num) {
thread_num_ = num;
}
EventLoopPtr nextLoop(load_balance_e lb = LB_RoundRobin) {
size_t numLoops = loop_threads_.size();
if (numLoops == 0) return NULL;
size_t idx = 0;
if (lb == LB_RoundRobin) {
if (++next_loop_idx_ >= numLoops) next_loop_idx_ = 0;
idx = next_loop_idx_ % numLoops;
} else if (lb == LB_Random) {
idx = hv_rand(0, numLoops - 1);
} else if (lb == LB_LeastConnections) {
for (size_t i = 1; i < numLoops; ++i) {
if (loop_threads_[i]->loop()->connectionNum < loop_threads_[idx]->loop()->connectionNum) {
idx = i;
}
}
} else {
// Not Implemented
}
return loop_threads_[idx]->loop();
}
EventLoopPtr loop(int idx = -1) {
if (idx >= 0 && idx < (int)loop_threads_.size()) {
return loop_threads_[idx]->loop();
}
return nextLoop();
}
hloop_t* hloop(int idx = -1) {
EventLoopPtr ptr = loop(idx);
return ptr ? ptr->loop() : NULL;
}
// @param wait_threads_started: if ture this method will block until all loop_threads started.
// @param pre: This functor will be executed when loop_thread started.
// @param post:This Functor will be executed when loop_thread stopped.
void start(bool wait_threads_started = false,
std::function<void(const EventLoopPtr&)> pre = NULL,
std::function<void(const EventLoopPtr&)> post = NULL) {
if (thread_num_ == 0) return;
if (status() >= kStarting && status() < kStopped) return;
setStatus(kStarting);
auto started_cnt = std::make_shared<std::atomic<int>>(0);
auto exited_cnt = std::make_shared<std::atomic<int>>(0);
loop_threads_.clear();
for (int i = 0; i < thread_num_; ++i) {
auto loop_thread = std::make_shared<EventLoopThread>();
const EventLoopPtr& loop = loop_thread->loop();
loop_thread->start(false,
[this, started_cnt, pre, &loop]() {
if (++(*started_cnt) == thread_num_) {
setStatus(kRunning);
}
if (pre) pre(loop);
return 0;
},
[this, exited_cnt, post, &loop]() {
if (post) post(loop);
if (++(*exited_cnt) == thread_num_) {
setStatus(kStopped);
}
return 0;
}
);
loop_threads_.push_back(loop_thread);
}
if (wait_threads_started) {
while (status() < kRunning) {
hv_delay(1);
}
}
}
// @param wait_threads_started: if ture this method will block until all loop_threads stopped.
// stop thread-safe
void stop(bool wait_threads_stopped = false) {
if (status() < kStarting || status() >= kStopping) return;
setStatus(kStopping);
for (auto& loop_thread : loop_threads_) {
loop_thread->stop(false);
}
if (wait_threads_stopped) {
join();
}
}
// @brief join all loop_threads
// @note destructor will join loop_threads if you forget to call this method.
void join() {
for (auto& loop_thread : loop_threads_) {
loop_thread->join();
}
}
private:
int thread_num_;
std::vector<EventLoopThreadPtr> loop_threads_;
std::atomic<unsigned int> next_loop_idx_;
};
}
#endif // HV_EVENT_LOOP_THREAD_POOL_HPP_

View File

@ -0,0 +1,171 @@
#ifndef HV_HTTP_CLIENT_H_
#define HV_HTTP_CLIENT_H_
#include "hexport.h"
#include "hssl.h"
#include "HttpMessage.h"
/*
#include <stdio.h>
#include "HttpClient.h"
int main(int argc, char* argv[]) {
HttpRequest req;
req.method = HTTP_GET;
req.url = "http://www.example.com";
HttpResponse res;
int ret = http_client_send(&req, &res);
printf("%s\n", req.Dump(true,true).c_str());
if (ret != 0) {
printf("* Failed:%s:%d\n", http_client_strerror(ret), ret);
}
else {
printf("%s\n", res.Dump(true,true).c_str());
}
return ret;
}
*/
typedef struct http_client_s http_client_t;
HV_EXPORT http_client_t* http_client_new(const char* host = NULL, int port = DEFAULT_HTTP_PORT, int https = 0);
HV_EXPORT int http_client_del(http_client_t* cli);
HV_EXPORT const char* http_client_strerror(int errcode);
// timeout: s
HV_EXPORT int http_client_set_timeout(http_client_t* cli, int timeout);
// SSL/TLS
HV_EXPORT int http_client_set_ssl_ctx(http_client_t* cli, hssl_ctx_t ssl_ctx);
// hssl_ctx_new(opt) -> http_client_set_ssl_ctx
HV_EXPORT int http_client_new_ssl_ctx(http_client_t* cli, hssl_ctx_opt_t* opt);
// common headers
HV_EXPORT int http_client_clear_headers(http_client_t* cli);
HV_EXPORT int http_client_set_header(http_client_t* cli, const char* key, const char* value);
HV_EXPORT int http_client_del_header(http_client_t* cli, const char* key);
HV_EXPORT const char* http_client_get_header(http_client_t* cli, const char* key);
// http_proxy
HV_EXPORT int http_client_set_http_proxy(http_client_t* cli, const char* host, int port);
// https_proxy
HV_EXPORT int http_client_set_https_proxy(http_client_t* cli, const char* host, int port);
// no_proxy
HV_EXPORT int http_client_add_no_proxy(http_client_t* cli, const char* host);
// sync
HV_EXPORT int http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* resp);
// async
// Intern will start an EventLoopThread when http_client_send_async first called,
// http_client_del will destroy the thread.
HV_EXPORT int http_client_send_async(http_client_t* cli, HttpRequestPtr req, HttpResponseCallback resp_cb = NULL);
// top-level api
// http_client_new -> http_client_send -> http_client_del
HV_EXPORT int http_client_send(HttpRequest* req, HttpResponse* resp);
// http_client_send_async(&default_async_client, ...)
HV_EXPORT int http_client_send_async(HttpRequestPtr req, HttpResponseCallback resp_cb = NULL);
// low-level api
// @retval >=0 connfd, <0 error
HV_EXPORT int http_client_connect(http_client_t* cli, const char* host, int port, int https, int timeout);
HV_EXPORT int http_client_send_header(http_client_t* cli, HttpRequest* req);
HV_EXPORT int http_client_send_data(http_client_t* cli, const char* data, int size);
HV_EXPORT int http_client_recv_data(http_client_t* cli, char* data, int size);
HV_EXPORT int http_client_recv_response(http_client_t* cli, HttpResponse* resp);
HV_EXPORT int http_client_close(http_client_t* cli);
namespace hv {
class HttpClient {
public:
HttpClient(const char* host = NULL, int port = DEFAULT_HTTP_PORT, int https = 0) {
_client = http_client_new(host, port, https);
}
~HttpClient() {
if (_client) {
http_client_del(_client);
_client = NULL;
}
}
// timeout: s
int setTimeout(int timeout) {
return http_client_set_timeout(_client, timeout);
}
// SSL/TLS
int setSslCtx(hssl_ctx_t ssl_ctx) {
return http_client_set_ssl_ctx(_client, ssl_ctx);
}
int newSslCtx(hssl_ctx_opt_t* opt) {
return http_client_new_ssl_ctx(_client, opt);
}
// headers
int clearHeaders() {
return http_client_clear_headers(_client);
}
int setHeader(const char* key, const char* value) {
return http_client_set_header(_client, key, value);
}
int delHeader(const char* key) {
return http_client_del_header(_client, key);
}
const char* getHeader(const char* key) {
return http_client_get_header(_client, key);
}
// http_proxy
int setHttpProxy(const char* host, int port) {
return http_client_set_http_proxy(_client, host, port);
}
// https_proxy
int setHttpsProxy(const char* host, int port) {
return http_client_set_https_proxy(_client, host, port);
}
// no_proxy
int addNoProxy(const char* host) {
return http_client_add_no_proxy(_client, host);
}
// sync
int send(HttpRequest* req, HttpResponse* resp) {
return http_client_send(_client, req, resp);
}
// async
int sendAsync(HttpRequestPtr req, HttpResponseCallback resp_cb = NULL) {
return http_client_send_async(_client, req, std::move(resp_cb));
}
// low-level api
int connect(const char* host, int port = DEFAULT_HTTP_PORT, int https = 0, int timeout = DEFAULT_HTTP_CONNECT_TIMEOUT) {
return http_client_connect(_client, host, port, https, timeout);
}
int sendHeader(HttpRequest* req) {
return http_client_send_header(_client, req);
}
int sendData(const char* data, int size) {
return http_client_send_data(_client, data, size);
}
int recvData(char* data, int size) {
return http_client_recv_data(_client, data, size);
}
int recvResponse(HttpResponse* resp) {
return http_client_recv_response(_client, resp);
}
int close() {
return http_client_close(_client);
}
private:
http_client_t* _client;
};
}
#endif // HV_HTTP_CLIENT_H_

View File

@ -0,0 +1,213 @@
#ifndef HV_HTTP_CONTEXT_H_
#define HV_HTTP_CONTEXT_H_
#include "hexport.h"
#include "HttpMessage.h"
#include "HttpResponseWriter.h"
namespace hv {
struct HttpService;
struct HV_EXPORT HttpContext {
HttpService* service;
HttpRequestPtr request;
HttpResponsePtr response;
HttpResponseWriterPtr writer;
void* userdata;
HttpContext() {
service = NULL;
userdata = NULL;
}
// HttpRequest aliases
// return request->xxx
std::string ip() {
return request->client_addr.ip;
}
int port() {
return request->client_addr.port;
}
http_method method() {
return request->method;
}
std::string url() {
return request->url;
}
std::string path() {
return request->Path();
}
std::string fullpath() {
return request->FullPath();
}
std::string host() {
return request->Host();
}
const http_headers& headers() {
return request->headers;
}
std::string header(const char* key, const std::string& defvalue = hv::empty_string) {
return request->GetHeader(key, defvalue);
}
const hv::QueryParams& params() {
return request->query_params;
}
std::string param(const char* key, const std::string& defvalue = hv::empty_string) {
return request->GetParam(key, defvalue);
}
const HttpCookie& cookie(const char* name) {
return request->GetCookie(name);
}
int length() {
return request->ContentLength();
}
http_content_type type() {
return request->ContentType();
}
bool is(http_content_type content_type) {
return request->ContentType() == content_type;
}
bool is(const char* content_type) {
return request->ContentType() == http_content_type_enum(content_type);
}
std::string& body() {
return request->body;
}
#ifndef WITHOUT_HTTP_CONTENT
// Content-Type: application/json
const hv::Json& json() {
return request->GetJson();
}
// Content-Type: multipart/form-data
const hv::MultiPart& form() {
return request->GetForm();
}
std::string form(const char* name, const std::string& defvalue = hv::empty_string) {
return request->GetFormData(name, defvalue);
}
// Content-Type: application/x-www-form-urlencoded
const hv::KeyValue& urlencoded() {
return request->GetUrlEncoded();
}
std::string urlencoded(const char* key, const std::string& defvalue = hv::empty_string) {
return request->GetUrlEncoded(key, defvalue);
}
// T=[bool, int, int64_t, float, double]
template<typename T>
T get(const char* key, T defvalue = 0) {
return request->Get(key, defvalue);
}
std::string get(const char* key, const std::string& defvalue = hv::empty_string) {
return request->GetString(key, defvalue);
}
#endif
// HttpResponse aliases
// response->xxx = xxx
void setStatus(http_status status) {
response->status_code = status;
}
void setContentType(http_content_type type) {
response->content_type = type;
}
void setContentType(const char* type) {
response->content_type = http_content_type_enum(type);
}
void setHeader(const char* key, const std::string& value) {
response->SetHeader(key, value);
if (stricmp(key, "Content-Type") == 0) {
setContentType(value.c_str());
}
}
void setCookie(const HttpCookie& cookie) {
response->AddCookie(cookie);
}
void setBody(const std::string& body) {
response->body = body;
}
// response->sendXxx
int send() {
if (writer) {
writer->End();
}
return response->status_code;
}
int send(const std::string& str, http_content_type type = APPLICATION_JSON) {
response->content_type = type;
response->body = str;
return send();
}
int sendString(const std::string& str) {
response->String(str);
return send();
}
int sendData(void* data, int len, bool nocopy = true) {
response->Data(data, len, nocopy);
return send();
}
int sendFile(const char* filepath) {
response->File(filepath);
return send();
}
#ifndef WITHOUT_HTTP_CONTENT
// T=[bool, int, int64_t, float, double, string]
template<typename T>
void set(const char* key, const T& value) {
response->Set(key, value);
}
// @see HttpMessage::Json
// @usage https://github.com/nlohmann/json
template<typename T>
int sendJson(const T& t) {
response->Json(t);
return send();
}
#endif
int redirect(const std::string& location, http_status status = HTTP_STATUS_FOUND) {
response->Redirect(location, status);
return send();
}
int close() {
return writer ? writer->close(true) : -1;
}
};
} // end namespace hv
typedef std::shared_ptr<hv::HttpContext> HttpContextPtr;
#endif // HV_HTTP_CONTEXT_H_

View File

@ -0,0 +1,501 @@
#ifndef HV_HTTP_MESSAGE_H_
#define HV_HTTP_MESSAGE_H_
/*
* @class HttpMessage
* HttpRequest extends HttpMessage
* HttpResponse extends HttpMessage
*
* @member
* request-line: GET / HTTP/1.1\r\n => method path
* response-line: HTTP/1.1 200 OK\r\n => status_code
* headers, cookies
* body
*
* content, content_length, content_type
* json, form, kv
*
* @function
* Content, ContentLength, ContentType
* ParseUrl, ParseBody
* DumpUrl, DumpHeaders, DumpBody, Dump
* GetHeader, GetParam, GetJson, GetFormData, GetUrlEncoded
* SetHeader, SetParam, SetBody, SetFormData, SetUrlEncoded
* Get<T>, Set<T>
* GetString, GetBool, GetInt, GetFloat
* String, Data, Json, File, FormFile
*
* @example
* examples/http_server_test.cpp
* examples/http_client_test.cpp
* examples/httpd
*
*/
#include <memory>
#include <string>
#include <map>
#include <functional>
#include "hexport.h"
#include "hbase.h"
#include "hstring.h"
#include "hfile.h"
#include "hpath.h"
#include "httpdef.h"
#include "http_content.h"
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
// Cookie: sessionid=1; domain=.example.com; path=/; max-age=86400; secure; httponly
struct HV_EXPORT HttpCookie {
std::string name;
std::string value;
std::string domain;
std::string path;
std::string expires;
int max_age;
bool secure;
bool httponly;
enum SameSite {
Default,
Strict,
Lax,
None
} samesite;
enum Priority {
NotSet,
Low,
Medium,
High,
} priority;
hv::KeyValue kv; // for multiple names
HttpCookie();
void init();
void reset();
bool parse(const std::string& str);
std::string dump() const;
};
typedef std::map<std::string, std::string, hv::StringCaseLess> http_headers;
typedef std::vector<HttpCookie> http_cookies;
typedef std::string http_body;
HV_EXPORT extern http_headers DefaultHeaders;
HV_EXPORT extern http_body NoBody;
HV_EXPORT extern HttpCookie NoCookie;
class HV_EXPORT HttpMessage {
public:
static char s_date[32];
int type;
unsigned short http_major;
unsigned short http_minor;
http_headers headers;
http_cookies cookies;
http_body body;
// http_cb
std::function<void(HttpMessage*, http_parser_state state, const char* data, size_t size)> http_cb;
// structured content
void* content; // DATA_NO_COPY
size_t content_length;
http_content_type content_type;
#ifndef WITHOUT_HTTP_CONTENT
hv::Json json; // APPLICATION_JSON
hv::MultiPart form; // MULTIPART_FORM_DATA
hv::KeyValue kv; // X_WWW_FORM_URLENCODED
// T=[bool, int, int64_t, float, double]
template<typename T>
T Get(const char* key, T defvalue = 0);
std::string GetString(const char* key, const std::string& = "");
bool GetBool(const char* key, bool defvalue = 0);
int64_t GetInt(const char* key, int64_t defvalue = 0);
double GetFloat(const char* key, double defvalue = 0);
template<typename T>
void Set(const char* key, const T& value) {
switch (ContentType()) {
case APPLICATION_JSON:
json[key] = value;
break;
case MULTIPART_FORM_DATA:
form[key] = hv::FormData(value);
break;
case X_WWW_FORM_URLENCODED:
kv[key] = hv::to_string(value);
break;
default:
break;
}
}
/*
* @usage https://github.com/nlohmann/json
*
* null: Json(nullptr);
* boolean: Json(true);
* number: Json(123);
* string: Json("hello");
* object: Json(std::map<string, ValueType>);
* Json(hv::Json::object({
{"k1", "v1"},
{"k2", "v2"}
}));
* array: Json(std::vector<ValueType>);
Json(hv::Json::array(
{1, 2, 3}
));
*/
// Content-Type: application/json
template<typename T>
int Json(const T& t) {
content_type = APPLICATION_JSON;
hv::Json j(t);
body = j.dump(2);
return 200;
}
const hv::Json& GetJson() {
if (json.empty() && ContentType() == APPLICATION_JSON) {
ParseBody();
}
return json;
}
// Content-Type: multipart/form-data
template<typename T>
void SetFormData(const char* name, const T& t) {
form[name] = hv::FormData(t);
}
void SetFormFile(const char* name, const char* filepath) {
form[name] = hv::FormData(NULL, filepath);
}
int FormFile(const char* name, const char* filepath) {
content_type = MULTIPART_FORM_DATA;
form[name] = hv::FormData(NULL, filepath);
return 200;
}
const hv::MultiPart& GetForm() {
if (form.empty() && ContentType() == MULTIPART_FORM_DATA) {
ParseBody();
}
return form;
}
std::string GetFormData(const char* name, const std::string& defvalue = hv::empty_string) {
if (form.empty() && ContentType() == MULTIPART_FORM_DATA) {
ParseBody();
}
auto iter = form.find(name);
return iter == form.end() ? defvalue : iter->second.content;
}
int SaveFormFile(const char* name, const char* path) {
if (ContentType() != MULTIPART_FORM_DATA) {
return HTTP_STATUS_BAD_REQUEST;
}
if (form.empty()) {
ParseBody();
if (form.empty()) return HTTP_STATUS_BAD_REQUEST;
}
auto iter = form.find(name);
if (iter == form.end()) {
return HTTP_STATUS_BAD_REQUEST;
}
const auto& formdata = iter->second;
if (formdata.content.empty()) {
return HTTP_STATUS_BAD_REQUEST;
}
std::string filepath(path);
if (HPath::isdir(path)) {
filepath = HPath::join(filepath, formdata.filename);
}
HFile file;
if (file.open(filepath.c_str(), "wb") != 0) {
return HTTP_STATUS_INTERNAL_SERVER_ERROR;
}
file.write(formdata.content.data(), formdata.content.size());
return 200;
}
// Content-Type: application/x-www-form-urlencoded
template<typename T>
void SetUrlEncoded(const char* key, const T& t) {
kv[key] = hv::to_string(t);
}
const hv::KeyValue& GetUrlEncoded() {
if (kv.empty() && ContentType() == X_WWW_FORM_URLENCODED) {
ParseBody();
}
return kv;
}
std::string GetUrlEncoded(const char* key, const std::string& defvalue = hv::empty_string) {
if (kv.empty() && ContentType() == X_WWW_FORM_URLENCODED) {
ParseBody();
}
auto iter = kv.find(key);
return iter == kv.end() ? defvalue : iter->second;
}
#endif
HttpMessage();
virtual ~HttpMessage();
void Init();
virtual void Reset();
// structured-content -> content_type <-> headers["Content-Type"]
void FillContentType();
// body.size -> content_length <-> headers["Content-Length"]
void FillContentLength();
bool IsChunked();
bool IsKeepAlive();
bool IsUpgrade();
// headers
void SetHeader(const char* key, const std::string& value);
std::string GetHeader(const char* key, const std::string& defvalue = hv::empty_string);
// cookies
void AddCookie(const HttpCookie& cookie);
const HttpCookie& GetCookie(const std::string& name);
// body
void SetBody(const std::string& body);
const std::string& Body();
// headers -> string
void DumpHeaders(std::string& str);
// structured content -> body
void DumpBody();
void DumpBody(std::string& str);
// body -> structured content
// @retval 0:succeed
int ParseBody();
virtual std::string Dump(bool is_dump_headers, bool is_dump_body);
void* Content() {
if (content == NULL && body.size() != 0) {
content = (void*)body.data();
content_length = body.size();
}
return content;
}
size_t ContentLength() {
if (content_length == 0) {
FillContentLength();
}
return content_length;
}
http_content_type ContentType() {
if (content_type == CONTENT_TYPE_NONE) {
FillContentType();
}
return content_type;
}
void SetContentType(http_content_type type) {
content_type = type;
}
void SetContentType(const char* type) {
content_type = http_content_type_enum(type);
}
void SetContentTypeByFilename(const char* filepath) {
const char* suffix = hv_suffixname(filepath);
if (suffix) {
content_type = http_content_type_enum_by_suffix(suffix);
}
if (content_type == CONTENT_TYPE_NONE || content_type == CONTENT_TYPE_UNDEFINED) {
content_type = APPLICATION_OCTET_STREAM;
}
}
int String(const std::string& str) {
content_type = TEXT_PLAIN;
body = str;
return 200;
}
int Data(void* data, int len, bool nocopy = true) {
content_type = APPLICATION_OCTET_STREAM;
if (nocopy) {
content = data;
content_length = len;
} else {
content_length = body.size();
body.resize(content_length + len);
memcpy((void*)(body.data() + content_length), data, len);
content_length += len;
}
return 200;
}
int File(const char* filepath) {
HFile file;
if (file.open(filepath, "rb") != 0) {
return HTTP_STATUS_NOT_FOUND;
}
SetContentTypeByFilename(filepath);
file.readall(body);
return 200;
}
int SaveFile(const char* filepath) {
HFile file;
if (file.open(filepath, "wb") != 0) {
return HTTP_STATUS_NOT_FOUND;
}
file.write(body.data(), body.size());
return 200;
}
};
#define DEFAULT_HTTP_USER_AGENT "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
#define DEFAULT_HTTP_TIMEOUT 60 // s
#define DEFAULT_HTTP_CONNECT_TIMEOUT 10 // s
#define DEFAULT_HTTP_FAIL_RETRY_COUNT 1
#define DEFAULT_HTTP_FAIL_RETRY_DELAY 1000 // ms
class HV_EXPORT HttpRequest : public HttpMessage {
public:
http_method method;
// scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
std::string url;
// structured url
std::string scheme;
std::string host;
int port;
std::string path;
hv::QueryParams query_params;
// client_addr
hv::NetAddr client_addr; // for http server save client addr of request
// for HttpClient
uint16_t timeout; // unit: s
uint16_t connect_timeout;// unit: s
uint32_t retry_count;
uint32_t retry_delay; // unit: ms
unsigned redirect: 1;
unsigned proxy : 1;
unsigned cancel : 1;
HttpRequest();
void Init();
virtual void Reset();
virtual std::string Dump(bool is_dump_headers = true, bool is_dump_body = false);
// method
void SetMethod(const char* method) {
this->method = http_method_enum(method);
}
const char* Method() {
return http_method_str(method);
}
// scheme
bool IsHttps() {
return strncmp(scheme.c_str(), "https", 5) == 0 ||
strncmp(url.c_str(), "https://", 8) == 0;
}
// url
void SetUrl(const char* url) {
this->url = url;
}
const std::string& Url() {
return url;
}
// structed url -> url
void DumpUrl();
// url -> structed url
void ParseUrl();
// /path?query#fragment
std::string FullPath() { return path; }
// /path
std::string Path();
// ?query_params
template<typename T>
void SetParam(const char* key, const T& t) {
query_params[key] = hv::to_string(t);
}
std::string GetParam(const char* key, const std::string& defvalue = hv::empty_string) {
auto iter = query_params.find(key);
return iter == query_params.end() ? defvalue : iter->second;
}
// Host:
std::string Host() {
auto iter = headers.find("Host");
return iter == headers.end() ? host : iter->second;
}
void FillHost(const char* host, int port = DEFAULT_HTTP_PORT);
void SetHost(const char* host, int port = DEFAULT_HTTP_PORT);
void SetProxy(const char* host, int port);
bool IsProxy() { return proxy; }
// Auth
void SetAuth(const std::string& auth);
void SetBasicAuth(const std::string& username, const std::string& password);
void SetBearerTokenAuth(const std::string& token);
void SetTimeout(int sec) { timeout = sec; }
void SetConnectTimeout(int sec) { connect_timeout = sec; }
void AllowRedirect(bool on = true) { redirect = on; }
// NOTE: SetRetry just for AsyncHttpClient
void SetRetry(int count = DEFAULT_HTTP_FAIL_RETRY_COUNT,
int delay = DEFAULT_HTTP_FAIL_RETRY_DELAY) {
retry_count = count;
retry_delay = delay;
}
void Cancel() { cancel = 1; }
bool IsCanceled() { return cancel == 1; }
// Range: bytes=0-4095
void SetRange(long from = 0, long to = -1);
bool GetRange(long& from, long& to);
};
class HV_EXPORT HttpResponse : public HttpMessage {
public:
http_status status_code;
const char* status_message() {
return http_status_str(status_code);
}
HttpResponse();
void Init();
virtual void Reset();
virtual std::string Dump(bool is_dump_headers = true, bool is_dump_body = false);
// Content-Range: bytes 0-4095/10240000
void SetRange(long from, long to, long total);
bool GetRange(long& from, long& to, long& total);
int Redirect(const std::string& location, http_status status = HTTP_STATUS_FOUND) {
status_code = status;
SetHeader("Location", location);
return status_code;
}
};
typedef std::shared_ptr<HttpRequest> HttpRequestPtr;
typedef std::shared_ptr<HttpResponse> HttpResponsePtr;
typedef std::function<void(const HttpResponsePtr&)> HttpResponseCallback;
#endif // HV_HTTP_MESSAGE_H_

View File

@ -0,0 +1,53 @@
#ifndef HV_HTTP_PARSER_H_
#define HV_HTTP_PARSER_H_
#include "hexport.h"
#include "HttpMessage.h"
class HV_EXPORT HttpParser {
public:
http_version version;
http_session_type type;
static HttpParser* New(http_session_type type = HTTP_CLIENT, http_version version = HTTP_V1);
virtual ~HttpParser() {}
virtual int GetSendData(char** data, size_t* len) = 0;
virtual int FeedRecvData(const char* data, size_t len) = 0;
// Http1Parser: http_parser_state
// Http2Parser: http2_session_state
virtual int GetState() = 0;
// Http1Parser: GetState() != HP_MESSAGE_COMPLETE
// Http2Parser: GetState() == H2_WANT_RECV
virtual bool WantRecv() = 0;
// Http1Parser: GetState() == HP_MESSAGE_COMPLETE
// Http2Parser: GetState() == H2_WANT_SEND
virtual bool WantSend() = 0;
// IsComplete: Is recved HttpRequest or HttpResponse complete?
// Http1Parser: GetState() == HP_MESSAGE_COMPLETE
// Http2Parser: (state == H2_RECV_HEADERS || state == H2_RECV_DATA) && stream_closed
virtual bool IsComplete() = 0;
virtual bool IsEof() { return false; }
// client
// SubmitRequest -> while(GetSendData) {send} -> InitResponse -> do {recv -> FeedRecvData} while(WantRecv)
virtual int SubmitRequest(HttpRequest* req) = 0;
virtual int InitResponse(HttpResponse* res) = 0;
// server
// InitRequest -> do {recv -> FeedRecvData} while(WantRecv) -> SubmitResponse -> while(GetSendData) {send}
virtual int InitRequest(HttpRequest* req) = 0;
virtual int SubmitResponse(HttpResponse* res) = 0;
virtual int GetError() = 0;
virtual const char* StrError(int error) = 0;
};
typedef std::shared_ptr<HttpParser> HttpParserPtr;
#endif // HV_HTTP_PARSER_H_

View File

@ -0,0 +1,100 @@
#ifndef HV_HTTP_RESPONSE_WRITER_H_
#define HV_HTTP_RESPONSE_WRITER_H_
#include "Channel.h"
#include "HttpMessage.h"
namespace hv {
class HV_EXPORT HttpResponseWriter : public SocketChannel {
public:
HttpResponsePtr response;
enum State {
SEND_BEGIN = 0,
SEND_HEADER,
SEND_BODY,
SEND_CHUNKED,
SEND_CHUNKED_END,
SEND_END,
} state: 8, end: 8;
HttpResponseWriter(hio_t* io, const HttpResponsePtr& resp)
: SocketChannel(io)
, response(resp)
, state(SEND_BEGIN)
, end(SEND_BEGIN)
{}
~HttpResponseWriter() {}
// Begin -> End
// Begin -> WriteResponse -> End
// Begin -> WriteStatus -> WriteHeader -> WriteBody -> End
// Begin -> EndHeaders("Content-Type", "text/event-stream") -> write -> write -> ... -> close
// Begin -> EndHeaders("Content-Length", content_length) -> WriteBody -> WriteBody -> ... -> End
// Begin -> EndHeaders("Transfer-Encoding", "chunked") -> WriteChunked -> WriteChunked -> ... -> End
int Begin() {
state = end = SEND_BEGIN;
return 0;
}
int WriteStatus(http_status status_codes) {
response->status_code = status_codes;
return 0;
}
int WriteHeader(const char* key, const char* value) {
response->SetHeader(key, value);
return 0;
}
template<typename T>
int WriteHeader(const char* key, T num) {
response->SetHeader(key, hv::to_string(num));
return 0;
}
int WriteCookie(const HttpCookie& cookie) {
response->cookies.push_back(cookie);
return 0;
}
int EndHeaders(const char* key = NULL, const char* value = NULL);
template<typename T>
int EndHeaders(const char* key, T num) {
std::string value = hv::to_string(num);
return EndHeaders(key, value.c_str());
}
int WriteChunked(const char* buf, int len = -1);
int WriteChunked(const std::string& str) {
return WriteChunked(str.c_str(), str.size());
}
int EndChunked() {
return WriteChunked(NULL, 0);
}
int WriteBody(const char* buf, int len = -1);
int WriteBody(const std::string& str) {
return WriteBody(str.c_str(), str.size());
}
int WriteResponse(HttpResponse* resp);
int SSEvent(const std::string& data, const char* event = "message");
int End(const char* buf = NULL, int len = -1);
int End(const std::string& str) {
return End(str.c_str(), str.size());
}
};
}
typedef std::shared_ptr<hv::HttpResponseWriter> HttpResponseWriterPtr;
#endif // HV_HTTP_RESPONSE_WRITER_H_

View File

@ -0,0 +1,163 @@
#ifndef HV_HTTP_SERVER_H_
#define HV_HTTP_SERVER_H_
#include "hexport.h"
#include "hssl.h"
// #include "EventLoop.h"
#include "HttpService.h"
// #include "WebSocketServer.h"
namespace hv {
class EventLoop;
struct WebSocketService;
}
using hv::HttpService;
using hv::WebSocketService;
typedef struct http_server_s {
char host[64];
int port; // http_port
int https_port;
int http_version;
int worker_processes;
int worker_threads;
uint32_t worker_connections; // max_connections = workers * worker_connections
HttpService* service; // http service
WebSocketService* ws; // websocket service
void* userdata;
int listenfd[2]; // 0: http, 1: https
void* privdata;
// hooks
std::function<void()> onWorkerStart;
std::function<void()> onWorkerStop;
// SSL/TLS
hssl_ctx_t ssl_ctx;
unsigned alloced_ssl_ctx: 1;
#ifdef __cplusplus
http_server_s() {
strcpy(host, "0.0.0.0");
// port = DEFAULT_HTTP_PORT;
// https_port = DEFAULT_HTTPS_PORT;
// port = 8080;
// https_port = 8443;
port = https_port = 0;
http_version = 1;
worker_processes = 0;
worker_threads = 0;
worker_connections = 1024;
service = NULL;
ws = NULL;
listenfd[0] = listenfd[1] = -1;
userdata = NULL;
privdata = NULL;
// SSL/TLS
ssl_ctx = NULL;
alloced_ssl_ctx = 0;
}
#endif
} http_server_t;
// @param wait: Whether to occupy current thread
HV_EXPORT int http_server_run(http_server_t* server, int wait = 1);
// NOTE: stop all loops and join all threads
HV_EXPORT int http_server_stop(http_server_t* server);
/*
#include "HttpServer.h"
using namespace hv;
int main() {
HttpService service;
service.GET("/ping", [](HttpRequest* req, HttpResponse* resp) {
resp->body = "pong";
return 200;
});
HttpServer server(&service);
server.setThreadNum(4);
server.run(":8080");
return 0;
}
*/
namespace hv {
class HV_EXPORT HttpServer : public http_server_t {
public:
HttpServer(HttpService* service = NULL)
: http_server_t()
{
this->service = service;
}
~HttpServer() { stop(); }
void registerHttpService(HttpService* service) {
this->service = service;
}
std::shared_ptr<hv::EventLoop> loop(int idx = -1);
void setHost(const char* host = "0.0.0.0") {
if (host) strcpy(this->host, host);
}
void setPort(int port = 0, int ssl_port = 0) {
if (port >= 0) this->port = port;
if (ssl_port >= 0) this->https_port = ssl_port;
}
void setListenFD(int fd = -1, int ssl_fd = -1) {
if (fd >= 0) this->listenfd[0] = fd;
if (ssl_fd >= 0) this->listenfd[1] = ssl_fd;
}
void setProcessNum(int num) {
this->worker_processes = num;
}
void setThreadNum(int num) {
this->worker_threads = num;
}
void setMaxWorkerConnectionNum(uint32_t num) {
this->worker_connections = num;
}
size_t connectionNum();
// SSL/TLS
int setSslCtx(hssl_ctx_t ssl_ctx) {
this->ssl_ctx = ssl_ctx;
return 0;
}
int newSslCtx(hssl_ctx_opt_t* opt) {
// NOTE: hssl_ctx_free in http_server_stop
hssl_ctx_t ssl_ctx = hssl_ctx_new(opt);
if (ssl_ctx == NULL) return -1;
this->alloced_ssl_ctx = 1;
return setSslCtx(ssl_ctx);
}
// run(":8080")
// run("0.0.0.0:8080")
// run("[::]:8080")
int run(const char* ip_port = NULL, bool wait = true) {
if (ip_port) {
hv::NetAddr listen_addr(ip_port);
if (listen_addr.ip.size() != 0) setHost(listen_addr.ip.c_str());
if (listen_addr.port != 0) setPort(listen_addr.port);
}
return http_server_run(this, wait);
}
int start(const char* ip_port = NULL) {
return run(ip_port, false);
}
int stop() {
return http_server_stop(this);
}
};
}
#endif // HV_HTTP_SERVER_H_

View File

@ -0,0 +1,281 @@
#ifndef HV_HTTP_SERVICE_H_
#define HV_HTTP_SERVICE_H_
#include <string>
#include <map>
#include <unordered_map>
#include <vector>
#include <list>
#include <memory>
#include <functional>
#include "hexport.h"
#include "HttpMessage.h"
#include "HttpResponseWriter.h"
#include "HttpContext.h"
#define DEFAULT_BASE_URL "/api/v1"
#define DEFAULT_DOCUMENT_ROOT "/var/www/html"
#define DEFAULT_HOME_PAGE "index.html"
#define DEFAULT_ERROR_PAGE "error.html"
#define DEFAULT_INDEXOF_DIR "/downloads/"
#define DEFAULT_KEEPALIVE_TIMEOUT 75000 // ms
// for FileCache
#define MAX_FILE_CACHE_SIZE (1 << 22) // 4M
#define DEFAULT_FILE_CACHE_STAT_INTERVAL 10 // s
#define DEFAULT_FILE_CACHE_EXPIRED_TIME 60 // s
/*
* @param[in] req: parsed structured http request
* @param[out] resp: structured http response
* @return 0: handle next
* http_status_code: handle done
*/
#define HTTP_STATUS_NEXT 0
#define HTTP_STATUS_UNFINISHED 0
// NOTE: http_sync_handler run on IO thread
typedef std::function<int(HttpRequest* req, HttpResponse* resp)> http_sync_handler;
// NOTE: http_async_handler run on hv::async threadpool
typedef std::function<void(const HttpRequestPtr& req, const HttpResponseWriterPtr& writer)> http_async_handler;
// NOTE: http_ctx_handler run on IO thread, you can easily post HttpContextPtr to your consumer thread for processing.
typedef std::function<int(const HttpContextPtr& ctx)> http_ctx_handler;
// NOTE: http_state_handler run on IO thread
typedef std::function<int(const HttpContextPtr& ctx, http_parser_state state, const char* data, size_t size)> http_state_handler;
struct http_handler {
http_sync_handler sync_handler;
http_async_handler async_handler;
http_ctx_handler ctx_handler;
http_state_handler state_handler;
http_handler() {}
http_handler(http_sync_handler fn) : sync_handler(std::move(fn)) {}
http_handler(http_async_handler fn) : async_handler(std::move(fn)) {}
http_handler(http_ctx_handler fn) : ctx_handler(std::move(fn)) {}
http_handler(http_state_handler fn) : state_handler(std::move(fn)) {}
http_handler(const http_handler& rhs)
: sync_handler(std::move(const_cast<http_handler&>(rhs).sync_handler))
, async_handler(std::move(const_cast<http_handler&>(rhs).async_handler))
, ctx_handler(std::move(const_cast<http_handler&>(rhs).ctx_handler))
, state_handler(std::move(const_cast<http_handler&>(rhs).state_handler))
{}
const http_handler& operator=(http_sync_handler fn) {
sync_handler = std::move(fn);
return *this;
}
const http_handler& operator=(http_async_handler fn) {
async_handler = std::move(fn);
return *this;
}
const http_handler& operator=(http_ctx_handler fn) {
ctx_handler = std::move(fn);
return *this;
}
const http_handler& operator=(http_state_handler fn) {
state_handler = std::move(fn);
return *this;
}
bool isNull() {
return sync_handler == NULL &&
async_handler == NULL &&
ctx_handler == NULL;
}
operator bool() {
return !isNull();
}
};
typedef std::vector<http_handler> http_handlers;
struct http_method_handler {
http_method method;
http_handler handler;
http_method_handler() {}
http_method_handler(http_method m, const http_handler& h) : method(m), handler(h) {}
};
// method => http_method_handler
typedef std::list<http_method_handler> http_method_handlers;
// path => http_method_handlers
typedef std::unordered_map<std::string, std::shared_ptr<http_method_handlers>> http_path_handlers;
namespace hv {
struct HV_EXPORT HttpService {
/* handler chain */
// preprocessor -> middleware -> processor -> postprocessor
http_handler preprocessor;
http_handlers middleware;
// processor: pathHandlers -> staticHandler -> errorHandler
http_handler processor;
http_handler postprocessor;
/* API handlers */
std::string base_url;
http_path_handlers pathHandlers;
/* Static file service */
http_handler staticHandler;
http_handler largeFileHandler;
std::string document_root;
std::string home_page;
std::string error_page;
// nginx: location => root
std::map<std::string, std::string, std::greater<std::string>> staticDirs;
/* Indexof directory service */
std::string index_of;
http_handler errorHandler;
/* Proxy service */
/* Reverse proxy service */
// nginx: location => proxy_pass
std::map<std::string, std::string, std::greater<std::string>> proxies;
/* Forward proxy service */
StringList trustProxies;
StringList noProxies;
int proxy_connect_timeout;
int proxy_read_timeout;
int proxy_write_timeout;
// options
int keepalive_timeout;
int max_file_cache_size; // cache small file
int file_cache_stat_interval; // stat file is modified
int file_cache_expired_time; // remove expired file cache
/*
* @test limit_rate
* @build make examples
* @server bin/httpd -c etc/httpd.conf -s restart -d
* @client bin/wget http://127.0.0.1:8080/downloads/test.zip
*/
int limit_rate; // limit send rate, unit: KB/s
unsigned enable_access_log :1;
unsigned enable_forward_proxy :1;
HttpService() {
// base_url = DEFAULT_BASE_URL;
document_root = DEFAULT_DOCUMENT_ROOT;
home_page = DEFAULT_HOME_PAGE;
// error_page = DEFAULT_ERROR_PAGE;
// index_of = DEFAULT_INDEXOF_DIR;
proxy_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
proxy_read_timeout = 0;
proxy_write_timeout = 0;
keepalive_timeout = DEFAULT_KEEPALIVE_TIMEOUT;
max_file_cache_size = MAX_FILE_CACHE_SIZE;
file_cache_stat_interval = DEFAULT_FILE_CACHE_STAT_INTERVAL;
file_cache_expired_time = DEFAULT_FILE_CACHE_EXPIRED_TIME;
limit_rate = -1; // unlimited
enable_access_log = 1;
enable_forward_proxy = 0;
}
void AddRoute(const char* path, http_method method, const http_handler& handler);
// @retval 0 OK, else HTTP_STATUS_NOT_FOUND, HTTP_STATUS_METHOD_NOT_ALLOWED
int GetRoute(const char* url, http_method method, http_handler** handler);
// RESTful API /:field/ => req->query_params["field"]
int GetRoute(HttpRequest* req, http_handler** handler);
// Static("/", "/var/www/html")
void Static(const char* path, const char* dir);
// @retval / => /var/www/html/index.html
std::string GetStaticFilepath(const char* path);
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
void AllowCORS();
// proxy
// forward proxy
void EnableForwardProxy() { enable_forward_proxy = 1; }
void AddTrustProxy(const char* host);
void AddNoProxy(const char* host);
bool IsTrustProxy(const char* host);
// reverse proxy
// Proxy("/api/v1/", "http://www.httpbin.org/");
void Proxy(const char* path, const char* url);
// @retval /api/v1/test => http://www.httpbin.org/test
std::string GetProxyUrl(const char* path);
hv::StringList Paths() {
hv::StringList paths;
for (auto& pair : pathHandlers) {
paths.emplace_back(pair.first);
}
return paths;
}
// Handler = [ http_sync_handler, http_ctx_handler ]
template<typename Handler>
void Use(Handler handlerFunc) {
middleware.emplace_back(handlerFunc);
}
// Inspired by github.com/gin-gonic/gin
// Handler = [ http_sync_handler, http_async_handler, http_ctx_handler, http_state_handler ]
template<typename Handler>
void Handle(const char* httpMethod, const char* relativePath, Handler handlerFunc) {
AddRoute(relativePath, http_method_enum(httpMethod), http_handler(handlerFunc));
}
// HEAD
template<typename Handler>
void HEAD(const char* relativePath, Handler handlerFunc) {
Handle("HEAD", relativePath, handlerFunc);
}
// GET
template<typename Handler>
void GET(const char* relativePath, Handler handlerFunc) {
Handle("GET", relativePath, handlerFunc);
}
// POST
template<typename Handler>
void POST(const char* relativePath, Handler handlerFunc) {
Handle("POST", relativePath, handlerFunc);
}
// PUT
template<typename Handler>
void PUT(const char* relativePath, Handler handlerFunc) {
Handle("PUT", relativePath, handlerFunc);
}
// DELETE
// NOTE: Windows <winnt.h> #define DELETE as a macro, we have to replace DELETE with Delete.
template<typename Handler>
void Delete(const char* relativePath, Handler handlerFunc) {
Handle("DELETE", relativePath, handlerFunc);
}
// PATCH
template<typename Handler>
void PATCH(const char* relativePath, Handler handlerFunc) {
Handle("PATCH", relativePath, handlerFunc);
}
// Any
template<typename Handler>
void Any(const char* relativePath, Handler handlerFunc) {
Handle("HEAD", relativePath, handlerFunc);
Handle("GET", relativePath, handlerFunc);
Handle("POST", relativePath, handlerFunc);
Handle("PUT", relativePath, handlerFunc);
Handle("DELETE", relativePath, handlerFunc);
Handle("PATCH", relativePath, handlerFunc);
}
};
}
#endif // HV_HTTP_SERVICE_H_

View File

@ -0,0 +1,56 @@
#ifndef HV_STATUS_HPP_
#define HV_STATUS_HPP_
#include <atomic>
namespace hv {
class Status {
public:
enum KStatus {
kNull = 0,
kInitializing = 1,
kInitialized = 2,
kStarting = 3,
kStarted = 4,
kRunning = 5,
kPause = 6,
kStopping = 7,
kStopped = 8,
kDestroyed = 9,
};
Status() {
status_ = kNull;
}
~Status() {
status_ = kDestroyed;
}
KStatus status() {
return status_;
}
void setStatus(KStatus status) {
status_ = status;
}
bool isRunning() {
return status_ == kRunning;
}
bool isPause() {
return status_ == kPause;
}
bool isStopped() {
return status_ == kStopped;
}
private:
std::atomic<KStatus> status_;
};
}
#endif // HV_STATUS_HPP_

View File

@ -0,0 +1,308 @@
#ifndef HV_TCP_CLIENT_HPP_
#define HV_TCP_CLIENT_HPP_
#include "hsocket.h"
#include "hssl.h"
#include "hlog.h"
#include "EventLoopThread.h"
#include "Channel.h"
namespace hv {
template<class TSocketChannel = SocketChannel>
class TcpClientEventLoopTmpl {
public:
typedef std::shared_ptr<TSocketChannel> TSocketChannelPtr;
TcpClientEventLoopTmpl(EventLoopPtr loop = NULL) {
loop_ = loop ? loop : std::make_shared<EventLoop>();
remote_port = 0;
connect_timeout = HIO_DEFAULT_CONNECT_TIMEOUT;
tls = false;
tls_setting = NULL;
reconn_setting = NULL;
unpack_setting = NULL;
}
virtual ~TcpClientEventLoopTmpl() {
HV_FREE(tls_setting);
HV_FREE(reconn_setting);
HV_FREE(unpack_setting);
}
const EventLoopPtr& loop() {
return loop_;
}
// delete thread-safe
void deleteInLoop() {
loop_->runInLoop([this](){
delete this;
});
}
// NOTE: By default, not bind local port. If necessary, you can call bind() after createsocket().
// @retval >=0 connfd, <0 error
int createsocket(int remote_port, const char* remote_host = "127.0.0.1") {
memset(&remote_addr, 0, sizeof(remote_addr));
int ret = sockaddr_set_ipport(&remote_addr, remote_host, remote_port);
if (ret != 0) {
return NABS(ret);
}
this->remote_host = remote_host;
this->remote_port = remote_port;
return createsocket(&remote_addr.sa);
}
int createsocket(struct sockaddr* remote_addr) {
int connfd = ::socket(remote_addr->sa_family, SOCK_STREAM, 0);
// SOCKADDR_PRINT(remote_addr);
if (connfd < 0) {
perror("socket");
return -2;
}
hio_t* io = hio_get(loop_->loop(), connfd);
assert(io != NULL);
hio_set_peeraddr(io, remote_addr, SOCKADDR_LEN(remote_addr));
channel = std::make_shared<TSocketChannel>(io);
return connfd;
}
int bind(int local_port, const char* local_host = "0.0.0.0") {
sockaddr_u local_addr;
memset(&local_addr, 0, sizeof(local_addr));
int ret = sockaddr_set_ipport(&local_addr, local_host, local_port);
if (ret != 0) {
return NABS(ret);
}
return bind(&local_addr.sa);
}
int bind(struct sockaddr* local_addr) {
if (channel == NULL || channel->isClosed()) {
return -1;
}
int ret = ::bind(channel->fd(), local_addr, SOCKADDR_LEN(local_addr));
if (ret != 0) {
perror("bind");
}
return ret;
}
// closesocket thread-safe
void closesocket() {
if (channel && channel->status != SocketChannel::CLOSED) {
loop_->runInLoop([this](){
if (channel) {
setReconnect(NULL);
channel->close();
}
});
}
}
int startConnect() {
if (channel == NULL || channel->isClosed()) {
int connfd = createsocket(&remote_addr.sa);
if (connfd < 0) {
hloge("createsocket %s:%d return %d!\n", remote_host.c_str(), remote_port, connfd);
return connfd;
}
}
if (channel == NULL || channel->status >= SocketChannel::CONNECTING) {
return -1;
}
if (connect_timeout) {
channel->setConnectTimeout(connect_timeout);
}
if (tls) {
channel->enableSSL();
if (tls_setting) {
int ret = channel->newSslCtx(tls_setting);
if (ret != 0) {
hloge("new SSL_CTX failed: %d", ret);
closesocket();
return ret;
}
}
if (!is_ipaddr(remote_host.c_str())) {
channel->setHostname(remote_host);
}
}
channel->onconnect = [this]() {
if (unpack_setting) {
channel->setUnpack(unpack_setting);
}
channel->startRead();
if (onConnection) {
onConnection(channel);
}
if (reconn_setting) {
reconn_setting_reset(reconn_setting);
}
};
channel->onread = [this](Buffer* buf) {
if (onMessage) {
onMessage(channel, buf);
}
};
channel->onwrite = [this](Buffer* buf) {
if (onWriteComplete) {
onWriteComplete(channel, buf);
}
};
channel->onclose = [this]() {
bool reconnect = reconn_setting != NULL;
if (onConnection) {
onConnection(channel);
}
if (reconnect) {
startReconnect();
}
};
return channel->startConnect();
}
int startReconnect() {
if (!reconn_setting) return -1;
if (!reconn_setting_can_retry(reconn_setting)) return -2;
uint32_t delay = reconn_setting_calc_delay(reconn_setting);
hlogi("reconnect... cnt=%d, delay=%d", reconn_setting->cur_retry_cnt, reconn_setting->cur_delay);
loop_->setTimeout(delay, [this](TimerID timerID){
startConnect();
});
return 0;
}
// start thread-safe
void start() {
loop_->runInLoop(std::bind(&TcpClientEventLoopTmpl::startConnect, this));
}
bool isConnected() {
if (channel == NULL) return false;
return channel->isConnected();
}
// send thread-safe
int send(const void* data, int size) {
if (!isConnected()) return -1;
return channel->write(data, size);
}
int send(Buffer* buf) {
return send(buf->data(), buf->size());
}
int send(const std::string& str) {
return send(str.data(), str.size());
}
int withTLS(hssl_ctx_opt_t* opt = NULL) {
tls = true;
if (opt) {
if (tls_setting == NULL) {
HV_ALLOC_SIZEOF(tls_setting);
}
opt->endpoint = HSSL_CLIENT;
*tls_setting = *opt;
}
return 0;
}
void setConnectTimeout(int ms) {
connect_timeout = ms;
}
void setReconnect(reconn_setting_t* setting) {
if (setting == NULL) {
HV_FREE(reconn_setting);
return;
}
if (reconn_setting == NULL) {
HV_ALLOC_SIZEOF(reconn_setting);
}
*reconn_setting = *setting;
}
bool isReconnect() {
return reconn_setting && reconn_setting->cur_retry_cnt > 0;
}
void setUnpack(unpack_setting_t* setting) {
if (setting == NULL) {
HV_FREE(unpack_setting);
return;
}
if (unpack_setting == NULL) {
HV_ALLOC_SIZEOF(unpack_setting);
}
*unpack_setting = *setting;
}
public:
TSocketChannelPtr channel;
std::string remote_host;
int remote_port;
sockaddr_u remote_addr;
int connect_timeout;
bool tls;
hssl_ctx_opt_t* tls_setting;
reconn_setting_t* reconn_setting;
unpack_setting_t* unpack_setting;
// Callback
std::function<void(const TSocketChannelPtr&)> onConnection;
std::function<void(const TSocketChannelPtr&, Buffer*)> onMessage;
// NOTE: Use Channel::isWriteComplete in onWriteComplete callback to determine whether all data has been written.
std::function<void(const TSocketChannelPtr&, Buffer*)> onWriteComplete;
private:
EventLoopPtr loop_;
};
template<class TSocketChannel = SocketChannel>
class TcpClientTmpl : private EventLoopThread, public TcpClientEventLoopTmpl<TSocketChannel> {
public:
TcpClientTmpl(EventLoopPtr loop = NULL)
: EventLoopThread(loop)
, TcpClientEventLoopTmpl<TSocketChannel>(EventLoopThread::loop())
, is_loop_owner(loop == NULL)
{}
virtual ~TcpClientTmpl() {
stop(true);
}
const EventLoopPtr& loop() {
return EventLoopThread::loop();
}
// start thread-safe
void start(bool wait_threads_started = true) {
if (isRunning()) {
TcpClientEventLoopTmpl<TSocketChannel>::start();
} else {
EventLoopThread::start(wait_threads_started, [this]() {
TcpClientTmpl::startConnect();
return 0;
});
}
}
// stop thread-safe
void stop(bool wait_threads_stopped = true) {
TcpClientEventLoopTmpl<TSocketChannel>::closesocket();
if (is_loop_owner) {
EventLoopThread::stop(wait_threads_stopped);
}
}
private:
bool is_loop_owner;
};
typedef TcpClientTmpl<SocketChannel> TcpClient;
}
#endif // HV_TCP_CLIENT_HPP_

View File

@ -0,0 +1,322 @@
#ifndef HV_TCP_SERVER_HPP_
#define HV_TCP_SERVER_HPP_
#include "hsocket.h"
#include "hssl.h"
#include "hlog.h"
#include "EventLoopThreadPool.h"
#include "Channel.h"
namespace hv {
template<class TSocketChannel = SocketChannel>
class TcpServerEventLoopTmpl {
public:
typedef std::shared_ptr<TSocketChannel> TSocketChannelPtr;
TcpServerEventLoopTmpl(EventLoopPtr loop = NULL) {
acceptor_loop = loop ? loop : std::make_shared<EventLoop>();
port = 0;
listenfd = -1;
tls = false;
tls_setting = NULL;
unpack_setting = NULL;
max_connections = 0xFFFFFFFF;
load_balance = LB_RoundRobin;
}
virtual ~TcpServerEventLoopTmpl() {
HV_FREE(tls_setting);
HV_FREE(unpack_setting);
}
EventLoopPtr loop(int idx = -1) {
EventLoopPtr worker_loop = worker_threads.loop(idx);
if (worker_loop == NULL) {
worker_loop = acceptor_loop;
}
return worker_loop;
}
//@retval >=0 listenfd, <0 error
int createsocket(int port, const char* host = "0.0.0.0") {
listenfd = Listen(port, host);
if (listenfd < 0) return listenfd;
this->host = host;
this->port = port;
return listenfd;
}
// closesocket thread-safe
void closesocket() {
if (listenfd >= 0) {
hloop_t* loop = acceptor_loop->loop();
if (loop) {
hio_t* listenio = hio_get(loop, listenfd);
assert(listenio != NULL);
hio_close_async(listenio);
}
listenfd = -1;
}
}
void setMaxConnectionNum(uint32_t num) {
max_connections = num;
}
void setLoadBalance(load_balance_e lb) {
load_balance = lb;
}
// NOTE: totalThreadNum = 1 acceptor_thread + N worker_threads (N can be 0)
void setThreadNum(int num) {
worker_threads.setThreadNum(num);
}
int startAccept() {
if (listenfd < 0) {
listenfd = createsocket(port, host.c_str());
if (listenfd < 0) {
hloge("createsocket %s:%d return %d!\n", host.c_str(), port, listenfd);
return listenfd;
}
}
hloop_t* loop = acceptor_loop->loop();
if (loop == NULL) return -2;
hio_t* listenio = haccept(loop, listenfd, onAccept);
assert(listenio != NULL);
hevent_set_userdata(listenio, this);
if (tls) {
hio_enable_ssl(listenio);
if (tls_setting) {
int ret = hio_new_ssl_ctx(listenio, tls_setting);
if (ret != 0) {
hloge("new SSL_CTX failed: %d", ret);
closesocket();
return ret;
}
}
}
return 0;
}
int stopAccept() {
if (listenfd < 0) return -1;
hloop_t* loop = acceptor_loop->loop();
if (loop == NULL) return -2;
hio_t* listenio = hio_get(loop, listenfd);
assert(listenio != NULL);
return hio_del(listenio, HV_READ);
}
// start thread-safe
void start(bool wait_threads_started = true) {
if (worker_threads.threadNum() > 0) {
worker_threads.start(wait_threads_started);
}
acceptor_loop->runInLoop(std::bind(&TcpServerEventLoopTmpl::startAccept, this));
}
// stop thread-safe
void stop(bool wait_threads_stopped = true) {
closesocket();
if (worker_threads.threadNum() > 0) {
worker_threads.stop(wait_threads_stopped);
}
}
int withTLS(hssl_ctx_opt_t* opt = NULL) {
tls = true;
if (opt) {
if (tls_setting == NULL) {
HV_ALLOC_SIZEOF(tls_setting);
}
opt->endpoint = HSSL_SERVER;
*tls_setting = *opt;
}
return 0;
}
void setUnpack(unpack_setting_t* setting) {
if (setting == NULL) {
HV_FREE(unpack_setting);
return;
}
if (unpack_setting == NULL) {
HV_ALLOC_SIZEOF(unpack_setting);
}
*unpack_setting = *setting;
}
// channel
const TSocketChannelPtr& addChannel(hio_t* io) {
uint32_t id = hio_id(io);
auto channel = std::make_shared<TSocketChannel>(io);
std::lock_guard<std::mutex> locker(mutex_);
channels[id] = channel;
return channels[id];
}
TSocketChannelPtr getChannelById(uint32_t id) {
std::lock_guard<std::mutex> locker(mutex_);
auto iter = channels.find(id);
return iter != channels.end() ? iter->second : NULL;
}
void removeChannel(const TSocketChannelPtr& channel) {
uint32_t id = channel->id();
std::lock_guard<std::mutex> locker(mutex_);
channels.erase(id);
}
size_t connectionNum() {
std::lock_guard<std::mutex> locker(mutex_);
return channels.size();
}
int foreachChannel(std::function<void(const TSocketChannelPtr& channel)> fn) {
std::lock_guard<std::mutex> locker(mutex_);
for (auto& pair : channels) {
fn(pair.second);
}
return channels.size();
}
// broadcast thread-safe
int broadcast(const void* data, int size) {
return foreachChannel([data, size](const TSocketChannelPtr& channel) {
channel->write(data, size);
});
}
int broadcast(const std::string& str) {
return broadcast(str.data(), str.size());
}
private:
static void newConnEvent(hio_t* connio) {
TcpServerEventLoopTmpl* server = (TcpServerEventLoopTmpl*)hevent_userdata(connio);
if (server->connectionNum() >= server->max_connections) {
hlogw("over max_connections");
hio_close(connio);
return;
}
// NOTE: attach to worker loop
EventLoop* worker_loop = currentThreadEventLoop;
assert(worker_loop != NULL);
hio_attach(worker_loop->loop(), connio);
const TSocketChannelPtr& channel = server->addChannel(connio);
channel->status = SocketChannel::CONNECTED;
channel->onread = [server, &channel](Buffer* buf) {
if (server->onMessage) {
server->onMessage(channel, buf);
}
};
channel->onwrite = [server, &channel](Buffer* buf) {
if (server->onWriteComplete) {
server->onWriteComplete(channel, buf);
}
};
channel->onclose = [server, &channel]() {
EventLoop* worker_loop = currentThreadEventLoop;
assert(worker_loop != NULL);
--worker_loop->connectionNum;
channel->status = SocketChannel::CLOSED;
if (server->onConnection) {
server->onConnection(channel);
}
server->removeChannel(channel);
// NOTE: After removeChannel, channel may be destroyed,
// so in this lambda function, no code should be added below.
};
if (server->unpack_setting) {
channel->setUnpack(server->unpack_setting);
}
channel->startRead();
if (server->onConnection) {
server->onConnection(channel);
}
}
static void onAccept(hio_t* connio) {
TcpServerEventLoopTmpl* server = (TcpServerEventLoopTmpl*)hevent_userdata(connio);
// NOTE: detach from acceptor loop
hio_detach(connio);
EventLoopPtr worker_loop = server->worker_threads.nextLoop(server->load_balance);
if (worker_loop == NULL) {
worker_loop = server->acceptor_loop;
}
++worker_loop->connectionNum;
worker_loop->runInLoop(std::bind(&TcpServerEventLoopTmpl::newConnEvent, connio));
}
public:
std::string host;
int port;
int listenfd;
bool tls;
hssl_ctx_opt_t* tls_setting;
unpack_setting_t* unpack_setting;
// Callback
std::function<void(const TSocketChannelPtr&)> onConnection;
std::function<void(const TSocketChannelPtr&, Buffer*)> onMessage;
// NOTE: Use Channel::isWriteComplete in onWriteComplete callback to determine whether all data has been written.
std::function<void(const TSocketChannelPtr&, Buffer*)> onWriteComplete;
uint32_t max_connections;
load_balance_e load_balance;
private:
// id => TSocketChannelPtr
std::map<uint32_t, TSocketChannelPtr> channels; // GUAREDE_BY(mutex_)
std::mutex mutex_;
EventLoopPtr acceptor_loop;
EventLoopThreadPool worker_threads;
};
template<class TSocketChannel = SocketChannel>
class TcpServerTmpl : private EventLoopThread, public TcpServerEventLoopTmpl<TSocketChannel> {
public:
TcpServerTmpl(EventLoopPtr loop = NULL)
: EventLoopThread(loop)
, TcpServerEventLoopTmpl<TSocketChannel>(EventLoopThread::loop())
, is_loop_owner(loop == NULL)
{}
virtual ~TcpServerTmpl() {
stop(true);
}
EventLoopPtr loop(int idx = -1) {
return TcpServerEventLoopTmpl<TSocketChannel>::loop(idx);
}
// start thread-safe
void start(bool wait_threads_started = true) {
TcpServerEventLoopTmpl<TSocketChannel>::start(wait_threads_started);
if (!isRunning()) {
EventLoopThread::start(wait_threads_started);
}
}
// stop thread-safe
void stop(bool wait_threads_stopped = true) {
if (is_loop_owner) {
EventLoopThread::stop(wait_threads_stopped);
}
TcpServerEventLoopTmpl<TSocketChannel>::stop(wait_threads_stopped);
}
private:
bool is_loop_owner;
};
typedef TcpServerTmpl<SocketChannel> TcpServer;
}
#endif // HV_TCP_SERVER_HPP_

View File

@ -0,0 +1,67 @@
#ifndef HV_THREAD_LOCAL_STORAGE_H_
#define HV_THREAD_LOCAL_STORAGE_H_
#include "hexport.h"
#include "hplatform.h"
#ifdef OS_WIN
#define hthread_key_t DWORD
#define INVALID_HTHREAD_KEY 0xFFFFFFFF
#define hthread_key_create(pkey) *pkey = TlsAlloc()
#define hthread_key_delete TlsFree
#define hthread_get_value TlsGetValue
#define hthread_set_value TlsSetValue
#else
#define hthread_key_t pthread_key_t
#define INVALID_HTHREAD_KEY 0xFFFFFFFF
#define hthread_key_create(pkey) pthread_key_create(pkey, NULL)
#define hthread_key_delete pthread_key_delete
#define hthread_get_value pthread_getspecific
#define hthread_set_value pthread_setspecific
#endif
#ifdef __cplusplus
namespace hv {
class HV_EXPORT ThreadLocalStorage {
public:
enum {
THREAD_NAME = 0,
EVENT_LOOP = 1,
MAX_NUM = 16,
};
ThreadLocalStorage() {
hthread_key_create(&key);
}
~ThreadLocalStorage() {
hthread_key_delete(key);
}
void set(void* val) {
hthread_set_value(key, val);
}
void* get() {
return hthread_get_value(key);
}
static void set(int idx, void* val);
static void* get(int idx);
static void setThreadName(const char* name);
static const char* threadName();
private:
hthread_key_t key;
static ThreadLocalStorage tls[MAX_NUM];
};
}
#endif
#endif // HV_THREAD_LOCAL_STORAGE_H_

View File

@ -0,0 +1,200 @@
#ifndef HV_UDP_CLIENT_HPP_
#define HV_UDP_CLIENT_HPP_
#include "hsocket.h"
#include "EventLoopThread.h"
#include "Channel.h"
namespace hv {
template<class TSocketChannel = SocketChannel>
class UdpClientEventLoopTmpl {
public:
typedef std::shared_ptr<TSocketChannel> TSocketChannelPtr;
UdpClientEventLoopTmpl(EventLoopPtr loop = NULL) {
loop_ = loop ? loop : std::make_shared<EventLoop>();
remote_port = 0;
#if WITH_KCP
kcp_setting = NULL;
#endif
}
virtual ~UdpClientEventLoopTmpl() {
#if WITH_KCP
HV_FREE(kcp_setting);
#endif
}
const EventLoopPtr& loop() {
return loop_;
}
// NOTE: By default, not bind local port. If necessary, you can call bind() after createsocket().
// @retval >=0 sockfd, <0 error
int createsocket(int remote_port, const char* remote_host = "127.0.0.1") {
hio_t* io = hloop_create_udp_client(loop_->loop(), remote_host, remote_port);
if (io == NULL) return -1;
this->remote_host = remote_host;
this->remote_port = remote_port;
channel = std::make_shared<TSocketChannel>(io);
int sockfd = channel->fd();
if (hv_strendswith(remote_host, ".255")) {
udp_broadcast(sockfd, 1);
}
return sockfd;
}
int bind(int local_port, const char* local_host = "0.0.0.0") {
if (channel == NULL || channel->isClosed()) {
return -1;
}
sockaddr_u local_addr;
memset(&local_addr, 0, sizeof(local_addr));
int ret = sockaddr_set_ipport(&local_addr, local_host, local_port);
if (ret != 0) {
return NABS(ret);
}
ret = ::bind(channel->fd(), &local_addr.sa, SOCKADDR_LEN(&local_addr));
if (ret != 0) {
perror("bind");
}
hio_set_localaddr(channel->io(), &local_addr.sa, SOCKADDR_LEN(&local_addr));
return ret;
}
// closesocket thread-safe
void closesocket() {
if (channel) {
channel->close(true);
}
}
int startRecv() {
if (channel == NULL || channel->isClosed()) {
int sockfd = createsocket(remote_port, remote_host.c_str());
if (sockfd < 0) {
hloge("createsocket %s:%d return %d!\n", remote_host.c_str(), remote_port, sockfd);
return sockfd;
}
}
if (channel == NULL || channel->isClosed()) {
return -1;
}
channel->onread = [this](Buffer* buf) {
if (onMessage) {
onMessage(channel, buf);
}
};
channel->onwrite = [this](Buffer* buf) {
if (onWriteComplete) {
onWriteComplete(channel, buf);
}
};
#if WITH_KCP
if (kcp_setting) {
hio_set_kcp(channel->io(), kcp_setting);
}
#endif
return channel->startRead();
}
int stopRecv() {
if (channel == NULL) return -1;
return channel->stopRead();
}
// start thread-safe
void start() {
loop_->runInLoop(std::bind(&UdpClientEventLoopTmpl::startRecv, this));
}
// sendto thread-safe
int sendto(const void* data, int size, struct sockaddr* peeraddr = NULL) {
if (channel == NULL) return -1;
std::lock_guard<std::mutex> locker(sendto_mutex);
if (peeraddr) hio_set_peeraddr(channel->io(), peeraddr, SOCKADDR_LEN(peeraddr));
return channel->write(data, size);
}
int sendto(Buffer* buf, struct sockaddr* peeraddr = NULL) {
return sendto(buf->data(), buf->size(), peeraddr);
}
int sendto(const std::string& str, struct sockaddr* peeraddr = NULL) {
return sendto(str.data(), str.size(), peeraddr);
}
#if WITH_KCP
void setKcp(kcp_setting_t* setting) {
if (setting == NULL) {
HV_FREE(kcp_setting);
return;
}
if (kcp_setting == NULL) {
HV_ALLOC_SIZEOF(kcp_setting);
}
*kcp_setting = *setting;
}
#endif
public:
TSocketChannelPtr channel;
std::string remote_host;
int remote_port;
#if WITH_KCP
kcp_setting_t* kcp_setting;
#endif
// Callback
std::function<void(const TSocketChannelPtr&, Buffer*)> onMessage;
// NOTE: Use Channel::isWriteComplete in onWriteComplete callback to determine whether all data has been written.
std::function<void(const TSocketChannelPtr&, Buffer*)> onWriteComplete;
private:
std::mutex sendto_mutex;
EventLoopPtr loop_;
};
template<class TSocketChannel = SocketChannel>
class UdpClientTmpl : private EventLoopThread, public UdpClientEventLoopTmpl<TSocketChannel> {
public:
UdpClientTmpl(EventLoopPtr loop = NULL)
: EventLoopThread(loop)
, UdpClientEventLoopTmpl<TSocketChannel>(EventLoopThread::loop())
, is_loop_owner(loop == NULL)
{}
virtual ~UdpClientTmpl() {
stop(true);
}
const EventLoopPtr& loop() {
return EventLoopThread::loop();
}
// start thread-safe
void start(bool wait_threads_started = true) {
if (isRunning()) {
UdpClientEventLoopTmpl<TSocketChannel>::start();
} else {
EventLoopThread::start(wait_threads_started, std::bind(&UdpClientTmpl::startRecv, this));
}
}
// stop thread-safe
void stop(bool wait_threads_stopped = true) {
UdpClientEventLoopTmpl<TSocketChannel>::closesocket();
if (is_loop_owner) {
EventLoopThread::stop(wait_threads_stopped);
}
}
private:
bool is_loop_owner;
};
typedef UdpClientTmpl<SocketChannel> UdpClient;
}
#endif // HV_UDP_CLIENT_HPP_

View File

@ -0,0 +1,174 @@
#ifndef HV_UDP_SERVER_HPP_
#define HV_UDP_SERVER_HPP_
#include "hsocket.h"
#include "EventLoopThreadPool.h"
#include "Channel.h"
namespace hv {
template<class TSocketChannel = SocketChannel>
class UdpServerEventLoopTmpl {
public:
typedef std::shared_ptr<TSocketChannel> TSocketChannelPtr;
UdpServerEventLoopTmpl(EventLoopPtr loop = NULL) {
loop_ = loop ? loop : std::make_shared<EventLoop>();
port = 0;
#if WITH_KCP
kcp_setting = NULL;
#endif
}
virtual ~UdpServerEventLoopTmpl() {
#if WITH_KCP
HV_FREE(kcp_setting);
#endif
}
const EventLoopPtr& loop() {
return loop_;
}
//@retval >=0 bindfd, <0 error
int createsocket(int port, const char* host = "0.0.0.0") {
hio_t* io = hloop_create_udp_server(loop_->loop(), host, port);
if (io == NULL) return -1;
this->host = host;
this->port = port;
channel = std::make_shared<TSocketChannel>(io);
return channel->fd();
}
// closesocket thread-safe
void closesocket() {
if (channel) {
channel->close(true);
}
}
int startRecv() {
if (channel == NULL || channel->isClosed()) {
int bindfd = createsocket(port, host.c_str());
if (bindfd < 0) {
hloge("createsocket %s:%d return %d!\n", host.c_str(), port, bindfd);
return bindfd;
}
}
if (channel == NULL || channel->isClosed()) {
return -1;
}
channel->onread = [this](Buffer* buf) {
if (onMessage) {
onMessage(channel, buf);
}
};
channel->onwrite = [this](Buffer* buf) {
if (onWriteComplete) {
onWriteComplete(channel, buf);
}
};
#if WITH_KCP
if (kcp_setting) {
hio_set_kcp(channel->io(), kcp_setting);
}
#endif
return channel->startRead();
}
int stopRecv() {
if (channel == NULL) return -1;
return channel->stopRead();
}
// start thread-safe
void start() {
loop_->runInLoop(std::bind(&UdpServerEventLoopTmpl::startRecv, this));
}
// sendto thread-safe
int sendto(const void* data, int size, struct sockaddr* peeraddr = NULL) {
if (channel == NULL) return -1;
std::lock_guard<std::mutex> locker(sendto_mutex);
if (peeraddr) hio_set_peeraddr(channel->io(), peeraddr, SOCKADDR_LEN(peeraddr));
return channel->write(data, size);
}
int sendto(Buffer* buf, struct sockaddr* peeraddr = NULL) {
return sendto(buf->data(), buf->size(), peeraddr);
}
int sendto(const std::string& str, struct sockaddr* peeraddr = NULL) {
return sendto(str.data(), str.size(), peeraddr);
}
#if WITH_KCP
void setKcp(kcp_setting_t* setting) {
if (setting == NULL) {
HV_FREE(kcp_setting);
return;
}
if (kcp_setting == NULL) {
HV_ALLOC_SIZEOF(kcp_setting);
}
*kcp_setting = *setting;
}
#endif
public:
std::string host;
int port;
TSocketChannelPtr channel;
#if WITH_KCP
kcp_setting_t* kcp_setting;
#endif
// Callback
std::function<void(const TSocketChannelPtr&, Buffer*)> onMessage;
// NOTE: Use Channel::isWriteComplete in onWriteComplete callback to determine whether all data has been written.
std::function<void(const TSocketChannelPtr&, Buffer*)> onWriteComplete;
private:
std::mutex sendto_mutex;
EventLoopPtr loop_;
};
template<class TSocketChannel = SocketChannel>
class UdpServerTmpl : private EventLoopThread, public UdpServerEventLoopTmpl<TSocketChannel> {
public:
UdpServerTmpl(EventLoopPtr loop = NULL)
: EventLoopThread(loop)
, UdpServerEventLoopTmpl<TSocketChannel>(EventLoopThread::loop())
, is_loop_owner(loop == NULL)
{}
virtual ~UdpServerTmpl() {
stop(true);
}
const EventLoopPtr& loop() {
return EventLoopThread::loop();
}
// start thread-safe
void start(bool wait_threads_started = true) {
if (isRunning()) {
UdpServerEventLoopTmpl<TSocketChannel>::start();
} else {
EventLoopThread::start(wait_threads_started, std::bind(&UdpServerTmpl::startRecv, this));
}
}
// stop thread-safe
void stop(bool wait_threads_stopped = true) {
UdpServerEventLoopTmpl<TSocketChannel>::closesocket();
if (is_loop_owner) {
EventLoopThread::stop(wait_threads_stopped);
}
}
private:
bool is_loop_owner;
};
typedef UdpServerTmpl<SocketChannel> UdpServer;
}
#endif // HV_UDP_SERVER_HPP_

View File

@ -0,0 +1,55 @@
#ifndef HV_WEBSOCKET_CHANNEL_H_
#define HV_WEBSOCKET_CHANNEL_H_
#include <mutex>
#include "Channel.h"
#include "wsdef.h"
#include "hmath.h"
namespace hv {
class HV_EXPORT WebSocketChannel : public SocketChannel {
public:
ws_session_type type;
WebSocketChannel(hio_t* io, ws_session_type type = WS_CLIENT)
: SocketChannel(io)
, type(type)
, opcode(WS_OPCODE_CLOSE)
{}
~WebSocketChannel() {}
// isConnected, send, close
int send(const std::string& msg, enum ws_opcode opcode = WS_OPCODE_TEXT, bool fin = true) {
return send(msg.c_str(), msg.size(), opcode, fin);
}
int send(const char* buf, int len, enum ws_opcode opcode = WS_OPCODE_BINARY, bool fin = true);
// websocket fragment
int send(const char* buf, int len, int fragment, enum ws_opcode opcode = WS_OPCODE_BINARY);
int sendPing();
int sendPong();
int close() {
return SocketChannel::close(type == WS_SERVER);
}
protected:
int sendFrame(const char* buf, int len, enum ws_opcode opcode = WS_OPCODE_BINARY, bool fin = true);
public:
enum ws_opcode opcode;
private:
Buffer sendbuf_;
std::mutex mutex_;
};
}
typedef std::shared_ptr<hv::WebSocketChannel> WebSocketChannelPtr;
#endif // HV_WEBSOCKET_CHANNEL_H_

View File

@ -0,0 +1,71 @@
#ifndef HV_WEBSOCKET_CLIENT_H_
#define HV_WEBSOCKET_CLIENT_H_
/*
* @demo examples/websocket_client_test.cpp
*/
#include "hexport.h"
#include "TcpClient.h"
#include "WebSocketChannel.h"
#include "HttpParser.h"
#include "WebSocketParser.h"
namespace hv {
class HV_EXPORT WebSocketClient : public TcpClientTmpl<WebSocketChannel> {
public:
std::string url;
std::function<void()> onopen;
std::function<void()> onclose;
std::function<void(const std::string& msg)> onmessage;
// PATCH: onmessage not given opcode
enum ws_opcode opcode() { return channel ? channel->opcode : WS_OPCODE_CLOSE; }
WebSocketClient(EventLoopPtr loop = NULL);
virtual ~WebSocketClient();
// url = ws://ip:port/path
// url = wss://ip:port/path
int open(const char* url, const http_headers& headers = DefaultHeaders);
int close();
int send(const std::string& msg);
int send(const char* buf, int len, enum ws_opcode opcode = WS_OPCODE_BINARY);
// setConnectTimeout / setPingInterval / setReconnect
void setPingInterval(int ms) {
ping_interval = ms;
}
// NOTE: call before open
void setHttpRequest(const HttpRequestPtr& req) {
http_req_ = req;
}
// NOTE: call when onopen
const HttpResponsePtr& getHttpResponse() {
return http_resp_;
}
private:
enum State {
CONNECTING,
CONNECTED,
WS_UPGRADING,
WS_OPENED,
WS_CLOSED,
} state;
HttpParserPtr http_parser_;
HttpRequestPtr http_req_;
HttpResponsePtr http_resp_;
WebSocketParserPtr ws_parser_;
// ping/pong
int ping_interval;
int ping_cnt;
};
}
#endif // HV_WEBSOCKET_CLIENT_H_

View File

@ -0,0 +1,35 @@
#ifndef HV_WEBSOCKET_PARSER_H_
#define HV_WEBSOCKET_PARSER_H_
#include "hexport.h"
#include <string>
#include <memory>
#include <functional>
enum websocket_parser_state {
WS_FRAME_BEGIN,
WS_FRAME_HEADER,
WS_FRAME_BODY,
WS_FRAME_END,
WS_FRAME_FIN,
};
struct websocket_parser;
class HV_EXPORT WebSocketParser {
public:
websocket_parser* parser;
websocket_parser_state state;
int opcode;
std::string message;
std::function<void(int opcode, const std::string& msg)> onMessage;
WebSocketParser();
~WebSocketParser();
int FeedRecvData(const char* data, size_t len);
};
typedef std::shared_ptr<WebSocketParser> WebSocketParserPtr;
#endif // HV_WEBSOCKET_PARSER_H_

View File

@ -0,0 +1,46 @@
#ifndef HV_WEBSOCKET_SERVER_H_
#define HV_WEBSOCKET_SERVER_H_
/*
* @demo examples/websocket_server_test.cpp
*/
#include "HttpServer.h"
#include "WebSocketChannel.h"
#define websocket_server_t http_server_t
#define websocket_server_run http_server_run
#define websocket_server_stop http_server_stop
namespace hv {
struct WebSocketService {
std::function<void(const WebSocketChannelPtr&, const HttpRequestPtr&)> onopen;
std::function<void(const WebSocketChannelPtr&, const std::string&)> onmessage;
std::function<void(const WebSocketChannelPtr&)> onclose;
int ping_interval;
WebSocketService() : ping_interval(0) {}
void setPingInterval(int ms) {
ping_interval = ms;
}
};
class WebSocketServer : public HttpServer {
public:
WebSocketServer(WebSocketService* service = NULL)
: HttpServer()
{
this->ws = service;
}
~WebSocketServer() { stop(); }
void registerWebSocketService(WebSocketService* service) {
this->ws = service;
}
};
}
#endif // HV_WEBSOCKET_SERVER_H_

View File

@ -0,0 +1,193 @@
#ifndef HV_AXIOS_H_
#define HV_AXIOS_H_
#include "json.hpp"
#include "requests.h"
/*
* Inspired by js axios
*
* @code
#include "axios.h"
int main() {
const char* strReq = R"(
{
"method": "POST",
"url": "http://127.0.0.1:8080/echo",
"timeout": 10,
"params": {
"page_no": "1",
"page_size": "10"
},
"headers": {
"Content-Type": "application/json"
},
"body": {
"app_id": "123456",
"app_secret": "abcdefg"
}
}
)";
// sync
auto resp = axios::axios(strReq);
if (resp == NULL) {
printf("request failed!\n");
} else {
printf("%s\n", resp->body.c_str());
}
// async
int finished = 0;
axios::axios(strReq, [&finished](const HttpResponsePtr& resp) {
if (resp == NULL) {
printf("request failed!\n");
} else {
printf("%s\n", resp->body.c_str());
}
finished = 1;
});
// wait async finished
while (!finished) hv_sleep(1);
return 0;
}
**/
using nlohmann::json;
using requests::Request;
using requests::Response;
using requests::ResponseCallback;
namespace axios {
HV_INLINE Request newRequestFromJson(const json& jreq) {
auto req = std::make_shared<HttpRequest>();
// url
if (jreq.contains("url")) {
req->url = jreq["url"];
}
// params
if (jreq.contains("params")) {
req->query_params = jreq["params"].get<hv::QueryParams>();
}
// headers
if (jreq.contains("headers")) {
req->headers = jreq["headers"].get<http_headers>();
}
// body/data
const char* body_field = nullptr;
if (jreq.contains("body")) {
body_field = "body";
} else if (jreq.contains("data")) {
body_field = "data";
}
if (body_field) {
const json& jbody = jreq[body_field];
if (jbody.is_object() || jbody.is_array()) {
req->json = jbody;
} else if (jbody.is_string()) {
req->body = jbody;
}
}
// method
if (jreq.contains("method")) {
std::string method = jreq["method"];
req->method = http_method_enum(method.c_str());
} else if (body_field) {
req->method = HTTP_POST;
} else {
req->method = HTTP_GET;
}
// timeout
if (jreq.contains("timeout")) {
req->timeout = jreq["timeout"];
}
return req;
}
HV_INLINE Request newRequestFromJsonString(const char* req_str) {
return newRequestFromJson(json::parse(req_str));
}
// sync
HV_INLINE Response axios(const json& jreq, http_method method = HTTP_GET, const char* url = nullptr) {
auto req = newRequestFromJson(jreq);
if (method != HTTP_GET) {
req->method = method;
}
if (url) {
req->url = url;
}
return req ? requests::request(req) : nullptr;
}
HV_INLINE Response axios(const char* req_str, http_method method = HTTP_GET, const char* url = nullptr) {
return req_str ? axios(json::parse(req_str), method, url)
: requests::request(method, url);
}
HV_INLINE Response head(const char* url, const json& jreq) {
return axios(jreq, HTTP_HEAD, url);
}
HV_INLINE Response head(const char* url, const char* req_str = nullptr) {
return axios(req_str, HTTP_HEAD, url);
}
HV_INLINE Response get(const char* url, const json& jreq) {
return axios(jreq, HTTP_GET, url);
}
HV_INLINE Response get(const char* url, const char* req_str = nullptr) {
return axios(req_str, HTTP_GET, url);
}
HV_INLINE Response post(const char* url, const json& jreq) {
return axios(jreq, HTTP_POST, url);
}
HV_INLINE Response post(const char* url, const char* req_str = nullptr) {
return axios(req_str, HTTP_POST, url);
}
HV_INLINE Response put(const char* url, const json& jreq) {
return axios(jreq, HTTP_PUT, url);
}
HV_INLINE Response put(const char* url, const char* req_str = nullptr) {
return axios(req_str, HTTP_PUT, url);
}
HV_INLINE Response patch(const char* url, const json& jreq) {
return axios(jreq, HTTP_PATCH, url);
}
HV_INLINE Response patch(const char* url, const char* req_str = nullptr) {
return axios(req_str, HTTP_PATCH, url);
}
HV_INLINE Response Delete(const char* url, const json& jreq) {
return axios(jreq, HTTP_DELETE, url);
}
HV_INLINE Response Delete(const char* url, const char* req_str = nullptr) {
return axios(req_str, HTTP_DELETE, url);
}
// async
HV_INLINE int axios(const json& jreq, ResponseCallback resp_cb) {
auto req = newRequestFromJson(jreq);
return req ? requests::async(req, std::move(resp_cb)) : -1;
}
HV_INLINE int axios(const char* req_str, ResponseCallback resp_cb) {
return axios(json::parse(req_str), std::move(resp_cb));
}
}
#endif // HV_AXIOS_H_

View File

@ -0,0 +1,50 @@
#ifndef HV_BASE64_H_
#define HV_BASE64_H_
#include "hexport.h"
#define BASE64_ENCODE_OUT_SIZE(s) (((s) + 2) / 3 * 4)
#define BASE64_DECODE_OUT_SIZE(s) (((s)) / 4 * 3)
BEGIN_EXTERN_C
// @return encoded size
HV_EXPORT int hv_base64_encode(const unsigned char *in, unsigned int inlen, char *out);
// @return decoded size
HV_EXPORT int hv_base64_decode(const char *in, unsigned int inlen, unsigned char *out);
END_EXTERN_C
#ifdef __cplusplus
#include <string.h>
#include <string>
namespace hv {
HV_INLINE std::string Base64Encode(const unsigned char* data, unsigned int len) {
int encoded_size = BASE64_ENCODE_OUT_SIZE(len);
std::string encoded_str(encoded_size + 1, 0);
encoded_size = hv_base64_encode(data, len, (char*)encoded_str.data());
encoded_str.resize(encoded_size);
return encoded_str;
}
HV_INLINE std::string Base64Decode(const char* str, unsigned int len = 0) {
if (len == 0) len = strlen(str);
int decoded_size = BASE64_DECODE_OUT_SIZE(len);
std::string decoded_buf(decoded_size + 1, 0);
decoded_size = hv_base64_decode(str, len, (unsigned char*)decoded_buf.data());
if (decoded_size > 0) {
decoded_buf.resize(decoded_size);
} else {
decoded_buf.clear();
}
return decoded_buf;
}
}
#endif
#endif // HV_BASE64_H_

View File

@ -0,0 +1,49 @@
#ifndef HV_ASYNC_H_
#define HV_ASYNC_H_
#include "hexport.h"
#include "hthreadpool.h"
#include "singleton.h"
namespace hv {
class HV_EXPORT GlobalThreadPool : public HThreadPool {
SINGLETON_DECL(GlobalThreadPool)
protected:
GlobalThreadPool() : HThreadPool() {}
~GlobalThreadPool() {}
};
/*
* return a future, calling future.get() will wait task done and return RetType.
* async(fn, args...)
* async(std::bind(&Class::mem_fn, &obj))
* async(std::mem_fn(&Class::mem_fn, &obj))
*
*/
template<class Fn, class... Args>
auto async(Fn&& fn, Args&&... args) -> std::future<decltype(fn(args...))> {
return GlobalThreadPool::instance()->commit(std::forward<Fn>(fn), std::forward<Args>(args)...);
}
class async {
public:
static void startup(int min_threads = DEFAULT_THREAD_POOL_MIN_THREAD_NUM,
int max_threads = DEFAULT_THREAD_POOL_MAX_THREAD_NUM,
int max_idle_ms = DEFAULT_THREAD_POOL_MAX_IDLE_TIME) {
GlobalThreadPool* gtp = GlobalThreadPool::instance();
if (gtp->isStarted()) return;
gtp->setMinThreadNum(min_threads);
gtp->setMaxThreadNum(max_threads);
gtp->setMaxIdleTime(max_idle_ms);
gtp->start();
}
static void cleanup() {
GlobalThreadPool::exitInstance();
}
};
} // end namespace hv
#endif // HV_ASYNC_H_

View File

@ -0,0 +1,130 @@
#ifndef HV_ATOMIC_H_
#define HV_ATOMIC_H_
#ifdef __cplusplus
// c++11
#include <atomic>
using std::atomic_flag;
using std::atomic_long;
#define ATOMIC_FLAG_TEST_AND_SET(p) ((p)->test_and_set())
#define ATOMIC_FLAG_CLEAR(p) ((p)->clear())
#else
#include "hplatform.h" // for HAVE_STDATOMIC_H
#if HAVE_STDATOMIC_H
// c11
#include <stdatomic.h>
#define ATOMIC_FLAG_TEST_AND_SET atomic_flag_test_and_set
#define ATOMIC_FLAG_CLEAR atomic_flag_clear
#define ATOMIC_ADD atomic_fetch_add
#define ATOMIC_SUB atomic_fetch_sub
#define ATOMIC_INC(p) ATOMIC_ADD(p, 1)
#define ATOMIC_DEC(p) ATOMIC_SUB(p, 1)
#else
typedef volatile bool atomic_bool;
typedef volatile char atomic_char;
typedef volatile unsigned char atomic_uchar;
typedef volatile short atomic_short;
typedef volatile unsigned short atomic_ushort;
typedef volatile int atomic_int;
typedef volatile unsigned int atomic_uint;
typedef volatile long atomic_long;
typedef volatile unsigned long atomic_ulong;
typedef volatile long long atomic_llong;
typedef volatile unsigned long long atomic_ullong;
typedef volatile size_t atomic_size_t;
typedef struct atomic_flag { atomic_bool _Value; } atomic_flag;
#ifdef _WIN32
#define ATOMIC_FLAG_TEST_AND_SET atomic_flag_test_and_set
static inline bool atomic_flag_test_and_set(atomic_flag* p) {
// return InterlockedIncrement((LONG*)&p->_Value, 1);
return InterlockedCompareExchange((LONG*)&p->_Value, 1, 0);
}
#define ATOMIC_ADD InterlockedAdd
#define ATOMIC_SUB(p, n) InterlockedAdd(p, -n)
#define ATOMIC_INC InterlockedIncrement
#define ATOMIC_DEC InterlockedDecrement
#elif defined(__GNUC__)
#define ATOMIC_FLAG_TEST_AND_SET atomic_flag_test_and_set
static inline bool atomic_flag_test_and_set(atomic_flag* p) {
return !__sync_bool_compare_and_swap(&p->_Value, 0, 1);
}
#define ATOMIC_ADD __sync_fetch_and_add
#define ATOMIC_SUB __sync_fetch_and_sub
#define ATOMIC_INC(p) ATOMIC_ADD(p, 1)
#define ATOMIC_DEC(p) ATOMIC_SUB(p, 1)
#endif
#endif // HAVE_STDATOMIC_H
#endif // __cplusplus
#ifndef ATOMIC_FLAG_INIT
#define ATOMIC_FLAG_INIT { 0 }
#endif
#ifndef ATOMIC_VAR_INIT
#define ATOMIC_VAR_INIT(value) (value)
#endif
#ifndef ATOMIC_FLAG_TEST_AND_SET
#define ATOMIC_FLAG_TEST_AND_SET atomic_flag_test_and_set
static inline bool atomic_flag_test_and_set(atomic_flag* p) {
bool ret = p->_Value;
p->_Value = 1;
return ret;
}
#endif
#ifndef ATOMIC_FLAG_CLEAR
#define ATOMIC_FLAG_CLEAR atomic_flag_clear
static inline void atomic_flag_clear(atomic_flag* p) {
p->_Value = 0;
}
#endif
#ifndef ATOMIC_ADD
#define ATOMIC_ADD(p, n) (*(p) += (n))
#endif
#ifndef ATOMIC_SUB
#define ATOMIC_SUB(p, n) (*(p) -= (n))
#endif
#ifndef ATOMIC_INC
#define ATOMIC_INC(p) ((*(p))++)
#endif
#ifndef ATOMIC_DEC
#define ATOMIC_DEC(p) ((*(p))--)
#endif
typedef atomic_flag hatomic_flag_t;
#define HATOMIC_FLAG_INIT ATOMIC_FLAG_INIT
#define hatomic_flag_test_and_set ATOMIC_FLAG_TEST_AND_SET
#define hatomic_flag_clear ATOMIC_FLAG_CLEAR
typedef atomic_long hatomic_t;
#define HATOMIC_VAR_INIT ATOMIC_VAR_INIT
#define hatomic_add ATOMIC_ADD
#define hatomic_sub ATOMIC_SUB
#define hatomic_inc ATOMIC_INC
#define hatomic_dec ATOMIC_DEC
#endif // HV_ATOMIC_H_

View File

@ -0,0 +1,145 @@
#ifndef HV_BASE_H_
#define HV_BASE_H_
#include "hexport.h"
#include "hplatform.h" // for bool
#include "hdef.h" // for printd
BEGIN_EXTERN_C
//--------------------alloc/free---------------------------
HV_EXPORT void* hv_malloc(size_t size);
HV_EXPORT void* hv_realloc(void* oldptr, size_t newsize, size_t oldsize);
HV_EXPORT void* hv_calloc(size_t nmemb, size_t size);
HV_EXPORT void* hv_zalloc(size_t size);
HV_EXPORT void hv_free(void* ptr);
#define HV_ALLOC(ptr, size)\
do {\
*(void**)&(ptr) = hv_zalloc(size);\
printd("alloc(%p, size=%llu)\tat [%s:%d:%s]\n", ptr, (unsigned long long)size, __FILE__, __LINE__, __FUNCTION__);\
} while(0)
#define HV_ALLOC_SIZEOF(ptr) HV_ALLOC(ptr, sizeof(*(ptr)))
#define HV_FREE(ptr)\
do {\
if (ptr) {\
hv_free(ptr);\
printd("free( %p )\tat [%s:%d:%s]\n", ptr, __FILE__, __LINE__, __FUNCTION__);\
ptr = NULL;\
}\
} while(0)
#define STACK_OR_HEAP_ALLOC(ptr, size, stack_size)\
unsigned char _stackbuf_[stack_size] = { 0 };\
if ((size) > (stack_size)) {\
HV_ALLOC(ptr, size);\
} else {\
*(unsigned char**)&(ptr) = _stackbuf_;\
}
#define STACK_OR_HEAP_FREE(ptr)\
if ((unsigned char*)(ptr) != _stackbuf_) {\
HV_FREE(ptr);\
}
#define HV_DEFAULT_STACKBUF_SIZE 1024
#define HV_STACK_ALLOC(ptr, size) STACK_OR_HEAP_ALLOC(ptr, size, HV_DEFAULT_STACKBUF_SIZE)
#define HV_STACK_FREE(ptr) STACK_OR_HEAP_FREE(ptr)
HV_EXPORT long hv_alloc_cnt();
HV_EXPORT long hv_free_cnt();
HV_INLINE void hv_memcheck(void) {
printf("Memcheck => alloc:%ld free:%ld\n", hv_alloc_cnt(), hv_free_cnt());
}
#define HV_MEMCHECK atexit(hv_memcheck);
//--------------------string-------------------------------
HV_EXPORT char* hv_strupper(char* str);
HV_EXPORT char* hv_strlower(char* str);
HV_EXPORT char* hv_strreverse(char* str);
HV_EXPORT bool hv_strstartswith(const char* str, const char* start);
HV_EXPORT bool hv_strendswith(const char* str, const char* end);
HV_EXPORT bool hv_strcontains(const char* str, const char* sub);
HV_EXPORT bool hv_wildcard_match(const char* str, const char* pattern);
// strncpy n = sizeof(dest_buf)-1
// hv_strncpy n = sizeof(dest_buf)
HV_EXPORT char* hv_strncpy(char* dest, const char* src, size_t n);
// strncat n = sizeof(dest_buf)-1-strlen(dest)
// hv_strncpy n = sizeof(dest_buf)
HV_EXPORT char* hv_strncat(char* dest, const char* src, size_t n);
#if !HAVE_STRLCPY
#define strlcpy hv_strncpy
#endif
#if !HAVE_STRLCAT
#define strlcat hv_strncat
#endif
HV_EXPORT char* hv_strnchr(const char* s, char c, size_t n);
HV_EXPORT char* hv_strnrchr(const char* s, char c, size_t n);
#define hv_strrchr_dot(str) strrchr(str, '.')
HV_EXPORT char* hv_strrchr_dir(const char* filepath);
// basename
HV_EXPORT const char* hv_basename(const char* filepath);
HV_EXPORT const char* hv_suffixname(const char* filename);
// mkdir -p
HV_EXPORT int hv_mkdir_p(const char* dir);
// rmdir -p
HV_EXPORT int hv_rmdir_p(const char* dir);
// path
HV_EXPORT bool hv_exists(const char* path);
HV_EXPORT bool hv_isdir(const char* path);
HV_EXPORT bool hv_isfile(const char* path);
HV_EXPORT bool hv_islink(const char* path);
HV_EXPORT size_t hv_filesize(const char* filepath);
HV_EXPORT char* get_executable_path(char* buf, int size);
HV_EXPORT char* get_executable_dir(char* buf, int size);
HV_EXPORT char* get_executable_file(char* buf, int size);
HV_EXPORT char* get_run_dir(char* buf, int size);
// random
HV_EXPORT int hv_rand(int min, int max);
HV_EXPORT char* hv_random_string(char *buf, int len);
// 1 y on yes true enable => true
HV_EXPORT bool hv_getboolean(const char* str);
// 1T2G3M4K5B => ?B
HV_EXPORT size_t hv_parse_size(const char* str);
// 1w2d3h4m5s => ?s
HV_EXPORT time_t hv_parse_time(const char* str);
// scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
typedef enum {
HV_URL_SCHEME,
HV_URL_USERNAME,
HV_URL_PASSWORD,
HV_URL_HOST,
HV_URL_PORT,
HV_URL_PATH,
HV_URL_QUERY,
HV_URL_FRAGMENT,
HV_URL_FIELD_NUM,
} hurl_field_e;
typedef struct hurl_s {
struct {
unsigned short off;
unsigned short len;
} fields[HV_URL_FIELD_NUM];
unsigned short port;
} hurl_t;
HV_EXPORT int hv_parse_url(hurl_t* stURL, const char* strURL);
END_EXTERN_C
#endif // HV_BASE_H_

View File

@ -0,0 +1,257 @@
#ifndef HV_BUF_H_
#define HV_BUF_H_
#include "hdef.h" // for MAX
#include "hbase.h" // for HV_ALLOC, HV_FREE
typedef struct hbuf_s {
char* base;
size_t len;
#ifdef __cplusplus
hbuf_s() {
base = NULL;
len = 0;
}
hbuf_s(void* data, size_t len) {
this->base = (char*)data;
this->len = len;
}
#endif
} hbuf_t;
typedef struct offset_buf_s {
char* base;
size_t len;
size_t offset;
#ifdef __cplusplus
offset_buf_s() {
base = NULL;
len = 0;
offset = 0;
}
offset_buf_s(void* data, size_t len) {
this->base = (char*)data;
this->len = len;
offset = 0;
}
#endif
} offset_buf_t;
typedef struct fifo_buf_s {
char* base;
size_t len;
size_t head;
size_t tail;
#ifdef __cplusplus
fifo_buf_s() {
base = NULL;
len = 0;
head = tail = 0;
}
fifo_buf_s(void* data, size_t len) {
this->base = (char*)data;
this->len = len;
head = tail = 0;
}
#endif
} fifo_buf_t;
#ifdef __cplusplus
class HBuf : public hbuf_t {
public:
HBuf() : hbuf_t() {
cleanup_ = false;
}
HBuf(void* data, size_t len) : hbuf_t(data, len) {
cleanup_ = false;
}
HBuf(size_t cap) { resize(cap); }
virtual ~HBuf() {
cleanup();
}
void* data() { return base; }
size_t size() { return len; }
bool isNull() { return base == NULL || len == 0; }
void cleanup() {
if (cleanup_) {
HV_FREE(base);
len = 0;
cleanup_ = false;
}
}
void resize(size_t cap) {
if (cap == len) return;
if (base == NULL) {
HV_ALLOC(base, cap);
}
else {
base = (char*)hv_realloc(base, cap, len);
}
len = cap;
cleanup_ = true;
}
void copy(void* data, size_t len) {
resize(len);
memcpy(base, data, len);
}
void copy(hbuf_t* buf) {
copy(buf->base, buf->len);
}
private:
bool cleanup_;
};
// VL: Variable-Length
class HVLBuf : public HBuf {
public:
HVLBuf() : HBuf() {_offset = _size = 0;}
HVLBuf(void* data, size_t len) : HBuf(data, len) {_offset = 0; _size = len;}
HVLBuf(size_t cap) : HBuf(cap) {_offset = _size = 0;}
virtual ~HVLBuf() {}
char* data() { return base + _offset; }
size_t size() { return _size; }
void push_front(void* ptr, size_t len) {
if (len > this->len - _size) {
size_t newsize = MAX(this->len, len)*2;
resize(newsize);
}
if (_offset < len) {
// move => end
memmove(base+this->len-_size, data(), _size);
_offset = this->len-_size;
}
memcpy(data()-len, ptr, len);
_offset -= len;
_size += len;
}
void push_back(void* ptr, size_t len) {
if (len > this->len - _size) {
size_t newsize = MAX(this->len, len)*2;
resize(newsize);
}
else if (len > this->len - _offset - _size) {
// move => start
memmove(base, data(), _size);
_offset = 0;
}
memcpy(data()+_size, ptr, len);
_size += len;
}
void pop_front(void* ptr, size_t len) {
if (len <= _size) {
if (ptr) {
memcpy(ptr, data(), len);
}
_offset += len;
if (_offset >= this->len) _offset = 0;
_size -= len;
}
}
void pop_back(void* ptr, size_t len) {
if (len <= _size) {
if (ptr) {
memcpy(ptr, data()+_size-len, len);
}
_size -= len;
}
}
void clear() {
_offset = _size = 0;
}
void prepend(void* ptr, size_t len) {
push_front(ptr, len);
}
void append(void* ptr, size_t len) {
push_back(ptr, len);
}
void insert(void* ptr, size_t len) {
push_back(ptr, len);
}
void remove(size_t len) {
pop_front(NULL, len);
}
private:
size_t _offset;
size_t _size;
};
class HRingBuf : public HBuf {
public:
HRingBuf() : HBuf() {_head = _tail = _size = 0;}
HRingBuf(size_t cap) : HBuf(cap) {_head = _tail = _size = 0;}
virtual ~HRingBuf() {}
char* alloc(size_t len) {
char* ret = NULL;
if (_head < _tail || _size == 0) {
// [_tail, this->len) && [0, _head)
if (this->len - _tail >= len) {
ret = base + _tail;
_tail += len;
if (_tail == this->len) _tail = 0;
}
else if (_head >= len) {
ret = base;
_tail = len;
}
}
else {
// [_tail, _head)
if (_head - _tail >= len) {
ret = base + _tail;
_tail += len;
}
}
_size += ret ? len : 0;
return ret;
}
void free(size_t len) {
_size -= len;
if (len <= this->len - _head) {
_head += len;
if (_head == this->len) _head = 0;
}
else {
_head = len;
}
}
void clear() {_head = _tail = _size = 0;}
size_t size() {return _size;}
private:
size_t _head;
size_t _tail;
size_t _size;
};
#endif
#endif // HV_BUF_H_

View File

@ -0,0 +1,102 @@
#ifndef HV_CONFIG_H_
#define HV_CONFIG_H_
#ifndef HAVE_STDBOOL_H
#define HAVE_STDBOOL_H 1
#endif
#ifndef HAVE_STDINT_H
#define HAVE_STDINT_H 1
#endif
#ifndef HAVE_STDATOMIC_H
#define HAVE_STDATOMIC_H 0
#endif
#ifndef HAVE_SYS_TYPES_H
#define HAVE_SYS_TYPES_H 1
#endif
#ifndef HAVE_SYS_STAT_H
#define HAVE_SYS_STAT_H 1
#endif
#ifndef HAVE_SYS_TIME_H
#define HAVE_SYS_TIME_H 0
#endif
#ifndef HAVE_FCNTL_H
#define HAVE_FCNTL_H 1
#endif
#ifndef HAVE_PTHREAD_H
#define HAVE_PTHREAD_H 0
#endif
#ifndef HAVE_ENDIAN_H
#define HAVE_ENDIAN_H 0
#endif
#ifndef HAVE_SYS_ENDIAN_H
#define HAVE_SYS_ENDIAN_H 0
#endif
#ifndef HAVE_GETTID
#define HAVE_GETTID 0
#endif
#ifndef HAVE_STRLCPY
#define HAVE_STRLCPY 0
#endif
#ifndef HAVE_STRLCAT
#define HAVE_STRLCAT 0
#endif
#ifndef HAVE_CLOCK_GETTIME
#define HAVE_CLOCK_GETTIME 0
#endif
#ifndef HAVE_GETTIMEOFDAY
#define HAVE_GETTIMEOFDAY 0
#endif
#ifndef HAVE_PTHREAD_SPIN_LOCK
#define HAVE_PTHREAD_SPIN_LOCK 0
#endif
#ifndef HAVE_PTHREAD_MUTEX_TIMEDLOCK
#define HAVE_PTHREAD_MUTEX_TIMEDLOCK 0
#endif
#ifndef HAVE_SEM_TIMEDWAIT
#define HAVE_SEM_TIMEDWAIT 0
#endif
#ifndef HAVE_PIPE
#define HAVE_PIPE 0
#endif
#ifndef HAVE_SOCKETPAIR
#define HAVE_SOCKETPAIR 0
#endif
#ifndef HAVE_EVENTFD
#define HAVE_EVENTFD 0
#endif
#ifndef HAVE_SETPROCTITLE
#define HAVE_SETPROCTITLE 0
#endif
#define WITH_OPENSSL 1
/* #undef WITH_GNUTLS */
/* #undef WITH_MBEDTLS */
/* #undef ENABLE_UDS */
/* #undef USE_MULTIMAP */
#define WITH_WEPOLL 1
/* #undef WITH_KCP */
#endif // HV_CONFIG_H_

View File

@ -0,0 +1,271 @@
#ifndef HV_DEF_H_
#define HV_DEF_H_
#include "hplatform.h"
#ifndef ABS
#define ABS(n) ((n) > 0 ? (n) : -(n))
#endif
#ifndef NABS
#define NABS(n) ((n) < 0 ? (n) : -(n))
#endif
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
#endif
#ifndef BITSET
#define BITSET(p, n) (*(p) |= (1u << (n)))
#endif
#ifndef BITCLR
#define BITCLR(p, n) (*(p) &= ~(1u << (n)))
#endif
#ifndef BITGET
#define BITGET(i, n) ((i) & (1u << (n)))
#endif
/*
#ifndef CR
#define CR '\r'
#endif
#ifndef LF
#define LF '\n'
#endif
#ifndef CRLF
#define CRLF "\r\n"
#endif
*/
#define FLOAT_PRECISION 1e-6
#define FLOAT_EQUAL_ZERO(f) (ABS(f) < FLOAT_PRECISION)
#ifndef INFINITE
#define INFINITE (uint32_t)-1
#endif
/*
ASCII:
[0, 0x20) control-charaters
[0x20, 0x7F) printable-charaters
0x0A => LF
0x0D => CR
0x20 => SPACE
0x7F => DEL
[0x09, 0x0D] => \t\n\v\f\r
[0x30, 0x39] => 0~9
[0x41, 0x5A] => A~Z
[0x61, 0x7A] => a~z
*/
#ifndef IS_ALPHA
#define IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z'))
#endif
// NOTE: IS_NUM conflicts with mysql.h
#ifndef IS_DIGIT
#define IS_DIGIT(c) ((c) >= '0' && (c) <= '9')
#endif
#ifndef IS_ALPHANUM
#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_DIGIT(c))
#endif
#ifndef IS_CNTRL
#define IS_CNTRL(c) ((c) >= 0 && (c) < 0x20)
#endif
#ifndef IS_GRAPH
#define IS_GRAPH(c) ((c) >= 0x20 && (c) < 0x7F)
#endif
#ifndef IS_HEX
#define IS_HEX(c) (IS_DIGIT(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))
#endif
#ifndef IS_LOWER
#define IS_LOWER(c) (((c) >= 'a' && (c) <= 'z'))
#endif
#ifndef IS_UPPER
#define IS_UPPER(c) (((c) >= 'A' && (c) <= 'Z'))
#endif
#ifndef LOWER
#define LOWER(c) ((c) | 0x20)
#endif
#ifndef UPPER
#define UPPER(c) ((c) & ~0x20)
#endif
// LD, LU, LLD, LLU for explicit conversion of integer
// #ifndef LD
// #define LD(v) ((long)(v))
// #endif
// #ifndef LU
// #define LU(v) ((unsigned long)(v))
// #endif
#ifndef LLD
#define LLD(v) ((long long)(v))
#endif
#ifndef LLU
#define LLU(v) ((unsigned long long)(v))
#endif
#ifndef _WIN32
// MAKEWORD, HIBYTE, LOBYTE
#ifndef MAKEWORD
#define MAKEWORD(h, l) ( (((WORD)h) << 8) | (l & 0xff) )
#endif
#ifndef HIBYTE
#define HIBYTE(w) ( (BYTE)(((WORD)w) >> 8) )
#endif
#ifndef LOBYTE
#define LOBYTE(w) ( (BYTE)(w & 0xff) )
#endif
// MAKELONG, HIWORD, LOWORD
#ifndef MAKELONG
#define MAKELONG(h, l) ( ((int32_t)h) << 16 | (l & 0xffff) )
#endif
#ifndef HIWORD
#define HIWORD(n) ( (WORD)(((int32_t)n) >> 16) )
#endif
#ifndef LOWORD
#define LOWORD(n) ( (WORD)(n & 0xffff) )
#endif
#endif // _WIN32
// MAKEINT64, HIINT, LOINT
#ifndef MAKEINT64
#define MAKEINT64(h, l) ( ((int64_t)h) << 32 | (l & 0xffffffff) )
#endif
#ifndef HIINT
#define HIINT(n) ( (int32_t)(((int64_t)n) >> 32) )
#endif
#ifndef LOINT
#define LOINT(n) ( (int32_t)(n & 0xffffffff) )
#endif
#ifndef MAKE_FOURCC
#define MAKE_FOURCC(a, b, c, d) \
( ((uint32)d) | ( ((uint32)c) << 8 ) | ( ((uint32)b) << 16 ) | ( ((uint32)a) << 24 ) )
#endif
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef LIMIT
#define LIMIT(lower, v, upper) ((v) < (lower) ? (lower) : (v) > (upper) ? (upper) : (v))
#endif
#ifndef MAX_PATH
#define MAX_PATH 260
#endif
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#ifndef SAFE_ALLOC
#define SAFE_ALLOC(p, size)\
do {\
void* ptr = malloc(size);\
if (!ptr) {\
fprintf(stderr, "malloc failed!\n");\
exit(-1);\
}\
memset(ptr, 0, size);\
*(void**)&(p) = ptr;\
} while(0)
#endif
#ifndef SAFE_FREE
#define SAFE_FREE(p) do {if (p) {free(p); (p) = NULL;}} while(0)
#endif
#ifndef SAFE_DELETE
#define SAFE_DELETE(p) do {if (p) {delete (p); (p) = NULL;}} while(0)
#endif
#ifndef SAFE_DELETE_ARRAY
#define SAFE_DELETE_ARRAY(p) do {if (p) {delete[] (p); (p) = NULL;}} while(0)
#endif
#ifndef SAFE_RELEASE
#define SAFE_RELEASE(p) do {if (p) {(p)->release(); (p) = NULL;}} while(0)
#endif
#ifndef SAFE_CLOSE
#define SAFE_CLOSE(fd) do {if ((fd) >= 0) {close(fd); (fd) = -1;}} while(0)
#endif
#define STRINGIFY(x) STRINGIFY_HELPER(x)
#define STRINGIFY_HELPER(x) #x
#define STRINGCAT(x, y) STRINGCAT_HELPER(x, y)
#define STRINGCAT_HELPER(x, y) x##y
#ifndef offsetof
#define offsetof(type, member) \
((size_t)(&((type*)0)->member))
#endif
#ifndef offsetofend
#define offsetofend(type, member) \
(offsetof(type, member) + sizeof(((type*)0)->member))
#endif
#ifndef container_of
#define container_of(ptr, type, member) \
((type*)((char*)(ptr) - offsetof(type, member)))
#endif
#ifdef PRINT_DEBUG
#define printd(...) printf(__VA_ARGS__)
#else
#define printd(...)
#endif
#ifdef PRINT_ERROR
#define printe(...) fprintf(stderr, __VA_ARGS__)
#else
#define printe(...)
#endif
#endif // HV_DEF_H_

View File

@ -0,0 +1,69 @@
#ifndef HV_DIR_H_
#define HV_DIR_H_
/*
*@code
int main(int argc, char* argv[]) {
const char* dir = ".";
if (argc > 1) {
dir = argv[1];
}
std::list<hdir_t> dirs;
listdir(dir, dirs);
for (auto& item : dirs) {
printf("%c%c%c%c%c%c%c%c%c%c\t",
item.type,
item.mode & 0400 ? 'r' : '-',
item.mode & 0200 ? 'w' : '-',
item.mode & 0100 ? 'x' : '-',
item.mode & 0040 ? 'r' : '-',
item.mode & 0020 ? 'w' : '-',
item.mode & 0010 ? 'x' : '-',
item.mode & 0004 ? 'r' : '-',
item.mode & 0002 ? 'w' : '-',
item.mode & 0001 ? 'x' : '-');
float hsize;
if (item.size < 1024) {
printf("%lu\t", item.size);
}
else if ((hsize = item.size/1024.0f) < 1024.0f) {
printf("%.1fK\t", hsize);
}
else if ((hsize /= 1024.0f) < 1024.0f) {
printf("%.1fM\t", hsize);
}
else {
hsize /= 1024.0f;
printf("%.1fG\t", hsize);
}
struct tm* tm = localtime(&item.mtime);
printf("%04d-%02d-%02d %02d:%02d:%02d\t",
tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
printf("%s%s\n", item.name, item.type == 'd' ? "/" : "");
}
return 0;
}
*/
#include <string.h>
#include <time.h>
#include <list>
#include "hexport.h"
typedef struct hdir_s {
char name[256];
char type; // f:file d:dir l:link b:block c:char s:socket p:pipe
char reserverd;
unsigned short mode;
size_t size;
time_t atime;
time_t mtime;
time_t ctime;
} hdir_t;
// listdir: same as ls on unix, dir on win
HV_EXPORT int listdir(const char* dir, std::list<hdir_t>& dirs);
#endif // HV_DIR_H_

View File

@ -0,0 +1,244 @@
#ifndef HV_ENDIAN_H_
#define HV_ENDIAN_H_
#include "hplatform.h"
#if defined(OS_MAC)
#include <libkern/OSByteOrder.h>
#define htobe16(v) OSSwapHostToBigInt16(v)
#define htobe32(v) OSSwapHostToBigInt32(v)
#define htobe64(v) OSSwapHostToBigInt64(v)
#define be16toh(v) OSSwapBigToHostInt16(v)
#define be32toh(v) OSSwapBigToHostInt32(v)
#define be64toh(v) OSSwapBigToHostInt64(v)
#define htole16(v) OSSwapHostToLittleInt16(v)
#define htole32(v) OSSwapHostToLittleInt32(v)
#define htole64(v) OSSwapHostToLittleInt64(v)
#define le16toh(v) OSSwapLittleToHostInt16(v)
#define le32toh(v) OSSwapLittleToHostInt32(v)
#define le64toh(v) OSSwapLittleToHostInt64(v)
#elif defined(OS_WIN)
#if _WIN32_WINNT < _WIN32_WINNT_WIN8
/*
* Byte order conversion functions for 64-bit integers and 32 + 64 bit
* floating-point numbers. IEEE big-endian format is used for the
* network floating point format.
*/
#define _WS2_32_WINSOCK_SWAP_LONG(l) \
( ( ((l) >> 24) & 0x000000FFL ) | \
( ((l) >> 8) & 0x0000FF00L ) | \
( ((l) << 8) & 0x00FF0000L ) | \
( ((l) << 24) & 0xFF000000L ) )
#define _WS2_32_WINSOCK_SWAP_LONGLONG(l) \
( ( ((l) >> 56) & 0x00000000000000FFLL ) | \
( ((l) >> 40) & 0x000000000000FF00LL ) | \
( ((l) >> 24) & 0x0000000000FF0000LL ) | \
( ((l) >> 8) & 0x00000000FF000000LL ) | \
( ((l) << 8) & 0x000000FF00000000LL ) | \
( ((l) << 24) & 0x0000FF0000000000LL ) | \
( ((l) << 40) & 0x00FF000000000000LL ) | \
( ((l) << 56) & 0xFF00000000000000LL ) )
#ifndef htonll
__inline unsigned __int64 htonll ( unsigned __int64 Value )
{
const unsigned __int64 Retval = _WS2_32_WINSOCK_SWAP_LONGLONG (Value);
return Retval;
}
#endif /* htonll */
#ifndef ntohll
__inline unsigned __int64 ntohll ( unsigned __int64 Value )
{
const unsigned __int64 Retval = _WS2_32_WINSOCK_SWAP_LONGLONG (Value);
return Retval;
}
#endif /* ntohll */
#ifndef htonf
__inline unsigned __int32 htonf ( float Value )
{
unsigned __int32 Tempval;
unsigned __int32 Retval;
Tempval = *(unsigned __int32*)(&Value);
Retval = _WS2_32_WINSOCK_SWAP_LONG (Tempval);
return Retval;
}
#endif /* htonf */
#ifndef ntohf
__inline float ntohf ( unsigned __int32 Value )
{
const unsigned __int32 Tempval = _WS2_32_WINSOCK_SWAP_LONG (Value);
float Retval;
*((unsigned __int32*)&Retval) = Tempval;
return Retval;
}
#endif /* ntohf */
#ifndef htond
__inline unsigned __int64 htond ( double Value )
{
unsigned __int64 Tempval;
unsigned __int64 Retval;
Tempval = *(unsigned __int64*)(&Value);
Retval = _WS2_32_WINSOCK_SWAP_LONGLONG (Tempval);
return Retval;
}
#endif /* htond */
#ifndef ntohd
__inline double ntohd ( unsigned __int64 Value )
{
const unsigned __int64 Tempval = _WS2_32_WINSOCK_SWAP_LONGLONG (Value);
double Retval;
*((unsigned __int64*)&Retval) = Tempval;
return Retval;
}
#endif /* ntohd */
#endif
#define htobe16(v) htons(v)
#define htobe32(v) htonl(v)
#define htobe64(v) htonll(v)
#define be16toh(v) ntohs(v)
#define be32toh(v) ntohl(v)
#define be64toh(v) ntohll(v)
#if (BYTE_ORDER == LITTLE_ENDIAN)
#define htole16(v) (v)
#define htole32(v) (v)
#define htole64(v) (v)
#define le16toh(v) (v)
#define le32toh(v) (v)
#define le64toh(v) (v)
#elif (BYTE_ORDER == BIG_ENDIAN)
#define htole16(v) __builtin_bswap16(v)
#define htole32(v) __builtin_bswap32(v)
#define htole64(v) __builtin_bswap64(v)
#define le16toh(v) __builtin_bswap16(v)
#define le32toh(v) __builtin_bswap32(v)
#define le64toh(v) __builtin_bswap64(v)
#endif
#elif HAVE_ENDIAN_H
#include <endian.h>
#elif HAVE_SYS_ENDIAN_H
#include <sys/endian.h>
#else
#warning "Not found endian.h!"
#endif
#define PI8(p) *(int8_t*)(p)
#define PI16(p) *(int16_t*)(p)
#define PI32(p) *(int32_t*)(p)
#define PI64(p) *(int64_t*)(p)
#define PU8(p) *(uint8_t*)(p)
#define PU16(p) *(uint16_t*)(p)
#define PU32(p) *(uint32_t*)(p)
#define PU64(p) *(uint64_t*)(p)
#define PF32(p) *(float*)(p)
#define PF64(p) *(double*)(p)
#define GET_BE16(p) be16toh(PU16(p))
#define GET_BE32(p) be32toh(PU32(p))
#define GET_BE64(p) be64toh(PU64(p))
#define GET_LE16(p) le16toh(PU16(p))
#define GET_LE32(p) le32toh(PU32(p))
#define GET_LE64(p) le64toh(PU64(p))
#define PUT_BE16(p, v) PU16(p) = htobe16(v)
#define PUT_BE32(p, v) PU32(p) = htobe32(v)
#define PUT_BE64(p, v) PU64(p) = htobe64(v)
#define PUT_LE16(p, v) PU16(p) = htole16(v)
#define PUT_LE32(p, v) PU32(p) = htole32(v)
#define PUT_LE64(p, v) PU64(p) = htole64(v)
// NOTE: uint8_t* p = (uint8_t*)buf;
#define POP_BE8(p, v) v = *p; ++p
#define POP_BE16(p, v) v = be16toh(PU16(p)); p += 2
#define POP_BE32(p, v) v = be32toh(PU32(p)); p += 4
#define POP_BE64(p, v) v = be64toh(PU64(p)); p += 8
#define POP_LE8(p, v) v= *p; ++p
#define POP_LE16(p, v) v = le16toh(PU16(p)); p += 2
#define POP_LE32(p, v) v = le32toh(PU32(p)); p += 4
#define POP_LE64(p, v) v = le64toh(PU64(p)); p += 8
#define PUSH_BE8(p, v) *p = v; ++p
#define PUSH_BE16(p, v) PU16(p) = htobe16(v); p += 2
#define PUSH_BE32(p, v) PU32(p) = htobe32(v); p += 4
#define PUSH_BE64(p, v) PU64(p) = htobe64(v); p += 8
#define PUSH_LE8(p, v) *p = v; ++p
#define PUSH_LE16(p, v) PU16(p) = htole16(v); p += 2
#define PUSH_LE32(p, v) PU32(p) = htole32(v); p += 4
#define PUSH_LE64(p, v) PU64(p) = htole64(v); p += 8
// NOTE: NET_ENDIAN = BIG_ENDIAN
#define POP8(p, v) POP_BE8(p, v)
#define POP16(p, v) POP_BE16(p, v)
#define POP32(p, v) POP_BE32(p, v)
#define POP64(p, v) POP_BE64(p, v)
#define POP_N(p, v, n) memcpy(v, p, n); p += n
#define PUSH8(p, v) PUSH_BE8(p, v)
#define PUSH16(p, v) PUSH_BE16(p, v)
#define PUSH32(p, v) PUSH_BE32(p, v)
#define PUSH64(p, v) PUSH_BE64(p, v)
#define PUSH_N(p, v, n) memcpy(p, v, n); p += n
static inline int detect_endian() {
union {
char c;
short s;
} u;
u.s = 0x1122;
return u.c ==0x11 ? BIG_ENDIAN : LITTLE_ENDIAN;
}
#ifdef __cplusplus
template <typename T>
uint8_t* serialize(uint8_t* buf, T value, int host_endian = LITTLE_ENDIAN, int buf_endian = BIG_ENDIAN) {
size_t size = sizeof(T);
uint8_t* pDst = buf;
uint8_t* pSrc = (uint8_t*)&value;
if (host_endian == buf_endian) {
memcpy(pDst, pSrc, size);
}
else {
for (int i = 0; i < size; ++i) {
pDst[i] = pSrc[size-i-1];
}
}
return buf+size;
}
template <typename T>
uint8_t* deserialize(uint8_t* buf, T* value, int host_endian = LITTLE_ENDIAN, int buf_endian = BIG_ENDIAN) {
size_t size = sizeof(T);
uint8_t* pSrc = buf;
uint8_t* pDst = (uint8_t*)value;
if (host_endian == buf_endian) {
memcpy(pDst, pSrc, size);
}
else {
for (int i = 0; i < size; ++i) {
pDst[i] = pSrc[size-i-1];
}
}
return buf+size;
}
#endif // __cplusplus
#endif // HV_ENDIAN_H_

View File

@ -0,0 +1,122 @@
#ifndef HV_ERR_H_
#define HV_ERR_H_
#include <errno.h>
#include "hexport.h"
#ifndef SYS_NERR
#define SYS_NERR 133
#endif
// F(errcode, name, errmsg)
// [1, 133]
#define FOREACH_ERR_SYS(F)
// [1xx~5xx]
#define FOREACH_ERR_STATUS(F)
// [1xxx]
#define FOREACH_ERR_COMMON(F) \
F(0, OK, "OK") \
F(1000, UNKNOWN, "Unknown error") \
\
F(1001, NULL_PARAM, "Null parameter") \
F(1002, NULL_POINTER, "Null pointer") \
F(1003, NULL_DATA, "Null data") \
F(1004, NULL_HANDLE, "Null handle") \
\
F(1011, INVALID_PARAM, "Invalid parameter")\
F(1012, INVALID_POINTER, "Invalid pointer") \
F(1013, INVALID_DATA, "Invalid data") \
F(1014, INVALID_HANDLE, "Invalid handle") \
F(1015, INVALID_JSON, "Invalid json") \
F(1016, INVALID_XML, "Invalid xml") \
F(1017, INVALID_FMT, "Invalid format") \
F(1018, INVALID_PROTOCOL, "Invalid protocol") \
F(1019, INVALID_PACKAGE, "Invalid package") \
\
F(1021, OUT_OF_RANGE, "Out of range") \
F(1022, OVER_LIMIT, "Over the limit") \
F(1023, MISMATCH, "Mismatch") \
F(1024, PARSE, "Parse failed") \
\
F(1030, OPEN_FILE, "Open file failed") \
F(1031, SAVE_FILE, "Save file failed") \
F(1032, READ_FILE, "Read file failed") \
F(1033, WRITE_FILE, "Write file failed")\
\
F(1040, SSL, "SSL/TLS error") \
F(1041, NEW_SSL_CTX, "New SSL_CTX failed") \
F(1042, NEW_SSL, "New SSL failed") \
F(1043, SSL_HANDSHAKE, "SSL handshake failed") \
\
F(1100, TASK_TIMEOUT, "Task timeout") \
F(1101, TASK_QUEUE_FULL, "Task queue full") \
F(1102, TASK_QUEUE_EMPTY, "Task queue empty") \
\
F(1400, REQUEST, "Bad request") \
F(1401, RESPONSE, "Bad response") \
// [-1xxx]
#define FOREACH_ERR_FUNC(F) \
F(-1001, MALLOC, "malloc() error") \
F(-1002, REALLOC, "realloc() error") \
F(-1003, CALLOC, "calloc() error") \
F(-1004, FREE, "free() error") \
\
F(-1011, SOCKET, "socket() error") \
F(-1012, BIND, "bind() error") \
F(-1013, LISTEN, "listen() error") \
F(-1014, ACCEPT, "accept() error") \
F(-1015, CONNECT, "connect() error") \
F(-1016, RECV, "recv() error") \
F(-1017, SEND, "send() error") \
F(-1018, RECVFROM, "recvfrom() error") \
F(-1019, SENDTO, "sendto() error") \
F(-1020, SETSOCKOPT, "setsockopt() error") \
F(-1021, GETSOCKOPT, "getsockopt() error") \
// grpc [4xxx]
#define FOREACH_ERR_GRPC(F) \
F(4000, GRPC_FIRST, "grpc no error") \
F(4001, GRPC_STATUS_CANCELLED, "grpc status: cancelled") \
F(4002, GRPC_STATUS_UNKNOWN, "grpc unknown error") \
F(4003, GRPC_STATUS_INVALID_ARGUMENT, "grpc status: invalid argument")\
F(4004, GRPC_STATUS_DEADLINE, "grpc status: deadline") \
F(4005, GRPC_STATUS_NOT_FOUND, "grpc status: not found") \
F(4006, GRPC_STATUS_ALREADY_EXISTS, "grpc status: already exists") \
F(4007, GRPC_STATUS_PERMISSION_DENIED, "grpc status: permission denied") \
F(4008, GRPC_STATUS_RESOURCE_EXHAUSTED, "grpc status: resource exhausted") \
F(4009, GRPC_STATUS_FAILED_PRECONDITION,"grpc status: failed precondition") \
F(4010, GRPC_STATUS_ABORTED, "grpc status: aborted") \
F(4011, GRPC_STATUS_OUT_OF_RANGE, "grpc status: out of range") \
F(4012, GRPC_STATUS_UNIMPLEMENTED, "grpc status: unimplemented") \
F(4013, GRPC_STATUS_INTERNAL, "grpc internal error") \
F(4014, GRPC_STATUS_UNAVAILABLE, "grpc service unavailable") \
F(4015, GRPC_STATUS_DATA_LOSS, "grpc status: data loss") \
#define FOREACH_ERR(F) \
FOREACH_ERR_COMMON(F) \
FOREACH_ERR_FUNC(F) \
FOREACH_ERR_GRPC(F) \
#undef ERR_OK // prevent conflict
enum {
#define F(errcode, name, errmsg) ERR_##name = errcode,
FOREACH_ERR(F)
#undef F
};
#ifdef __cplusplus
extern "C" {
#endif
// errcode => errmsg
HV_EXPORT const char* hv_strerror(int err);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // HV_ERR_H_

View File

@ -0,0 +1,131 @@
#ifndef HV_EXPORT_H_
#define HV_EXPORT_H_
// HV_EXPORT
#if defined(HV_STATICLIB) || defined(HV_SOURCE)
#define HV_EXPORT
#elif defined(_MSC_VER)
#if defined(HV_DYNAMICLIB) || defined(HV_EXPORTS) || defined(hv_EXPORTS)
#define HV_EXPORT __declspec(dllexport)
#else
#define HV_EXPORT __declspec(dllimport)
#endif
#elif defined(__GNUC__)
#define HV_EXPORT __attribute__((visibility("default")))
#else
#define HV_EXPORT
#endif
// HV_INLINE
#define HV_INLINE static inline
// HV_DEPRECATED
#if defined(HV_NO_DEPRECATED)
#define HV_DEPRECATED
#elif defined(__GNUC__) || defined(__clang__)
#define HV_DEPRECATED __attribute__((deprecated))
#elif defined(_MSC_VER)
#define HV_DEPRECATED __declspec(deprecated)
#else
#define HV_DEPRECATED
#endif
// HV_UNUSED
#if defined(__GNUC__)
#define HV_UNUSED __attribute__((visibility("unused")))
#else
#define HV_UNUSED
#endif
#ifdef __cplusplus
#ifndef EXTERN_C
#define EXTERN_C extern "C"
#endif
#ifndef BEGIN_EXTERN_C
#define BEGIN_EXTERN_C extern "C" {
#endif
#ifndef END_EXTERN_C
#define END_EXTERN_C } // extern "C"
#endif
#ifndef BEGIN_NAMESPACE
#define BEGIN_NAMESPACE(ns) namespace ns {
#endif
#ifndef END_NAMESPACE
#define END_NAMESPACE(ns) } // namespace ns
#endif
#ifndef USING_NAMESPACE
#define USING_NAMESPACE(ns) using namespace ns;
#endif
#ifndef DEFAULT
#define DEFAULT(x) = x
#endif
#ifndef ENUM
#define ENUM(e) enum e
#endif
#ifndef STRUCT
#define STRUCT(s) struct s
#endif
#else
#define EXTERN_C extern
#define BEGIN_EXTERN_C
#define END_EXTERN_C
#define BEGIN_NAMESPACE(ns)
#define END_NAMESPACE(ns)
#define USING_NAMESPACE(ns)
#ifndef DEFAULT
#define DEFAULT(x)
#endif
#ifndef ENUM
#define ENUM(e)\
typedef enum e e;\
enum e
#endif
#ifndef STRUCT
#define STRUCT(s)\
typedef struct s s;\
struct s
#endif
#endif // __cplusplus
#define BEGIN_NAMESPACE_HV BEGIN_NAMESPACE(hv)
#define END_NAMESPACE_HV END_NAMESPACE(hv)
#define USING_NAMESPACE_HV USING_NAMESPACE(hv)
// MSVC ports
#ifdef _MSC_VER
#pragma warning (disable: 4251) // STL dll
#pragma warning (disable: 4275) // dll-interface
#if _MSC_VER < 1900 // < VS2015
#ifndef __cplusplus
#ifndef inline
#define inline __inline
#endif
#endif
#ifndef snprintf
#define snprintf _snprintf
#endif
#endif
#endif
#endif // HV_EXPORT_H_

View File

@ -0,0 +1,134 @@
#ifndef HV_FILE_H_
#define HV_FILE_H_
#include <string> // for std::string
#include "hplatform.h" // for stat
#include "hbuf.h" // for HBuf
class HFile {
public:
HFile() {
filepath[0] = '\0';
fp = NULL;
}
~HFile() {
close();
}
int open(const char* filepath, const char* mode) {
close();
strncpy(this->filepath, filepath, MAX_PATH - 1);
fp = fopen(filepath, mode);
return fp ? 0 : errno;
}
void close() {
if (fp) {
fclose(fp);
fp = NULL;
}
}
bool isopen() {
return fp != NULL;
}
int remove() {
close();
return ::remove(filepath);
}
int rename(const char* newpath) {
close();
return ::rename(filepath, newpath);
}
size_t read(void* ptr, size_t len) {
return fread(ptr, 1, len, fp);
}
size_t write(const void* ptr, size_t len) {
return fwrite(ptr, 1, len, fp);
}
size_t write(const std::string& str) {
return write(str.c_str(), str.length());
}
int seek(size_t offset, int whence = SEEK_SET) {
return fseek(fp, offset, whence);
}
int tell() {
return ftell(fp);
}
int flush() {
return fflush(fp);
}
static size_t size(const char* filepath) {
struct stat st;
memset(&st, 0, sizeof(st));
stat(filepath, &st);
return st.st_size;
}
size_t size() {
return HFile::size(filepath);
}
size_t readall(HBuf& buf) {
size_t filesize = size();
if (filesize == 0) return 0;
buf.resize(filesize);
return fread(buf.base, 1, filesize, fp);
}
size_t readall(std::string& str) {
size_t filesize = size();
if (filesize == 0) return 0;
str.resize(filesize);
return fread((void*)str.data(), 1, filesize, fp);
}
bool readline(std::string& str) {
str.clear();
char ch;
while (fread(&ch, 1, 1, fp)) {
if (ch == '\n') {
// unix: LF
return true;
}
if (ch == '\r') {
// dos: CRLF
// read LF
if (fread(&ch, 1, 1, fp) && ch != '\n') {
// mac: CR
fseek(fp, -1, SEEK_CUR);
}
return true;
}
str += ch;
}
return str.length() != 0;
}
int readrange(std::string& str, size_t from = 0, size_t to = 0) {
size_t filesize = size();
if (filesize == 0) return 0;
if (to == 0 || to >= filesize) to = filesize - 1;
size_t readbytes = to - from + 1;
str.resize(readbytes);
fseek(fp, from, SEEK_SET);
return fread((void*)str.data(), 1, readbytes, fp);
}
public:
char filepath[MAX_PATH];
FILE* fp;
};
#endif // HV_FILE_H_

View File

@ -0,0 +1,176 @@
#ifndef HV_LOG_H_
#define HV_LOG_H_
/*
* hlog is thread-safe
*/
#include <string.h>
#ifdef _WIN32
#define DIR_SEPARATOR '\\'
#define DIR_SEPARATOR_STR "\\"
#else
#define DIR_SEPARATOR '/'
#define DIR_SEPARATOR_STR "/"
#endif
#ifndef __FILENAME__
// #define __FILENAME__ (strrchr(__FILE__, DIR_SEPARATOR) ? strrchr(__FILE__, DIR_SEPARATOR) + 1 : __FILE__)
#define __FILENAME__ (strrchr(DIR_SEPARATOR_STR __FILE__, DIR_SEPARATOR) + 1)
#endif
#include "hexport.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CLR_CLR "\033[0m" /* 恢复颜色 */
#define CLR_BLACK "\033[30m" /* 黑色字 */
#define CLR_RED "\033[31m" /* 红色字 */
#define CLR_GREEN "\033[32m" /* 绿色字 */
#define CLR_YELLOW "\033[33m" /* 黄色字 */
#define CLR_BLUE "\033[34m" /* 蓝色字 */
#define CLR_PURPLE "\033[35m" /* 紫色字 */
#define CLR_SKYBLUE "\033[36m" /* 天蓝字 */
#define CLR_WHITE "\033[37m" /* 白色字 */
#define CLR_BLK_WHT "\033[40;37m" /* 黑底白字 */
#define CLR_RED_WHT "\033[41;37m" /* 红底白字 */
#define CLR_GREEN_WHT "\033[42;37m" /* 绿底白字 */
#define CLR_YELLOW_WHT "\033[43;37m" /* 黄底白字 */
#define CLR_BLUE_WHT "\033[44;37m" /* 蓝底白字 */
#define CLR_PURPLE_WHT "\033[45;37m" /* 紫底白字 */
#define CLR_SKYBLUE_WHT "\033[46;37m" /* 天蓝底白字 */
#define CLR_WHT_BLK "\033[47;30m" /* 白底黑字 */
// XXX(id, str, clr)
#define LOG_LEVEL_MAP(XXX) \
XXX(LOG_LEVEL_DEBUG, "DEBUG", CLR_WHITE) \
XXX(LOG_LEVEL_INFO, "INFO ", CLR_GREEN) \
XXX(LOG_LEVEL_WARN, "WARN ", CLR_YELLOW) \
XXX(LOG_LEVEL_ERROR, "ERROR", CLR_RED) \
XXX(LOG_LEVEL_FATAL, "FATAL", CLR_RED_WHT)
typedef enum {
LOG_LEVEL_VERBOSE = 0,
#define XXX(id, str, clr) id,
LOG_LEVEL_MAP(XXX)
#undef XXX
LOG_LEVEL_SILENT
} log_level_e;
#define DEFAULT_LOG_FILE "libhv"
#define DEFAULT_LOG_LEVEL LOG_LEVEL_INFO
#define DEFAULT_LOG_FORMAT "%y-%m-%d %H:%M:%S.%z %L %s"
#define DEFAULT_LOG_REMAIN_DAYS 1
#define DEFAULT_LOG_MAX_BUFSIZE (1<<14) // 16k
#define DEFAULT_LOG_MAX_FILESIZE (1<<24) // 16M
// logger: default file_logger
// network_logger() see event/nlog.h
typedef void (*logger_handler)(int loglevel, const char* buf, int len);
HV_EXPORT void stdout_logger(int loglevel, const char* buf, int len);
HV_EXPORT void stderr_logger(int loglevel, const char* buf, int len);
HV_EXPORT void file_logger(int loglevel, const char* buf, int len);
// network_logger implement see event/nlog.h
// HV_EXPORT void network_logger(int loglevel, const char* buf, int len);
typedef struct logger_s logger_t;
HV_EXPORT logger_t* logger_create();
HV_EXPORT void logger_destroy(logger_t* logger);
HV_EXPORT void logger_set_handler(logger_t* logger, logger_handler fn);
HV_EXPORT void logger_set_level(logger_t* logger, int level);
// level = [VERBOSE,DEBUG,INFO,WARN,ERROR,FATAL,SILENT]
HV_EXPORT void logger_set_level_by_str(logger_t* logger, const char* level);
/*
* format = "%y-%m-%d %H:%M:%S.%z %L %s"
* message = "2020-01-02 03:04:05.067 DEBUG message"
* %y year
* %m month
* %d day
* %H hour
* %M min
* %S sec
* %z ms
* %Z us
* %l First character of level
* %L All characters of level
* %s message
* %% %
*/
HV_EXPORT void logger_set_format(logger_t* logger, const char* format);
HV_EXPORT void logger_set_max_bufsize(logger_t* logger, unsigned int bufsize);
HV_EXPORT void logger_enable_color(logger_t* logger, int on);
HV_EXPORT int logger_print(logger_t* logger, int level, const char* fmt, ...);
// below for file logger
HV_EXPORT void logger_set_file(logger_t* logger, const char* filepath);
HV_EXPORT void logger_set_max_filesize(logger_t* logger, unsigned long long filesize);
// 16, 16M, 16MB
HV_EXPORT void logger_set_max_filesize_by_str(logger_t* logger, const char* filesize);
HV_EXPORT void logger_set_remain_days(logger_t* logger, int days);
HV_EXPORT void logger_enable_fsync(logger_t* logger, int on);
HV_EXPORT void logger_fsync(logger_t* logger);
HV_EXPORT const char* logger_get_cur_file(logger_t* logger);
// hlog: default logger instance
HV_EXPORT logger_t* hv_default_logger();
HV_EXPORT void hv_destroy_default_logger(void);
// macro hlog*
#define hlog hv_default_logger()
#define hlog_destory() hv_destroy_default_logger()
#define hlog_disable() logger_set_level(hlog, LOG_LEVEL_SILENT)
#define hlog_set_file(filepath) logger_set_file(hlog, filepath)
#define hlog_set_level(level) logger_set_level(hlog, level)
#define hlog_set_level_by_str(level) logger_set_level_by_str(hlog, level)
#define hlog_set_handler(fn) logger_set_handler(hlog, fn)
#define hlog_set_format(format) logger_set_format(hlog, format)
#define hlog_set_max_filesize(filesize) logger_set_max_filesize(hlog, filesize)
#define hlog_set_max_filesize_by_str(filesize) logger_set_max_filesize_by_str(hlog, filesize)
#define hlog_set_remain_days(days) logger_set_remain_days(hlog, days)
#define hlog_enable_fsync() logger_enable_fsync(hlog, 1)
#define hlog_disable_fsync() logger_enable_fsync(hlog, 0)
#define hlog_fsync() logger_fsync(hlog)
#define hlog_get_cur_file() logger_get_cur_file(hlog)
#define hlogd(fmt, ...) logger_print(hlog, LOG_LEVEL_DEBUG, fmt " [%s:%d:%s]", ## __VA_ARGS__, __FILENAME__, __LINE__, __FUNCTION__)
#define hlogi(fmt, ...) logger_print(hlog, LOG_LEVEL_INFO, fmt " [%s:%d:%s]", ## __VA_ARGS__, __FILENAME__, __LINE__, __FUNCTION__)
#define hlogw(fmt, ...) logger_print(hlog, LOG_LEVEL_WARN, fmt " [%s:%d:%s]", ## __VA_ARGS__, __FILENAME__, __LINE__, __FUNCTION__)
#define hloge(fmt, ...) logger_print(hlog, LOG_LEVEL_ERROR, fmt " [%s:%d:%s]", ## __VA_ARGS__, __FILENAME__, __LINE__, __FUNCTION__)
#define hlogf(fmt, ...) logger_print(hlog, LOG_LEVEL_FATAL, fmt " [%s:%d:%s]", ## __VA_ARGS__, __FILENAME__, __LINE__, __FUNCTION__)
// below for android
#if defined(ANDROID) || defined(__ANDROID__)
#include <android/log.h>
#define LOG_TAG "JNI"
#undef hlogd
#undef hlogi
#undef hlogw
#undef hloge
#undef hlogf
#define hlogd(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define hlogi(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define hlogw(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define hloge(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define hlogf(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
#endif
// macro alias
#if !defined(LOGD) && !defined(LOGI) && !defined(LOGW) && !defined(LOGE) && !defined(LOGF)
#define LOGD hlogd
#define LOGI hlogi
#define LOGW hlogw
#define LOGE hloge
#define LOGF hlogf
#endif
#ifdef __cplusplus
} // extern "C"
#endif
#endif // HV_LOG_H_

View File

@ -0,0 +1,758 @@
#ifndef HV_LOOP_H_
#define HV_LOOP_H_
#include "hexport.h"
#include "hplatform.h"
#include "hdef.h"
#include "hssl.h"
typedef struct hloop_s hloop_t;
typedef struct hevent_s hevent_t;
// NOTE: The following structures are subclasses of hevent_t,
// inheriting hevent_t data members and function members.
typedef struct hio_s hio_t;
typedef struct hidle_s hidle_t;
typedef struct htimer_s htimer_t;
typedef struct htimeout_s htimeout_t;
typedef struct hperiod_s hperiod_t;
typedef struct hevent_s hsignal_t;
typedef void (*hevent_cb) (hevent_t* ev);
typedef void (*hio_cb) (hio_t* io);
typedef void (*hidle_cb) (hidle_t* idle);
typedef void (*htimer_cb) (htimer_t* timer);
typedef void (*hsignal_cb) (hsignal_t* sig);
typedef void (*haccept_cb) (hio_t* io);
typedef void (*hconnect_cb) (hio_t* io);
typedef void (*hread_cb) (hio_t* io, void* buf, int readbytes);
typedef void (*hwrite_cb) (hio_t* io, const void* buf, int writebytes);
typedef void (*hclose_cb) (hio_t* io);
typedef enum {
HLOOP_STATUS_STOP,
HLOOP_STATUS_RUNNING,
HLOOP_STATUS_PAUSE,
HLOOP_STATUS_DESTROY
} hloop_status_e;
typedef enum {
HEVENT_TYPE_NONE = 0,
HEVENT_TYPE_IO = 0x00000001,
HEVENT_TYPE_TIMEOUT = 0x00000010,
HEVENT_TYPE_PERIOD = 0x00000020,
HEVENT_TYPE_TIMER = HEVENT_TYPE_TIMEOUT|HEVENT_TYPE_PERIOD,
HEVENT_TYPE_IDLE = 0x00000100,
HEVENT_TYPE_SIGNAL = 0x00000200,
HEVENT_TYPE_CUSTOM = 0x00000400, // 1024
} hevent_type_e;
#define HEVENT_LOWEST_PRIORITY (-5)
#define HEVENT_LOW_PRIORITY (-3)
#define HEVENT_NORMAL_PRIORITY 0
#define HEVENT_HIGH_PRIORITY 3
#define HEVENT_HIGHEST_PRIORITY 5
#define HEVENT_PRIORITY_SIZE (HEVENT_HIGHEST_PRIORITY-HEVENT_LOWEST_PRIORITY+1)
#define HEVENT_PRIORITY_INDEX(priority) (priority-HEVENT_LOWEST_PRIORITY)
#define HEVENT_FLAGS \
unsigned destroy :1; \
unsigned active :1; \
unsigned pending :1;
#define HEVENT_FIELDS \
hloop_t* loop; \
hevent_type_e event_type; \
uint64_t event_id; \
hevent_cb cb; \
void* userdata; \
void* privdata; \
struct hevent_s* pending_next; \
int priority; \
HEVENT_FLAGS
// sizeof(struct hevent_s)=64 on x64
struct hevent_s {
HEVENT_FIELDS
};
#define hevent_set_id(ev, id) ((hevent_t*)(ev))->event_id = id
#define hevent_set_cb(ev, cb) ((hevent_t*)(ev))->cb = cb
#define hevent_set_priority(ev, prio) ((hevent_t*)(ev))->priority = prio
#define hevent_set_userdata(ev, udata) ((hevent_t*)(ev))->userdata = (void*)udata
#define hevent_loop(ev) (((hevent_t*)(ev))->loop)
#define hevent_type(ev) (((hevent_t*)(ev))->event_type)
#define hevent_id(ev) (((hevent_t*)(ev))->event_id)
#define hevent_cb(ev) (((hevent_t*)(ev))->cb)
#define hevent_priority(ev) (((hevent_t*)(ev))->priority)
#define hevent_userdata(ev) (((hevent_t*)(ev))->userdata)
typedef enum {
HIO_TYPE_UNKNOWN = 0,
HIO_TYPE_STDIN = 0x00000001,
HIO_TYPE_STDOUT = 0x00000002,
HIO_TYPE_STDERR = 0x00000004,
HIO_TYPE_STDIO = 0x0000000F,
HIO_TYPE_FILE = 0x00000010,
HIO_TYPE_PIPE = 0x00000020,
HIO_TYPE_IP = 0x00000100,
HIO_TYPE_SOCK_RAW = 0x00000F00,
HIO_TYPE_UDP = 0x00001000,
HIO_TYPE_KCP = 0x00002000,
HIO_TYPE_DTLS = 0x00010000,
HIO_TYPE_SOCK_DGRAM = 0x000FF000,
HIO_TYPE_TCP = 0x00100000,
HIO_TYPE_SSL = 0x01000000,
HIO_TYPE_TLS = HIO_TYPE_SSL,
HIO_TYPE_SOCK_STREAM= 0x0FF00000,
HIO_TYPE_SOCKET = 0x0FFFFF00,
} hio_type_e;
typedef enum {
HIO_SERVER_SIDE = 0,
HIO_CLIENT_SIDE = 1,
} hio_side_e;
#define HIO_DEFAULT_CONNECT_TIMEOUT 10000 // ms
#define HIO_DEFAULT_CLOSE_TIMEOUT 60000 // ms
#define HIO_DEFAULT_KEEPALIVE_TIMEOUT 75000 // ms
#define HIO_DEFAULT_HEARTBEAT_INTERVAL 10000 // ms
BEGIN_EXTERN_C
// loop
#define HLOOP_FLAG_RUN_ONCE 0x00000001
#define HLOOP_FLAG_AUTO_FREE 0x00000002
#define HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS 0x00000004
HV_EXPORT hloop_t* hloop_new(int flags DEFAULT(HLOOP_FLAG_AUTO_FREE));
// WARN: Forbid to call hloop_free if HLOOP_FLAG_AUTO_FREE set.
HV_EXPORT void hloop_free(hloop_t** pp);
HV_EXPORT int hloop_process_events(hloop_t* loop, int timeout_ms DEFAULT(0));
// NOTE: when no active events, loop will quit if HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS set.
HV_EXPORT int hloop_run(hloop_t* loop);
// NOTE: hloop_stop called in loop-thread just set flag to quit in next loop,
// if called in other thread, it will wakeup loop-thread from blocking poll system call,
// then you should join loop thread to safely exit loop thread.
HV_EXPORT int hloop_stop(hloop_t* loop);
HV_EXPORT int hloop_pause(hloop_t* loop);
HV_EXPORT int hloop_resume(hloop_t* loop);
HV_EXPORT int hloop_wakeup(hloop_t* loop);
HV_EXPORT hloop_status_e hloop_status(hloop_t* loop);
HV_EXPORT void hloop_update_time(hloop_t* loop);
HV_EXPORT uint64_t hloop_now(hloop_t* loop); // s
HV_EXPORT uint64_t hloop_now_ms(hloop_t* loop); // ms
HV_EXPORT uint64_t hloop_now_us(hloop_t* loop); // us
HV_EXPORT uint64_t hloop_now_hrtime(hloop_t* loop); // us
// export some hloop's members
// @return pid of hloop_run
HV_EXPORT long hloop_pid(hloop_t* loop);
// @return tid of hloop_run
HV_EXPORT long hloop_tid(hloop_t* loop);
// @return count of loop
HV_EXPORT uint64_t hloop_count(hloop_t* loop);
// @return number of ios
HV_EXPORT uint32_t hloop_nios(hloop_t* loop);
// @return number of timers
HV_EXPORT uint32_t hloop_ntimers(hloop_t* loop);
// @return number of idles
HV_EXPORT uint32_t hloop_nidles(hloop_t* loop);
// @return number of active events
HV_EXPORT uint32_t hloop_nactives(hloop_t* loop);
// userdata
HV_EXPORT void hloop_set_userdata(hloop_t* loop, void* userdata);
HV_EXPORT void* hloop_userdata(hloop_t* loop);
// custom_event
/*
* hevent_t ev;
* memset(&ev, 0, sizeof(hevent_t));
* ev.event_type = (hevent_type_e)(HEVENT_TYPE_CUSTOM + 1);
* ev.cb = custom_event_cb;
* ev.userdata = userdata;
* hloop_post_event(loop, &ev);
*/
// NOTE: hloop_post_event is thread-safe, used to post event from other thread to loop thread.
HV_EXPORT void hloop_post_event(hloop_t* loop, hevent_t* ev);
// signal
HV_EXPORT hsignal_t* hsignal_add(hloop_t* loop, hsignal_cb cb, int signo);
HV_EXPORT void hsignal_del(hsignal_t* sig);
// idle
HV_EXPORT hidle_t* hidle_add(hloop_t* loop, hidle_cb cb, uint32_t repeat DEFAULT(INFINITE));
HV_EXPORT void hidle_del(hidle_t* idle);
// timer
HV_EXPORT htimer_t* htimer_add(hloop_t* loop, htimer_cb cb, uint32_t timeout_ms, uint32_t repeat DEFAULT(INFINITE));
/*
* minute hour day week month cb
* 0~59 0~23 1~31 0~6 1~12
* -1 -1 -1 -1 -1 cron.minutely
* 30 -1 -1 -1 -1 cron.hourly
* 30 1 -1 -1 -1 cron.daily
* 30 1 15 -1 -1 cron.monthly
* 30 1 -1 5 -1 cron.weekly
* 30 1 1 -1 10 cron.yearly
*/
HV_EXPORT htimer_t* htimer_add_period(hloop_t* loop, htimer_cb cb,
int8_t minute DEFAULT(0), int8_t hour DEFAULT(-1), int8_t day DEFAULT(-1),
int8_t week DEFAULT(-1), int8_t month DEFAULT(-1), uint32_t repeat DEFAULT(INFINITE));
HV_EXPORT void htimer_del(htimer_t* timer);
HV_EXPORT void htimer_reset(htimer_t* timer, uint32_t timeout_ms DEFAULT(0));
// io
//-----------------------low-level apis---------------------------------------
#define HV_READ 0x0001
#define HV_WRITE 0x0004
#define HV_RDWR (HV_READ|HV_WRITE)
/*
const char* hio_engine() {
#ifdef EVENT_SELECT
return "select";
#elif defined(EVENT_POLL)
return "poll";
#elif defined(EVENT_EPOLL)
return "epoll";
#elif defined(EVENT_KQUEUE)
return "kqueue";
#elif defined(EVENT_IOCP)
return "iocp";
#elif defined(EVENT_PORT)
return "evport";
#else
return "noevent";
#endif
}
*/
HV_EXPORT const char* hio_engine();
HV_EXPORT hio_t* hio_get(hloop_t* loop, int fd);
HV_EXPORT int hio_add(hio_t* io, hio_cb cb, int events DEFAULT(HV_READ));
HV_EXPORT int hio_del(hio_t* io, int events DEFAULT(HV_RDWR));
// NOTE: io detach from old loop and attach to new loop
/* @see examples/multi-thread/one-acceptor-multi-workers.c
void new_conn_event(hevent_t* ev) {
hloop_t* loop = ev->loop;
hio_t* io = (hio_t*)hevent_userdata(ev);
hio_attach(loop, io);
}
void on_accpet(hio_t* io) {
hio_detach(io);
hloop_t* worker_loop = get_one_loop();
hevent_t ev;
memset(&ev, 0, sizeof(ev));
ev.loop = worker_loop;
ev.cb = new_conn_event;
ev.userdata = io;
hloop_post_event(worker_loop, &ev);
}
*/
HV_EXPORT void hio_detach(/*hloop_t* loop,*/ hio_t* io);
HV_EXPORT void hio_attach(hloop_t* loop, hio_t* io);
HV_EXPORT bool hio_exists(hloop_t* loop, int fd);
// hio_t fields
// NOTE: fd cannot be used as unique identifier, so we provide an id.
HV_EXPORT uint32_t hio_id (hio_t* io);
HV_EXPORT int hio_fd (hio_t* io);
HV_EXPORT int hio_error (hio_t* io);
HV_EXPORT int hio_events (hio_t* io);
HV_EXPORT int hio_revents (hio_t* io);
HV_EXPORT hio_type_e hio_type (hio_t* io);
HV_EXPORT struct sockaddr* hio_localaddr(hio_t* io);
HV_EXPORT struct sockaddr* hio_peeraddr (hio_t* io);
HV_EXPORT void hio_set_context(hio_t* io, void* ctx);
HV_EXPORT void* hio_context(hio_t* io);
HV_EXPORT bool hio_is_opened(hio_t* io);
HV_EXPORT bool hio_is_connected(hio_t* io);
HV_EXPORT bool hio_is_closed(hio_t* io);
// iobuf
// #include "hbuf.h"
typedef struct fifo_buf_s hio_readbuf_t;
// NOTE: One loop per thread, one readbuf per loop.
// But you can pass in your own readbuf instead of the default readbuf to avoid memcopy.
HV_EXPORT void hio_set_readbuf(hio_t* io, void* buf, size_t len);
HV_EXPORT hio_readbuf_t* hio_get_readbuf(hio_t* io);
HV_EXPORT void hio_set_max_read_bufsize (hio_t* io, uint32_t size);
HV_EXPORT void hio_set_max_write_bufsize(hio_t* io, uint32_t size);
// NOTE: hio_write is non-blocking, so there is a write queue inside hio_t to cache unwritten data and wait for writable.
// @return current buffer size of write queue.
HV_EXPORT size_t hio_write_bufsize(hio_t* io);
#define hio_write_is_complete(io) (hio_write_bufsize(io) == 0)
HV_EXPORT uint64_t hio_last_read_time(hio_t* io); // ms
HV_EXPORT uint64_t hio_last_write_time(hio_t* io); // ms
// set callbacks
HV_EXPORT void hio_setcb_accept (hio_t* io, haccept_cb accept_cb);
HV_EXPORT void hio_setcb_connect (hio_t* io, hconnect_cb connect_cb);
HV_EXPORT void hio_setcb_read (hio_t* io, hread_cb read_cb);
HV_EXPORT void hio_setcb_write (hio_t* io, hwrite_cb write_cb);
HV_EXPORT void hio_setcb_close (hio_t* io, hclose_cb close_cb);
// get callbacks
HV_EXPORT haccept_cb hio_getcb_accept(hio_t* io);
HV_EXPORT hconnect_cb hio_getcb_connect(hio_t* io);
HV_EXPORT hread_cb hio_getcb_read(hio_t* io);
HV_EXPORT hwrite_cb hio_getcb_write(hio_t* io);
HV_EXPORT hclose_cb hio_getcb_close(hio_t* io);
// Enable SSL/TLS is so easy :)
HV_EXPORT int hio_enable_ssl(hio_t* io);
HV_EXPORT bool hio_is_ssl(hio_t* io);
HV_EXPORT int hio_set_ssl (hio_t* io, hssl_t ssl);
HV_EXPORT int hio_set_ssl_ctx(hio_t* io, hssl_ctx_t ssl_ctx);
// hssl_ctx_new(opt) -> hio_set_ssl_ctx
HV_EXPORT int hio_new_ssl_ctx(hio_t* io, hssl_ctx_opt_t* opt);
HV_EXPORT hssl_t hio_get_ssl(hio_t* io);
HV_EXPORT hssl_ctx_t hio_get_ssl_ctx(hio_t* io);
// for hssl_set_sni_hostname
HV_EXPORT int hio_set_hostname(hio_t* io, const char* hostname);
HV_EXPORT const char* hio_get_hostname(hio_t* io);
// connect timeout => hclose_cb
HV_EXPORT void hio_set_connect_timeout(hio_t* io, int timeout_ms DEFAULT(HIO_DEFAULT_CONNECT_TIMEOUT));
// close timeout => hclose_cb
HV_EXPORT void hio_set_close_timeout(hio_t* io, int timeout_ms DEFAULT(HIO_DEFAULT_CLOSE_TIMEOUT));
// read timeout => hclose_cb
HV_EXPORT void hio_set_read_timeout(hio_t* io, int timeout_ms);
// write timeout => hclose_cb
HV_EXPORT void hio_set_write_timeout(hio_t* io, int timeout_ms);
// keepalive timeout => hclose_cb
HV_EXPORT void hio_set_keepalive_timeout(hio_t* io, int timeout_ms DEFAULT(HIO_DEFAULT_KEEPALIVE_TIMEOUT));
/*
void send_heartbeat(hio_t* io) {
static char buf[] = "PING\r\n";
hio_write(io, buf, 6);
}
hio_set_heartbeat(io, 3000, send_heartbeat);
*/
typedef void (*hio_send_heartbeat_fn)(hio_t* io);
// heartbeat interval => hio_send_heartbeat_fn
HV_EXPORT void hio_set_heartbeat(hio_t* io, int interval_ms, hio_send_heartbeat_fn fn);
// Nonblocking, poll IO events in the loop to call corresponding callback.
// hio_add(io, HV_READ) => accept => haccept_cb
HV_EXPORT int hio_accept (hio_t* io);
// connect => hio_add(io, HV_WRITE) => hconnect_cb
HV_EXPORT int hio_connect(hio_t* io);
// hio_add(io, HV_READ) => read => hread_cb
HV_EXPORT int hio_read (hio_t* io);
#define hio_read_start(io) hio_read(io)
#define hio_read_stop(io) hio_del(io, HV_READ)
// hio_read_start => hread_cb => hio_read_stop
HV_EXPORT int hio_read_once (hio_t* io);
// hio_read_once => hread_cb(len)
HV_EXPORT int hio_read_until_length(hio_t* io, unsigned int len);
// hio_read_once => hread_cb(...delim)
HV_EXPORT int hio_read_until_delim (hio_t* io, unsigned char delim);
HV_EXPORT int hio_read_remain(hio_t* io);
// @see examples/tinyhttpd.c examples/tinyproxyd.c
#define hio_readline(io) hio_read_until_delim(io, '\n')
#define hio_readstring(io) hio_read_until_delim(io, '\0')
#define hio_readbytes(io, len) hio_read_until_length(io, len)
#define hio_read_until(io, len) hio_read_until_length(io, len)
// NOTE: hio_write is thread-safe, locked by recursive_mutex, allow to be called by other threads.
// hio_try_write => hio_add(io, HV_WRITE) => write => hwrite_cb
HV_EXPORT int hio_write (hio_t* io, const void* buf, size_t len);
// NOTE: hio_close is thread-safe, hio_close_async will be called actually in other thread.
// hio_del(io, HV_RDWR) => close => hclose_cb
HV_EXPORT int hio_close (hio_t* io);
// NOTE: hloop_post_event(hio_close_event)
HV_EXPORT int hio_close_async(hio_t* io);
//------------------high-level apis-------------------------------------------
// hio_get -> hio_set_readbuf -> hio_setcb_read -> hio_read
HV_EXPORT hio_t* hread (hloop_t* loop, int fd, void* buf, size_t len, hread_cb read_cb);
// hio_get -> hio_setcb_write -> hio_write
HV_EXPORT hio_t* hwrite (hloop_t* loop, int fd, const void* buf, size_t len, hwrite_cb write_cb DEFAULT(NULL));
// hio_get -> hio_close
HV_EXPORT void hclose (hloop_t* loop, int fd);
// tcp
// hio_get -> hio_setcb_accept -> hio_accept
HV_EXPORT hio_t* haccept (hloop_t* loop, int listenfd, haccept_cb accept_cb);
// hio_get -> hio_setcb_connect -> hio_connect
HV_EXPORT hio_t* hconnect (hloop_t* loop, int connfd, hconnect_cb connect_cb);
// hio_get -> hio_set_readbuf -> hio_setcb_read -> hio_read
HV_EXPORT hio_t* hrecv (hloop_t* loop, int connfd, void* buf, size_t len, hread_cb read_cb);
// hio_get -> hio_setcb_write -> hio_write
HV_EXPORT hio_t* hsend (hloop_t* loop, int connfd, const void* buf, size_t len, hwrite_cb write_cb DEFAULT(NULL));
// udp
HV_EXPORT void hio_set_type(hio_t* io, hio_type_e type);
HV_EXPORT void hio_set_localaddr(hio_t* io, struct sockaddr* addr, int addrlen);
HV_EXPORT void hio_set_peeraddr (hio_t* io, struct sockaddr* addr, int addrlen);
// NOTE: must call hio_set_peeraddr before hrecvfrom/hsendto
// hio_get -> hio_set_readbuf -> hio_setcb_read -> hio_read
HV_EXPORT hio_t* hrecvfrom (hloop_t* loop, int sockfd, void* buf, size_t len, hread_cb read_cb);
// hio_get -> hio_setcb_write -> hio_write
HV_EXPORT hio_t* hsendto (hloop_t* loop, int sockfd, const void* buf, size_t len, hwrite_cb write_cb DEFAULT(NULL));
//-----------------top-level apis---------------------------------------------
// @hio_create_socket: socket -> bind -> listen
// sockaddr_set_ipport -> socket -> hio_get(loop, sockfd) ->
// side == HIO_SERVER_SIDE ? bind ->
// type & HIO_TYPE_SOCK_STREAM ? listen ->
HV_EXPORT hio_t* hio_create_socket(hloop_t* loop, const char* host, int port,
hio_type_e type DEFAULT(HIO_TYPE_TCP),
hio_side_e side DEFAULT(HIO_SERVER_SIDE));
// @tcp_server: hio_create_socket(loop, host, port, HIO_TYPE_TCP, HIO_SERVER_SIDE) -> hio_setcb_accept -> hio_accept
// @see examples/tcp_echo_server.c
HV_EXPORT hio_t* hloop_create_tcp_server (hloop_t* loop, const char* host, int port, haccept_cb accept_cb);
// @tcp_client: hio_create_socket(loop, host, port, HIO_TYPE_TCP, HIO_CLIENT_SIDE) -> hio_setcb_connect -> hio_setcb_close -> hio_connect
// @see examples/nc.c
HV_EXPORT hio_t* hloop_create_tcp_client (hloop_t* loop, const char* host, int port, hconnect_cb connect_cb, hclose_cb close_cb);
// @ssl_server: hio_create_socket(loop, host, port, HIO_TYPE_SSL, HIO_SERVER_SIDE) -> hio_setcb_accept -> hio_accept
// @see examples/tcp_echo_server.c => #define TEST_SSL 1
HV_EXPORT hio_t* hloop_create_ssl_server (hloop_t* loop, const char* host, int port, haccept_cb accept_cb);
// @ssl_client: hio_create_socket(loop, host, port, HIO_TYPE_SSL, HIO_CLIENT_SIDE) -> hio_setcb_connect -> hio_setcb_close -> hio_connect
// @see examples/nc.c => #define TEST_SSL 1
HV_EXPORT hio_t* hloop_create_ssl_client (hloop_t* loop, const char* host, int port, hconnect_cb connect_cb, hclose_cb close_cb);
// @udp_server: hio_create_socket(loop, host, port, HIO_TYPE_UDP, HIO_SERVER_SIDE)
// @see examples/udp_echo_server.c
HV_EXPORT hio_t* hloop_create_udp_server (hloop_t* loop, const char* host, int port);
// @udp_server: hio_create_socket(loop, host, port, HIO_TYPE_UDP, HIO_CLIENT_SIDE)
// @see examples/nc.c
HV_EXPORT hio_t* hloop_create_udp_client (hloop_t* loop, const char* host, int port);
//-----------------pipe---------------------------------------------
// @see examples/pipe_test.c
HV_EXPORT int hio_create_pipe(hloop_t* loop, hio_t* pipeio[2]);
//-----------------upstream---------------------------------------------
// hio_read(io)
// hio_read(io->upstream_io)
HV_EXPORT void hio_read_upstream(hio_t* io);
// on_write(io) -> hio_write_is_complete(io) -> hio_read(io->upstream_io)
HV_EXPORT void hio_read_upstream_on_write_complete(hio_t* io, const void* buf, int writebytes);
// hio_write(io->upstream_io, buf, bytes)
HV_EXPORT void hio_write_upstream(hio_t* io, void* buf, int bytes);
// hio_close(io->upstream_io)
HV_EXPORT void hio_close_upstream(hio_t* io);
// io1->upstream_io = io2;
// io2->upstream_io = io1;
// @see examples/socks5_proxy_server.c
HV_EXPORT void hio_setup_upstream(hio_t* io1, hio_t* io2);
// @return io->upstream_io
HV_EXPORT hio_t* hio_get_upstream(hio_t* io);
// @tcp_upstream: hio_create_socket -> hio_setup_upstream -> hio_connect -> on_connect -> hio_read_upstream
// @return upstream_io
// @see examples/tcp_proxy_server.c
HV_EXPORT hio_t* hio_setup_tcp_upstream(hio_t* io, const char* host, int port, int ssl DEFAULT(0));
#define hio_setup_ssl_upstream(io, host, port) hio_setup_tcp_upstream(io, host, port, 1)
// @udp_upstream: hio_create_socket -> hio_setup_upstream -> hio_read_upstream
// @return upstream_io
// @see examples/udp_proxy_server.c
HV_EXPORT hio_t* hio_setup_udp_upstream(hio_t* io, const char* host, int port);
//-----------------unpack---------------------------------------------
typedef enum {
UNPACK_MODE_NONE = 0,
UNPACK_BY_FIXED_LENGTH = 1, // Not recommended
UNPACK_BY_DELIMITER = 2, // Suitable for text protocol
UNPACK_BY_LENGTH_FIELD = 3, // Suitable for binary protocol
} unpack_mode_e;
#define DEFAULT_PACKAGE_MAX_LENGTH (1 << 21) // 2M
// UNPACK_BY_DELIMITER
#define PACKAGE_MAX_DELIMITER_BYTES 8
// UNPACK_BY_LENGTH_FIELD
typedef enum {
ENCODE_BY_VARINT = 17, // 1 MSB + 7 bits
ENCODE_BY_LITTEL_ENDIAN = LITTLE_ENDIAN, // 1234
ENCODE_BY_BIG_ENDIAN = BIG_ENDIAN, // 4321
} unpack_coding_e;
typedef struct unpack_setting_s {
unpack_mode_e mode;
unsigned int package_max_length;
union {
// UNPACK_BY_FIXED_LENGTH
struct {
unsigned int fixed_length;
};
// UNPACK_BY_DELIMITER
struct {
unsigned char delimiter[PACKAGE_MAX_DELIMITER_BYTES];
unsigned short delimiter_bytes;
};
/*
* UNPACK_BY_LENGTH_FIELD
*
* package_len = head_len + body_len + length_adjustment
*
* if (length_field_coding == ENCODE_BY_VARINT) head_len = body_offset + varint_bytes - length_field_bytes;
* else head_len = body_offset;
*
* length_field stores body length, exclude head length,
* if length_field = head_len + body_len, then length_adjustment should be set to -head_len.
*
*/
struct {
unsigned short body_offset; // Equal to head length usually
unsigned short length_field_offset;
unsigned short length_field_bytes;
short length_adjustment;
unpack_coding_e length_field_coding;
};
};
#ifdef __cplusplus
unpack_setting_s() {
// Recommended setting:
// head = flags:1byte + length:4bytes = 5bytes
mode = UNPACK_BY_LENGTH_FIELD;
package_max_length = DEFAULT_PACKAGE_MAX_LENGTH;
fixed_length = 0;
delimiter_bytes = 0;
body_offset = 5;
length_field_offset = 1;
length_field_bytes = 4;
length_field_coding = ENCODE_BY_BIG_ENDIAN;
length_adjustment = 0;
}
#endif
} unpack_setting_t;
/*
* @see examples/jsonrpc examples/protorpc
*
* NOTE: unpack_setting_t of multiple IOs of the same function also are same,
* so only the pointer of unpack_setting_t is stored in hio_t,
* the life time of unpack_setting_t shoud be guaranteed by caller.
*/
HV_EXPORT void hio_set_unpack(hio_t* io, unpack_setting_t* setting);
HV_EXPORT void hio_unset_unpack(hio_t* io);
// unpack examples
/*
unpack_setting_t ftp_unpack_setting;
memset(&ftp_unpack_setting, 0, sizeof(unpack_setting_t));
ftp_unpack_setting.package_max_length = DEFAULT_PACKAGE_MAX_LENGTH;
ftp_unpack_setting.mode = UNPACK_BY_DELIMITER;
ftp_unpack_setting.delimiter[0] = '\r';
ftp_unpack_setting.delimiter[1] = '\n';
ftp_unpack_setting.delimiter_bytes = 2;
unpack_setting_t mqtt_unpack_setting = {
.mode = UNPACK_BY_LENGTH_FIELD,
.package_max_length = DEFAULT_PACKAGE_MAX_LENGTH,
.body_offset = 2,
.length_field_offset = 1,
.length_field_bytes = 1,
.length_field_coding = ENCODE_BY_VARINT,
};
unpack_setting_t grpc_unpack_setting = {
.mode = UNPACK_BY_LENGTH_FIELD,
.package_max_length = DEFAULT_PACKAGE_MAX_LENGTH,
.body_offset = 5,
.length_field_offset = 1,
.length_field_bytes = 4,
.length_field_coding = ENCODE_BY_BIG_ENDIAN,
};
*/
//-----------------reconnect----------------------------------------
#define DEFAULT_RECONNECT_MIN_DELAY 1000 // ms
#define DEFAULT_RECONNECT_MAX_DELAY 60000 // ms
#define DEFAULT_RECONNECT_DELAY_POLICY 2 // exponential
#define DEFAULT_RECONNECT_MAX_RETRY_CNT INFINITE
typedef struct reconn_setting_s {
uint32_t min_delay; // ms
uint32_t max_delay; // ms
uint32_t cur_delay; // ms
/*
* @delay_policy
* 0: fixed
* min_delay=3s => 3,3,3...
* 1: linear
* min_delay=3s max_delay=10s => 3,6,9,10,10...
* other: exponential
* min_delay=3s max_delay=60s delay_policy=2 => 3,6,12,24,48,60,60...
*/
uint32_t delay_policy;
uint32_t max_retry_cnt;
uint32_t cur_retry_cnt;
#ifdef __cplusplus
reconn_setting_s() {
min_delay = DEFAULT_RECONNECT_MIN_DELAY;
max_delay = DEFAULT_RECONNECT_MAX_DELAY;
cur_delay = 0;
// 1,2,4,8,16,32,60,60...
delay_policy = DEFAULT_RECONNECT_DELAY_POLICY;
max_retry_cnt = DEFAULT_RECONNECT_MAX_RETRY_CNT;
cur_retry_cnt = 0;
}
#endif
} reconn_setting_t;
HV_INLINE void reconn_setting_init(reconn_setting_t* reconn) {
reconn->min_delay = DEFAULT_RECONNECT_MIN_DELAY;
reconn->max_delay = DEFAULT_RECONNECT_MAX_DELAY;
reconn->cur_delay = 0;
// 1,2,4,8,16,32,60,60...
reconn->delay_policy = DEFAULT_RECONNECT_DELAY_POLICY;
reconn->max_retry_cnt = DEFAULT_RECONNECT_MAX_RETRY_CNT;
reconn->cur_retry_cnt = 0;
}
HV_INLINE void reconn_setting_reset(reconn_setting_t* reconn) {
reconn->cur_delay = 0;
reconn->cur_retry_cnt = 0;
}
HV_INLINE bool reconn_setting_can_retry(reconn_setting_t* reconn) {
++reconn->cur_retry_cnt;
return reconn->max_retry_cnt == INFINITE ||
reconn->cur_retry_cnt < reconn->max_retry_cnt;
}
HV_INLINE uint32_t reconn_setting_calc_delay(reconn_setting_t* reconn) {
if (reconn->delay_policy == 0) {
// fixed
reconn->cur_delay = reconn->min_delay;
} else if (reconn->delay_policy == 1) {
// linear
reconn->cur_delay += reconn->min_delay;
} else {
// exponential
reconn->cur_delay *= reconn->delay_policy;
}
reconn->cur_delay = MAX(reconn->cur_delay, reconn->min_delay);
reconn->cur_delay = MIN(reconn->cur_delay, reconn->max_delay);
return reconn->cur_delay;
}
//-----------------LoadBalance-------------------------------------
typedef enum {
LB_RoundRobin,
LB_Random,
LB_LeastConnections,
LB_IpHash,
LB_UrlHash,
} load_balance_e;
//-----------------rudp---------------------------------------------
#if WITH_KCP
#define WITH_RUDP 1
#endif
#if WITH_RUDP
// NOTE: hio_close_rudp is thread-safe.
HV_EXPORT int hio_close_rudp(hio_t* io, struct sockaddr* peeraddr DEFAULT(NULL));
#endif
#if WITH_KCP
typedef struct kcp_setting_s {
// ikcp_create(conv, ...)
unsigned int conv;
// ikcp_nodelay(kcp, nodelay, interval, fastresend, nocwnd)
int nodelay;
int interval;
int fastresend;
int nocwnd;
// ikcp_wndsize(kcp, sndwnd, rcvwnd)
int sndwnd;
int rcvwnd;
// ikcp_setmtu(kcp, mtu)
int mtu;
// ikcp_update
int update_interval;
#ifdef __cplusplus
kcp_setting_s() {
conv = 0x11223344;
// normal mode
nodelay = 0;
interval = 40;
fastresend = 0;
nocwnd = 0;
// fast mode
// nodelay = 1;
// interval = 10;
// fastresend = 2;
// nocwnd = 1;
sndwnd = 0;
rcvwnd = 0;
mtu = 1400;
update_interval = 10; // ms
}
#endif
} kcp_setting_t;
HV_INLINE void kcp_setting_init_with_normal_mode(kcp_setting_t* setting) {
memset(setting, 0, sizeof(kcp_setting_t));
setting->nodelay = 0;
setting->interval = 40;
setting->fastresend = 0;
setting->nocwnd = 0;
}
HV_INLINE void kcp_setting_init_with_fast_mode(kcp_setting_t* setting) {
memset(setting, 0, sizeof(kcp_setting_t));
setting->nodelay = 0;
setting->interval = 30;
setting->fastresend = 2;
setting->nocwnd = 1;
}
HV_INLINE void kcp_setting_init_with_fast2_mode(kcp_setting_t* setting) {
memset(setting, 0, sizeof(kcp_setting_t));
setting->nodelay = 1;
setting->interval = 20;
setting->fastresend = 2;
setting->nocwnd = 1;
}
HV_INLINE void kcp_setting_init_with_fast3_mode(kcp_setting_t* setting) {
memset(setting, 0, sizeof(kcp_setting_t));
setting->nodelay = 1;
setting->interval = 10;
setting->fastresend = 2;
setting->nocwnd = 1;
}
// @see examples/udp_echo_server.c => #define TEST_KCP 1
HV_EXPORT int hio_set_kcp(hio_t* io, kcp_setting_t* setting DEFAULT(NULL));
#endif
END_EXTERN_C
#endif // HV_LOOP_H_

View File

@ -0,0 +1,119 @@
#ifndef HV_MAIN_H_
#define HV_MAIN_H_
#include "hexport.h"
#include "hplatform.h"
#include "hdef.h"
#include "hproc.h"
#ifdef _MSC_VER
#pragma comment(lib, "winmm.lib") // for timeSetEvent
#endif
BEGIN_EXTERN_C
typedef struct main_ctx_s {
char run_dir[MAX_PATH];
char program_name[MAX_PATH];
char confile[MAX_PATH]; // default etc/${program}.conf
char pidfile[MAX_PATH]; // default logs/${program}.pid
char logfile[MAX_PATH]; // default logs/${program}.log
pid_t pid; // getpid
pid_t oldpid; // getpid_from_pidfile
// arg
int argc;
int arg_len;
char** os_argv;
char** save_argv;
char* cmdline;
// parsed arg
int arg_kv_size;
char** arg_kv;
int arg_list_size;
char** arg_list;
// env
int envc;
int env_len;
char** os_envp;
char** save_envp;
// signals
procedure_t reload_fn;
void* reload_userdata;
// master workers model
int worker_processes;
int worker_threads;
procedure_t worker_fn;
void* worker_userdata;
proc_ctx_t* proc_ctxs;
} main_ctx_t;
// arg_type
#define NO_ARGUMENT 0
#define REQUIRED_ARGUMENT 1
#define OPTIONAL_ARGUMENT 2
// option define
#define OPTION_PREFIX '-'
#define OPTION_DELIM '='
#define OPTION_ENABLE "1"
#define OPTION_DISABLE "0"
typedef struct option_s {
char short_opt;
const char* long_opt;
int arg_type;
const char* description;
} option_t;
HV_EXPORT int main_ctx_init(int argc, char** argv);
HV_EXPORT void main_ctx_free(void);
// ls -a -l
// ls -al
// watch -n 10 ls
// watch -n10 ls
HV_EXPORT int parse_opt(int argc, char** argv, const char* opt);
// gcc -g -Wall -O3 -std=cpp main.c
HV_EXPORT int parse_opt_long(int argc, char** argv, const option_t* long_options, int opt_size);
HV_EXPORT int dump_opt_long(const option_t* long_options, int opt_size, char* out_str, int out_size);
HV_EXPORT const char* get_arg(const char* key);
HV_EXPORT const char* get_env(const char* key);
#if defined(OS_UNIX) && !HAVE_SETPROCTITLE
HV_EXPORT void setproctitle(const char* fmt, ...);
#endif
// pidfile
HV_EXPORT int create_pidfile();
HV_EXPORT void delete_pidfile(void);
HV_EXPORT pid_t getpid_from_pidfile();
// signal=[start,stop,restart,status,reload]
HV_EXPORT int signal_init(procedure_t reload_fn DEFAULT(NULL), void* reload_userdata DEFAULT(NULL));
HV_EXPORT void signal_handle(const char* signal);
#ifdef OS_UNIX
// we use SIGTERM to quit process, SIGUSR1 to reload confile
#define SIGNAL_TERMINATE SIGTERM
#define SIGNAL_RELOAD SIGUSR1
void signal_handler(int signo);
#endif
// global var
#define DEFAULT_WORKER_PROCESSES 4
#define MAXNUM_WORKER_PROCESSES 256
HV_EXPORT extern main_ctx_t g_main_ctx;
// master-workers processes
HV_EXPORT int master_workers_run(
procedure_t worker_fn,
void* worker_userdata DEFAULT(NULL),
int worker_processes DEFAULT(DEFAULT_WORKER_PROCESSES),
int worker_threads DEFAULT(0),
bool wait DEFAULT(true));
END_EXTERN_C
#endif // HV_MAIN_H_

View File

@ -0,0 +1,55 @@
#ifndef HV_MAP_H_
#define HV_MAP_H_
#include "hconfig.h"
#include <map>
#include <string>
// MultiMap
namespace std {
/*
int main() {
std::MultiMap<std::string, std::string> kvs;
kvs["name"] = "hw";
kvs["filename"] = "1.jpg";
kvs["filename"] = "2.jpg";
//kvs.insert(std::pair<std::string,std::string>("name", "hw"));
//kvs.insert(std::pair<std::string,std::string>("filename", "1.jpg"));
//kvs.insert(std::pair<std::string,std::string>("filename", "2.jpg"));
for (auto& pair : kvs) {
printf("%s:%s\n", pair.first.c_str(), pair.second.c_str());
}
auto iter = kvs.find("filename");
if (iter != kvs.end()) {
for (int i = 0; i < kvs.count("filename"); ++i, ++iter) {
printf("%s:%s\n", iter->first.c_str(), iter->second.c_str());
}
}
return 0;
}
*/
template<typename Key,typename Value>
class MultiMap : public multimap<Key, Value> {
public:
Value& operator[](Key key) {
auto iter = this->insert(std::pair<Key,Value>(key,Value()));
return (*iter).second;
}
};
}
#ifdef USE_MULTIMAP
#define HV_MAP std::MultiMap
#else
#define HV_MAP std::map
#endif
// KeyValue
namespace hv {
typedef std::map<std::string, std::string> keyval_t;
typedef std::MultiMap<std::string, std::string> multi_keyval_t;
typedef HV_MAP<std::string, std::string> KeyValue;
}
#endif // HV_MAP_H_

View File

@ -0,0 +1,68 @@
#ifndef HV_MATH_H_
#define HV_MATH_H_
#include <math.h>
static inline unsigned long floor2e(unsigned long num) {
unsigned long n = num;
int e = 0;
while (n>>=1) ++e;
unsigned long ret = 1;
while (e--) ret<<=1;
return ret;
}
static inline unsigned long ceil2e(unsigned long num) {
// 2**0 = 1
if (num == 0 || num == 1) return 1;
unsigned long n = num - 1;
int e = 1;
while (n>>=1) ++e;
unsigned long ret = 1;
while (e--) ret<<=1;
return ret;
}
// varint little-endian
// MSB
static inline int varint_encode(long long value, unsigned char* buf) {
unsigned char ch;
unsigned char *p = buf;
int bytes = 0;
do {
ch = value & 0x7F;
value >>= 7;
*p++ = value == 0 ? ch : (ch | 0x80);
++bytes;
} while (value);
return bytes;
}
// @param[IN|OUT] len: in=>buflen, out=>varint bytesize
static inline long long varint_decode(const unsigned char* buf, int* len) {
long long ret = 0;
int bytes = 0, bits = 0;
const unsigned char *p = buf;
do {
if (len && *len && bytes == *len) {
// Not enough length
*len = 0;
return 0;
}
ret |= ((long long)(*p & 0x7F)) << bits;
++bytes;
if ((*p & 0x80) == 0) {
// Found end
if (len) *len = bytes;
return ret;
}
++p;
bits += 7;
} while(bytes < 10);
// Not found end
if (len) *len = -1;
return ret;
}
#endif // HV_MATH_H_

View File

@ -0,0 +1,268 @@
#ifndef HV_MUTEX_H_
#define HV_MUTEX_H_
#include "hexport.h"
#include "hplatform.h"
#include "htime.h"
BEGIN_EXTERN_C
#ifdef OS_WIN
#define hmutex_t CRITICAL_SECTION
#define hmutex_init InitializeCriticalSection
#define hmutex_destroy DeleteCriticalSection
#define hmutex_lock EnterCriticalSection
#define hmutex_unlock LeaveCriticalSection
#define hrecursive_mutex_t CRITICAL_SECTION
#define hrecursive_mutex_init InitializeCriticalSection
#define hrecursive_mutex_destroy DeleteCriticalSection
#define hrecursive_mutex_lock EnterCriticalSection
#define hrecursive_mutex_unlock LeaveCriticalSection
#define HSPINLOCK_COUNT -1
#define hspinlock_t CRITICAL_SECTION
#define hspinlock_init(pspin) InitializeCriticalSectionAndSpinCount(pspin, HSPINLOCK_COUNT)
#define hspinlock_destroy DeleteCriticalSection
#define hspinlock_lock EnterCriticalSection
#define hspinlock_unlock LeaveCriticalSection
#define hrwlock_t SRWLOCK
#define hrwlock_init InitializeSRWLock
#define hrwlock_destroy(plock)
#define hrwlock_rdlock AcquireSRWLockShared
#define hrwlock_rdunlock ReleaseSRWLockShared
#define hrwlock_wrlock AcquireSRWLockExclusive
#define hrwlock_wrunlock ReleaseSRWLockExclusive
#define htimed_mutex_t HANDLE
#define htimed_mutex_init(pmutex) *(pmutex) = CreateMutex(NULL, FALSE, NULL)
#define htimed_mutex_destroy(pmutex) CloseHandle(*(pmutex))
#define htimed_mutex_lock(pmutex) WaitForSingleObject(*(pmutex), INFINITE)
#define htimed_mutex_unlock(pmutex) ReleaseMutex(*(pmutex))
// true: WAIT_OBJECT_0
// false: WAIT_OBJECT_TIMEOUT
#define htimed_mutex_lock_for(pmutex, ms) ( WaitForSingleObject(*(pmutex), ms) == WAIT_OBJECT_0 )
#define hcondvar_t CONDITION_VARIABLE
#define hcondvar_init InitializeConditionVariable
#define hcondvar_destroy(pcond)
#define hcondvar_wait(pcond, pmutex) SleepConditionVariableCS(pcond, pmutex, INFINITE)
#define hcondvar_wait_for(pcond, pmutex, ms) SleepConditionVariableCS(pcond, pmutex, ms)
#define hcondvar_signal WakeConditionVariable
#define hcondvar_broadcast WakeAllConditionVariable
#define honce_t INIT_ONCE
#define HONCE_INIT INIT_ONCE_STATIC_INIT
typedef void (*honce_fn)();
static inline BOOL WINAPI s_once_func(INIT_ONCE* once, PVOID arg, PVOID* _) {
honce_fn fn = (honce_fn)arg;
fn();
return TRUE;
}
static inline void honce(honce_t* once, honce_fn fn) {
PVOID dummy = NULL;
InitOnceExecuteOnce(once, s_once_func, (PVOID)fn, &dummy);
}
#define hsem_t HANDLE
#define hsem_init(psem, value) *(psem) = CreateSemaphore(NULL, value, value+100000, NULL)
#define hsem_destroy(psem) CloseHandle(*(psem))
#define hsem_wait(psem) WaitForSingleObject(*(psem), INFINITE)
#define hsem_post(psem) ReleaseSemaphore(*(psem), 1, NULL)
// true: WAIT_OBJECT_0
// false: WAIT_OBJECT_TIMEOUT
#define hsem_wait_for(psem, ms) ( WaitForSingleObject(*(psem), ms) == WAIT_OBJECT_0 )
#else
#define hmutex_t pthread_mutex_t
#define hmutex_init(pmutex) pthread_mutex_init(pmutex, NULL)
#define hmutex_destroy pthread_mutex_destroy
#define hmutex_lock pthread_mutex_lock
#define hmutex_unlock pthread_mutex_unlock
#define hrecursive_mutex_t pthread_mutex_t
#define hrecursive_mutex_init(pmutex) \
do {\
pthread_mutexattr_t attr;\
pthread_mutexattr_init(&attr);\
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);\
pthread_mutex_init(pmutex, &attr);\
} while(0)
#define hrecursive_mutex_destroy pthread_mutex_destroy
#define hrecursive_mutex_lock pthread_mutex_lock
#define hrecursive_mutex_unlock pthread_mutex_unlock
#if HAVE_PTHREAD_SPIN_LOCK
#define hspinlock_t pthread_spinlock_t
#define hspinlock_init(pspin) pthread_spin_init(pspin, PTHREAD_PROCESS_PRIVATE)
#define hspinlock_destroy pthread_spin_destroy
#define hspinlock_lock pthread_spin_lock
#define hspinlock_unlock pthread_spin_unlock
#else
#define hspinlock_t pthread_mutex_t
#define hspinlock_init(pmutex) pthread_mutex_init(pmutex, NULL)
#define hspinlock_destroy pthread_mutex_destroy
#define hspinlock_lock pthread_mutex_lock
#define hspinlock_unlock pthread_mutex_unlock
#endif
#define hrwlock_t pthread_rwlock_t
#define hrwlock_init(prwlock) pthread_rwlock_init(prwlock, NULL)
#define hrwlock_destroy pthread_rwlock_destroy
#define hrwlock_rdlock pthread_rwlock_rdlock
#define hrwlock_rdunlock pthread_rwlock_unlock
#define hrwlock_wrlock pthread_rwlock_wrlock
#define hrwlock_wrunlock pthread_rwlock_unlock
#define htimed_mutex_t pthread_mutex_t
#define htimed_mutex_init(pmutex) pthread_mutex_init(pmutex, NULL)
#define htimed_mutex_destroy pthread_mutex_destroy
#define htimed_mutex_lock pthread_mutex_lock
#define htimed_mutex_unlock pthread_mutex_unlock
static inline void timespec_after(struct timespec* ts, unsigned int ms) {
struct timeval tv;
gettimeofday(&tv, NULL);
ts->tv_sec = tv.tv_sec + ms / 1000;
ts->tv_nsec = tv.tv_usec * 1000 + ms % 1000 * 1000000;
if (ts->tv_nsec >= 1000000000) {
ts->tv_nsec -= 1000000000;
ts->tv_sec += 1;
}
}
// true: OK
// false: ETIMEDOUT
static inline int htimed_mutex_lock_for(htimed_mutex_t* mutex, unsigned int ms) {
#if HAVE_PTHREAD_MUTEX_TIMEDLOCK
struct timespec ts;
timespec_after(&ts, ms);
return pthread_mutex_timedlock(mutex, &ts) != ETIMEDOUT;
#else
int ret = 0;
unsigned int end = gettick_ms() + ms;
while ((ret = pthread_mutex_trylock(mutex)) != 0) {
if (gettick_ms() >= end) {
break;
}
hv_msleep(1);
}
return ret == 0;
#endif
}
#define hcondvar_t pthread_cond_t
#define hcondvar_init(pcond) pthread_cond_init(pcond, NULL)
#define hcondvar_destroy pthread_cond_destroy
#define hcondvar_wait pthread_cond_wait
#define hcondvar_signal pthread_cond_signal
#define hcondvar_broadcast pthread_cond_broadcast
// true: OK
// false: ETIMEDOUT
static inline int hcondvar_wait_for(hcondvar_t* cond, hmutex_t* mutex, unsigned int ms) {
struct timespec ts;
timespec_after(&ts, ms);
return pthread_cond_timedwait(cond, mutex, &ts) != ETIMEDOUT;
}
#define honce_t pthread_once_t
#define HONCE_INIT PTHREAD_ONCE_INIT
#define honce pthread_once
#include <semaphore.h>
#define hsem_t sem_t
#define hsem_init(psem, value) sem_init(psem, 0, value)
#define hsem_destroy sem_destroy
#define hsem_wait sem_wait
#define hsem_post sem_post
// true: OK
// false: ETIMEDOUT
static inline int hsem_wait_for(hsem_t* sem, unsigned int ms) {
#if HAVE_SEM_TIMEDWAIT
struct timespec ts;
timespec_after(&ts, ms);
return sem_timedwait(sem, &ts) != ETIMEDOUT;
#else
int ret = 0;
unsigned int end = gettick_ms() + ms;
while ((ret = sem_trywait(sem)) != 0) {
if (gettick_ms() >= end) {
break;
}
hv_msleep(1);
}
return ret == 0;
#endif
}
#endif
END_EXTERN_C
#ifdef __cplusplus
#include <mutex>
#include <condition_variable>
// using std::mutex;
// NOTE: test std::timed_mutex incorrect in some platforms, use htimed_mutex_t
// using std::timed_mutex;
using std::condition_variable;
using std::lock_guard;
using std::unique_lock;
BEGIN_NAMESPACE_HV
class MutexLock {
public:
MutexLock() { hmutex_init(&_mutex); }
~MutexLock() { hmutex_destroy(&_mutex); }
void lock() { hmutex_lock(&_mutex); }
void unlock() { hmutex_unlock(&_mutex); }
protected:
hmutex_t _mutex;
};
class SpinLock {
public:
SpinLock() { hspinlock_init(&_spin); }
~SpinLock() { hspinlock_destroy(&_spin); }
void lock() { hspinlock_lock(&_spin); }
void unlock() { hspinlock_unlock(&_spin); }
protected:
hspinlock_t _spin;
};
class RWLock {
public:
RWLock() { hrwlock_init(&_rwlock); }
~RWLock() { hrwlock_destroy(&_rwlock); }
void rdlock() { hrwlock_rdlock(&_rwlock); }
void rdunlock() { hrwlock_rdunlock(&_rwlock); }
void wrlock() { hrwlock_wrlock(&_rwlock); }
void wrunlock() { hrwlock_wrunlock(&_rwlock); }
void lock() { rdlock(); }
void unlock() { rdunlock(); }
protected:
hrwlock_t _rwlock;
};
template<class T>
class LockGuard {
public:
LockGuard(T& t) : _lock(t) { _lock.lock(); }
~LockGuard() { _lock.unlock(); }
protected:
T& _lock;
};
END_NAMESPACE_HV
// same as java synchronized(lock) { ... }
#define synchronized(lock) for (std::lock_guard<std::mutex> _lock_(lock), *p = &_lock_; p != NULL; p = NULL)
#endif // __cplusplus
#endif // HV_MUTEX_H_

View File

@ -0,0 +1,183 @@
#ifndef HV_OBJECT_POOL_H_
#define HV_OBJECT_POOL_H_
/*
* @usage unittest/objectpool_test.cpp
*/
#include <list>
#include <memory>
#include <mutex>
#include <condition_variable>
#define DEFAULT_OBJECT_POOL_INIT_NUM 0
#define DEFAULT_OBJECT_POOL_MAX_NUM 4
#define DEFAULT_OBJECT_POOL_TIMEOUT 3000 // ms
template<class T>
class HObjectFactory {
public:
static T* create() {
return new T;
}
};
template<class T, class TFactory = HObjectFactory<T>>
class HObjectPool {
public:
HObjectPool(
int init_num = DEFAULT_OBJECT_POOL_INIT_NUM,
int max_num = DEFAULT_OBJECT_POOL_MAX_NUM,
int timeout = DEFAULT_OBJECT_POOL_TIMEOUT)
: _max_num(max_num)
, _timeout(timeout)
{
for (int i = 0; i < init_num; ++i) {
T* p = TFactory::create();
if (p) {
objects_.push_back(std::shared_ptr<T>(p));
}
}
_object_num = objects_.size();
}
~HObjectPool() {}
int ObjectNum() { return _object_num; }
int IdleNum() { return objects_.size(); }
int BorrowNum() { return ObjectNum() - IdleNum(); }
std::shared_ptr<T> TryBorrow() {
std::shared_ptr<T> pObj = NULL;
std::lock_guard<std::mutex> locker(mutex_);
if (!objects_.empty()) {
pObj = objects_.front();
objects_.pop_front();
}
return pObj;
}
std::shared_ptr<T> Borrow() {
std::shared_ptr<T> pObj = TryBorrow();
if (pObj) {
return pObj;
}
std::unique_lock<std::mutex> locker(mutex_);
if (_object_num < _max_num) {
++_object_num;
// NOTE: unlock to avoid TFactory::create block
mutex_.unlock();
T* p = TFactory::create();
mutex_.lock();
if (!p) --_object_num;
return std::shared_ptr<T>(p);
}
if (_timeout > 0) {
std::cv_status status = cond_.wait_for(locker, std::chrono::milliseconds(_timeout));
if (status == std::cv_status::timeout) {
return NULL;
}
if (!objects_.empty()) {
pObj = objects_.front();
objects_.pop_front();
return pObj;
}
else {
// WARN: No idle object
}
}
return pObj;
}
void Return(std::shared_ptr<T>& pObj) {
if (!pObj) return;
std::lock_guard<std::mutex> locker(mutex_);
objects_.push_back(pObj);
cond_.notify_one();
}
bool Add(std::shared_ptr<T>& pObj) {
std::lock_guard<std::mutex> locker(mutex_);
if (_object_num >= _max_num) {
return false;
}
objects_.push_back(pObj);
++_object_num;
cond_.notify_one();
return true;
}
bool Remove(std::shared_ptr<T>& pObj) {
std::lock_guard<std::mutex> locker(mutex_);
auto iter = objects_.begin();
while (iter != objects_.end()) {
if (*iter == pObj) {
iter = objects_.erase(iter);
--_object_num;
return true;
}
else {
++iter;
}
}
return false;
}
void Clear() {
std::lock_guard<std::mutex> locker(mutex_);
objects_.clear();
_object_num = 0;
}
int _object_num;
int _max_num;
int _timeout;
private:
std::list<std::shared_ptr<T>> objects_;
std::mutex mutex_;
std::condition_variable cond_;
};
template<class T, class TFactory = HObjectFactory<T>>
class HPoolObject {
public:
typedef HObjectPool<T, TFactory> PoolType;
HPoolObject(PoolType& pool) : pool_(pool)
{
sptr_ = pool_.Borrow();
}
~HPoolObject() {
if (sptr_) {
pool_.Return(sptr_);
}
}
HPoolObject(const HPoolObject<T>&) = delete;
HPoolObject<T>& operator=(const HPoolObject<T>&) = delete;
T* get() {
return sptr_.get();
}
operator bool() {
return sptr_.get() != NULL;
}
T* operator->() {
return sptr_.get();
}
T operator*() {
return *sptr_.get();
}
private:
PoolType& pool_;
std::shared_ptr<T> sptr_;
};
#endif // HV_OBJECT_POOL_H_

View File

@ -0,0 +1,28 @@
#ifndef HV_PATH_H_
#define HV_PATH_H_
#include <string> // for std::string
#include "hexport.h"
class HV_EXPORT HPath {
public:
static bool exists(const char* path);
static bool isdir(const char* path);
static bool isfile(const char* path);
static bool islink(const char* path);
// filepath = /mnt/share/image/test.jpg
// basename = test.jpg
// dirname = /mnt/share/image
// filename = test
// suffixname = jpg
static std::string basename(const std::string& filepath);
static std::string dirname(const std::string& filepath);
static std::string filename(const std::string& filepath);
static std::string suffixname(const std::string& filepath);
static std::string join(const std::string& dir, const std::string& filename);
};
#endif // HV_PATH_H_

View File

@ -0,0 +1,337 @@
#ifndef HV_PLATFORM_H_
#define HV_PLATFORM_H_
#include "hconfig.h"
// OS
#if defined(WIN64) || defined(_WIN64)
#define OS_WIN64
#define OS_WIN32
#elif defined(WIN32)|| defined(_WIN32)
#define OS_WIN32
#elif defined(ANDROID) || defined(__ANDROID__)
#define OS_ANDROID
#define OS_LINUX
#elif defined(linux) || defined(__linux) || defined(__linux__)
#define OS_LINUX
#elif defined(__APPLE__) && (defined(__GNUC__) || defined(__xlC__) || defined(__xlc__))
#include <TargetConditionals.h>
#if defined(TARGET_OS_MAC) && TARGET_OS_MAC
#define OS_MAC
#elif defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
#define OS_IOS
#endif
#define OS_DARWIN
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
#define OS_FREEBSD
#define OS_BSD
#elif defined(__NetBSD__)
#define OS_NETBSD
#define OS_BSD
#elif defined(__OpenBSD__)
#define OS_OPENBSD
#define OS_BSD
#elif defined(sun) || defined(__sun) || defined(__sun__)
#define OS_SOLARIS
#else
#warning "Untested operating system platform!"
#endif
#if defined(OS_WIN32) || defined(OS_WIN64)
#undef OS_UNIX
#define OS_WIN
#else
#undef OS_WIN
#define OS_UNIX
#endif
// ARCH
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64)
#define ARCH_X64
#define ARCH_X86_64
#elif defined(__i386) || defined(__i386__) || defined(_M_IX86)
#define ARCH_X86
#define ARCH_X86_32
#elif defined(__aarch64__) || defined(__ARM64__) || defined(_M_ARM64)
#define ARCH_ARM64
#elif defined(__arm__) || defined(_M_ARM)
#define ARCH_ARM
#elif defined(__mips64__)
#define ARCH_MIPS64
#elif defined(__mips__)
#define ARCH_MIPS
#else
#warning "Untested hardware architecture!"
#endif
// COMPILER
#if defined (_MSC_VER)
#define COMPILER_MSVC
#if (_MSC_VER < 1200) // Visual C++ 6.0
#define MSVS_VERSION 1998
#define MSVC_VERSION 60
#elif (_MSC_VER >= 1200) && (_MSC_VER < 1300) // Visual Studio 2002, MSVC++ 7.0
#define MSVS_VERSION 2002
#define MSVC_VERSION 70
#elif (_MSC_VER >= 1300) && (_MSC_VER < 1400) // Visual Studio 2003, MSVC++ 7.1
#define MSVS_VERSION 2003
#define MSVC_VERSION 71
#elif (_MSC_VER >= 1400) && (_MSC_VER < 1500) // Visual Studio 2005, MSVC++ 8.0
#define MSVS_VERSION 2005
#define MSVC_VERSION 80
#elif (_MSC_VER >= 1500) && (_MSC_VER < 1600) // Visual Studio 2008, MSVC++ 9.0
#define MSVS_VERSION 2008
#define MSVC_VERSION 90
#elif (_MSC_VER >= 1600) && (_MSC_VER < 1700) // Visual Studio 2010, MSVC++ 10.0
#define MSVS_VERSION 2010
#define MSVC_VERSION 100
#elif (_MSC_VER >= 1700) && (_MSC_VER < 1800) // Visual Studio 2012, MSVC++ 11.0
#define MSVS_VERSION 2012
#define MSVC_VERSION 110
#elif (_MSC_VER >= 1800) && (_MSC_VER < 1900) // Visual Studio 2013, MSVC++ 12.0
#define MSVS_VERSION 2013
#define MSVC_VERSION 120
#elif (_MSC_VER >= 1900) && (_MSC_VER < 1910) // Visual Studio 2015, MSVC++ 14.0
#define MSVS_VERSION 2015
#define MSVC_VERSION 140
#elif (_MSC_VER >= 1910) && (_MSC_VER < 1920) // Visual Studio 2017, MSVC++ 15.0
#define MSVS_VERSION 2017
#define MSVC_VERSION 150
#elif (_MSC_VER >= 1920) && (_MSC_VER < 2000) // Visual Studio 2019, MSVC++ 16.0
#define MSVS_VERSION 2019
#define MSVC_VERSION 160
#endif
#undef HAVE_STDATOMIC_H
#define HAVE_STDATOMIC_H 0
#undef HAVE_SYS_TIME_H
#define HAVE_SYS_TIME_H 0
#undef HAVE_PTHREAD_H
#define HAVE_PTHREAD_H 0
#pragma warning (disable: 4018) // signed/unsigned comparison
#pragma warning (disable: 4100) // unused param
#pragma warning (disable: 4102) // unreferenced label
#pragma warning (disable: 4244) // conversion loss of data
#pragma warning (disable: 4267) // size_t => int
#pragma warning (disable: 4819) // Unicode
#pragma warning (disable: 4996) // _CRT_SECURE_NO_WARNINGS
#elif defined(__GNUC__)
#define COMPILER_GCC
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#elif defined(__clang__)
#define COMPILER_CLANG
#elif defined(__MINGW32__) || defined(__MINGW64__)
#define COMPILER_MINGW
#elif defined(__MSYS__)
#define COMPILER_MSYS
#elif defined(__CYGWIN__)
#define COMPILER_CYGWIN
#else
#warning "Untested compiler!"
#endif
// headers
#ifdef OS_WIN
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#elif _WIN32_WINNT < 0x0600
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef _CRT_NONSTDC_NO_DEPRECATE
#define _CRT_NONSTDC_NO_DEPRECATE
#endif
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#endif
#include <winsock2.h>
#include <ws2tcpip.h> // for inet_pton,inet_ntop
#include <windows.h>
#include <process.h> // for getpid,exec
#include <direct.h> // for mkdir,rmdir,chdir,getcwd
#include <io.h> // for open,close,read,write,lseek,tell
#define hv_sleep(s) Sleep((s) * 1000)
#define hv_msleep(ms) Sleep(ms)
#define hv_usleep(us) Sleep((us) / 1000)
#define hv_delay(ms) hv_msleep(ms)
#define hv_mkdir(dir) mkdir(dir)
// access
#ifndef F_OK
#define F_OK 0 /* test for existence of file */
#endif
#ifndef X_OK
#define X_OK (1<<0) /* test for execute or search permission */
#endif
#ifndef W_OK
#define W_OK (1<<1) /* test for write permission */
#endif
#ifndef R_OK
#define R_OK (1<<2) /* test for read permission */
#endif
// stat
#ifndef S_ISREG
#define S_ISREG(st_mode) (((st_mode) & S_IFMT) == S_IFREG)
#endif
#ifndef S_ISDIR
#define S_ISDIR(st_mode) (((st_mode) & S_IFMT) == S_IFDIR)
#endif
#else
#include <unistd.h>
#include <dirent.h> // for mkdir,rmdir,chdir,getcwd
// socket
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netdb.h> // for gethostbyname
#define hv_sleep(s) sleep(s)
#define hv_msleep(ms) usleep((ms) * 1000)
#define hv_usleep(us) usleep(us)
#define hv_delay(ms) hv_msleep(ms)
#define hv_mkdir(dir) mkdir(dir, 0777)
#endif
#ifdef _MSC_VER
typedef int pid_t;
typedef int gid_t;
typedef int uid_t;
#define strcasecmp stricmp
#define strncasecmp strnicmp
#else
typedef int BOOL;
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef void* HANDLE;
#include <strings.h>
#define stricmp strcasecmp
#define strnicmp strncasecmp
#endif
// ENDIAN
#ifndef BIG_ENDIAN
#define BIG_ENDIAN 4321
#endif
#ifndef LITTLE_ENDIAN
#define LITTLE_ENDIAN 1234
#endif
#ifndef NET_ENDIAN
#define NET_ENDIAN BIG_ENDIAN
#endif
// BYTE_ORDER
#ifndef BYTE_ORDER
#if defined(__BYTE_ORDER)
#define BYTE_ORDER __BYTE_ORDER
#elif defined(__BYTE_ORDER__)
#define BYTE_ORDER __BYTE_ORDER__
#elif defined(ARCH_X86) || defined(ARCH_X86_64) || \
defined(__ARMEL__) || defined(__AARCH64EL__) || \
defined(__MIPSEL) || defined(__MIPS64EL)
#define BYTE_ORDER LITTLE_ENDIAN
#elif defined(__ARMEB__) || defined(__AARCH64EB__) || \
defined(__MIPSEB) || defined(__MIPS64EB)
#define BYTE_ORDER BIG_ENDIAN
#elif defined(OS_WIN)
#define BYTE_ORDER LITTLE_ENDIAN
#else
#warning "Unknown byte order!"
#endif
#endif
// ANSI C
#include <assert.h>
#include <stddef.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <float.h>
#include <limits.h>
#include <errno.h>
#include <time.h>
#include <math.h>
#include <signal.h>
#ifndef __cplusplus
#if HAVE_STDBOOL_H
#include <stdbool.h>
#else
#ifndef bool
#define bool char
#endif
#ifndef true
#define true 1
#endif
#ifndef false
#define false 0
#endif
#endif
#endif
#if HAVE_STDINT_H
#include <stdint.h>
#elif defined(_MSC_VER) && _MSC_VER < 1700
typedef __int8 int8_t;
typedef __int16 int16_t;
typedef __int32 int32_t;
typedef __int64 int64_t;
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
#endif
typedef float float32_t;
typedef double float64_t;
typedef int (*method_t)(void* userdata);
typedef void (*procedure_t)(void* userdata);
#if HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#if HAVE_SYS_TIME_H
#include <sys/time.h> // for gettimeofday
#endif
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#if HAVE_PTHREAD_H
#include <pthread.h>
#endif
#endif // HV_PLATFORM_H_

View File

@ -0,0 +1,69 @@
#ifndef HV_PROC_H_
#define HV_PROC_H_
#include "hplatform.h"
typedef struct proc_ctx_s {
pid_t pid; // tid in Windows
time_t start_time;
int spawn_cnt;
procedure_t init;
void* init_userdata;
procedure_t proc;
void* proc_userdata;
procedure_t exit;
void* exit_userdata;
} proc_ctx_t;
static inline void hproc_run(proc_ctx_t* ctx) {
if (ctx->init) {
ctx->init(ctx->init_userdata);
}
if (ctx->proc) {
ctx->proc(ctx->proc_userdata);
}
if (ctx->exit) {
ctx->exit(ctx->exit_userdata);
}
}
#ifdef OS_UNIX
// unix use multi-processes
static inline int hproc_spawn(proc_ctx_t* ctx) {
++ctx->spawn_cnt;
ctx->start_time = time(NULL);
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return -1;
} else if (pid == 0) {
// child process
ctx->pid = getpid();
hproc_run(ctx);
exit(0);
} else if (pid > 0) {
// parent process
ctx->pid = pid;
}
return pid;
}
#elif defined(OS_WIN)
// win32 use multi-threads
static void win_thread(void* userdata) {
proc_ctx_t* ctx = (proc_ctx_t*)userdata;
ctx->pid = GetCurrentThreadId(); // tid in Windows
hproc_run(ctx);
}
static inline int hproc_spawn(proc_ctx_t* ctx) {
++ctx->spawn_cnt;
ctx->start_time = time(NULL);
HANDLE h = (HANDLE)_beginthread(win_thread, 0, ctx);
if (h == NULL) {
return -1;
}
ctx->pid = GetThreadId(h); // tid in Windows
return ctx->pid;
}
#endif
#endif // HV_PROC_H_

View File

@ -0,0 +1,79 @@
#ifndef HV_SCOPE_H_
#define HV_SCOPE_H_
#include <functional>
typedef std::function<void()> Function;
#include "hdef.h"
// same as golang defer
class Defer {
public:
Defer(Function&& fn) : _fn(std::move(fn)) {}
~Defer() { if(_fn) _fn();}
private:
Function _fn;
};
#define defer(code) Defer STRINGCAT(_defer_, __LINE__)([&](){code});
class ScopeCleanup {
public:
template<typename Fn, typename... Args>
ScopeCleanup(Fn&& fn, Args&&... args) {
_cleanup = std::bind(std::forward<Fn>(fn), std::forward<Args>(args)...);
}
~ScopeCleanup() {
_cleanup();
}
private:
Function _cleanup;
};
template<typename T>
class ScopeFree {
public:
ScopeFree(T* p) : _p(p) {}
~ScopeFree() {SAFE_FREE(_p);}
private:
T* _p;
};
template<typename T>
class ScopeDelete {
public:
ScopeDelete(T* p) : _p(p) {}
~ScopeDelete() {SAFE_DELETE(_p);}
private:
T* _p;
};
template<typename T>
class ScopeDeleteArray {
public:
ScopeDeleteArray(T* p) : _p(p) {}
~ScopeDeleteArray() {SAFE_DELETE_ARRAY(_p);}
private:
T* _p;
};
template<typename T>
class ScopeRelease {
public:
ScopeRelease(T* p) : _p(p) {}
~ScopeRelease() {SAFE_RELEASE(_p);}
private:
T* _p;
};
template<typename T>
class ScopeLock {
public:
ScopeLock(T& mutex) : _mutex(mutex) {_mutex.lock();}
~ScopeLock() {_mutex.unlock();}
private:
T& _mutex;
};
#endif // HV_SCOPE_H_

View File

@ -0,0 +1,287 @@
#ifndef HV_SOCKET_H_
#define HV_SOCKET_H_
#include "hexport.h"
#include "hplatform.h"
#ifdef ENABLE_UDS
#ifdef OS_WIN
#include <afunix.h> // import struct sockaddr_un
#else
#include <sys/un.h> // import struct sockaddr_un
#endif
#endif
#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib")
#endif
#define LOCALHOST "127.0.0.1"
#define ANYADDR "0.0.0.0"
BEGIN_EXTERN_C
HV_INLINE int socket_errno() {
#ifdef OS_WIN
return WSAGetLastError();
#else
return errno;
#endif
}
HV_EXPORT const char* socket_strerror(int err);
#ifdef OS_WIN
typedef SOCKET hsocket_t;
typedef int socklen_t;
void WSAInit();
void WSADeinit();
HV_INLINE int blocking(int sockfd) {
unsigned long nb = 0;
return ioctlsocket(sockfd, FIONBIO, &nb);
}
HV_INLINE int nonblocking(int sockfd) {
unsigned long nb = 1;
return ioctlsocket(sockfd, FIONBIO, &nb);
}
#undef EAGAIN
#define EAGAIN WSAEWOULDBLOCK
#undef EINPROGRESS
#define EINPROGRESS WSAEINPROGRESS
#undef EINTR
#define EINTR WSAEINTR
#undef ENOTSOCK
#define ENOTSOCK WSAENOTSOCK
#undef EMSGSIZE
#define EMSGSIZE WSAEMSGSIZE
#else
typedef int hsocket_t;
#ifndef SOCKET
#define SOCKET int
#endif
#ifndef INVALID_SOCKET
#define INVALID_SOCKET -1
#endif
HV_INLINE int blocking(int s) {
return fcntl(s, F_SETFL, fcntl(s, F_GETFL) & ~O_NONBLOCK);
}
HV_INLINE int nonblocking(int s) {
return fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);
}
#ifndef closesocket
HV_INLINE int closesocket(int sockfd) {
return close(sockfd);
}
#endif
#endif
#ifndef SAFE_CLOSESOCKET
#define SAFE_CLOSESOCKET(fd) do {if ((fd) >= 0) {closesocket(fd); (fd) = -1;}} while(0)
#endif
//-----------------------------sockaddr_u----------------------------------------------
typedef union {
struct sockaddr sa;
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
#ifdef ENABLE_UDS
struct sockaddr_un sun;
#endif
} sockaddr_u;
HV_EXPORT bool is_ipv4(const char* host);
HV_EXPORT bool is_ipv6(const char* host);
HV_INLINE bool is_ipaddr(const char* host) {
return is_ipv4(host) || is_ipv6(host);
}
// @param host: domain or ip
// @retval 0:succeed
HV_EXPORT int ResolveAddr(const char* host, sockaddr_u* addr);
HV_EXPORT const char* sockaddr_ip(sockaddr_u* addr, char *ip, int len);
HV_EXPORT uint16_t sockaddr_port(sockaddr_u* addr);
HV_EXPORT int sockaddr_set_ip(sockaddr_u* addr, const char* host);
HV_EXPORT void sockaddr_set_port(sockaddr_u* addr, int port);
HV_EXPORT int sockaddr_set_ipport(sockaddr_u* addr, const char* host, int port);
HV_EXPORT socklen_t sockaddr_len(sockaddr_u* addr);
HV_EXPORT const char* sockaddr_str(sockaddr_u* addr, char* buf, int len);
//#define INET_ADDRSTRLEN 16
//#define INET6_ADDRSTRLEN 46
#ifdef ENABLE_UDS
#define SOCKADDR_STRLEN sizeof(((struct sockaddr_un*)(NULL))->sun_path)
HV_INLINE void sockaddr_set_path(sockaddr_u* addr, const char* path) {
addr->sa.sa_family = AF_UNIX;
strncpy(addr->sun.sun_path, path, sizeof(addr->sun.sun_path));
}
#else
#define SOCKADDR_STRLEN 64 // ipv4:port | [ipv6]:port
#endif
HV_INLINE void sockaddr_print(sockaddr_u* addr) {
char buf[SOCKADDR_STRLEN] = {0};
sockaddr_str(addr, buf, sizeof(buf));
puts(buf);
}
#define SOCKADDR_LEN(addr) sockaddr_len((sockaddr_u*)addr)
#define SOCKADDR_STR(addr, buf) sockaddr_str((sockaddr_u*)addr, buf, sizeof(buf))
#define SOCKADDR_PRINT(addr) sockaddr_print((sockaddr_u*)addr)
//=====================================================================================
// socket -> setsockopt -> bind
// @param type: SOCK_STREAM(tcp) SOCK_DGRAM(udp)
// @return sockfd
HV_EXPORT int Bind(int port, const char* host DEFAULT(ANYADDR), int type DEFAULT(SOCK_STREAM));
// Bind -> listen
// @return listenfd
HV_EXPORT int Listen(int port, const char* host DEFAULT(ANYADDR));
// @return connfd
// ResolveAddr -> socket -> nonblocking -> connect
HV_EXPORT int Connect(const char* host, int port, int nonblock DEFAULT(0));
// Connect(host, port, 1)
HV_EXPORT int ConnectNonblock(const char* host, int port);
// Connect(host, port, 1) -> select -> blocking
#define DEFAULT_CONNECT_TIMEOUT 10000 // ms
HV_EXPORT int ConnectTimeout(const char* host, int port, int ms DEFAULT(DEFAULT_CONNECT_TIMEOUT));
#ifdef ENABLE_UDS
HV_EXPORT int BindUnix(const char* path, int type DEFAULT(SOCK_STREAM));
HV_EXPORT int ListenUnix(const char* path);
HV_EXPORT int ConnectUnix(const char* path, int nonblock DEFAULT(0));
HV_EXPORT int ConnectUnixNonblock(const char* path);
HV_EXPORT int ConnectUnixTimeout(const char* path, int ms DEFAULT(DEFAULT_CONNECT_TIMEOUT));
#endif
// Just implement Socketpair(AF_INET, SOCK_STREAM, 0, sv);
HV_EXPORT int Socketpair(int family, int type, int protocol, int sv[2]);
HV_INLINE int tcp_nodelay(int sockfd, int on DEFAULT(1)) {
return setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (const char*)&on, sizeof(int));
}
HV_INLINE int tcp_nopush(int sockfd, int on DEFAULT(1)) {
#ifdef TCP_NOPUSH
return setsockopt(sockfd, IPPROTO_TCP, TCP_NOPUSH, (const char*)&on, sizeof(int));
#elif defined(TCP_CORK)
return setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, (const char*)&on, sizeof(int));
#else
return 0;
#endif
}
HV_INLINE int tcp_keepalive(int sockfd, int on DEFAULT(1), int delay DEFAULT(60)) {
if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&on, sizeof(int)) != 0) {
return socket_errno();
}
#ifdef TCP_KEEPALIVE
return setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, (const char*)&delay, sizeof(int));
#elif defined(TCP_KEEPIDLE)
// TCP_KEEPIDLE => tcp_keepalive_time
// TCP_KEEPCNT => tcp_keepalive_probes
// TCP_KEEPINTVL => tcp_keepalive_intvl
return setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, (const char*)&delay, sizeof(int));
#else
return 0;
#endif
}
HV_INLINE int udp_broadcast(int sockfd, int on DEFAULT(1)) {
return setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (const char*)&on, sizeof(int));
}
HV_INLINE int ip_v6only(int sockfd, int on DEFAULT(1)) {
#ifdef IPV6_V6ONLY
return setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&on, sizeof(int));
#else
return 0;
#endif
}
// send timeout
HV_INLINE int so_sndtimeo(int sockfd, int timeout) {
#ifdef OS_WIN
return setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(int));
#else
struct timeval tv = {timeout/1000, (timeout%1000)*1000};
return setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
#endif
}
// recv timeout
HV_INLINE int so_rcvtimeo(int sockfd, int timeout) {
#ifdef OS_WIN
return setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(int));
#else
struct timeval tv = {timeout/1000, (timeout%1000)*1000};
return setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
#endif
}
// send buffer size
HV_INLINE int so_sndbuf(int sockfd, int len) {
return setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char*)&len, sizeof(int));
}
// recv buffer size
HV_INLINE int so_rcvbuf(int sockfd, int len) {
return setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (const char*)&len, sizeof(int));
}
HV_INLINE int so_reuseaddr(int sockfd, int on DEFAULT(1)) {
#ifdef SO_REUSEADDR
// NOTE: SO_REUSEADDR allow to reuse sockaddr of TIME_WAIT status
return setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(int));
#else
return 0;
#endif
}
HV_INLINE int so_reuseport(int sockfd, int on DEFAULT(1)) {
#ifdef SO_REUSEPORT
// NOTE: SO_REUSEPORT allow multiple sockets to bind same port
return setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const char*)&on, sizeof(int));
#else
return 0;
#endif
}
HV_INLINE int so_linger(int sockfd, int timeout DEFAULT(1)) {
#ifdef SO_LINGER
struct linger linger;
if (timeout >= 0) {
linger.l_onoff = 1;
linger.l_linger = timeout;
} else {
linger.l_onoff = 0;
linger.l_linger = 0;
}
// NOTE: SO_LINGER change the default behavior of close, send RST, avoid TIME_WAIT
return setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (const char*)&linger, sizeof(linger));
#else
return 0;
#endif
}
END_EXTERN_C
#endif // HV_SOCKET_H_

View File

@ -0,0 +1,91 @@
#ifndef HV_SSL_H_
#define HV_SSL_H_
#include "hexport.h"
#include "hplatform.h"
#if !defined(WITH_OPENSSL) && \
!defined(WITH_GNUTLS) && \
!defined(WITH_MBEDTLS)
#ifdef OS_WIN
#define WITH_WINTLS
#ifdef _MSC_VER
#pragma comment(lib, "secur32.lib")
#pragma comment(lib, "crypt32.lib")
#endif
#elif defined(OS_DARWIN)
#define WITH_APPLETLS
#else
#define HV_WITHOUT_SSL
#endif
#endif
typedef void* hssl_ctx_t; ///> SSL_CTX
typedef void* hssl_t; ///> SSL
enum {
HSSL_SERVER = 0,
HSSL_CLIENT = 1,
};
enum {
HSSL_OK = 0,
HSSL_ERROR = -1,
HSSL_WANT_READ = -2,
HSSL_WANT_WRITE = -3,
HSSL_WOULD_BLOCK = -4,
};
typedef struct {
const char* crt_file;
const char* key_file;
const char* ca_file;
const char* ca_path;
short verify_peer;
short endpoint; // HSSL_SERVER / HSSL_CLIENT
} hssl_ctx_opt_t, hssl_ctx_init_param_t;
BEGIN_EXTERN_C
/*
const char* hssl_backend() {
#ifdef WITH_OPENSSL
return "openssl";
#elif defined(WITH_GNUTLS)
return "gnutls";
#elif defined(WITH_MBEDTLS)
return "mbedtls";
#else
return "nossl";
#endif
}
*/
HV_EXPORT const char* hssl_backend();
#define HV_WITH_SSL (strcmp(hssl_backend(), "nossl") != 0)
HV_EXPORT extern hssl_ctx_t g_ssl_ctx;
HV_EXPORT hssl_ctx_t hssl_ctx_init(hssl_ctx_init_param_t* param);
HV_EXPORT void hssl_ctx_cleanup(hssl_ctx_t ssl_ctx);
HV_EXPORT hssl_ctx_t hssl_ctx_instance();
HV_EXPORT hssl_ctx_t hssl_ctx_new(hssl_ctx_opt_t* opt);
HV_EXPORT void hssl_ctx_free(hssl_ctx_t ssl_ctx);
HV_EXPORT hssl_t hssl_new(hssl_ctx_t ssl_ctx, int fd);
HV_EXPORT void hssl_free(hssl_t ssl);
HV_EXPORT int hssl_accept(hssl_t ssl);
HV_EXPORT int hssl_connect(hssl_t ssl);
HV_EXPORT int hssl_read(hssl_t ssl, void* buf, int len);
HV_EXPORT int hssl_write(hssl_t ssl, const void* buf, int len);
HV_EXPORT int hssl_close(hssl_t ssl);
HV_EXPORT int hssl_set_sni_hostname(hssl_t ssl, const char* hostname);
#ifdef WITH_OPENSSL
HV_EXPORT int hssl_ctx_set_alpn_protos(hssl_ctx_t ssl_ctx, const unsigned char* protos, unsigned int protos_len);
#endif
END_EXTERN_C
#endif // HV_SSL_H_

View File

@ -0,0 +1,92 @@
#ifndef HV_STRING_H_
#define HV_STRING_H_
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include "hexport.h"
#include "hplatform.h"
#include "hmap.h"
#define SPACE_CHARS " \t\r\n"
#define PAIR_CHARS "{}[]()<>\"\"\'\'``"
namespace hv {
HV_EXPORT extern std::string empty_string;
HV_EXPORT extern std::map<std::string, std::string> empty_map;
typedef std::vector<std::string> StringList;
// std::map<std::string, std::string, StringCaseLess>
class StringCaseLess : public std::less<std::string> {
public:
bool operator()(const std::string& lhs, const std::string& rhs) const {
return strcasecmp(lhs.c_str(), rhs.c_str()) < 0;
}
};
// NOTE: low-version NDK not provide std::to_string
template<typename T>
HV_INLINE std::string to_string(const T& t) {
std::ostringstream oss;
oss << t;
return oss.str();
}
template<typename T>
HV_INLINE T from_string(const std::string& str) {
T t;
std::istringstream iss(str);
iss >> t;
return t;
}
template<typename T>
HV_INLINE void print(const T& t) {
std::cout << t;
}
template<typename T>
HV_INLINE void println(const T& t) {
std::cout << t << std::endl;
}
HV_EXPORT std::string& toupper(std::string& str);
HV_EXPORT std::string& tolower(std::string& str);
HV_EXPORT std::string& reverse(std::string& str);
HV_EXPORT bool startswith(const std::string& str, const std::string& start);
HV_EXPORT bool endswith(const std::string& str, const std::string& end);
HV_EXPORT bool contains(const std::string& str, const std::string& sub);
HV_EXPORT std::string asprintf(const char* fmt, ...);
// x,y,z
HV_EXPORT StringList split(const std::string& str, char delim = ',');
// k1=v1&k2=v2
HV_EXPORT hv::KeyValue splitKV(const std::string& str, char kv_kv = '&', char k_v = '=');
HV_EXPORT std::string trim(const std::string& str, const char* chars = SPACE_CHARS);
HV_EXPORT std::string ltrim(const std::string& str, const char* chars = SPACE_CHARS);
HV_EXPORT std::string rtrim(const std::string& str, const char* chars = SPACE_CHARS);
HV_EXPORT std::string trim_pairs(const std::string& str, const char* pairs = PAIR_CHARS);
HV_EXPORT std::string replace(const std::string& str, const std::string& find, const std::string& rep);
HV_EXPORT std::string replaceAll(const std::string& str, const std::string& find, const std::string& rep);
struct HV_EXPORT NetAddr {
std::string ip;
int port;
NetAddr() : port(0) {}
NetAddr(const std::string& _ip, int _port) : ip(_ip), port(_port) {}
NetAddr(const std::string& ipport) { from_string(ipport); }
void from_string(const std::string& ipport);
std::string to_string();
};
} // end namespace hv
#endif // HV_STRING_H_

View File

@ -0,0 +1,68 @@
#ifndef HV_SYS_INFO_H_
#define HV_SYS_INFO_H_
#include "hplatform.h"
#ifdef OS_LINUX
#include <sys/sysinfo.h>
#endif
#ifdef OS_DARWIN
#include <mach/mach_host.h>
#include <sys/sysctl.h>
#endif
static inline int get_ncpu() {
#ifdef OS_WIN
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.dwNumberOfProcessors;
#else
//return get_nprocs();
//return get_nprocs_conf();
//return sysconf(_SC_NPROCESSORS_ONLN); // processors available
return sysconf(_SC_NPROCESSORS_CONF); // processors configured
#endif
}
typedef struct meminfo_s {
unsigned long total; // KB
unsigned long free; // KB
} meminfo_t;
static inline int get_meminfo(meminfo_t* mem) {
#ifdef OS_WIN
MEMORYSTATUSEX memstat;
memset(&memstat, 0, sizeof(memstat));
memstat.dwLength = sizeof(memstat);
GlobalMemoryStatusEx(&memstat);
mem->total = (unsigned long)(memstat.ullTotalPhys >> 10);
mem->free = (unsigned long)(memstat.ullAvailPhys >> 10);
return 0;
#elif defined(OS_LINUX)
struct sysinfo info;
if (sysinfo(&info) < 0) {
return errno;
}
mem->total = info.totalram * info.mem_unit >> 10;
mem->free = info.freeram * info.mem_unit >> 10;
return 0;
#elif defined(OS_DARWIN)
uint64_t memsize = 0;
size_t size = sizeof(memsize);
int which[2] = {CTL_HW, HW_MEMSIZE};
sysctl(which, 2, &memsize, &size, NULL, 0);
mem->total = memsize >> 10;
vm_statistics_data_t info;
mach_msg_type_number_t count = sizeof(info) / sizeof(integer_t);
host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&info, &count);
mem->free = ((uint64_t)info.free_count * sysconf(_SC_PAGESIZE)) >> 10;
return 0;
#else
(void)(mem);
return -10;
#endif
}
#endif // HV_SYS_INFO_H_

View File

@ -0,0 +1,217 @@
#ifndef HV_THREAD_H_
#define HV_THREAD_H_
#include "hplatform.h"
#ifdef OS_WIN
#define hv_getpid (long)GetCurrentProcessId
#else
#define hv_getpid (long)getpid
#endif
#ifdef OS_WIN
#define hv_gettid (long)GetCurrentThreadId
#elif HAVE_GETTID || defined(OS_ANDROID)
#define hv_gettid (long)gettid
#elif defined(OS_LINUX)
#include <sys/syscall.h>
#define hv_gettid() (long)syscall(SYS_gettid)
#elif defined(OS_DARWIN)
static inline long hv_gettid() {
uint64_t tid = 0;
pthread_threadid_np(NULL, &tid);
return tid;
}
#elif HAVE_PTHREAD_H
#define hv_gettid (long)pthread_self
#else
#define hv_gettid hv_getpid
#endif
/*
#include "hthread.h"
HTHREAD_ROUTINE(thread_demo) {
printf("thread[%ld] start\n", hv_gettid());
hv_delay(3000);
printf("thread[%ld] end\n", hv_gettid());
return 0;
}
int main() {
hthread_t th = hthread_create(thread_demo, NULL);
hthread_join(th);
return 0;
}
*/
#ifdef OS_WIN
typedef HANDLE hthread_t;
typedef DWORD (WINAPI *hthread_routine)(void*);
#define HTHREAD_RETTYPE DWORD
#define HTHREAD_ROUTINE(fname) DWORD WINAPI fname(void* userdata)
static inline hthread_t hthread_create(hthread_routine fn, void* userdata) {
return CreateThread(NULL, 0, fn, userdata, 0, NULL);
}
static inline int hthread_join(hthread_t th) {
WaitForSingleObject(th, INFINITE);
CloseHandle(th);
return 0;
}
#else
typedef pthread_t hthread_t;
typedef void* (*hthread_routine)(void*);
#define HTHREAD_RETTYPE void*
#define HTHREAD_ROUTINE(fname) void* fname(void* userdata)
static inline hthread_t hthread_create(hthread_routine fn, void* userdata) {
pthread_t th;
pthread_create(&th, NULL, fn, userdata);
return th;
}
static inline int hthread_join(hthread_t th) {
return pthread_join(th, NULL);
}
#endif
#ifdef __cplusplus
/************************************************
* HThread
* Status: STOP,RUNNING,PAUSE
* Control: start,stop,pause,resume
* first-level virtual: doTask
* second-level virtual: run
************************************************/
#include <thread>
#include <atomic>
#include <chrono>
class HThread {
public:
enum Status {
STOP,
RUNNING,
PAUSE,
};
enum SleepPolicy {
YIELD,
SLEEP_FOR,
SLEEP_UNTIL,
NO_SLEEP,
};
HThread() {
status = STOP;
status_changed = false;
dotask_cnt = 0;
sleep_policy = YIELD;
sleep_ms = 0;
}
virtual ~HThread() {}
void setStatus(Status stat) {
status_changed = true;
status = stat;
}
void setSleepPolicy(SleepPolicy policy, uint32_t ms = 0) {
sleep_policy = policy;
sleep_ms = ms;
setStatus(status);
}
virtual int start() {
if (status == STOP) {
thread = std::thread([this] {
if (!doPrepare()) return;
setStatus(RUNNING);
run();
setStatus(STOP);
if (!doFinish()) return;
});
}
return 0;
}
virtual int stop() {
if (status != STOP) {
setStatus(STOP);
}
if (thread.joinable()) {
thread.join(); // wait thread exit
}
return 0;
}
virtual int pause() {
if (status == RUNNING) {
setStatus(PAUSE);
}
return 0;
}
virtual int resume() {
if (status == PAUSE) {
setStatus(RUNNING);
}
return 0;
}
virtual void run() {
while (status != STOP) {
while (status == PAUSE) {
std::this_thread::yield();
}
doTask();
++dotask_cnt;
HThread::sleep();
}
}
virtual bool doPrepare() {return true;}
virtual void doTask() {}
virtual bool doFinish() {return true;}
std::thread thread;
std::atomic<Status> status;
uint32_t dotask_cnt;
protected:
void sleep() {
switch (sleep_policy) {
case YIELD:
std::this_thread::yield();
break;
case SLEEP_FOR:
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
break;
case SLEEP_UNTIL: {
if (status_changed) {
status_changed = false;
base_tp = std::chrono::steady_clock::now();
}
base_tp += std::chrono::milliseconds(sleep_ms);
std::this_thread::sleep_until(base_tp);
}
break;
default: // donothing, go all out.
break;
}
}
SleepPolicy sleep_policy;
uint32_t sleep_ms;
// for SLEEP_UNTIL
std::atomic<bool> status_changed;
std::chrono::steady_clock::time_point base_tp;
};
#endif
#endif // HV_THREAD_H_

View File

@ -0,0 +1,249 @@
#ifndef HV_THREAD_POOL_H_
#define HV_THREAD_POOL_H_
/*
* @usage unittest/threadpool_test.cpp
*/
#include <time.h>
#include <thread>
#include <list>
#include <queue>
#include <functional>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <future>
#include <memory>
#include <utility>
#include <chrono>
#define DEFAULT_THREAD_POOL_MIN_THREAD_NUM 1
#define DEFAULT_THREAD_POOL_MAX_THREAD_NUM std::thread::hardware_concurrency()
#define DEFAULT_THREAD_POOL_MAX_IDLE_TIME 60000 // ms
class HThreadPool {
public:
using Task = std::function<void()>;
HThreadPool(int min_threads = DEFAULT_THREAD_POOL_MIN_THREAD_NUM,
int max_threads = DEFAULT_THREAD_POOL_MAX_THREAD_NUM,
int max_idle_ms = DEFAULT_THREAD_POOL_MAX_IDLE_TIME)
: min_thread_num(min_threads)
, max_thread_num(max_threads)
, max_idle_time(max_idle_ms)
, status(STOP)
, cur_thread_num(0)
, idle_thread_num(0)
{}
virtual ~HThreadPool() {
stop();
}
void setMinThreadNum(int min_threads) {
min_thread_num = min_threads;
}
void setMaxThreadNum(int max_threads) {
max_thread_num = max_threads;
}
void setMaxIdleTime(int ms) {
max_idle_time = ms;
}
int currentThreadNum() {
return cur_thread_num;
}
int idleThreadNum() {
return idle_thread_num;
}
size_t taskNum() {
std::lock_guard<std::mutex> locker(task_mutex);
return tasks.size();
}
bool isStarted() {
return status != STOP;
}
bool isStopped() {
return status == STOP;
}
int start(int start_threads = 0) {
if (status != STOP) return -1;
status = RUNNING;
if (start_threads < min_thread_num) start_threads = min_thread_num;
if (start_threads > max_thread_num) start_threads = max_thread_num;
for (int i = 0; i < start_threads; ++i) {
createThread();
}
return 0;
}
int stop() {
if (status == STOP) return -1;
status = STOP;
task_cond.notify_all();
for (auto& i : threads) {
if (i.thread->joinable()) {
i.thread->join();
}
}
threads.clear();
cur_thread_num = 0;
idle_thread_num = 0;
return 0;
}
int pause() {
if (status == RUNNING) {
status = PAUSE;
}
return 0;
}
int resume() {
if (status == PAUSE) {
status = RUNNING;
}
return 0;
}
int wait() {
while (status != STOP) {
if (tasks.empty() && idle_thread_num == cur_thread_num) {
break;
}
std::this_thread::yield();
}
return 0;
}
/*
* return a future, calling future.get() will wait task done and return RetType.
* commit(fn, args...)
* commit(std::bind(&Class::mem_fn, &obj))
* commit(std::mem_fn(&Class::mem_fn, &obj))
*
*/
template<class Fn, class... Args>
auto commit(Fn&& fn, Args&&... args) -> std::future<decltype(fn(args...))> {
if (status == STOP) start();
if (idle_thread_num <= tasks.size() && cur_thread_num < max_thread_num) {
createThread();
}
using RetType = decltype(fn(args...));
auto task = std::make_shared<std::packaged_task<RetType()> >(
std::bind(std::forward<Fn>(fn), std::forward<Args>(args)...));
std::future<RetType> future = task->get_future();
{
std::lock_guard<std::mutex> locker(task_mutex);
tasks.emplace([task]{
(*task)();
});
}
task_cond.notify_one();
return future;
}
protected:
bool createThread() {
if (cur_thread_num >= max_thread_num) return false;
std::thread* thread = new std::thread([this] {
while (status != STOP) {
while (status == PAUSE) {
std::this_thread::yield();
}
Task task;
{
std::unique_lock<std::mutex> locker(task_mutex);
task_cond.wait_for(locker, std::chrono::milliseconds(max_idle_time), [this]() {
return status == STOP || !tasks.empty();
});
if (status == STOP) return;
if (tasks.empty()) {
if (cur_thread_num > min_thread_num) {
delThread(std::this_thread::get_id());
return;
}
continue;
}
--idle_thread_num;
task = std::move(tasks.front());
tasks.pop();
}
if (task) {
task();
++idle_thread_num;
}
}
});
addThread(thread);
return true;
}
void addThread(std::thread* thread) {
thread_mutex.lock();
++cur_thread_num;
++idle_thread_num;
ThreadData data;
data.thread = std::shared_ptr<std::thread>(thread);
data.id = thread->get_id();
data.status = RUNNING;
data.start_time = time(NULL);
data.stop_time = 0;
threads.emplace_back(data);
thread_mutex.unlock();
}
void delThread(std::thread::id id) {
time_t now = time(NULL);
thread_mutex.lock();
--cur_thread_num;
--idle_thread_num;
auto iter = threads.begin();
while (iter != threads.end()) {
if (iter->status == STOP && now > iter->stop_time) {
if (iter->thread->joinable()) {
iter->thread->join();
iter = threads.erase(iter);
continue;
}
} else if (iter->id == id) {
iter->status = STOP;
iter->stop_time = time(NULL);
}
++iter;
}
thread_mutex.unlock();
}
public:
int min_thread_num;
int max_thread_num;
int max_idle_time;
protected:
enum Status {
STOP,
RUNNING,
PAUSE,
};
struct ThreadData {
std::shared_ptr<std::thread> thread;
std::thread::id id;
Status status;
time_t start_time;
time_t stop_time;
};
std::atomic<Status> status;
std::atomic<int> cur_thread_num;
std::atomic<int> idle_thread_num;
std::list<ThreadData> threads;
std::mutex thread_mutex;
std::queue<Task> tasks;
std::mutex task_mutex;
std::condition_variable task_cond;
};
#endif // HV_THREAD_POOL_H_

View File

@ -0,0 +1,114 @@
#ifndef HV_TIME_H_
#define HV_TIME_H_
#include "hexport.h"
#include "hplatform.h"
BEGIN_EXTERN_C
#define SECONDS_PER_MINUTE 60
#define SECONDS_PER_HOUR 3600
#define SECONDS_PER_DAY 86400 // 24*3600
#define SECONDS_PER_WEEK 604800 // 7*24*3600
#define IS_LEAP_YEAR(year) (((year)%4 == 0 && (year)%100 != 0) || (year)%400 == 0)
typedef struct datetime_s {
int year;
int month;
int day;
int hour;
int min;
int sec;
int ms;
} datetime_t;
#ifdef _MSC_VER
/* @see winsock2.h
// Structure used in select() call, taken from the BSD file sys/time.h
struct timeval {
long tv_sec;
long tv_usec;
};
*/
struct timezone {
int tz_minuteswest; /* of Greenwich */
int tz_dsttime; /* type of dst correction to apply */
};
#include <sys/timeb.h>
HV_INLINE int gettimeofday(struct timeval *tv, struct timezone *tz) {
struct _timeb tb;
_ftime(&tb);
if (tv) {
tv->tv_sec = (long)tb.time;
tv->tv_usec = tb.millitm * 1000;
}
if (tz) {
tz->tz_minuteswest = tb.timezone;
tz->tz_dsttime = tb.dstflag;
}
return 0;
}
#endif
HV_EXPORT unsigned int gettick_ms();
HV_INLINE unsigned long long gettimeofday_ms() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * (unsigned long long)1000 + tv.tv_usec/1000;
}
HV_INLINE unsigned long long gettimeofday_us() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * (unsigned long long)1000000 + tv.tv_usec;
}
HV_EXPORT unsigned long long gethrtime_us();
HV_EXPORT datetime_t datetime_now();
HV_EXPORT datetime_t datetime_localtime(time_t seconds);
HV_EXPORT time_t datetime_mktime(datetime_t* dt);
HV_EXPORT datetime_t* datetime_past(datetime_t* dt, int days DEFAULT(1));
HV_EXPORT datetime_t* datetime_future(datetime_t* dt, int days DEFAULT(1));
#define TIME_FMT "%02d:%02d:%02d"
#define TIME_FMT_BUFLEN 12
HV_EXPORT char* duration_fmt(int sec, char* buf);
#define DATETIME_FMT "%04d-%02d-%02d %02d:%02d:%02d"
#define DATETIME_FMT_ISO "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ"
#define DATETIME_FMT_BUFLEN 30
HV_EXPORT char* datetime_fmt(datetime_t* dt, char* buf);
HV_EXPORT char* datetime_fmt_iso(datetime_t* dt, char* buf);
#define GMTIME_FMT "%.3s, %02d %.3s %04d %02d:%02d:%02d GMT"
#define GMTIME_FMT_BUFLEN 30
HV_EXPORT char* gmtime_fmt(time_t time, char* buf);
HV_EXPORT int days_of_month(int month, int year);
HV_EXPORT int month_atoi(const char* month);
HV_EXPORT const char* month_itoa(int month);
HV_EXPORT int weekday_atoi(const char* weekday);
HV_EXPORT const char* weekday_itoa(int weekday);
HV_EXPORT datetime_t hv_compile_datetime();
/*
* minute hour day week month action
* 0~59 0~23 1~31 0~6 1~12
* -1 -1 -1 -1 -1 cron.minutely
* 30 -1 -1 -1 -1 cron.hourly
* 30 1 -1 -1 -1 cron.daily
* 30 1 15 -1 -1 cron.monthly
* 30 1 -1 0 -1 cron.weekly
* 30 1 1 -1 10 cron.yearly
*/
HV_EXPORT time_t cron_next_timeout(int minute, int hour, int day, int week, int month);
END_EXTERN_C
#endif // HV_TIME_H_

View File

@ -0,0 +1,78 @@
#ifndef HV_HTTP_CONTENT_H_
#define HV_HTTP_CONTENT_H_
#include "hexport.h"
#include "hstring.h"
// NOTE: WITHOUT_HTTP_CONTENT
// ndk-r10e no std::to_string and can't compile modern json.hpp
#ifndef WITHOUT_HTTP_CONTENT
#include "json.hpp" // https://github.com/nlohmann/json
#endif
BEGIN_NAMESPACE_HV
// QueryParams
using QueryParams = hv::KeyValue;
HV_EXPORT std::string dump_query_params(const QueryParams& query_params);
HV_EXPORT int parse_query_params(const char* query_string, QueryParams& query_params);
#ifndef WITHOUT_HTTP_CONTENT
/**************multipart/form-data*************************************
--boundary
Content-Disposition: form-data; name="user"
content
--boundary
Content-Disposition: form-data; name="avatar"; filename="user.jpg"
Content-Type: image/jpeg
content
--boundary--
***********************************************************************/
// FormData
struct FormData {
std::string filename;
std::string content;
FormData(const char* content = NULL, const char* filename = NULL) {
if (content) {
this->content = content;
}
if (filename) {
this->filename = filename;
}
}
template<typename T>
FormData(T num) {
content = hv::to_string(num);
}
};
// FormFile
struct FormFile : public FormData {
FormFile(const char* filename = NULL) {
if (filename) {
this->filename = filename;
}
}
};
// MultiPart
// name => FormData
typedef HV_MAP<std::string, FormData> MultiPart;
#define DEFAULT_MULTIPART_BOUNDARY "----WebKitFormBoundary7MA4YWxkTrZu0gW"
HV_EXPORT std::string dump_multipart(MultiPart& mp, const char* boundary = DEFAULT_MULTIPART_BOUNDARY);
HV_EXPORT int parse_multipart(const std::string& str, MultiPart& mp, const char* boundary);
// Json
using Json = nlohmann::json;
// using Json = nlohmann::ordered_json;
HV_EXPORT std::string dump_json(const hv::Json& json, int indent = -1);
HV_EXPORT int parse_json(const char* str, hv::Json& json, std::string& errmsg);
#endif
END_NAMESPACE_HV
#endif // HV_HTTP_CONTENT_H_

View File

@ -0,0 +1,295 @@
#ifndef HV_HTTP_DEF_H_
#define HV_HTTP_DEF_H_
#include "hexport.h"
#define DEFAULT_HTTP_PORT 80
#define DEFAULT_HTTPS_PORT 443
enum http_version { HTTP_V1 = 1, HTTP_V2 = 2 };
enum http_session_type { HTTP_CLIENT, HTTP_SERVER };
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
enum http_parser_state {
HP_START_REQ_OR_RES,
HP_MESSAGE_BEGIN,
HP_URL,
HP_STATUS,
HP_HEADER_FIELD,
HP_HEADER_VALUE,
HP_HEADERS_COMPLETE,
HP_CHUNK_HEADER,
HP_BODY,
HP_CHUNK_COMPLETE,
HP_MESSAGE_COMPLETE,
HP_ERROR
};
// http_status
// XX(num, name, string)
#define HTTP_STATUS_MAP(XX) \
XX(100, CONTINUE, Continue) \
XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \
XX(102, PROCESSING, Processing) \
XX(200, OK, OK) \
XX(201, CREATED, Created) \
XX(202, ACCEPTED, Accepted) \
XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \
XX(204, NO_CONTENT, No Content) \
XX(205, RESET_CONTENT, Reset Content) \
XX(206, PARTIAL_CONTENT, Partial Content) \
XX(207, MULTI_STATUS, Multi-Status) \
XX(208, ALREADY_REPORTED, Already Reported) \
XX(226, IM_USED, IM Used) \
XX(300, MULTIPLE_CHOICES, Multiple Choices) \
XX(301, MOVED_PERMANENTLY, Moved Permanently) \
XX(302, FOUND, Found) \
XX(303, SEE_OTHER, See Other) \
XX(304, NOT_MODIFIED, Not Modified) \
XX(305, USE_PROXY, Use Proxy) \
XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \
XX(308, PERMANENT_REDIRECT, Permanent Redirect) \
XX(400, BAD_REQUEST, Bad Request) \
XX(401, UNAUTHORIZED, Unauthorized) \
XX(402, PAYMENT_REQUIRED, Payment Required) \
XX(403, FORBIDDEN, Forbidden) \
XX(404, NOT_FOUND, Not Found) \
XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \
XX(406, NOT_ACCEPTABLE, Not Acceptable) \
XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \
XX(408, REQUEST_TIMEOUT, Request Timeout) \
XX(409, CONFLICT, Conflict) \
XX(410, GONE, Gone) \
XX(411, LENGTH_REQUIRED, Length Required) \
XX(412, PRECONDITION_FAILED, Precondition Failed) \
XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \
XX(414, URI_TOO_LONG, URI Too Long) \
XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \
XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \
XX(417, EXPECTATION_FAILED, Expectation Failed) \
XX(421, MISDIRECTED_REQUEST, Misdirected Request) \
XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \
XX(423, LOCKED, Locked) \
XX(424, FAILED_DEPENDENCY, Failed Dependency) \
XX(426, UPGRADE_REQUIRED, Upgrade Required) \
XX(428, PRECONDITION_REQUIRED, Precondition Required) \
XX(429, TOO_MANY_REQUESTS, Too Many Requests) \
XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \
XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \
XX(501, NOT_IMPLEMENTED, Not Implemented) \
XX(502, BAD_GATEWAY, Bad Gateway) \
XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \
XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \
XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \
XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \
XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \
XX(508, LOOP_DETECTED, Loop Detected) \
XX(510, NOT_EXTENDED, Not Extended) \
XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \
// HTTP_STATUS_##name
enum http_status {
#define XX(num, name, string) HTTP_STATUS_##name = num,
HTTP_STATUS_MAP(XX)
#undef XX
HTTP_CUSTOM_STATUS
};
#define HTTP_STATUS_IS_REDIRECT(status) \
( \
(status) == HTTP_STATUS_MOVED_PERMANENTLY || \
(status) == HTTP_STATUS_FOUND || \
(status) == HTTP_STATUS_SEE_OTHER || \
(status) == HTTP_STATUS_TEMPORARY_REDIRECT || \
(status) == HTTP_STATUS_PERMANENT_REDIRECT \
)
// http_mehtod
// XX(num, name, string)
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
/* pathological */ \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
/* WebDAV */ \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
/* subversion */ \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
/* upnp */ \
XX(24, MSEARCH, M-SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
/* RFC-5789 */ \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
/* CalDAV */ \
XX(30, MKCALENDAR, MKCALENDAR) \
/* RFC-2068, section 19.6.1.2 */ \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK) \
/* icecast */ \
XX(33, SOURCE, SOURCE) \
// HTTP_##name
enum http_method {
#define XX(num, name, string) HTTP_##name = num,
HTTP_METHOD_MAP(XX)
#undef XX
HTTP_CUSTOM_METHOD
};
// MIME: https://www.iana.org/assignments/media-types/media-types.xhtml
// XX(name, mime, suffix)
#define MIME_TYPE_TEXT_MAP(XX) \
XX(TEXT_PLAIN, text/plain, txt) \
XX(TEXT_HTML, text/html, html) \
XX(TEXT_CSS, text/css, css) \
XX(TEXT_CSV, text/csv, csv) \
XX(TEXT_MARKDOWN, text/markdown, md) \
XX(TEXT_EVENT_STREAM, text/event-stream, sse) \
#define MIME_TYPE_APPLICATION_MAP(XX) \
XX(APPLICATION_JAVASCRIPT, application/javascript, js) \
XX(APPLICATION_JSON, application/json, json) \
XX(APPLICATION_XML, application/xml, xml) \
XX(APPLICATION_URLENCODED, application/x-www-form-urlencoded, kv) \
XX(APPLICATION_OCTET_STREAM,application/octet-stream, bin) \
XX(APPLICATION_ZIP, application/zip, zip) \
XX(APPLICATION_GZIP, application/gzip, gzip) \
XX(APPLICATION_7Z, application/x-7z-compressed, 7z) \
XX(APPLICATION_RAR, application/x-rar-compressed, rar) \
XX(APPLICATION_PDF, application/pdf, pdf) \
XX(APPLICATION_RTF, application/rtf, rtf) \
XX(APPLICATION_GRPC, application/grpc, grpc) \
XX(APPLICATION_WASM, application/wasm, wasm) \
XX(APPLICATION_JAR, application/java-archive, jar) \
XX(APPLICATION_XHTML, application/xhtml+xml, xhtml) \
XX(APPLICATION_ATOM, application/atom+xml, atom) \
XX(APPLICATION_RSS, application/rss+xml, rss) \
XX(APPLICATION_WORD, application/msword, doc) \
XX(APPLICATION_EXCEL, application/vnd.ms-excel, xls) \
XX(APPLICATION_PPT, application/vnd.ms-powerpoint, ppt) \
XX(APPLICATION_EOT, application/vnd.ms-fontobject, eot) \
XX(APPLICATION_M3U8, application/vnd.apple.mpegurl, m3u8) \
XX(APPLICATION_DOCX, application/vnd.openxmlformats-officedocument.wordprocessingml.document, docx) \
XX(APPLICATION_XLSX, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, xlsx) \
XX(APPLICATION_PPTX, application/vnd.openxmlformats-officedocument.presentationml.presentation, pptx) \
#define MIME_TYPE_MULTIPART_MAP(XX) \
XX(MULTIPART_FORM_DATA, multipart/form-data, mp) \
#define MIME_TYPE_IMAGE_MAP(XX) \
XX(IMAGE_JPEG, image/jpeg, jpg) \
XX(IMAGE_PNG, image/png, png) \
XX(IMAGE_GIF, image/gif, gif) \
XX(IMAGE_ICO, image/x-icon, ico) \
XX(IMAGE_BMP, image/x-ms-bmp, bmp) \
XX(IMAGE_SVG, image/svg+xml, svg) \
XX(IMAGE_TIFF, image/tiff, tiff) \
XX(IMAGE_WEBP, image/webp, webp) \
#define MIME_TYPE_VIDEO_MAP(XX) \
XX(VIDEO_MP4, video/mp4, mp4) \
XX(VIDEO_FLV, video/x-flv, flv) \
XX(VIDEO_M4V, video/x-m4v, m4v) \
XX(VIDEO_MNG, video/x-mng, mng) \
XX(VIDEO_TS, video/mp2t, ts) \
XX(VIDEO_MPEG, video/mpeg, mpeg) \
XX(VIDEO_WEBM, video/webm, webm) \
XX(VIDEO_MOV, video/quicktime, mov) \
XX(VIDEO_3GPP, video/3gpp, 3gpp) \
XX(VIDEO_AVI, video/x-msvideo, avi) \
XX(VIDEO_WMV, video/x-ms-wmv, wmv) \
XX(VIDEO_ASF, video/x-ms-asf, asf) \
#define MIME_TYPE_AUDIO_MAP(XX) \
XX(AUDIO_MP3, audio/mpeg, mp3) \
XX(AUDIO_OGG, audio/ogg, ogg) \
XX(AUDIO_M4A, audio/x-m4a, m4a) \
XX(AUDIO_AAC, audio/aac, aac) \
XX(AUDIO_PCMA, audio/PCMA, pcma) \
XX(AUDIO_OPUS, audio/opus, opus) \
#define MIME_TYPE_FONT_MAP(XX) \
XX(FONT_TTF, font/ttf, ttf) \
XX(FONT_OTF, font/otf, otf) \
XX(FONT_WOFF, font/woff, woff) \
XX(FONT_WOFF2, font/woff2, woff2) \
#define HTTP_CONTENT_TYPE_MAP(XX) \
MIME_TYPE_TEXT_MAP(XX) \
MIME_TYPE_APPLICATION_MAP(XX) \
MIME_TYPE_MULTIPART_MAP(XX) \
MIME_TYPE_IMAGE_MAP(XX) \
MIME_TYPE_VIDEO_MAP(XX) \
MIME_TYPE_AUDIO_MAP(XX) \
MIME_TYPE_FONT_MAP(XX) \
#define X_WWW_FORM_URLENCODED APPLICATION_URLENCODED // for compatibility
enum http_content_type {
#define XX(name, string, suffix) name,
CONTENT_TYPE_NONE = 0,
CONTENT_TYPE_TEXT = 100,
MIME_TYPE_TEXT_MAP(XX)
CONTENT_TYPE_APPLICATION = 200,
MIME_TYPE_APPLICATION_MAP(XX)
CONTENT_TYPE_MULTIPART = 300,
MIME_TYPE_MULTIPART_MAP(XX)
CONTENT_TYPE_IMAGE = 400,
MIME_TYPE_IMAGE_MAP(XX)
CONTENT_TYPE_VIDEO = 500,
MIME_TYPE_VIDEO_MAP(XX)
CONTENT_TYPE_AUDIO = 600,
MIME_TYPE_AUDIO_MAP(XX)
CONTENT_TYPE_FONT = 700,
MIME_TYPE_FONT_MAP(XX)
CONTENT_TYPE_UNDEFINED = 1000
#undef XX
};
BEGIN_EXTERN_C
HV_EXPORT const char* http_status_str(enum http_status status);
HV_EXPORT const char* http_method_str(enum http_method method);
HV_EXPORT const char* http_content_type_str(enum http_content_type type);
HV_EXPORT enum http_status http_status_enum(const char* str);
HV_EXPORT enum http_method http_method_enum(const char* str);
HV_EXPORT enum http_content_type http_content_type_enum(const char* str);
HV_EXPORT const char* http_content_type_suffix(enum http_content_type type);
HV_EXPORT const char* http_content_type_str_by_suffix(const char* suffix);
HV_EXPORT enum http_content_type http_content_type_enum_by_suffix(const char* suffix);
END_EXTERN_C
#endif // HV_HTTP_DEF_H_

View File

@ -0,0 +1,41 @@
#ifndef HV_URL_H_
#define HV_URL_H_
#include <string> // import std::string
#include "hexport.h"
class HV_EXPORT HUrl {
public:
static std::string escape(const std::string& str, const char* unescaped_chars = "");
static std::string unescape(const std::string& str);
HUrl() : port(0) {}
~HUrl() {}
void reset();
bool parse(const std::string& url);
const std::string& dump();
std::string url;
std::string scheme;
std::string username;
std::string password;
std::string host;
int port;
std::string path;
std::string query;
std::string fragment;
};
namespace hv {
HV_INLINE std::string escapeURL(const std::string& url) {
return HUrl::escape(url, ":/@?=&#+");
}
HV_EXPORT std::string escapeHTML(const std::string& str);
} // end namespace hv
#endif // HV_URL_H_

View File

@ -0,0 +1,41 @@
#ifndef HV_H_
#define HV_H_
/**
* @copyright 2018 HeWei, all rights reserved.
*/
// platform
#include "hconfig.h"
#include "hexport.h"
#include "hplatform.h"
// c
#include "hdef.h" // <stddef.h>
#include "hatomic.h"// <stdatomic.h>
#include "herr.h" // <errno.h>
#include "htime.h" // <time.h>
#include "hmath.h" // <math.h>
#include "hbase.h"
#include "hversion.h"
#include "hsysinfo.h"
#include "hproc.h"
#include "hthread.h"
#include "hmutex.h"
#include "hsocket.h"
#include "hlog.h"
#include "hbuf.h"
// cpp
#ifdef __cplusplus
#include "hmap.h" // <map>
#include "hstring.h" // <string>
#include "hfile.h"
#include "hpath.h"
#include "hdir.h"
#include "hurl.h"
#endif
#endif // HV_H_

View File

@ -0,0 +1,34 @@
#ifndef HV_VERSION_H_
#define HV_VERSION_H_
#include "hexport.h"
#include "hdef.h"
BEGIN_EXTERN_C
#define HV_VERSION_MAJOR 1
#define HV_VERSION_MINOR 3
#define HV_VERSION_PATCH 3
#define HV_VERSION_STRING STRINGIFY(HV_VERSION_MAJOR) "." \
STRINGIFY(HV_VERSION_MINOR) "." \
STRINGIFY(HV_VERSION_PATCH)
#define HV_VERSION_NUMBER ((HV_VERSION_MAJOR << 16) | (HV_VERSION_MINOR << 8) | HV_VERSION_PATCH)
HV_INLINE const char* hv_version() {
return HV_VERSION_STRING;
}
HV_EXPORT const char* hv_compile_version();
// 1.2.3.4 => 0x01020304
HV_EXPORT int version_atoi(const char* str);
// 0x01020304 => 1.2.3.4
HV_EXPORT void version_itoa(int hex, char* str);
END_EXTERN_C
#endif // HV_VERSION_H_

View File

@ -0,0 +1,36 @@
#ifndef HV_IFCONFIG_H_
#define HV_IFCONFIG_H_
#include <vector>
#include "hexport.h"
#ifdef _MSC_VER
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")
#endif
typedef struct ifconfig_s {
char name[128];
char ip[16];
char mask[16];
char broadcast[16];
char mac[20];
} ifconfig_t;
/*
* @test
std::vector<ifconfig_t> ifcs;
ifconfig(ifcs);
for (auto& item : ifcs) {
printf("%s\nip: %s\nmask: %s\nbroadcast: %s\nmac: %s\n\n",
item.name,
item.ip,
item.mask,
item.broadcast,
item.mac);
}
*/
HV_EXPORT int ifconfig(std::vector<ifconfig_t>& ifcs);
#endif // HV_IFCONFIG_H_

View File

@ -0,0 +1,53 @@
#ifndef HV_INI_PARSER_H_
#define HV_INI_PARSER_H_
#include <string>
#include <list>
#include "hexport.h"
#define DEFAULT_INI_COMMENT "#"
#define DEFAULT_INI_DELIM "="
// fwd
class IniNode;
class HV_EXPORT IniParser {
public:
IniParser();
~IniParser();
int LoadFromFile(const char* filepath);
int LoadFromMem(const char* data);
int Unload();
int Reload();
std::string DumpString();
int Save();
int SaveAs(const char* filepath);
std::list<std::string> GetSections();
std::list<std::string> GetKeys(const std::string& section = "");
std::string GetValue(const std::string& key, const std::string& section = "");
void SetValue(const std::string& key, const std::string& value, const std::string& section = "");
// T = [bool, int, float]
template<typename T>
T Get(const std::string& key, const std::string& section = "", T defvalue = 0);
// T = [bool, int, float]
template<typename T>
void Set(const std::string& key, const T& value, const std::string& section = "");
protected:
void DumpString(IniNode* pNode, std::string& str);
public:
std::string _comment;
std::string _delim;
std::string _filepath;
private:
IniNode* root_;
};
#endif // HV_INI_PARSER_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
#ifndef HV_MD5_H_
#define HV_MD5_H_
#include "hexport.h"
typedef struct {
unsigned int count[2];
unsigned int state[4];
unsigned char buffer[64];
} HV_MD5_CTX;
BEGIN_EXTERN_C
HV_EXPORT void HV_MD5Init(HV_MD5_CTX *ctx);
HV_EXPORT void HV_MD5Update(HV_MD5_CTX *ctx, unsigned char *input, unsigned int inputlen);
HV_EXPORT void HV_MD5Final(HV_MD5_CTX *ctx, unsigned char digest[16]);
HV_EXPORT void hv_md5(unsigned char* input, unsigned int inputlen, unsigned char digest[16]);
// NOTE: if outputlen > 32: output[32] = '\0'
HV_EXPORT void hv_md5_hex(unsigned char* input, unsigned int inputlen, char* output, unsigned int outputlen);
END_EXTERN_C
#endif // HV_MD5_H_

View File

@ -0,0 +1,340 @@
#ifndef HV_MQTT_CLIENT_H_
#define HV_MQTT_CLIENT_H_
#include "mqtt_protocol.h"
#include "hloop.h"
#include "hssl.h"
#include "hmutex.h"
#define DEFAULT_MQTT_KEEPALIVE 60 // s
typedef struct mqtt_client_s mqtt_client_t;
// @type mqtt_type_e
// @example examples/mqtt
typedef void (*mqtt_client_cb)(mqtt_client_t* cli, int type);
struct mqtt_client_s {
// connect: host:port
char host[256];
int port;
int connect_timeout; // ms
// reconnect
reconn_setting_t* reconn_setting;
// login: flags + keepalive + client_id + will + username + password
// flags
unsigned char protocol_version; // Default MQTT_PROTOCOL_V311
unsigned char clean_session: 1;
unsigned char ssl: 1; // Read Only
unsigned char alloced_ssl_ctx: 1; // intern
unsigned char connected : 1;
unsigned short keepalive;
int ping_cnt;
char client_id[64];
// will
mqtt_message_t* will;
// auth
char username[64];
char password[64];
// message
mqtt_head_t head;
int error; // for MQTT_TYPE_CONNACK
int mid; // for MQTT_TYPE_SUBACK, MQTT_TYPE_PUBACK
mqtt_message_t message; // for MQTT_TYPE_PUBLISH
// callback
mqtt_client_cb cb;
// userdata
void* userdata;
// privdata
hloop_t* loop;
hio_t* io;
htimer_t* timer;
// SSL/TLS
hssl_ctx_t ssl_ctx;
// thread-safe
hmutex_t mutex_;
};
BEGIN_EXTERN_C
// hloop_new -> malloc(mqtt_client_t)
HV_EXPORT mqtt_client_t* mqtt_client_new(hloop_t* loop DEFAULT(NULL));
// @see hloop_run
HV_EXPORT void mqtt_client_run (mqtt_client_t* cli);
// @see hloop_stop
HV_EXPORT void mqtt_client_stop(mqtt_client_t* cli);
// hloop_free -> free(mqtt_client_t)
HV_EXPORT void mqtt_client_free(mqtt_client_t* cli);
// id
HV_EXPORT void mqtt_client_set_id(mqtt_client_t* cli, const char* id);
// will
HV_EXPORT void mqtt_client_set_will(mqtt_client_t* cli,
mqtt_message_t* will);
// auth
HV_EXPORT void mqtt_client_set_auth(mqtt_client_t* cli,
const char* username, const char* password);
// callback
HV_EXPORT void mqtt_client_set_callback(mqtt_client_t* cli, mqtt_client_cb cb);
// userdata
HV_EXPORT void mqtt_client_set_userdata(mqtt_client_t* cli, void* userdata);
HV_EXPORT void* mqtt_client_get_userdata(mqtt_client_t* cli);
// error
HV_EXPORT int mqtt_client_get_last_error(mqtt_client_t* cli);
// SSL/TLS
HV_EXPORT int mqtt_client_set_ssl_ctx(mqtt_client_t* cli, hssl_ctx_t ssl_ctx);
// hssl_ctx_new(opt) -> mqtt_client_set_ssl_ctx
HV_EXPORT int mqtt_client_new_ssl_ctx(mqtt_client_t* cli, hssl_ctx_opt_t* opt);
// reconnect
HV_EXPORT int mqtt_client_set_reconnect(mqtt_client_t* cli,
reconn_setting_t* reconn);
HV_EXPORT int mqtt_client_reconnect(mqtt_client_t* cli);
// connect
// hio_create_socket -> hio_connect ->
// on_connect -> mqtt_client_login ->
// on_connack
HV_EXPORT void mqtt_client_set_connect_timeout(mqtt_client_t* cli, int ms);
HV_EXPORT void mqtt_client_set_host(mqtt_client_t* cli, const char* host, int port, int ssl);
HV_EXPORT int mqtt_client_connect(mqtt_client_t* cli,
const char* host,
int port DEFAULT(DEFAULT_MQTT_PORT),
int ssl DEFAULT(0));
HV_EXPORT bool mqtt_client_is_connected(mqtt_client_t* cli);
// disconnect
// @see hio_close
HV_EXPORT int mqtt_client_disconnect(mqtt_client_t* cli);
// publish
HV_EXPORT int mqtt_client_publish(mqtt_client_t* cli,
mqtt_message_t* msg);
// subscribe
HV_EXPORT int mqtt_client_subscribe(mqtt_client_t* cli,
const char* topic, int qos DEFAULT(0));
// unsubscribe
HV_EXPORT int mqtt_client_unsubscribe(mqtt_client_t* cli,
const char* topic);
END_EXTERN_C
#ifdef __cplusplus
#include <functional>
#include <map>
#include <mutex>
#include <string>
namespace hv {
// @usage examples/mqtt/mqtt_client_test.cpp
class MqttClient {
public:
mqtt_client_t* client;
// callbacks
typedef std::function<void(MqttClient*)> MqttCallback;
typedef std::function<void(MqttClient*, mqtt_message_t*)> MqttMessageCallback;
MqttCallback onConnect;
MqttCallback onClose;
MqttMessageCallback onMessage;
MqttClient(hloop_t* loop = NULL) {
client = mqtt_client_new(loop);
}
~MqttClient() {
if (client) {
mqtt_client_free(client);
client = NULL;
}
}
void run() {
mqtt_client_set_callback(client, on_mqtt);
mqtt_client_set_userdata(client, this);
mqtt_client_run(client);
}
void stop() {
mqtt_client_stop(client);
}
void setID(const char* id) {
mqtt_client_set_id(client, id);
}
void setWill(mqtt_message_t* will) {
mqtt_client_set_will(client, will);
}
void setAuth(const char* username, const char* password) {
mqtt_client_set_auth(client, username, password);
}
void setPingInterval(int sec) {
client->keepalive = sec;
}
int lastError() {
return mqtt_client_get_last_error(client);
}
// SSL/TLS
int setSslCtx(hssl_ctx_t ssl_ctx) {
return mqtt_client_set_ssl_ctx(client, ssl_ctx);
}
int newSslCtx(hssl_ctx_opt_t* opt) {
return mqtt_client_new_ssl_ctx(client, opt);
}
void setReconnect(reconn_setting_t* reconn) {
mqtt_client_set_reconnect(client, reconn);
}
void setConnectTimeout(int ms) {
mqtt_client_set_connect_timeout(client, ms);
}
void setHost(const char* host, int port = DEFAULT_MQTT_PORT, int ssl = 0) {
mqtt_client_set_host(client, host, port, ssl);
}
int connect(const char* host, int port = DEFAULT_MQTT_PORT, int ssl = 0) {
return mqtt_client_connect(client, host, port, ssl);
}
int reconnect() {
return mqtt_client_reconnect(client);
}
int disconnect() {
return mqtt_client_disconnect(client);
}
bool isConnected() {
return mqtt_client_is_connected(client);
}
int publish(mqtt_message_t* msg, MqttCallback ack_cb = NULL) {
int mid = mqtt_client_publish(client, msg);
if (msg->qos > 0 && mid >= 0 && ack_cb) {
setAckCallback(mid, ack_cb);
}
return mid;
}
int publish(const std::string& topic, const std::string& payload, int qos = 0, int retain = 0, MqttCallback ack_cb = NULL) {
mqtt_message_t msg;
memset(&msg, 0, sizeof(msg));
msg.topic_len = topic.size();
msg.topic = topic.c_str();
msg.payload_len = payload.size();
msg.payload = payload.c_str();
msg.qos = qos;
msg.retain = retain;
return publish(&msg, ack_cb);
}
int subscribe(const char* topic, int qos = 0, MqttCallback ack_cb = NULL) {
int mid = mqtt_client_subscribe(client, topic, qos);
if (qos > 0 && mid >= 0 && ack_cb) {
setAckCallback(mid, ack_cb);
}
return mid;
}
int unsubscribe(const char* topic, MqttCallback ack_cb = NULL) {
int mid = mqtt_client_unsubscribe(client, topic);
if (mid >= 0 && ack_cb) {
setAckCallback(mid, ack_cb);
}
return mid;
}
protected:
void setAckCallback(int mid, MqttCallback cb) {
ack_cbs_mutex.lock();
ack_cbs[mid] = std::move(cb);
ack_cbs_mutex.unlock();
}
void invokeAckCallback(int mid) {
MqttCallback ack_cb = NULL;
ack_cbs_mutex.lock();
auto iter = ack_cbs.find(mid);
if (iter != ack_cbs.end()) {
ack_cb = std::move(iter->second);
ack_cbs.erase(iter);
}
ack_cbs_mutex.unlock();
if (ack_cb) ack_cb(this);
}
static void on_mqtt(mqtt_client_t* cli, int type) {
MqttClient* client = (MqttClient*)mqtt_client_get_userdata(cli);
// printf("on_mqtt type=%d\n", type);
switch(type) {
case MQTT_TYPE_CONNECT:
// printf("mqtt connected!\n");
break;
case MQTT_TYPE_DISCONNECT:
// printf("mqtt disconnected!\n");
if (client->onClose) {
client->onClose(client);
}
break;
case MQTT_TYPE_CONNACK:
// printf("mqtt connack!\n");
if (client->onConnect) {
client->onConnect(client);
}
break;
case MQTT_TYPE_PUBLISH:
if (client->onMessage) {
client->onMessage(client, &cli->message);
}
break;
case MQTT_TYPE_PUBACK: /* qos = 1 */
// printf("mqtt puback mid=%d\n", cli->mid);
client->invokeAckCallback(cli->mid);
break;
case MQTT_TYPE_PUBREC: /* qos = 2 */
// printf("mqtt pubrec mid=%d\n", cli->mid);
// wait MQTT_TYPE_PUBCOMP
break;
case MQTT_TYPE_PUBCOMP: /* qos = 2 */
// printf("mqtt pubcomp mid=%d\n", cli->mid);
client->invokeAckCallback(cli->mid);
break;
case MQTT_TYPE_SUBACK:
// printf("mqtt suback mid=%d\n", cli->mid);
client->invokeAckCallback(cli->mid);
break;
case MQTT_TYPE_UNSUBACK:
// printf("mqtt unsuback mid=%d\n", cli->mid);
client->invokeAckCallback(cli->mid);
break;
default:
break;
}
}
private:
// mid => ack callback
std::map<int, MqttCallback> ack_cbs;
std::mutex ack_cbs_mutex;
};
}
#endif
#endif // HV_MQTT_CLIENT_H_

View File

@ -0,0 +1,82 @@
#ifndef HV_MQTT_PROTOCOL_H_
#define HV_MQTT_PROTOCOL_H_
#include "hexport.h"
#define DEFAULT_MQTT_PORT 1883
#define MQTT_PROTOCOL_V31 3
#define MQTT_PROTOCOL_V311 4
#define MQTT_PROTOCOL_V5 5 // Not yet supproted
#define MQTT_PROTOCOL_NAME "MQTT"
#define MQTT_PROTOCOL_NAME_v31 "MQIsdp"
/*
* connect flags
* 0 1 2 3-4 5 6 7
* reserved clean_session has_will will_qos will_retain has_password has_username
*/
#define MQTT_CONN_CLEAN_SESSION 0x02
#define MQTT_CONN_HAS_WILL 0x04
#define MQTT_CONN_WILL_RETAIN 0x20
#define MQTT_CONN_HAS_PASSWORD 0x40
#define MQTT_CONN_HAS_USERNAME 0x80
typedef enum {
MQTT_TYPE_CONNECT = 1,
MQTT_TYPE_CONNACK = 2,
MQTT_TYPE_PUBLISH = 3,
MQTT_TYPE_PUBACK = 4,
MQTT_TYPE_PUBREC = 5,
MQTT_TYPE_PUBREL = 6,
MQTT_TYPE_PUBCOMP = 7,
MQTT_TYPE_SUBSCRIBE = 8,
MQTT_TYPE_SUBACK = 9,
MQTT_TYPE_UNSUBSCRIBE = 10,
MQTT_TYPE_UNSUBACK = 11,
MQTT_TYPE_PINGREQ = 12,
MQTT_TYPE_PINGRESP = 13,
MQTT_TYPE_DISCONNECT = 14,
} mqtt_type_e;
typedef enum {
MQTT_CONNACK_ACCEPTED = 0,
MQTT_CONNACK_REFUSED_PROTOCOL_VERSION = 1,
MQTT_CONNACK_REFUSED_IDENTIFIER_REJECTED = 2,
MQTT_CONNACK_REFUSED_SERVER_UNAVAILABLE = 3,
MQTT_CONNACK_REFUSED_BAD_USERNAME_PASSWORD = 4,
MQTT_CONNACK_REFUSED_NOT_AUTHORIZED = 5,
} mqtt_connack_e;
typedef struct mqtt_head_s {
unsigned char type: 4;
unsigned char dup: 1;
unsigned char qos: 2;
unsigned char retain: 1;
unsigned int length;
} mqtt_head_t;
typedef struct mqtt_message_s {
unsigned int topic_len;
const char* topic;
unsigned int payload_len;
const char* payload;
unsigned char qos;
unsigned char retain;
} mqtt_message_t;
BEGIN_EXTERN_C
#define DEFAULT_MQTT_PACKAGE_MAX_LENGTH (1 << 28) // 256M
HV_INLINE int mqtt_estimate_length(mqtt_head_t* head) {
// 28 bits => 4*7 bits varint
return 1 + 4 + head->length;
}
HV_EXPORT int mqtt_head_pack(mqtt_head_t* head, unsigned char buf[]);
HV_EXPORT int mqtt_head_unpack(mqtt_head_t* head, const unsigned char* buf, int len);
END_EXTERN_C
#endif // HV_MQTT_PROTOCOL_H_

View File

@ -0,0 +1,44 @@
#ifndef HV_NLOG_H_
#define HV_NLOG_H_
// nlog: extend hlog use hloop
/* you can recv log by:
* Windows: telnet ip port
* Linux: nc ip port
*/
/*
* @see examples/hloop_test.c
#include "hlog.h"
#include "nlog.h"
void timer_write_log(htimer_t* timer) {
static int cnt = 0;
hlogi("[%d] Do you recv me?", ++cnt);
}
int main() {
hloop_t* loop = hloop_new(0);
hlog_set_handler(network_logger);
nlog_listen(loop, DEFAULT_LOG_PORT);
htimer_add(loop, timer_write_log, 1000, INFINITE);
hloop_run(loop);
hloop_free(&loop);
}
*/
#include "hexport.h"
#include "hloop.h"
#define DEFAULT_LOG_PORT 10514
BEGIN_EXTERN_C
HV_EXPORT void network_logger(int loglevel, const char* buf, int len);
HV_EXPORT hio_t* nlog_listen(hloop_t* loop, int port);
END_EXTERN_C
#endif // HV_NLOG_H_

View File

@ -0,0 +1,233 @@
#ifndef HV_REQUESTS_H_
#define HV_REQUESTS_H_
/*
* Inspired by python requests
*
* @code
#include "requests.h"
int main() {
auto resp = requests::get("http://127.0.0.1:8080/ping");
if (resp == NULL) {
printf("request failed!\n");
} else {
printf("%d %s\r\n", resp->status_code, resp->status_message());
printf("%s\n", resp->body.c_str());
}
resp = requests::post("http://127.0.0.1:8080/echo", "hello,world!");
if (resp == NULL) {
printf("request failed!\n");
} else {
printf("%d %s\r\n", resp->status_code, resp->status_message());
printf("%s\n", resp->body.c_str());
}
return 0;
}
**/
#include <memory>
#include "HttpClient.h"
namespace requests {
typedef HttpRequestPtr Request;
typedef HttpResponsePtr Response;
typedef HttpResponseCallback ResponseCallback;
HV_INLINE Response request(Request req) {
auto resp = std::make_shared<HttpResponse>();
int ret = http_client_send(req.get(), resp.get());
return ret ? NULL : resp;
}
HV_INLINE Response request(http_method method, const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
auto req = std::make_shared<HttpRequest>();
req->method = method;
req->url = url;
if (&body != &NoBody) {
req->body = body;
}
if (&headers != &DefaultHeaders) {
req->headers = headers;
}
return request(req);
}
HV_INLINE Response head(const char* url, const http_headers& headers = DefaultHeaders) {
return request(HTTP_HEAD, url, NoBody, headers);
}
HV_INLINE Response get(const char* url, const http_headers& headers = DefaultHeaders) {
return request(HTTP_GET, url, NoBody, headers);
}
HV_INLINE Response post(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
return request(HTTP_POST, url, body, headers);
}
HV_INLINE Response put(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
return request(HTTP_PUT, url, body, headers);
}
HV_INLINE Response patch(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
return request(HTTP_PATCH, url, body, headers);
}
// delete is c++ keyword, we have to replace delete with Delete.
HV_INLINE Response Delete(const char* url, const http_headers& headers = DefaultHeaders) {
return request(HTTP_DELETE, url, NoBody, headers);
}
HV_INLINE int async(Request req, ResponseCallback resp_cb) {
return http_client_send_async(req, std::move(resp_cb));
}
// Sample codes for uploading and downloading files
HV_INLINE Response uploadFile(const char* url, const char* filepath, http_method method = HTTP_POST, const http_headers& headers = DefaultHeaders) {
auto req = std::make_shared<HttpRequest>();
req->method = method;
req->url = url;
req->timeout = 600; // 10min
if (req->File(filepath) != 200) return NULL;
if (&headers != &DefaultHeaders) {
req->headers = headers;
}
return request(req);
}
#ifndef WITHOUT_HTTP_CONTENT
HV_INLINE Response uploadFormFile(const char* url, const char* name, const char* filepath, std::map<std::string, std::string>& params = hv::empty_map, http_method method = HTTP_POST, const http_headers& headers = DefaultHeaders) {
auto req = std::make_shared<HttpRequest>();
req->method = method;
req->url = url;
req->timeout = 600; // 10min
req->content_type = MULTIPART_FORM_DATA;
req->SetFormFile(name, filepath);
for (auto& param : params) {
req->SetFormData(param.first.c_str(), param.second);
}
if (&headers != &DefaultHeaders) {
req->headers = headers;
}
return request(req);
}
#endif
typedef std::function<void(size_t sended_bytes, size_t total_bytes)> upload_progress_cb;
HV_INLINE Response uploadLargeFile(const char* url, const char* filepath, upload_progress_cb progress_cb = NULL, http_method method = HTTP_POST, const http_headers& headers = DefaultHeaders) {
// open file
HFile file;
int ret = file.open(filepath, "rb");
if (ret != 0) {
return NULL;
}
hv::HttpClient cli;
auto req = std::make_shared<HttpRequest>();
req->method = method;
req->url = url;
req->timeout = 3600; // 1h
if (&headers != &DefaultHeaders) {
req->headers = headers;
}
// connect
req->ParseUrl();
int connfd = cli.connect(req->host.c_str(), req->port, req->IsHttps(), req->connect_timeout);
if (connfd < 0) {
return NULL;
}
// send header
size_t total_bytes = file.size(filepath);
req->SetHeader("Content-Length", hv::to_string(total_bytes));
ret = cli.sendHeader(req.get());
if (ret != 0) {
return NULL;
}
// send file
size_t sended_bytes = 0;
char filebuf[40960]; // 40K
int filebuflen = sizeof(filebuf);
int nread = 0, nsend = 0;
while (sended_bytes < total_bytes) {
nread = file.read(filebuf, filebuflen);
if (nread <= 0) {
return NULL;
}
nsend = cli.sendData(filebuf, nread);
if (nsend != nread) {
return NULL;
}
sended_bytes += nsend;
if (progress_cb) {
progress_cb(sended_bytes, total_bytes);
}
}
// recv response
auto resp = std::make_shared<HttpResponse>();
ret = cli.recvResponse(resp.get());
if (ret != 0) {
return NULL;
}
return resp;
}
// see examples/wget.cpp
typedef std::function<void(size_t received_bytes, size_t total_bytes)> download_progress_cb;
HV_INLINE size_t downloadFile(const char* url, const char* filepath, download_progress_cb progress_cb = NULL) {
// open file
std::string filepath_download(filepath);
filepath_download += ".download";
HFile file;
int ret = file.open(filepath_download.c_str(), "wb");
if (ret != 0) {
return 0;
}
// download
auto req = std::make_shared<HttpRequest>();
req->method = HTTP_GET;
req->url = url;
req->timeout = 3600; // 1h
size_t content_length = 0;
size_t received_bytes = 0;
req->http_cb = [&file, &content_length, &received_bytes, &progress_cb]
(HttpMessage* resp, http_parser_state state, const char* data, size_t size) {
if (!resp->headers["Location"].empty()) return;
if (state == HP_HEADERS_COMPLETE) {
content_length = hv::from_string<size_t>(resp->GetHeader("Content-Length"));
} else if (state == HP_BODY) {
if (data && size) {
// write file
file.write(data, size);
received_bytes += size;
if (progress_cb) {
progress_cb(received_bytes, content_length);
}
}
}
};
auto resp = request(req);
file.close();
if (resp == NULL || resp->status_code != 200) {
return 0;
}
// check filesize
if (content_length != 0 && hv_filesize(filepath_download.c_str()) != content_length) {
remove(filepath_download.c_str());
return 0;
}
rename(filepath_download.c_str(), filepath);
return hv_filesize(filepath);
}
}
#endif // HV_REQUESTS_H_

Some files were not shown because too many files have changed in this diff Show More