docker是非常流行的容器技術,解決了部署環境不一致的問題,open62541的工程也可以在docker容器中運行,本文講述如何把open62541工程放到docker容器中運行。
本文使用WSL ubuntu 22.04作為宿主環境,其它linux也是一樣。
一 拉取debian鏡像
執行docker命令拉取debian鏡像
docker pull debian:bookworm
這個debian鏡像創建的容器非常干凈,啥都沒有,都需要自己安裝,如果我想創建多個容器,那就要安裝多次,所以這里基于debian:bookworm來制作自定義鏡像。
二 編寫Dockerfile
通過Dockerfile來制作自定義鏡像,
# 使用 debian:bookworm 作為基礎鏡像
FROM debian:bookworm# 設置環境變量以避免交互式配置提示
ENV DEBIAN_FRONTEND=noninteractive# 確保 /etc/apt/sources.list 文件存在,并使用國內鏡像源(以阿里云為例)
RUN if [ ! -f "/etc/apt/sources.list" ]; then echo "deb http://mirrors.aliyun.com/debian/ bookworm main non-free contrib" > /etc/apt/sources.list && \echo "deb-src http://mirrors.aliyun.com/debian/ bookworm main non-free contrib" >> /etc/apt/sources.list && \echo "deb http://mirrors.aliyun.com/debian/ bookworm-updates main non-free contrib" >> /etc/apt/sources.list && \echo "deb-src http://mirrors.aliyun.com/debian/ bookworm-updates main non-free contrib" >> /etc/apt/sources.list; fi# 更新包列表并安裝常用開發工具
RUN apt-get update && \apt-get install -y \git \curl \wget \cmake \vim \sudo \build-essential \python3 \python-is-python3 \python3-pip \python3.11-venv \openssh-client \net-tools \iproute2 \iputils-ping \dnsutils \ca-certificates \gnupg \lsb-release \&& apt-get clean \&& rm -rf /var/lib/apt/lists/*# 創建虛擬環境目錄
RUN python3 -m venv /opt/venv# 設置虛擬環境為當前環境
ENV PATH="/opt/venv/bin:$PATH"# 設置容器啟動時默認執行的命令
CMD ["bash"]
然后運行docker命令來制作鏡像,
docker build -t myimage:0.0.1 .
執行完畢后,運行docker images來查看當前的本地鏡像,
這個鏡像可以推送到docker hub或者其他倉庫里,然后分享給別人使用,這樣就保證開發環境是一樣的了。
三 創建open62541工程
在WSL ubuntu下創建一個簡單的open62541工程,工程結構如下,包含server和client
open62541使用的版本是v1.4.12,
client.c內容如下,
// client.c,功能主要是從server那里獲取時間
#include <stdlib.h>
#include "open62541.h"int main(void)
{UA_Client *client = UA_Client_new();UA_ClientConfig_setDefault(UA_Client_getConfig(client));UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");if(retval != UA_STATUSCODE_GOOD) {UA_Client_delete(client);return (int)retval;}/* Read the value attribute of the node. UA_Client_readValueAttribute is a* wrapper for the raw read service available as UA_Client_Service_read. */UA_Variant value; /* Variants can hold scalar values and arrays of any type */UA_Variant_init(&value);/* NodeId of the variable holding the current time */const UA_NodeId nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);retval = UA_Client_readValueAttribute(client, nodeId, &value);if(retval == UA_STATUSCODE_GOOD && UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_DATETIME])) {UA_DateTime raw_date = *(UA_DateTime *) value.data;UA_DateTimeStruct dts = UA_DateTime_toStruct(raw_date);UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "date is: %u-%u-%u %u:%u:%u.%03u\n",dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);}/* Clean up */UA_Variant_clear(&value);UA_Client_delete(client); /* Disconnects the client internally */return EXIT_SUCCESS;
}
server.c內容如下,
// server.c
#include "open62541.h"#include <signal.h>
#include <stdlib.h>UA_Boolean running = true;static void stopHandler(int sign) {UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");running = false;
}int main(void)
{signal(SIGINT, stopHandler);signal(SIGTERM, stopHandler);UA_Server *server = UA_Server_new();UA_ServerConfig_setDefault(UA_Server_getConfig(server));UA_StatusCode retval = UA_Server_run(server, &running);UA_Server_delete(server);return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
CMakeLists.txt內容如下,
cmake_minimum_required(VERSION 3.10)project(demo)add_definitions(-std=c99)add_library(open62541 open62541/open62541.copen62541/open62541.h
)add_executable(client src/client.c)
target_include_directories(client PRIVATE open62541)
target_link_libraries(client open62541 pthread)add_executable(server src/server.c)
target_include_directories(server PRIVATE open62541)
target_link_libraries(server open62541 pthread)
最后,在工程根目錄下添加.devcontainer.json,內容如下,
{"image":"myimage:0.0.1"
}
這個用于指導使用哪個鏡像來生成容器。
四 在容器中運行工程
使用VSCode打開工程,然后Ctrl+Shift+p打開命令界面,執行“Dev Containers: Reopen in Container”
這個執行后會通過鏡像創建容器,然后在容器里打開本工程,最后VScode左下角會變成下面這樣,
表示已經在容器里打開了,默認是root用戶。
工程在容器中的目錄是/workspaces/prj_001/
在工程根目錄下創建build目錄,然后cd進入后執行下面命令進行編譯,
cmake .. && make
這里需要創建終端執行命令,有2種方式,
- 使用VSCode的終端,因為VSCode已經和容器連接成功了
- 在WSL linux下打開終端,然后輸入docker ps來查看容器id,最后執行docker exec -it 容器id bash,就可以進入容器里了
編譯成功后運行server,默認端口是4840,VSCode里會自動轉發,
這樣我們在容器外面使用UaExpert也可以訪問,非常方便,
這里解釋一下為什么使用Reopen in container:因為可以把工程放在宿主機里,如果容器掛了,那么重新開個容器就可以了,如果把工程放到容器里,那么就存在一定的風險了。
還有一點,工程在容器里打開后,里面的文件權限都變成root的了,因為容器默認的用戶就是root,當我們在宿主機里想修改工程文件時,就會提示權限不夠,解決辦法是在工程根目錄下,執行下面的命令,把文件的owner再變回來,
sudo chown -R user_name .
user_name就是宿主機的登錄用戶名。