前文有提到一种新方法来获取OPC数据,为扩展到其它语言打下了基础。现在能否一如既往地满足不同场景下不同语言获得OPC数据的需要?本文打算用四种不同的常用语言 (Python/C#/C++/Java) 来尝试一下获得OPC数据。先看一下Python语言下的例子, 在Visual Studio 2019下使用,基本的设置如下,
用的是Python 3.9,下载所需要的websockets的包,添加一个py文件(WebSocketPython.py)和如下程序,
import asyncio
import websockets
async def main():
async with websockets.connect("ws://localhost/OPC/main.opc") as ws:
i = 0
while ws.open == True:
message = await ws.recv()
print(f"{message}")
if i == 1:
await ws.send("browse")
elif i == 2:
await ws.send("subscribe: Random.Int1")
if i == 8:
break
i += 1
asyncio.run(main())
非常简短直接,使用一个异步包和websockets包,开启一个连接。连接成功后等待服务端发来的信息并展示。然后发送一个浏览的请求并异步等待。收到浏览结果后予以展示,进入下一个订阅请求并异步等待。每当有新值来时予以展示,展示6个结果后退出。逻辑简洁明了,下面是输出的结果,
这个方案和OpenOPC for Python比更加容易上手,程序中没有很多针对服务端的特有设置。对于非Windows的系统不需要服务端装任何Gateway Service,不用担心DCOM的设置及安全漏洞,也不用考虑防火墙等等,只要知道服务器名字或IP就可以了。Python的程序行数是最少的,和其它语言一比真的是不比较就没有伤害🥺下面看下C#的应用,只有一个.cs文件(Program.cs)并添加相应程序见下,
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WebSocketSharp
{
class Program
{
static async Task Main(string[] args)
{
using (var ws = new ClientWebSocket())
{
await ws.ConnectAsync(new Uri("ws://localhost/OPC/main.opc"), CancellationToken.None);
byte[] buffer = new byte[256];
var count = 0;
while (ws.State == WebSocketState.Open)
{
WebSocketReceiveResult result = null;
do
{
result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
Console.WriteLine(System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count));
} while (!result.EndOfMessage);
if (count == 1) {
await ws.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("browse")), WebSocketMessageType.Text, true, CancellationToken.None);
}
else if (count == 2)
await ws.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("subscribe:Random.Int1")), WebSocketMessageType.Text, true, CancellationToken.None);
else if (count > 8)
await ws.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "I am closing", CancellationToken.None);
count++;
}
}
}
}
}
程序明显比Python要大,其执行逻辑是一样的,打开一个连接,进行异步等待,展示服务端送来的信息,执行下一个命令,输出结果等等。运行结果如下图,和Python输出的结果非常类似。
再往下是C++的例子, 只有一个.cpp文件(winHttpWebSocket.cpp),添加程序如下,
#include <Windows.h>
#include <WinHttp.h>
#include <stdio.h>
int __cdecl wmain()
{
DWORD dwError = ERROR_SUCCESS;
BOOL fStatus = FALSE;
HINTERNET hSessionHandle = NULL;
HINTERNET hConnectionHandle = NULL;
HINTERNET hRequestHandle = NULL;
HINTERNET hWebSocketHandle = NULL;
BYTE rgbCloseReasonBuffer[123];
BYTE rgbBuffer[MAXBYTE];
DWORD dwBufferLength = ARRAYSIZE(rgbBuffer);
DWORD dwBytesTransferred = 0;
DWORD dwCloseReasonLength = 0;
USHORT usStatus = 0;
WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType;
//
// Create session, connection and request handles.
//
hSessionHandle = WinHttpOpen(L"WebSocket sample", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
if (hSessionHandle == NULL)
{
dwError = GetLastError();
goto quit;
}
hConnectionHandle = WinHttpConnect(hSessionHandle, L"localhost", INTERNET_DEFAULT_HTTP_PORT, 0);
if (hConnectionHandle == NULL)
{
dwError = GetLastError();
goto quit;
}
hRequestHandle = WinHttpOpenRequest(hConnectionHandle, L"GET", L"/OPC/main.opc", NULL, NULL, NULL, 0);
if (hRequestHandle == NULL)
{
dwError = GetLastError();
goto quit;
}
//
// Request protocol upgrade from http to websocket.
//
#pragma prefast(suppress:6387, "WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET does not take any arguments.")
fStatus = WinHttpSetOption(hRequestHandle, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, NULL, 0);
if (!fStatus)
{
dwError = GetLastError();
goto quit;
}
//
// Perform websocket handshake by sending a request and receiving server's response.
// Application may specify additional headers if needed.
//
fStatus = WinHttpSendRequest(hRequestHandle, WINHTTP_NO_ADDITIONAL_HEADERS, 0, NULL, 0, 0, 0);
if (!fStatus)
{
dwError = GetLastError();
goto quit;
}
fStatus = WinHttpReceiveResponse(hRequestHandle, 0);
if (!fStatus)
{
dwError = GetLastError();
goto quit;
}
//
// Application should check what is the HTTP status code returned by the server and behave accordingly.
// WinHttpWebSocketCompleteUpgrade will fail if the HTTP status code is different than 101.
//
hWebSocketHandle = WinHttpWebSocketCompleteUpgrade(hRequestHandle, NULL);
if (hWebSocketHandle == NULL)
{
dwError = GetLastError();
goto quit;
}
//
// The request handle is not needed anymore. From now on we will use the websocket handle.
//
WinHttpCloseHandle(hRequestHandle);
hRequestHandle = NULL;
int count = 0;
do
{
if (dwBufferLength == 0)
{
dwError = ERROR_NOT_ENOUGH_MEMORY;
break;
}
dwError = WinHttpWebSocketReceive(hWebSocketHandle, rgbBuffer, dwBufferLength, &dwBytesTransferred, &eBufferType);
if (dwError != ERROR_SUCCESS)
{
break;
}
wprintf(L"%.*S", dwBytesTransferred, rgbBuffer);
if (dwBytesTransferred < sizeof rgbBuffer)
wprintf(L"\n");
if (count == 1) {
char browse[] = "browse";
dwError = WinHttpWebSocketSend(hWebSocketHandle, WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE, (PVOID)browse, (DWORD)strlen(browse));
if (dwError != ERROR_SUCCESS)
{
break;
}
}
else if (count == 2) {
char subscribe[] = "subscribe: Random.Int1";
dwError = WinHttpWebSocketSend(hWebSocketHandle, WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE, (PVOID)subscribe, (DWORD)strlen(subscribe));
if (dwError != ERROR_SUCCESS)
{
break;
}
}
else
if (count > 8)
break;
if (dwBytesTransferred < sizeof rgbBuffer)
++count;
} while (true);
//
// Gracefully close the connection.
//
dwError = WinHttpWebSocketClose(hWebSocketHandle, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, NULL, 0);
if (dwError != ERROR_SUCCESS)
{
goto quit;
}
//
// Check close status returned by the server.
//
dwError = WinHttpWebSocketQueryCloseStatus(hWebSocketHandle, &usStatus, rgbCloseReasonBuffer, ARRAYSIZE(rgbCloseReasonBuffer), &dwCloseReasonLength);
if (dwError != ERROR_SUCCESS)
{
goto quit;
}
wprintf(L"The server closed the connection with status code: '%d' and reason: '%.*S'\n", (int)usStatus, dwCloseReasonLength, rgbCloseReasonBuffer);
quit:
if (hRequestHandle != NULL)
{
WinHttpCloseHandle(hRequestHandle);
hRequestHandle = NULL;
}
if (hWebSocketHandle != NULL)
{
WinHttpCloseHandle(hWebSocketHandle);
hWebSocketHandle = NULL;
}
if (hConnectionHandle != NULL)
{
WinHttpCloseHandle(hConnectionHandle);
hConnectionHandle = NULL;
}
if (hSessionHandle != NULL)
{
WinHttpCloseHandle(hSessionHandle);
hSessionHandle = NULL;
}
if (dwError != ERROR_SUCCESS)
{
wprintf(L"Application failed with error: %u\n", dwError);
return -1;
}
return 0;
}
明显地C++是所有语言中行数最多的,很多细节需要自己来照顾。程序上的注释已经标注地很清楚了,解释了整个过程:获得session->服务端连接->发送请求到指定的URL->设立选项来请求websocket升级->发送请求->获得响应->升级完成->发送命令->异步等待->显示输出。注意这里是同步等待服务端返回的值并展示,其它逻辑都一样。运行后的结果如下,和其它语言没有什么区别。总之用C++亲力亲为的地方太多,需要对WebSocket协议本身有较深入的理解
最后看下Eclipse环境下的Java应用,只有一个.java文件(Main.java),添加程序如下,
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
public class Main {
public static void main(String[] args) throws Exception {
WebSocket ws = HttpClient.newHttpClient().newWebSocketBuilder()
.buildAsync(URI.create("ws://localhost/OPC/main.opc"), new WebSocketClient()).join();
ws.sendText("browse", true);
Thread.sleep(1000);
ws.sendText("subscribe:Random.Int1", true);
int count = 0;
while (count < 8) {
Thread.sleep(1000);
++count;
}
ws.sendClose(WebSocket.NORMAL_CLOSURE, "");
}
private static class WebSocketClient implements WebSocket.Listener {
private StringBuilder builder = new StringBuilder();
@Override
public void onOpen(WebSocket webSocket) {
WebSocket.Listener.super.onOpen(webSocket);
}
@Override
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
builder.append(data);
if (last) {
System.out.println(builder);
builder = new StringBuilder();
} else {
webSocket.request(1);
}
return WebSocket.Listener.super.onText(webSocket, data, last);
}
@Override
public void onError(WebSocket webSocket, Throwable error) {
System.out.println("Bad day! " + webSocket.toString());
WebSocket.Listener.super.onError(webSocket, error);
}
}
}
使用Java自带的WebSocket包,构建一个异步回调的类WebSocketClient,按序发出浏览和订阅的命令,等上几秒来显示服务端送回的值,这些值都用回调类的onText()触发并展示,结果和其它语言都差不多,Java的程序行数和C#不相上下。
总结一下,Python所用的程序行数最少,简洁上手快,难怪在IIOT领域兴起了用Python获取OPC数据的热潮。WebSocket开启了获取OPC数据的新窗口,不同语言的使用更便利,更符合直觉。感兴趣的同学可以和我联系来获得服务端的软件下载来进行测试。
楼主最近还看过