2026/02/16

Apache Drill

Apache Drill 使用 Java 撰寫, 它參考了谷歌的 BigQuery 的想法,是一個開放原始碼、可擴展、支援複雜資料的分散式 columnar SQL 查詢引擎, 它可以訪問 Apache Hadoop 的 HDFS,從各種主流文件格式(例如 Parquet、JSON 和 CSV)以及支援的資料庫中讀取數據。 Apache Drill 的優點就是它可以使用 SQL 查詢語言查詢各種不同格式的資料並且與資料互動。

下載程式後解壓縮放在某個目錄,使用 embedded mode 檢查是否可以執行:

bin/drill-embedded

結束程式:

!quit

Distributed Mode 需要 Apache ZooKeeper,並且在 drill-override.conf 設定相關的設定。

drill.exec: {
  cluster-id: "drillbits1",
  zk.connect: "localhost:2181"
}

首先要先啟動 ZooKeeper service。
再來是啟動 Drill server:

bin/drillbit.sh start

停止 server:

bin/drillbit.sh stop

執行 sqlline 驗證可以連線到 Drill server:

bin/sqlline -u jdbc:drill:schema=dfs;zk=localhost

SSL/TLS

使用 keytool 建立 keystroe:

keytool -genkeypair -alias server \
-dname "CN=localhost, OU=IT Department, O=Orange Inc. ,L=Taipei, S=Taiwan,C=TW" \
-ext SAN=DNS:localhost,IP:127.0.0.1 \
-keyalg RSA -keysize 2048 -sigalg SHA256withRSA -storetype PKCS12 \
-validity 3650 \
-keypass password -keystore ./trusted.keystore -storepass password

在 drill-override.conf 設定相關的設定。

drill.exec: {
  cluster-id: "drillbits1",
  zk.connect: "localhost:2181",
  ssl: {
    protocol: "TLSv1.3",
    keyStoreType: "pkcs12",
    keyStorePath: "/home/danilo/Programs/drill/conf/trusted.keystore",
    keyStorePassword: "password",
    trustStoreType: "pkcs12",
    trustStorePath: "/home/danilo/Programs/drill/conf/trusted.keystore",
    trustStorePassword: "password"
  },
  security.user.encryption.ssl: {
    enabled: true,
  },
}

在 drill-override.conf 設定相關的設定,對 WEB UI 允許 SSL/TLS:

drill.exec: {
  cluster-id: "drillbits1",
  zk.connect: "localhost:2181",
  ssl: {
    protocol: "TLSv1.3",
    keyStoreType: "pkcs12",
    keyStorePath: "/home/danilo/Programs/drill/conf/trusted.keystore",
    keyStorePassword: "password",
    trustStoreType: "pkcs12",
    trustStorePath: "/home/danilo/Programs/drill/conf/trusted.keystore",
    trustStorePassword: "password"
  },
  security.user.encryption.ssl: {
    enabled: true,
  },
  http: {
    enabled: true,
    ssl_enabled: true,
  },
}

Plain Security

在 drill-override.conf 設定相關的設定,允許 Plain Security:

drill.exec: {
  cluster-id: "drillbits1",
  zk.connect: "localhost:2181",
  ssl: {
    protocol: "TLSv1.3",
    keyStoreType: "pkcs12",
    keyStorePath: "/home/danilo/Programs/drill/conf/trusted.keystore",
    keyStorePassword: "password",
    trustStoreType: "pkcs12",
    trustStorePath: "/home/danilo/Programs/drill/conf/trusted.keystore",
    trustStorePassword: "password"
  },
  security: {
    auth.mechanisms : ["PLAIN"],
  },
  security.user.auth {
    enabled: true,
    packages += "org.apache.drill.exec.rpc.user.security",
    impl: "pam4j",
    pam_profiles: [ "sudo", "login" ]
  },
  security.user.encryption.ssl: {
    enabled: true,
  },
  http: {
    enabled: true,
    ssl_enabled: true,
    auth: {
      mechanisms: ["FORM"],
    },
  },
}

Apache Drill 支援使用 libpam4j 或者是 jpam 作為 PAM Authenticator。 其中 libpam4j 已被內建,所以只要設定正確就可以使用。

相關連結

2026/02/12

Apache HBase

Apache HBase 是一個開放原始碼、分散式、 列存儲 (Wide column store) 的 NoSQL 資料庫。 它參考了谷歌的 BigTable 論文,實現的程式語言為 Java,建構於 Apache Hadoop HDFS 之上, 其核心架構採用 Master-Slave 模式,主要包含協調服務 ZooKeeper、 管理節點 HMaster、處理資料讀寫的 RegionServers,以及負責儲存資料的 HDFS。 Apache HBase 安裝與 Apache Hadoop 一樣分為三種模式:Standalone, Pseudo-Distributed 與Fully-Distributed。 Fully-Distributed 只能運行在Apache Hadoop 上面。

將下載的 HBase 檔案放到 /home/danilo/Programs/hbase,並且設定好環境變數:

export HBASE_HOME=/home/danilo/Programs/hbase
export PATH=$PATH:$HBASE_HOME/bin

使用 Pseudo-Distributed 與 local file system 安裝模式,修改 hbase-site.xml

<configuration>
  <property>
    <name>hbase.rootdir</name>
    <value>file:///home/danilo/Programs/hbase</value>
  </property>
  <property>
    <name>hbase.tmp.dir</name>
    <value>./tmp</value>
  </property>
  <property>
    <name>hbase.unsafe.stream.capability.enforce</name>
    <value>false</value>
  </property>
  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
  </property>  
  <property>
    <name>hbase.zookeeper.property.dataDir</name>
    <value>/var/lib/zookeeper</value>
  </property>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>localhost</value>
  </property>
  <property>
    <name>hbase.zookeeper.property.clientPort</name>
    <value>2181</value>
  </property>  
</configuration>

修改 hbase-env.sh

# Tell HBase whether it should manage it's own instance of ZooKeeper or not.
export HBASE_MANAGES_ZK=false

(HBase 可以設定是否自行管理自己的 ZooKeeper 叢集,這裡選擇使用外部的 ZooKeeper service。)

首先需要啟動 ZooKeeper service。
再來啟動 HBase:

bin/start-hbase.sh

停止 HBase:

bin/stop-hbase.sh

如果要執行 HBase shell:

bin/hbase shell

REST interface

啟動 HBase REST server(前景,使用 -port 指定 port):

bin/hbase rest start -p 8090

啟動 HBase REST server(背景,使用 -port 指定 port):

bin/hbase-daemon.sh start rest -p 8090

停止 HBaseHBase REST server(背景):

bin/hbase-daemon.sh stop rest

SSL/TLS

使用 keytool 建立 keystroe:

keytool -genkeypair -alias server \
-dname "CN=localhost, OU=IT Department, O=Orange Inc. ,L=Taipei, S=Taiwan,C=TW" \
-ext SAN=DNS:localhost,IP:127.0.0.1 \
-keyalg RSA -keysize 2048 -sigalg SHA256withRSA -storetype PKCS12 \
-validity 3650 \
-keypass password -keystore ./trusted.keystore -storepass password

修改 hbase-site.xml

<configuration>
  <property>
    <name>hbase.rootdir</name>
    <value>file:///home/danilo/Programs/hbase</value>
  </property>
  <property>
    <name>hbase.tmp.dir</name>
    <value>./tmp</value>
  </property>
  <property>
    <name>hbase.unsafe.stream.capability.enforce</name>
    <value>false</value>
  </property>
  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
  </property>  
  <property>
    <name>hbase.zookeeper.property.dataDir</name>
    <value>/var/lib/zookeeper</value>
  </property>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>localhost</value>
  </property>
  <property>
    <name>hbase.zookeeper.property.clientPort</name>
    <value>2181</value>
  </property>
  <property>
    <name>hbase.rest.ssl.enabled</name>
    <value>true</value>
  </property> 
  <property>
    <name>hbase.rest.ssl.keystore.store</name>
    <value>/home/danilo/Programs/hbase/conf/trusted.keystore</value>
  </property> 
  <property>
    <name>hbase.rest.ssl.keystore.password</name>
    <value>password</value>
  </property> 
  <property>
    <name>hbase.rest.ssl.keystore.type</name>
    <value>pkcs12</value>
  </property>
  <property>
    <name>hbase.rest.ssl.truststore.store</name>
    <value>/home/danilo/Programs/hbase/conf/trusted.keystore</value>
  </property>
  <property>
    <name>hbase.rest.ssl.truststore.password</name>
    <value>password</value>
  </property>
  <property>
    <name>hbase.rest.ssl.truststore.type</name>
    <value>pkcs12</value>
  </property>
</configuration>

再來重新啟動 HBase server 與 HBase REST server 即可。

相關連結

2026/02/08

Apache ZooKeeper

Apache ZooKeeper 是 Apache 軟體基金會的一個軟體專案, 使用 Java 撰寫,它為大型分散式計算提供開源的分散式組態設定服務、同步服務和命名註冊。 ZooKeeper 曾經是 Hadoop 的一個子專案,但現在是一個獨立的頂級專案。

下面為設定 single node (Standalone Operation) 的方式。

將下載的 Apache ZooKeeper 檔案解壓縮以後放到 /opt/zookeeper,並且建立 /var/lib/zookeeper 目錄。

複製 conf/zoo_sample.cfg 為 conf/zoo.cfg,確定有下列的設定:

tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181

啟動 server:

sudo bin/zkServer.sh start

如果要停止 server:

sudo bin/zkServer.sh stop

確定可以連線:

bin/zkCli.sh -server localhost:2181

接下來嘗試使用 systedm 管理,在 /usr/lib/systemd/system 目銾下建立 zookeeper.service

[Unit]
Description=ZooKeeper Service
Documentation=http://zookeeper.apache.org
Requires=network.target
After=network.target

[Service]
Type=forking
User=root
Group=root
SuccessExitStatus=143 # For stop service but status code is failed
ExecStart=/opt/zookeeper/bin/zkServer.sh start /opt/zookeeper/conf/zoo.cfg
ExecStop=/opt/zookeeper/bin/zkServer.sh stop /opt/zookeeper/conf/zoo.cfg
ExecReload=/opt/zookeeper/bin/zkServer.sh restart /opt/zookeeper/conf/zoo.cfg
WorkingDirectory=/var/lib/zookeeper
TimeoutSec=30
Restart=on-failure

[Install]
WantedBy=default.target

而後使用 systemd 啟動服務:

sudo systemctl start zookeeper.service

而後使用 systemd 查詢服務狀態:

sudo systemctl status zookeeper.service

而後使用 systemd 停止服務:

sudo systemctl stop zookeeper.service

相關連結

2026/02/04

Apache Geode

簡介

Apache Geode 是一個數據管理平台, 它為廣泛分佈的雲端架構中的資料密集型應用程式提供即時、一致的存取, 一般而言作為 In-Memory Data Grid (IMDG)、快取 (cache) 以及需要即時處理的場合使用。 Apache Geode 提供了 SQL-like 查詢語言,稱為 OQL (Object Query Language), Apache Geode 以 Java object 的方式儲存資料,所以選擇使用 OQL 查詢儲存的資料。

下載 Apache Geode 以後解壓縮放到某個目錄。gfsh 為 Apache Geode 用來管理的 shell tool。

執行 gfsh。下面是在 gfsh 執行的指令,資料來自於 Apache Geode in 15 Minutes or Less 的教學。

Locator 是 Geode 行程 (processes),它告訴新連線的成員正在執行的成員在哪裡,並為伺服器使用提供負載平衡。

start locator --name=locator1

Geode 提供了 web 界面的監控界面,下面是啟動的指令。預設使用者為 admin,密碼為 admin

start pulse

Geode server 是一個行程 (process),它作為叢集中一個長期運行且可配置的成員而存在。 Geode server 主要用於託管長期運行的資料區域,以及運行標準的 Geode 行程,例如用戶端/伺服器配置中的伺服器。

start server --name=server1 --server-port=40411

Regions 是 Geode 叢集的核心建置模組,用於組織資料。在此練習中建立的 Region 採用複製機制在叢集成員之間複製數據, 並利用持久化機制將資料儲存到磁碟。

create region --name=regionA --type=REPLICATE_PERSISTENT

列出目前的 regions:

list regions

列出 Geode 叢集的成員:

list members

描述 Geode region regionA 的資料:

describe region --name=regionA

下面使用 put 新增資料以及使用 query 查詢資料。

put --region=regionA --key="1" --value="one"
put --region=regionA --key="2" --value="two"
query --query="select * from /regionA"

如果你需要刪除一個 region,可以這樣做:

destroy region --name=regionA

如果要停止 server:

stop server --name=server1

關閉系統,包括 locator。

shutdown --include-locators=true

REST

Geode 讓使用者能夠使用 REST 介面存取資料。

啟動一個 locator。

start locator --name=locator1

並且使用以下的設定:

configure pdx --read-serialized=true --disk-store

然後在啟動 Geode server 時加入 --start-rest-api 選項。

start server --name=server1 --server-port=40411 \
--start-rest-api=true \
--http-service-port=8080 --http-service-bind-address=localhost

使用 curl 驗證是否可以使用:

curl -i http://localhost:8080/geode/v1

SSL

使用 keytool 建立 keystroe:

keytool -genkeypair -alias server \
-dname "CN=localhost, OU=IT Department, O=Orange Inc. ,L=Taipei, S=Taiwan,C=TW" \
-ext SAN=DNS:localhost,IP:127.0.0.1 \
-keyalg RSA -keysize 2048 -sigalg SHA256withRSA -storetype PKCS12 \
-validity 3650 \
-keypass password -keystore ./trusted.keystore -storepass password

在 Geode 的 config 目錄下建立一個新的檔案 gfsecurity.properties。 Apache Geode 使用 ssl-enabled-components 設定不同組件間的通訊是否需要使用 SSL/TLS。 all 表示全部都要使用,這裡設定為 web,表示使用在 REST 介面。

ssl-enabled-components=web
ssl-protocols=TLSv1.2,TLSv1.3
ssl-ciphers=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384
ssl-keystore=/home/danilo/Programs/geode/config/trusted.keystore
ssl-keystore-password=password
ssl-keystore-type=pkcs12
ssl-truststore=/home/danilo/Programs/geode/config/trusted.keystore
ssl-truststore-password=password
ssl-truststore-type=pkcs12

使用 gfsh 啟動一個 locator。

start locator --name=locator1 --port=12345 \
--security-properties-file=/home/danilo/Programs/geode/config/gfsecurity.properties

並且使用以下的設定:

configure pdx --read-serialized=true --disk-store

使用 gfsh 啟動一個 server。

start server --name=server1 --server-port=40411 \
--start-rest-api=true \
--http-service-port=8080 --http-service-bind-address=localhost \
--security-properties-file=/home/danilo/Programs/geode/config/gfsecurity.properties

使用 curl 驗證是否可以使用:

curl -k -i https://localhost:8080/geode/v1

Authentication

以下的方式在 Java 24/25 以後,因為 SecurityManager 被禁止而無法使用。 In Java 17, the Security Manager was deprecated for removal under JEP 411. With JDK 24, its functionality will be effectively disabled. So you could not setup HTTP Basic Authentication support for Apache Geode by using SecurityManager since JDK 24.

將下列的內容儲存為 security.json,並且放到各個 locator 與 server 的目錄下。

{
  "roles": [
    {
      "name": "cluster",
      "operationsAllowed": [
        "CLUSTER:MANAGE",
        "CLUSTER:WRITE",
        "CLUSTER:READ"
      ]
    },
    {
      "name": "data",
      "operationsAllowed": [
        "DATA:MANAGE",
        "DATA:WRITE",
        "DATA:READ"
      ]
    },
    {
      "name": "region1&2Reader",
      "operationsAllowed": [
        "DATA:READ"
      ],
      "regions": ["region1", "region2"]
    }
  ],
  "users": [
    {
      "name": "super-user",
      "password": "1234567",
      "roles": [
        "cluster",
        "data"
      ]
    },
    {
      "name": "joebloggs",
      "password": "1234567",
      "roles": [
        "data"
      ]
    }
  ]
}

使用 gfsh 啟動一個 locator。

start locator --name=locator1 --port=12345 \
--security-properties-file=/home/danilo/Programs/geode/config/gfsecurity.properties \
--J=-Dgemfire.security-manager=org.apache.geode.examples.security.ExampleSecurityManager \
--classpath=.

連線到 locator 需要驗證:

connect --locator=localhost[12345] --user=super-user --password=1234567

並且使用以下的設定:

configure pdx --read-serialized=true --disk-store

使用 gfsh 啟動一個 server。

start server --name=server1 --locators=localhost[12345] --server-port=40411 \
--start-rest-api=true \
--http-service-port=8080 --http-service-bind-address=localhost \
--security-properties-file=/home/danilo/Programs/geode/config/gfsecurity.properties \
--J=-Dgemfire.security-manager=org.apache.geode.examples.security.ExampleSecurityManager \
--classpath=. --user=super-user --password=1234567

Memcached

Apache Geode 提供了 memcached 協議相容的介面。

使用 gfsh 啟動一個 locator。

start locator --name=locator1

並且在 config 新增或者修改 cache.xml,使用下列的設定:

<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns="http://geode.apache.org/schema/cache" 
       xsi:schemaLocation="http://geode.apache.org/schema/cache http://geode.apache.org/schema/cache/cache-1.0.xsd"
       version="1.0">
  <region name="gemcached">
    <region-attributes refid="PARTITION" />
  </region>
</cache>

使用 gfsh 啟動一個 server。

start server --name=server1 --server-port=40411 \
--memcached-port=11211 --memcached-bind-address=localhost \
--memcached-protocol=BINARY \
--cache-xml-file=/home/danilo/Programs/geode/config/cache.xml

--memcached-protocol 可以設為 ASCII 或者是 BINARY。 ASCII 是 libMemcached 的預設值,如果要使用 BINARY 需要設定。

使用 memcached-for-Tcl 進行驗證。

package require Memcache

memcache server add localhost 11211
memcache behavior MEMCACHED_BEHAVIOR_BINARY_PROTOCOL 1
memcache set moo "cows go moo"
memcache get moo result
puts $result

相關連結

2026/02/02

BaseX database

BaseX 是一個使用 Java 撰寫的 XML 資料庫, 也可以作為 XQuery processor 使用,採用 BSD-3-Clause 授權, 使用 XQuery 作為其查詢語言,實作 XQuery 多項相關標準, 並且設計了自己的 Server Protocol

basexserver 預設的 port 為 1984,初次啟動需要使用 -cPASSWORD 設定 admin 的密碼。

basexserver -cPASSWORD

下面使用 basexclient 連線到 server(-U 為 username,-P 為 password)。

basexclient -Uadmin -Padmin

建立一個新的使用者 danilo,並且給予全域的 CREATE 權限(BaseX 將權限分為 global 與 local, global 有 NONE, READ, WRITE, CREATE, ADMIN 的區別,而 local 則是 NONE, READ, WRITE)。

CREATE USER danilo password
ALTER PASSWORD danilo password
GRANT CREATE TO danilo

其中 CREATE USER 可以在建立使用者的時候同時設定密碼,在之後如果要修改密碼可以使用 ALTER PASSWORD 修改。

如果要移除使用者:

DROP USER danilo

使用 basexclient 建立新的資料庫:

  • Create an empty database
    CREATE DB <database_name>
    
  • To create a database with initial data
    CREATE DB <database_name> <path/to/input>
    
  • To add multiple documents to a newly created empty database
    CREATE DB store
    ADD factbook.xml
    ADD https://files.basex.org/xml/xmark.xml
    

如果使用 XQuery 建立資料庫:

db:create('myDatabaseName', 'path/to/input.xml')

如果要列出目前的資料庫:

LIST

如果使用 XQuery 列出目前的資料庫:

db:list()

使用 basexclient 刪除資料庫:

DROP DB [name]

如果使用 XQuery 刪除資料庫:

db:drop($name)

Tools

BaseX 提供了一些工具可以使用。 basexserver 為 BaseX database server,而 basexclient 則是其 client 的程式。 basexgui 使用 Java Swing 撰寫,是 BaseX 的圖型界面管理工具,可以用來管理其資料庫以及測試一些 XQuery 語句。 basex 可以用來執行 BaseX 或者是 XQuery 的指令。

下面是一個使用 basex 的例子,用來解 1-9位數不重複印出來的練習問題,
使用者輸入1 印1-9
使用者輸入2 印1-98 (11, 22, 33等重複的不印)
使用者輸入3 印1-987 (121, 988, 667等有重複的不印)

basex -bnumber=9 -Qcount.xq

參數 -b 用來設定外部變數。count.xq 的內容如下:

declare variable $number external;
if (xs:integer($number) lt 1 or xs:integer($number) gt 9) then (
    <error>
      <message>Invalid data</message>
    </error>
) else (
    let $max := xs:integer(math:exp10($number) - 1)
    for $i in 1 to $max
    return
    if(not(matches(fn:string($i), '1.*1|2.*2|3.*3|4.*4|5.*5|6.*6|7.*7|8.*8|9.*9|0.*0'))) then (
        $i
    )
)

相關連結

XQuery 學習筆記

XML 簡介

XML stands for Extensible Markup Language. It is a text-based markup language derived from Standard Generalized Markup Language (SGML).

XML is a markup language that defines set of rules for encoding documents in a format that is both human-readable and machine-readable. Following example shows how XML markup looks, when embedded in a piece of text −

<message>
   <text>Hello, world!</text>
</message>

You can notice there are two kinds of information in the above example −

  • Markup (tag)
  • The text, or the character data

XML 文件可以使用樹 (tree) 來表示。一個 XML 樹開始於 root element, 並且從 root element 開始其 child elements 的分支。 XML elements 可以有屬性 (attributes),例如下面的例子:

<note date="2023/04/28">
  <name>Orange</name>
</note> 

The XML document can optionally have an XML declaration. It is written as follows −

<?xml version = "1.0" encoding = "UTF-8"?>

XPath 簡介

XPath (XML Path Language) is an expression language designed to support the query or transformation of XML documents. It was defined by the World Wide Web Consortium (W3C) in 1999.

In XPath, there are seven kinds of nodes: element, attribute, text, namespace, processing-instruction, comment, and root nodes.

The most important kind of expression in XPath is a location path. A location path consists of a sequence of location steps. Each location step has three components:

  • an axis
  • a node test
  • zero or more predicates.
Axis specifiers in XPath
Full syntax Abbreviated syntax Notes
ancestor
ancestor-or-self
attribute @ @abc is short for attribute::abc
child xyz is short for child::xyz
descendant
descendant-or-self // // is short for /descendant-or-self::node()/
following
following-sibling
namespace
parent .. .. is short for parent::node()
preceding
preceding-sibling
self . . is short for self::node()

Node tests may consist of specific node names or more general expressions. In the case of an XML document in which the namespace prefix gs has been defined, //gs:enquiry will find all the enquiry elements in that namespace, and //gs:* will find all elements, regardless of local name, in that namespace.

Other node test formats are:

comment()
finds an XML comment node, e.g. <!-- Comment -->
text()
finds a node of type text excluding any children, e.g. the hello in <k>hello<m> world</m></k>
processing-instruction()
finds XML processing instructions such as <?php echo $a; ?>. In this case, processing-instruction('php') would match.
node()
finds any node at all.

Predicates, written as expressions in square brackets, can be used to filter a node-set according to some condition. For example, a returns a node-set (all the a elements which are children of the context node), and a[@href='help.php'] keeps only those elements having an href attribute with the value help.php.

XQuery 簡介

XQuery 是由 W3C 定義的查詢語言,程式風格為函數式程式設計 (functional programming), 建立在 XPath 的基礎上,使用 Xpath 表達要查詢的路徑資訊, 專門用於在結構化或半結構化 XML 資料中進行搜尋、操作和轉換,類似 SQL 之於關聯式資料庫。 在 XQuery 3.1 增加了對於 JSON 的支援,所以 XQuery 也可以用來處理 JSON 資料(如果你想要這樣做的話)。

目前 XQuery 的版本如下:

  • XQuery 1.0 became a W3C Recommendation on January 23, 2007
  • XQuery 3.0 became a W3C Recommendation on April 8, 2014
  • XQuery 3.1 became a W3C Recommendation on March 21, 2017

XQuery 所有用於執行計算的 XQuery 語句都是表達式 (expressions),其核心是 FLWOR,用於更複雜的查詢:

  • For: 循環存取資料項目。
  • Let: 賦值。
  • Where: 設定篩選條件。
  • Order By: 排序結果。
  • Return: 定義輸出結果。

下面是一個 XQuery 的 Hello World 例子:

let $message := 'Hello World!'
return
<results>
   <message>{$message}</message>
</results>

下面是一個 XML 檔案 books.xml:

<?xml version="1.0" encoding="UTF-8"?>
<books>
   
   <book category="JAVA">
      <title lang="en">Learn Java in 24 Hours</title>
      <author>Robert</author>
      <year>2005</year>
      <price>30.00</price>
   </book>
   
   <book category="DOTNET">
      <title lang="en">Learn .Net in 24 hours</title>
      <author>Peter</author>
      <year>2011</year>
      <price>70.50</price>
   </book>
   
   <book category="XML">
      <title lang="en">Learn XQuery in 24 hours</title>
      <author>Robert</author>
      <author>Peter</author> 
      <year>2013</year>
      <price>50.00</price>
   </book>
   
   <book category="XML">
      <title lang="en">Learn XPath in 24 hours</title>
      <author>Jay Ban</author>
      <year>2010</year>
      <price>16.50</price>
   </book>
   
</books>

XQuery 可以使用 doc() 函數取得 XML 檔案的內容。下面就是一個 XQuery 的例子:

(: XQuery Comment :)
let $books := (doc("books.xml")/books/book)
return <results>
{
   for $x in $books
   where $x/price>30
   order by $x/price
   return $x/title
}
</results>

XQuery 可以使用 for 執行迴圈任務,如下面所示:

for $n in 1 to 10
return
    <result>{$n}</result>

Sequences represent an ordered collection of items where items can be of similar or of different types. Sequences are created using parenthesis with strings inside quotes or double quotes and numbers as such. XML elements can also be used as the items of a sequence.

Viewing items in a sequence

let $sequence := ('a', 'b', 'c', 'd', 'e', 'f')
let $count := count($sequence)
return
   <results>
      <count>{$count}</count>
      <items>
       {
         for $item in $sequence
         return
           <item>{$item}</item>
       }
      </items>
   </results>

XQuery 內建支援 Regular Expressions,下面是一個例子:

let $input := 'TutorialsPoint Simply Easy Learning'
return (
  matches($input, 'Hello') =  true(),
  matches($input, 'T.* S.* E.* L.*') =  true()
)

XQuery 使用 if-then-else 支援條件判斷。

<result>
{
   for $book in doc("books.xml")/books/book
   return
   if ($book/@category = "XML") then (
     $book/title
   )
}
</result>

XQuery 3.0 加入 lambda functions 的支援,下面是一個例子:

let $fn := function($x, $y) { $x + $y }
return $fn(99, 2)

XQuery 3.0 加入 switch 的支援,下面是一個例子:

for $fruit in ("Apple", "Pear", "Peach")
return switch ($fruit)
  case "Apple" return "red"
  case "Pear"  return "green"
  case "Peach" return "pink"
  default      return "unknown"

XQuery 3.0 加入 try catch 的支援,下面是一個例子:

try {
  1 + '2'
} catch * {
  'Error [' || $err:code || ']: ' || $err:description
}

XQuery 3.0 加入 || operator 作為 String Concatenations 使用,其實際上為 concat() 函數的快捷方式。

'Hello' || ' ' || 'Universe'

XQuery 3.0 加入 Simple Map Operator !,用於將第一個表達式的結果應用於第二個表達式,下面是一個例子:

(1 to 10) ! element node { . }

XQuery 3.1 加入 Arrow Operator operator =>,提供了一種方便的替代語法,用於將函數傳遞給值。 運算子前面的表達式將提供作為箭頭後面函數的第一個參數。

'w e l c o m e' => upper-case() => tokenize() => string-join('-')

下面則是沒有 Arrow Operator operator 之前的寫法:

string-join(tokenize(upper-case('w e l c o m e')), '-')

XQuery 3.1 加入了 Map 與 Array 支援對於 JSON 資料格式的處理。 Map 是將一組鍵與值關聯起來的函數,從而產生一組鍵/值對,用來處理 JSON 的 object。 Array 是將一組位置(以正整數表示的鍵)與值關聯起來的功能。Array 中的第一個位置對應整數 1,用來處理 JSON 的 array。

let $map := map { 'foo': 42, 'bar': 'baz', 123: 456 }
return for-each(map:keys($map), $map)
let $array := array { 48 to 52 }
for $i in 1 to array:size($array)
return $array($i)

Lookup operator 提供了一種語法糖,用於存取 Map 或 Array 元素的值。它以問號 (?) 開頭,後面跟著一個說明符。說明符可以是:

  1. A wildcard *,
  2. The name of the key,
  3. The integer offset, or
  4. Any other parenthesized expression.
let $map := map { 'R': 'red', 'G': 'green', 'B': 'blue' }
return (
  $map?*           (: returns all values; same as: map:keys($map) ! $map(.) :),
  $map?R           (: returns the value for key 'R'; same as: $map('R') :),
  $map?('G', 'B')  (: returns the values for key 'G' and 'B' :)
)
let $maps := (
  map { 'name': 'Guðrún', 'city': 'Reykjavík' },
  map { 'name': 'Hildur', 'city': 'Akureyri' }
)
return $maps[?name = 'Hildur'] ?city

XQuery 3.1 提供了 JSON Serialization,下面是一個例子:

declare option output:method 'json';
map { "key": "value" }

XQuery 3.1 使用 fn:parse-json() 執行 JSON deserialization 的工作:

let $json-input := '{ "firstName": "John", "lastName": "Smith", "address": { "city": "New York" }, "phoneNumbers": ["212-732-1234", "646-123-4567"] }'
let $json-data := fn:parse-json($json-input)
return
  $json-data

XQuery 3.1 支援讀取外部的 JSON 文件檔案。

let $json-data := fn:json-doc("/path/to/data.json")
return $json-data

也可以將 JSON 轉換為 XML 文件:

let $json-string := '{ "name": "John", "age": 30, "city": "New York" }'
return fn:json-to-xml($json-string)

參考資料

2025/11/29

Boost.JSON

Boost.JSON 是一個 C++ JSON parser 函式庫, 提供了雖然不是最快但是也足夠快的執行效率、以及雖然不是最方便但是足以滿足使用者需要的便利使用方式, 就綜合條件來說,我認為是十分優秀的 C++ JSON parser 函式庫。 他有二個使用方式,第一種需要連結函式庫:

#include <boost/json.hpp>

第二種是 header-only:

#include <boost/json/src.hpp>

下面是從一個字串分析 JSON 的測試:

#include <boost/json.hpp>
#include <iostream>
#include <string>

namespace json = boost::json;

int main() {
    const std::string json_str = R"(
        {
            "user": "johndoe",
            "id": 12345,
            "active": true,
            "numbers": [1, 2, 3, 4, 5]
        }
    )";

    // Parse the JSON string
    json::value data = json::parse(json_str);

    // Access the values
    std::string username = json::value_to<std::string>(data.at("user"));
    int user_id = json::value_to<int>(data.at("id"));
    bool is_active = json::value_to<bool>(data.at("active"));

    std::cout << "Username: " << username << std::endl;
    std::cout << "ID: " << user_id << std::endl;
    std::cout << "Active: " << (is_active ? "Yes" : "No") << std::endl;

    // For array
    json::array &arr = data.at("numbers").as_array();
    std::vector<int> numbers;
    for (auto const &value : arr) {
        numbers.push_back(json::value_to<int>(value));
    }

    std::cout << "Parsed Numbers: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

使用 CMake 編譯,CMakeLists.txt 的內容如下:

cmake_minimum_required(VERSION 3.18)

project(parse)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(Boost 1.89.0 REQUIRED CONFIG COMPONENTS json)

add_executable(parse parse.cpp)
target_link_libraries(parse PRIVATE Boost::json)

如果採用 header-only 的方式,CMakeLists.txt 的內容如下:

cmake_minimum_required(VERSION 3.18)

project(parse)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(Boost 1.89.0 REQUIRED CONFIG COMPONENTS)

add_executable(parse parse.cpp)
target_link_libraries(parse)

下面是建立 JSON 內容的測試:

#include <boost/json.hpp>
#include <iostream>
#include <string>

namespace json = boost::json;

int main() {
    // Create a JSON object
    json::object obj;
    obj["user"] = "johndoe";
    obj["id"] = 12345;
    obj["active"] = true;

    // Create a JSON array
    json::array numbers;
    numbers.push_back(1);
    numbers.push_back(2);
    numbers.push_back(3);
    numbers.push_back(4);
    numbers.push_back(5);

    obj["numbers"] = numbers;

    // Serialize the object to a string
    std::string serialized_json = json::serialize(obj);

    std::cout << "Generated JSON: " << serialized_json << std::endl;

    return 0;
}

參考連結

Asio C++ Library

Asio C++ Library 是一個免費、開放原始碼、跨平台的 C++ 網路程式庫。 它為開發者提供一致的非同步 I/O 模型(包含 Timer、File、Pipe、Serial Port 以及網路協定 TCP, UDP 與 ICMP), Boost.Asio 在 20 天的審查後,於 2005 年 12 月 30 日被 Boost 函式庫接納。 目前 Asio C++ Library 提供二種函式庫,一種可以獨立使用的 Asio C++ library,一種是與 Boost 函式庫整合的 Boost.Asio, 二種函式庫的核心相同,差別在於 Boost.Asio 跟隨 Boost 函式庫的發佈時程(這表示當 bugs 修正的時候, 有時候會慢一點才會隨著 Boost 的新版更正)。因為已經有安裝 Boost 函式庫,所以我使用的是 Boost.Asio。

Asio 在設計上使用 Proactor pattern。 Proactor 是一種用於事件處理的軟體設計模式,其中耗時較長的活動在非同步部分運行(在 Asio 就是 I/O 處理的部份)。 非同步部分終止後,會呼叫完成處理程序。 所有使用 asio 的程式都需要至少一個 I/O execution context,例如 io_context 或 thread_pool 物件。 I/O execution context 提供對 I/O 功能的存取。如果是非同步的操作,那麼需要實作 completion handler 來提供工作完成之後的通知目標。

下面是一個測試的程式,來自 Asio 教學網頁的 Using a timer synchronously。 boost::asio::io_context 就是執行 I/O 的部份。

#include <boost/asio.hpp>
#include <iostream>

int main() {
    boost::asio::io_context io;

    boost::asio::steady_timer t(io, boost::asio::chrono::seconds(3));
    t.wait();

    std::cout << "Hello, world!" << std::endl;

    return 0;
}

使用 CMake 編譯,CMakeLists.txt 的內容如下:

cmake_minimum_required(VERSION 3.18)

project(timer)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(Boost 1.89.0 REQUIRED CONFIG COMPONENTS)

add_executable(timer timer.cpp)

Using a timer asynchronously

使用 asio 的非同步功能意味著需要一個 completion token,該 token 決定了非同步操作完成後如何將結果傳遞給完成處理程序。 在這裡使用 print 函數,該函數將在非同步等待結束後被呼叫。

務必記住,在呼叫 boost::asio::io_context::run() 之前,要先給 io_context 一些工作。 如果沒指定一些工作(在本例中是 steady_timer::async_wait()),boost::asio::io_context::run() 會立即返回。

#include <boost/asio.hpp>
#include <iostream>

void print(const boost::system::error_code & /*e*/) {
    std::cout << "Hello, world!" << std::endl;
}

int main() {
    boost::asio::io_context io;

    boost::asio::steady_timer t(io, boost::asio::chrono::seconds(3));
    t.async_wait(&print);

    io.run();

    return 0;
}

Binding arguments to a completion handler

要使用 asio 實作重複定時器,需要在完成處理程序中更改定時器的過期時間,然後啟動新的非同步等待。 這意味著 completion handler 需要能夠存取定時器物件。

#include <boost/asio.hpp>
#include <functional>
#include <iostream>

void print(const boost::system::error_code & /*e*/,
           boost::asio::steady_timer *t, int *count) {
    if (*count < 5) {
        std::cout << *count << std::endl;
        ++(*count);

        t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
        t->async_wait(
            std::bind(print, boost::asio::placeholders::error, t, count));
    }
}

int main() {
    boost::asio::io_context io;

    int count = 0;
    boost::asio::steady_timer t(io, boost::asio::chrono::seconds(1));
    t.async_wait(
        std::bind(print, boost::asio::placeholders::error, &t, &count));

    io.run();

    std::cout << "Final count is " << count << std::endl;

    return 0;
}

Using a member function as a completion handler

std::bind 函式對類別成員函式和函式同樣有效。由於所有非靜態類別成員函數都有一個隱式的 this 參數,我們需要將 this 綁定到函數上。 std::bind 將我們的 completion handler(現在是成員函數)轉換為函數對象。

#include <boost/asio.hpp>
#include <functional>
#include <iostream>

class printer {
public:
    printer(boost::asio::io_context &io)
        : timer_(io, boost::asio::chrono::seconds(1)), count_(0) {
        timer_.async_wait(std::bind(&printer::print, this));
    }

    ~printer() { std::cout << "Final count is " << count_ << std::endl; }

    void print() {
        if (count_ < 5) {
            std::cout << count_ << std::endl;
            ++count_;

            timer_.expires_at(timer_.expiry() +
                              boost::asio::chrono::seconds(1));
            timer_.async_wait(std::bind(&printer::print, this));
        }
    }

private:
    boost::asio::steady_timer timer_;
    int count_;
};

int main() {
    boost::asio::io_context io;
    printer p(io);
    io.run();

    return 0;
}

Synchronising completion handlers in multithreaded programs

strand class template 是 executor adapter,它保證透過它分發的處理程序,在下一個處理程序啟動之前, 目前正在執行的處理程序必須完成。無論呼叫 boost::asio::io_context::run() 的執行緒數是多少,此保證都有效。 當然,這些處理程序仍然可能與其他未透過 strand 分發的處理程序,或透過不同 strand 物件分發的處理程序並發執行。


#include <boost/asio.hpp>
#include <functional>
#include <iostream>
#include <thread>

class printer {
public:
    printer(boost::asio::io_context &io)
        : strand_(boost::asio::make_strand(io)),
          timer1_(io, boost::asio::chrono::seconds(1)),
          timer2_(io, boost::asio::chrono::seconds(1)), count_(0) {
        timer1_.async_wait(boost::asio::bind_executor(
            strand_, std::bind(&printer::print1, this)));

        timer2_.async_wait(boost::asio::bind_executor(
            strand_, std::bind(&printer::print2, this)));
    }

    ~printer() { std::cout << "Final count is " << count_ << std::endl; }

    void print1() {
        if (count_ < 10) {
            std::cout << "Timer 1: " << count_ << std::endl;
            ++count_;

            timer1_.expires_at(timer1_.expiry() +
                               boost::asio::chrono::seconds(1));

            timer1_.async_wait(boost::asio::bind_executor(
                strand_, std::bind(&printer::print1, this)));
        }
    }

    void print2() {
        if (count_ < 10) {
            std::cout << "Timer 2: " << count_ << std::endl;
            ++count_;

            timer2_.expires_at(timer2_.expiry() +
                               boost::asio::chrono::seconds(1));

            timer2_.async_wait(boost::asio::bind_executor(
                strand_, std::bind(&printer::print2, this)));
        }
    }

private:
    boost::asio::strand<boost::asio::io_context::executor_type> strand_;
    boost::asio::steady_timer timer1_;
    boost::asio::steady_timer timer2_;
    int count_;
};

int main() {
    boost::asio::io_context io;
    printer p(io);
    std::thread t([&] { io.run(); });
    io.run();
    t.join();

    return 0;
}

File

Linux io_uring 在 Kernel 5.1 加入,其主要目標是透過高效率的非同步 I/O 框架,解決傳統 I/O 模型中系統呼叫和上下文切換的效能瓶頸, 移除傳統同步I/O 與 epoll 就緒通知模型需要頻繁切換使用者空間與核心空間的負擔,進而大幅提升系統在處理大量並發 I/O 操作時的效能。 liburing 是 Jens Axboe 維護的輔助函式庫,其主要目的是簡化 io_uring 的使用。 Asio 對於 Linux liburing 提供了包裝(目前需要使用者使用 flag 啟用),下面是我測試的程式, 讀取 /etc/os-release 取得 Linux Distribution Name:

#include <boost/asio.hpp>
#include <boost/asio/stream_file.hpp>
#include <filesystem>
#include <iostream>
#include <vector>

namespace asio = boost::asio;
namespace fs = std::filesystem;

std::vector<std::string> split(const std::string &str,
                               const std::string &delim) {
    std::vector<std::string> tokens;
    size_t prev = 0, pos = 0;
    do {
        pos = str.find(delim, prev);
        if (pos == std::string::npos)
            pos = str.length();
        std::string token = str.substr(prev, pos - prev);
        if (!token.empty())
            tokens.push_back(token);
        prev = pos + delim.length();
    } while (pos < str.length() && prev < str.length());

    return tokens;
}

void read_next_line(asio::stream_file &file, asio::streambuf &buffer) {
    asio::async_read_until(file, buffer, '\n',
                           [&](const boost::system::error_code &ec,
                               std::size_t bytes_transferred) {
                               if (!ec) {
                                   std::istream is(&buffer);
                                   std::string line;
                                   std::getline(is, line);

                                   auto splitArray = split(line, "=");
                                   if (splitArray[0].compare("NAME") == 0) {
                                       std::cout << splitArray[1] << std::endl;
                                   } else {
                                       read_next_line(file, buffer);
                                   }
                               } else if (ec == asio::error::eof) {
                                   std::cout << "End of file reached."
                                             << std::endl;
                               } else {
                                   std::cerr
                                       << "Error reading file: " << ec.message()
                                       << std::endl;
                               }
                           });
}

int main() {
    fs::path test_file_path = "/etc/os-release";

    asio::io_context io_context;

    boost::system::error_code ec_open;
    asio::stream_file file(io_context);
    file.open(test_file_path.string(), asio::stream_file::read_only, ec_open);

    if (ec_open) {
        std::cerr << "Failed to open file: " << ec_open.message() << std::endl;
        return 1;
    }

    asio::streambuf buffer;
    read_next_line(file, buffer);

    io_context.run();
    file.close();

    return 0;
}

使用 CMake 編譯,CMakeLists.txt 的內容如下:

cmake_minimum_required(VERSION 3.18)

project(name)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(PkgConfig REQUIRED)
pkg_check_modules(uring REQUIRED IMPORTED_TARGET liburing)

find_package(Boost 1.89.0 REQUIRED CONFIG COMPONENTS)

add_executable(name name.cpp)
target_link_libraries(name PRIVATE PkgConfig::uring)
target_compile_definitions(name PRIVATE BOOST_ASIO_HAS_IO_URING BOOST_ASIO_DISABLE_EPOLL)

Tcp

A synchronous TCP daytime client

我們需要將作為參數傳遞給應用程式的伺服器名稱轉換為 TCP 端點。為此,我們使用 ip::tcp::resolver 物件。 resolver 接收主機名稱和服務名,並將它們轉換為端點列表。 程式接下來建立並連接 Socket。上面獲得的端點列表可能同時包含 IPv4 和 IPv6 端點,因此我們需要逐一嘗試,直到找到可用的端點。 這樣可以確保客戶端程式與特定的 IP 版本無關。boost::asio::connect() 函數會自動執行此操作。

#include <array>
#include <boost/asio.hpp>
#include <iostream>

namespace asio = boost::asio;

int main(int argc, char *argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        asio::io_context io_context;

        asio::ip::tcp::resolver resolver(io_context);
        asio::ip::tcp::resolver::results_type endpoints =
           resolver.resolve(argv[1], "daytime");

        asio::ip::tcp::socket socket(io_context);
        asio::connect(socket, endpoints);

        for (;;) {
            std::array<char, 128> buf;
            boost::system::error_code error;

            size_t len = socket.read_some(asio::buffer(buf), error);

            if (error == asio::error::eof)
                break; // Connection closed cleanly by peer.
            else if (error)
                throw boost::system::system_error(error); // Some other error.

            std::cout.write(buf.data(), len);
        }
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

A synchronous TCP daytime server

需要建立一個 ip::tcp::acceptor 物件來監聽新連線。它被初始化為監聽 TCP 連接埠 13,支援 IP 版本 6。

#include <boost/asio.hpp>
#include <ctime>
#include <iostream>
#include <string>

namespace asio = boost::asio;

std::string make_daytime_string() {
    std::time_t now = std::time(0);
    return std::ctime(&now);
}

int main() {
    try {
        asio::io_context io_context;

        asio::ip::tcp::acceptor acceptor(
            io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v6(), 13));

        for (;;) {
            asio::ip::tcp::socket socket(io_context);
            acceptor.accept(socket);

            std::string message = make_daytime_string();

            boost::system::error_code ignored_error;
            asio::write(socket, boost::asio::buffer(message), ignored_error);
        }
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

An asynchronous TCP daytime server

#include <boost/asio.hpp>
#include <ctime>
#include <functional>
#include <iostream>
#include <memory>
#include <string>

namespace asio = boost::asio;

std::string make_daytime_string() {
    std::time_t now = std::time(0);
    return std::ctime(&now);
}

class tcp_connection : public std::enable_shared_from_this<tcp_connection> {
public:
    typedef std::shared_ptr<tcp_connection> pointer;

    static pointer create(asio::io_context &io_context) {
        return pointer(new tcp_connection(io_context));
    }

    asio::ip::tcp::socket &socket() { return socket_; }

    void start() {
        message_ = make_daytime_string();

        asio::async_write(socket_, asio::buffer(message_),
                          std::bind(&tcp_connection::handle_write,
                                    shared_from_this(),
                                    asio::placeholders::error,
                                    asio::placeholders::bytes_transferred));
    }

private:
    tcp_connection(asio::io_context &io_context) : socket_(io_context) {}

    void handle_write(const boost::system::error_code & /*error*/,
                      size_t /*bytes_transferred*/) {}

    asio::ip::tcp::socket socket_;
    std::string message_;
};

class tcp_server {
public:
    tcp_server(asio::io_context &io_context)
        : io_context_(io_context),
          acceptor_(io_context,
                    asio::ip::tcp::endpoint(asio::ip::tcp::v6(), 13)) {
        start_accept();
    }

private:
    void start_accept() {
        tcp_connection::pointer new_connection =
            tcp_connection::create(io_context_);

        acceptor_.async_accept(new_connection->socket(),
                               std::bind(&tcp_server::handle_accept, this,
                                         new_connection,
                                         asio::placeholders::error));
    }

    void handle_accept(tcp_connection::pointer new_connection,
                       const boost::system::error_code &error) {
        if (!error) {
            new_connection->start();
        }

        start_accept();
    }

    asio::io_context &io_context_;
    asio::ip::tcp::acceptor acceptor_;
};

int main() {
    try {
        asio::io_context io_context;
        tcp_server server(io_context);
        io_context.run();
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

下面是我的練習程式,將 client 改寫為 asynchronous:

#include <boost/asio.hpp>
#include <iostream>
#include <vector>

namespace asio = boost::asio;

const int BUFFER_SIZE = 128;

void handle_read(const boost::system::error_code &error,
                 std::size_t bytes_transferred, asio::ip::tcp::socket &socket,
                 std::vector<char> &buffer) {
    if (!error) {
        for (std::size_t i = 0; i < bytes_transferred; ++i) {
            std::cout << buffer[i];
        }
    } else {
        std::cerr << "Error during read: " << error.message() << std::endl;
    }
}

int main(int argc, char *argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        asio::io_context io_context;

        asio::ip::tcp::resolver resolver(io_context);
        asio::ip::tcp::resolver::results_type endpoints =
            resolver.resolve(argv[1], "daytime");

        asio::ip::tcp::socket socket(io_context);
        asio::connect(socket, endpoints);

        std::vector<char> buffer(BUFFER_SIZE);
        socket.async_read_some(asio::buffer(buffer),
                               std::bind(handle_read, std::placeholders::_1,
                                         std::placeholders::_2,
                                         std::ref(socket), std::ref(buffer)));

        io_context.run();
        socket.close();
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

UDP

A synchronous UDP daytime client

我們使用 ip::udp::resolver 物件,根據主機名稱和服務名稱尋找要使用的正確遠端端點。 透過 ip::udp::v6() 參數,查詢被限制為僅傳回 IPv6 端點。 如果 ip::udp::resolver::resolve()函數沒有失敗,則保證至少會傳回清單中的一個端點。這意味著直接解引用回傳值是安全的。

#include <boost/asio.hpp>
#include <array>
#include <iostream>

namespace asio = boost::asio;

int main(int argc, char *argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        asio::io_context io_context;

        asio::ip::udp::resolver resolver(io_context);
        asio::ip::udp::endpoint receiver_endpoint =
            *resolver.resolve(asio::ip::udp::v6(), argv[1], "daytime").begin();

        asio::ip::udp::socket socket(io_context);
        socket.open(asio::ip::udp::v6());

        std::array<char, 1> send_buf = {{0}};
        socket.send_to(asio::buffer(send_buf), receiver_endpoint);

        std::array<char, 128> recv_buf;
        asio::ip::udp::endpoint sender_endpoint;
        size_t len =
            socket.receive_from(asio::buffer(recv_buf), sender_endpoint);

        std::cout.write(recv_buf.data(), len);
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

A synchronous UDP daytime server

#include <boost/asio.hpp>
#include <array>
#include <ctime>
#include <iostream>
#include <string>

namespace asio = boost::asio;

std::string make_daytime_string() {
    std::time_t now = std::time(0);
    return std::ctime(&now);
}

int main() {
    try {
        asio::io_context io_context;

        asio::ip::udp::socket socket(
            io_context, asio::ip::udp::endpoint(asio::ip::udp::v6(), 13));

        for (;;) {
            std::array<char, 1> recv_buf;
            asio::ip::udp::endpoint remote_endpoint;
            socket.receive_from(asio::buffer(recv_buf), remote_endpoint);

            std::string message = make_daytime_string();

            boost::system::error_code ignored_error;
            socket.send_to(asio::buffer(message), remote_endpoint, 0,
                           ignored_error);
        }
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

An asynchronous UDP daytime server

#include <boost/asio.hpp>
#include <array>
#include <ctime>
#include <functional>
#include <iostream>
#include <memory>
#include <string>

namespace asio = boost::asio;

std::string make_daytime_string() {
    std::time_t now = std::time(0);
    return std::ctime(&now);
}

class udp_server {
public:
    udp_server(asio::io_context &io_context)
        : socket_(io_context,
                  asio::ip::udp::endpoint(asio::ip::udp::v6(), 13)) {
        start_receive();
    }

private:
    void start_receive() {
        socket_.async_receive_from(
            asio::buffer(recv_buffer_), remote_endpoint_,
            std::bind(&udp_server::handle_receive, this,
                      asio::placeholders::error,
                      asio::placeholders::bytes_transferred));
    }

    void handle_receive(const boost::system::error_code &error,
                        std::size_t /*bytes_transferred*/) {
        if (!error) {
            std::shared_ptr<std::string> message(
                new std::string(make_daytime_string()));

            socket_.async_send_to(
                asio::buffer(*message), remote_endpoint_,
                std::bind(&udp_server::handle_send, this, message,
                          asio::placeholders::error,
                          asio::placeholders::bytes_transferred));

            start_receive();
        }
    }

    void handle_send(std::shared_ptr<std::string> /*message*/,
                     const boost::system::error_code & /*error*/,
                     std::size_t /*bytes_transferred*/) {}

    asio::ip::udp::socket socket_;
    asio::ip::udp::endpoint remote_endpoint_;
    std::array<char, 1> recv_buffer_;
};

int main() {
    try {
        asio::io_context io_context;
        udp_server server(io_context);
        io_context.run();
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

下面是我的練習程式,將 client 改寫為 asynchronous:

#include <array>
#include <boost/asio.hpp>
#include <iostream>

namespace asio = boost::asio;

class udp_client {
public:
    udp_client(asio::io_context &io_context, const std::string &host)
        : socket_(io_context) {
        asio::ip::udp::resolver resolver(io_context);
        remote_endpoint_ =
            *resolver.resolve(asio::ip::udp::v6(), host, "daytime").begin();

        socket_.open(asio::ip::udp::v6());

        start_send();
    }

private:
    void start_send() {
        socket_.async_send_to(asio::buffer(send_buffer_), remote_endpoint_,
                              std::bind(&udp_client::handle_send, this,
                                        asio::placeholders::error,
                                        asio::placeholders::bytes_transferred));
    }

    void handle_send(const boost::system::error_code &error,
                     std::size_t /*bytes_transferred*/) {
        if (!error) {
            socket_.async_receive_from(
                asio::buffer(recv_buffer_), remote_endpoint_,
                std::bind(&udp_client::handle_receive, this,
                          asio::placeholders::error,
                          asio::placeholders::bytes_transferred));
        } else {
            std::cerr << "Error during send: " << error.message() << std::endl;
        }
    }

    void handle_receive(const boost::system::error_code &error,
                        std::size_t bytes_transferred) {

        if (!error) {
            std::cout.write(recv_buffer_.data(), bytes_transferred);
        } else {
            std::cerr << "Error during receive: " << error.message()
                      << std::endl;
        }
    }

    asio::ip::udp::socket socket_;
    asio::ip::udp::endpoint remote_endpoint_;
    std::array<char, 1> send_buffer_ = {{0}};
    std::array<char, 128> recv_buffer_;
};

int main(int argc, char *argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        asio::io_context io_context;

        udp_client client(io_context, argv[1]);

        io_context.run();
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

相關連結

2025/11/07

SOCI

SOCI (Simple Oracle Call Interface) 一開始是由 Maciej Sobczak 在 CERN 工作時開發, 作為 Oracle 資料庫函式庫的 abstraction layer 並且在 CERN 的工作環境中使用,之後則又加入了數個資料庫的支援。

SOCI 目前支援 Oracle, MySQL, PostgreSQL, SQLite3 等資料庫以及 ODBC 作為通用的 backend。 下面是在 openSUSE 安裝 SOCI core 以及 SOCI SQLite3 開發檔案的指令:
sudo zypper in soci-devel soci-sqlite3-devel

(注意:SOCI 本身的體積並不大,但是安裝時需要 boost-devel,而 boost-devel 是個包含很多模組的函式庫, 如果之前就有安裝 boost-devel 那麼這就不是一個問題)

下面是一個使用 SOCI 取得 SQLite3 版本的測試程式:

#include <soci/soci.h>
#include <soci/sqlite3/soci-sqlite3.h>

#include <iostream>
#include <string>

int main() {
    try {
        soci::session sql("sqlite3", "db=:memory:");

        std::string version;
        sql << "select sqlite_version()", soci::into(version);
        std::cout << version << std::endl;
    } catch (soci::soci_error const &e) {
        std::cerr << "Failed: " << e.what() << std::endl;
    } catch (std::runtime_error const &e) {
        std::cerr << "Unexpected standard exception occurred: " << e.what()
                  << std::endl;
    } catch (...) {
        std::cerr << "Unexpected unknown exception occurred." << std::endl;
    }

    return 0;
}

使用下列的指令編譯:

g++ version.cpp -lsoci_core -lsoci_sqlite3 -o version

下面是在 openSUSE 安裝 SOCI core 以及 SOCI ODBC 開發檔案的指令:

sudo zypper in soci-devel soci-odbc-devel

下面是一個使用 SOCI ODBC 取得 PostgreSQL 版本的測試程式:

#include <soci/odbc/soci-odbc.h>
#include <soci/soci.h>

#include <iostream>
#include <string>

int main() {
    try {
        soci::session sql("odbc",
                          "DSN=PostgreSQL; UID=postgres; PWD=postgres;");

        std::string version;
        soci::rowset<std::string> rs = (sql.prepare << "select version()");

        for (soci::rowset<std::string>::const_iterator it = rs.begin();
             it != rs.end(); ++it) {
            std::cout << *it << std::endl;
        }
    } catch (soci::soci_error const &e) {
        std::cerr << "Failed: " << e.what() << std::endl;
    } catch (std::runtime_error const &e) {
        std::cerr << "Unexpected standard exception occurred: " << e.what()
                  << std::endl;
    } catch (...) {
        std::cerr << "Unexpected unknown exception occurred." << std::endl;
    }

    return 0;
}

使用下列的指令編譯:

g++ version.cpp -lsoci_core -lsoci_odbc -o version

參考連結

2025/10/30

Beyond 國語歌曲歌單

歌單的歌曲不固定,這只是我現在的歌單。

  • 大地

    收錄在 1990 年發行的專輯《大地》中。劉卓輝作詞,黃家駒作曲,黃貫中擔任主唱,粵語版為《大地》。大地來自於黃家駒看到兩岸開放,國民黨老兵回到中國大陸探親的新聞報導,結果產生了相關的靈感。歌曲的主角可以視為在台灣的國民黨老兵,或者是因為戰亂而離開中國大陸,在改革開放後又重新回到中國大陸的老一輩,是一首十分有時代意義的歌曲。

  • 漆黑的空間

    收錄在 1990 年發行的專輯《大地》中。粵語版為《灰色軌跡》,由黃家駒主唱。

  • 短暫的溫柔

    收錄在 1990 年發行的專輯《大地》中。粵語版為《未曾後悔》,由黃貫中主唱。

  • 不需要太懂

    收錄在 1990 年發行的專輯《大地》中。粵語版為《是錯也再不分》,由黃家強主唱。

  • 九十年代的憂傷

    收錄在 1990 年發行的專輯《大地》中。粵語版為《相依的心》。

  • 你知道我的迷惘

    收錄在 1990 年發行的專輯《大地》中。由劉卓輝重新填詞,粵語版為《真的愛你》。

  • 送給不懂環保的人(包括我)

    收錄在 1990 年發行的專輯《大地》中。粵語版為《送給不知怎去保護環境的人(包括我)》。

  • 和自己的心比賽

    收錄在 1990 年發行的專輯《大地》中。粵語版為《戰勝心魔》。

  • 破壞遊戲的孩子

    收錄在 1990 年發行的專輯《大地》中。粵語版為《衝開一切》。

  • 今天有我

    收錄在 1990 年發行的專輯《大地》中。粵語版為《秘密警察》。

  • 光輝歲月

    收錄在 1991 年發行的專輯《光輝歲月》中。周治平/何啟弘作詞,黃家駒作曲,粵語版為《光輝歲月》。 主打歌《光輝歲月》的粵語版是一首讚美南非的非洲人國民大會主席納爾遜·曼德拉的歌曲,以歌頌他在南非種族隔離時期為黑人所付出的努力, 當時曼德拉在監禁 28 年後剛被釋放,光輝歲月表達他的一生。在國語版裡面,這首《光輝歲月》是為激勵年輕人努力拼搏而作, 而當中的種族議題被淡化。

  • 午夜怨曲

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《午夜怨曲》。

  • 撒旦的咀咒

    收錄在 1991 年發行的專輯《光輝歲月》中。何惠晶作詞,黃貫中作曲,粵語版為《撒旦的詛咒》。

  • 射手座咒語

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《亞拉伯跳舞女郎》。

  • 歲月無聲

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《歲月無聲》。

  • 曾經擁有

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《曾是擁有》。

  • 心中的太陽

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《又是黃昏》。

  • 兩顆心

    收錄在 1991 年發行的專輯《光輝歲月》中。何惠晶作詞,黃家強作曲,粵語版為《兩顆心》。

  • 長城

    收錄在 1992 年發行的專輯《信念》中。詹德茂作詞,黃家駒作曲,粵語版為《長城》。喜多郎創作的前奏十分優秀,我非常喜歡的其中一首國語歌。

  • 關心永遠在

    收錄在 1992 年發行的專輯《信念》中。粵語版為《遙望》。

  • 愛過的罪

    收錄在 1992 年發行的專輯《信念》中。陳昇作詞,黃貫中作曲,粵語版為《繼續沉醉》。

  • 今天就做

    收錄在 1992 年發行的專輯《信念》中。狗毛作詞,黃家駒作曲,粵語版為《不可一世》。我非常喜歡的其中一首國語歌。

  • 農民

    收錄在 1992 年發行的專輯《信念》中。粵語版為《農民》。《農民》的內容大抵是描述一個中國農民的生活,如何在艱困的生活中逆境自強。但廣東話和國語的版本卻有著極大不同。廣東話版本是由劉卓輝填詞,是對山區農民生活的影射;而國語版則由姚若龍填詞,內容更為廣泛,是描述北方人重視固有生活的個性。這是 Beyond 創作中十分嚴肅的作品。

  • 最想念妳

    收錄在 1992 年發行的專輯《信念》中。粵語版為《早班火車》。

  • 可否衝破

    收錄在 1992 年發行的專輯《信念》中。粵語版為《可否衝破》。

  • 問自己

    收錄在 1992 年發行的專輯《信念》中。粵語版為《無語問蒼天》。

  • 年輕

    收錄在 1992 年發行的專輯《信念》中。粵語版為《快樂王國》。

  • 愛不容易說

    收錄在 1993 年發行的專輯《海闊天空》中。《海闊天空》是 Beyond 發行的第 4 張國語專輯,1993 年 6 月 24 日黃家駒發生意外昏迷,6 月 30 日逝世, 原定計畫無法實現,滾石唱片公司以這張專輯保留黃家駒原聲以及詞曲創作來完成黃家駒未完成的計畫和心願。黃家駒逝世後, 其餘 3 名成員決定進入錄音室,由黃家強和黃貫中演唱完成計畫中的國語專輯。歌曲結合粵語專輯《樂與怒》和日語專輯《This Is Love 1》。 雖然維基百科上說這是第 4 張國語專輯,但我個人感覺更像是個精選輯。

  • 身不由己

    收錄在 1993 年發行的專輯《海闊天空》中。

  • Paradise

    收錄在 1994 年發行的專輯《Paradise》中。《Paradise》是 Beyond 樂隊在主音黃家駒逝世後,三人復出發行的首張國語專輯。 《Paradise》這首歌為黃貫中作詞作曲並且擔任主唱,為懷念黃家駒之作。

  • 一輩子陪我走

    收錄在 1994 年發行的專輯《Paradise》中。《一輩子陪我走》則是以一個輕快的伴奏,高歌四子手足之情。

  • 無名英雄

    收錄在 1994 年發行的專輯《Paradise》中。鄭智化填詞,黃家強與黃貫中作曲,黃家強擔任主唱。

  • 因為有你有我

    收錄在 1994 年發行的專輯《Paradise》中。厲曼婷填詞,黃家強作曲,黃家強擔任主唱。

  • 對嗎

    收錄在 1994 年發行的專輯《Paradise》中。

  • 祝您愉快

    收錄在 1994 年發行的專輯《Paradise》中,為黃家強懷念黃家駒之作。

  • 唯一

    收錄在 1995 年發行的專輯《愛與生活》中。《唯一》《Love》《活得精彩》《夢》四首歌都是國語歌曲創作, 而只有《活得精彩》後來有錄製粵語版本。

  • Love

    收錄在 1995 年發行的專輯《愛與生活》中。

  • 活得精彩

    收錄在 1995 年發行的專輯《愛與生活》中。


  • 收錄在 1995 年發行的專輯《愛與生活》中。

  • 荒謬世界

    收錄在 1995 年發行的專輯《愛與生活》中。粵語版為《教壞細路》。

  • 忘記你

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 緩慢

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 候診室

    收錄在 1998 年發行的專輯《這裡那裡》中。粵語版為《不再猶豫》。我非常喜歡的其中一首國語歌。

  • 情人

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 熱情過後

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 十字路口

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • Amani

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 命運是我家

    收錄在 1998 年發行的專輯《這裡那裡》中。《這裡那裡》當中有七首是把黃家駒的作品,重新填上國語詞及編曲, 《命運是我家》這首的編曲是保留黃家駒原曲,為的是要保留黃家駒彈奏吉他的部份,是對黃家駒的敬意與尊重。