main
parent
bac55f5902
commit
3fc451546d
|
@ -3,3 +3,5 @@
|
|||
/appRelease/test.release
|
||||
/sdk/include/SQLiteCpp
|
||||
/appRelease/mysql/mysql-arm.tar
|
||||
/applications/ems_datahubs/.vs
|
||||
/applications/ems_datahubs/obj
|
||||
|
|
|
@ -223,3 +223,110 @@ bool OpDatabase::queryUser(const std::string& user_id, const std::string& passwd
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
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:
|
||||
//std::unique_ptr<sql::Connection> m_pDbConnection;
|
||||
sql::Connection* m_pDbConnection;
|
||||
|
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
|||
#include "qtlockedfile.h"
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
}
|
|
@ -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
|
|
@ -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.
|
||||
*/
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
*/
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
*/
|
|
@ -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
|
|
@ -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)
|
||||
}
|
|
@ -29,7 +29,7 @@ win32:LIBS += Ws2_32.lib
|
|||
win32:LIBS += $$PWD/..\..\SDK\lib\libdes.lib
|
||||
win32:LIBS += $$PWD/..\..\SDK\lib\OpenSSL_VC\libcrypto64MD.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
|
||||
}
|
||||
|
||||
|
@ -40,12 +40,10 @@ win32:LIBS += Ws2_32.lib
|
|||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
mainwindow.cpp \
|
||||
cloggermaganer.cpp
|
||||
mainwindow.cpp
|
||||
|
||||
HEADERS += \
|
||||
mainwindow.h \
|
||||
cloggermaganer.h
|
||||
mainwindow.h
|
||||
|
||||
FORMS += \
|
||||
mainwindow.ui
|
||||
|
@ -54,3 +52,6 @@ FORMS += \
|
|||
qnx: target.path = /tmp/$${TARGET}/bin
|
||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
|
||||
RESOURCES += \
|
||||
emscfgres.qrc
|
||||
|
|
|
@ -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 |
|
@ -17,7 +17,6 @@
|
|||
#include <hv/hsocket.h>
|
||||
#include <hv/hssl.h>
|
||||
|
||||
#include "cloggermaganer.h"
|
||||
|
||||
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();
|
||||
|
||||
hlog_set_file(logFilePath.c_str());
|
||||
hlogi("=========--- Welcome to the Earth ---=========\n");
|
||||
hlogi("%s version: %s\n", argv[0], "1.1.0");
|
||||
hlogi("=========--- Welcome to the Earth ---=========");
|
||||
hlogi("%s version: %s", argv[0], "1.1.0");
|
||||
hlog_fsync();
|
||||
hlogi("%s versionversionversionversionversionversion: %s\n", argv[0], "1.1.0");
|
||||
|
||||
|
||||
QTranslator translator;
|
||||
const QStringList uiLanguages = QLocale::system().uiLanguages();
|
||||
|
@ -55,6 +52,13 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
|
||||
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();
|
||||
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.show();
|
||||
return app.exec();
|
||||
int ret = app.exec();
|
||||
hlogi("=========--- I'll be back! ---=========");
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,29 @@
|
|||
#include "mainwindow.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
#include <QPixmap>
|
||||
#include <QMessageBox>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionValidator>
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
, ui(new Ui::MainWindow)
|
||||
{
|
||||
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()
|
||||
|
@ -13,3 +31,7 @@ MainWindow::~MainWindow()
|
|||
delete ui;
|
||||
}
|
||||
|
||||
void MainWindow::setIp(const QString &ip)
|
||||
{
|
||||
ui->serverIp->setText(ip);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#ifndef MAINWINDOW_H
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
@ -15,6 +15,9 @@ public:
|
|||
MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
protected:
|
||||
void setIp(const QString &ip);
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
};
|
||||
|
|
|
@ -2,20 +2,217 @@
|
|||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
<width>551</width>
|
||||
<height>352</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::NoContextMenu</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget"/>
|
||||
<widget class="QMenuBar" name="menubar"/>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
<property name="windowTitle">
|
||||
<string>EMS Configurer</string>
|
||||
</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>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "openjson.h"
|
||||
#include "iconv-utils.h"
|
||||
#include "kutilities.h"
|
||||
#include "opmysql.h"
|
||||
#include "mqtt_msg.h"
|
||||
|
||||
EventHandler::EventHandler()
|
||||
{
|
||||
|
@ -58,6 +60,23 @@ void EventHandler::onRecvHandler(hio_t* io, void* buf, int readbytes)
|
|||
|
||||
if( Frame_DeviceData_Request == pReadFrame->frame_type )
|
||||
{
|
||||
handleGatherData(io, buf, readbytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EventHandler::handleGatherData(hio_t* io, void* buf, int readbytes)
|
||||
{
|
||||
assert(buf);
|
||||
|
||||
__USING_NAMESPACE_HJ__;
|
||||
|
||||
MessageFrame respFrame;
|
||||
MessageFrame* pReadFrame = (MessageFrame*)buf;
|
||||
hlogi("<=== reveive device data request");
|
||||
|
||||
OpenJson json;
|
||||
|
@ -66,8 +85,18 @@ void EventHandler::onRecvHandler(hio_t* io, void* buf, int readbytes)
|
|||
|
||||
hlogi("<=== recieve buffer coding is [%s]", buf_code == GBK ? "GBK" : (buf_code == UTF8 ? "UTF8" : "UNKNOWN CODING"));
|
||||
|
||||
MessageData* pData = (MessageData*)pReadFrame->frame_content;
|
||||
|
||||
#ifdef _DEBUG
|
||||
hlogd("<=== MessageData structure [\n%s\n]", printHex(pData, pReadFrame->frame_len).c_str());
|
||||
#endif
|
||||
|
||||
//这里将帧内帧的内容转换为字符串,符合json格式的字符串,详见mqtt_msg.h的MessageData结构定义
|
||||
std::string msg((char*)pReadFrame->frame_content + 8, pReadFrame->frame_len - 8);
|
||||
std::string msg((char*)pReadFrame->frame_content + MSG_HEADER_LENGTH, pReadFrame->frame_len - MSG_HEADER_LENGTH);
|
||||
|
||||
#ifdef _DEBUG
|
||||
hlogd("<=== json content [\n%s\n]", msg.c_str());
|
||||
#endif
|
||||
|
||||
if( buf_code == CODING::GBK
|
||||
|| buf_code == CODING::UNKOWN )
|
||||
|
@ -190,7 +219,7 @@ void EventHandler::onRecvHandler(hio_t* io, void* buf, int readbytes)
|
|||
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);
|
||||
OpDatabase::getInstance()->InsertMessage(timestamp, msg_type, fsucode, out_compress, (int)pData->mqtt_topic, (int)pData->device_id);
|
||||
}
|
||||
|
||||
if( msg_type == "web-read" )
|
||||
|
@ -206,7 +235,7 @@ void EventHandler::onRecvHandler(hio_t* io, void* buf, int readbytes)
|
|||
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);
|
||||
OpDatabase::getInstance()->InsertMessage(timestamp, msg_type, fsucode, out_compress, (int)pData->mqtt_topic, (int)pData->device_id);
|
||||
}
|
||||
//delete[] pTmp;
|
||||
}
|
||||
|
@ -217,6 +246,4 @@ void EventHandler::onRecvHandler(hio_t* io, void* buf, int readbytes)
|
|||
hio_write(io, (void*)&respFrame, sizeof respFrame);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -10,5 +10,9 @@ public:
|
|||
|
||||
public:
|
||||
static void onRecvHandler(hio_t* io, void* buf, int readbytes);
|
||||
|
||||
protected:
|
||||
//处理采集程序上传的数据,以JSON数据上报
|
||||
static void handleGatherData(hio_t* io, void* buf, int readbytes);
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
|
||||
__NAMESPACE_BEGIN__(HJ)
|
||||
|
||||
#define FRAME_HEADER_LENGTH (5)
|
||||
#define FRAME_TAILE_LENGTH (4)
|
||||
|
||||
typedef enum tagErrorCode : unsigned char
|
||||
{
|
||||
ERR_OK = 0X00,
|
||||
|
|
|
@ -6,14 +6,6 @@
|
|||
#include <fstream>
|
||||
#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/hmain.h>
|
||||
#include <hv/iniparser.h>
|
||||
|
@ -21,6 +13,8 @@
|
|||
#include <hv/hsocket.h>
|
||||
#include <hv/hssl.h>
|
||||
|
||||
#include "kdefine.h"
|
||||
#include "opmysql.h"
|
||||
#include "eventhandler.h"
|
||||
|
||||
#ifndef TEST_UNPACK
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
#pragma once
|
||||
#include "kdefine.h"
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
__NAMESPACE_BEGIN__(HJ)
|
||||
|
||||
#define MSG_HEADER_LENGTH (8)
|
||||
|
||||
typedef enum tagTopic : char
|
||||
{
|
||||
GateWayPublicTopic_Server = 0,
|
||||
ServerPublicTopic_GateWay = 1,
|
||||
}FrameType, MQTT_Topic;
|
||||
} MQTT_Topic;
|
||||
|
||||
typedef enum tagDataType : char
|
||||
{
|
||||
|
@ -19,7 +25,7 @@ typedef enum tagDataType : char
|
|||
DT_GATEWAY_WRITE = 7,
|
||||
FRAME_REQUEST = 8,
|
||||
FRAME_RESPONSE = 9,
|
||||
}FrameDataType,MQTT_DataType;
|
||||
}MQTT_DataType;
|
||||
|
||||
/*
|
||||
{
|
||||
|
@ -50,6 +56,6 @@ typedef struct tagMsgData
|
|||
char content_data[1]; //MQTT包字符串的内容
|
||||
}MessageData;
|
||||
|
||||
|
||||
__NAMESPACE_END__(HJ)
|
||||
|
||||
#pragma pack()
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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
Loading…
Reference in New Issue