Electron與工業CCD通過TCP Server協議連接通訊
CCD
可以理解為是一個相機,在工業上可以通過CCD
給物體拍照,然后識別出物體表面激光刻印的信息,大概如下圖,下圖是我公司研發的軟件一張截圖,軟件是使用C#
實現的,這個工業軟件還是很厲害的,里面涉及到了許多與工業傳感器的數據交互。現在我的工作主要就是使用前端知識重構這個軟件,要與硬件打交道了,當然要選擇Electron
和Node
技術棧了。
CCD
底層是通過TCP Server
協議進行通訊的,所以要想與CCD
進行數據對接,就要在Electron
中實現TCP Server
服務了。
這個知識我是不會的,最先是讀C#
的代碼,看之前人家是怎么實現的,6000
行的代碼,看的我也是頭暈腦脹的。再后來就是在網上找資料了,可是發現網上關於Electron
連接TCP Server
的資料真不多,但還是有的,下面就開始實現吧。
參考資料一:electron測試TCP通信,這篇文章寫的估計也就只有博主自己能看懂吧!沒辦法參考着他的代碼,我居然還悟出來點東西。
首先聲明我的Electron
是通過Vue CLI Plugin Electron Builder
創建出來的。在主進程(background.js
)中添加下面這段代碼:
1 // TCP/IP通訊 2 3 // 試了半天,才發現這是創建一個 TCP Server 的 4 function createServer(port) { 5 const HOST = '127.0.0.1'; 6 // const port = 7899; 7 if (server) { 8 server.close(); 9 } 10 11 server = net.createServer(); 12 13 server.listen(port, HOST, function () { 14 console.log('Server listen on port:' + server.address().address); 15 console.log('--------------------------------------服務正在監聽中---------------------------------------'); 16 sendServerData('start-server', '服務正在監聽中,server is listening...'); 17 }); 18 19 20 server.on('connection', socket => { 21 sendServerData('connect-server', 'Get conneciton from:' + socket.remoteAddress); 22 23 socket.on('data', data => { 24 sendServerData('data-server', 'Get data from socket:' + socket.remoteAddress + '. The data:' + data); 25 socket.write('you said:' + data); 26 }); 27 28 socket.on('close', () => { 29 sendServerData('close-server', 'Socket:' + socket.remoteAddress + " closed"); 30 }) 31 }); 32 33 } 34 35 let server; 36 let client; 37 let serverEvent, clientEvent; 38 // 通過這個方法就可以建立一個TCP服務器,當收到前端發送的event,就可以創建了,這里前端發送的消息是 start-server 39 ipcMain.on('start-server', (event, arg) => { 40 serverEvent = event; 41 // event.sender.send() 42 console.log('+++++++++++++++++++++++++++ event ++++++++++++++++++++++++++++++++++++++++++++++') 43 console.log(event) 44 console.log('=================================== arg =============================================') 45 console.log(arg) 46 createServer(arg); 47 }) 48 49 // 將消息返回給前端 50 function sendServerData(channel, msg) { 51 try { 52 console.log(`server send event ${channel}, ${msg}`); 53 if (serverEvent) { 54 serverEvent.sender.send(channel, msg); 55 } 56 } catch (error) { 57 console.error('gt error:' + error); 58 } 59 }
然后在前端頁面上新增兩個輸入框和一個按鈕,如下圖:
Home.vue
代碼:
1 <template> 2 <div class="home"> 3 <img alt="Vue logo" src="../assets/logo.png" /> 4 <div> 5 發送的消息:<input type="text" v-model="msg" /> 6 </div> 7 <div> 8 接收的消息:<input type="text" v-model="msg2"> 9 </div> 10 <div> 11 <button @click="tcpServer">TCP通信</button> 12 </div> 13 <!-- <HelloWorld msg="Welcome to Your Vue.js App" /> --> 14 </div> 15 </template> 16 17 <script> 18 const ipc = window.require("electron").ipcRenderer; 19 ipc.on("start-server", (evnet, args) => { 20 console.log(evnet,args) 21 }); 22 ipc.on("data-server", (evnet, args) => { 23 console.log(evnet,args) 24 }); 25 26 ipc.on("close-server", (evnet, args) => { 27 console.log(evnet,args) 28 }); 29 30 // @ is an alias to /src 31 import HelloWorld from "@/components/HelloWorld.vue"; 32 33 export default { 34 name: "Home", 35 components: { 36 HelloWorld, 37 }, 38 data() { 39 return { 40 msg: "", 41 msg2: "", 42 }; 43 }, 44 methods: { 45 tcpServer() { 46 console.log(this.msg); 47 ipc.send("start-server", this.msg); 48 }, 49 addText(msg) { 50 this.msg2 += msg + "\n"; 51 }, 52 }, 53 }; 54 </script> 55 56 <style lang="css" scoped> 57 img { 58 -webkit-app-region: drag; 59 } 60 </style>
下面這張圖是主進程在控制台輸出的信息,從中可以看出event
參數是這么一大坨東西,arg
參數就是我們輸入框輸入的111。
如果我們先通過網絡調試助手,創建一個ip
為 127.0.0.1 端口號為 7899 的 TCP Server
,然后再回到頁面上在輸入框內也輸入7899,然后點擊按鈕,就發現報錯了,報錯說127.0.0.1:7899已經被創建了。
可以看出在主進程里面加的這段代碼其實是通過渲染進程來創建一個TCP Server
的,可這並不是我的需求,我的需求是將Electron
作為一個客戶端,可以向CCD
的TCP
服務器發送消息,並且可以接收CCD
返回給我的信息,下面實現一下。
參考資料二、electron 使用tcp套接字(一)
參考資料三、Node.js Net 模塊
在主進程(background.js
)中添加下面這段代碼:
1 // 作為客戶端 2 var net = require('net'); 3 var HOST = '127.0.0.1'; 4 var PORT = 7899; 5 6 var client = new net.Socket(); 7 client.connect(PORT, HOST, function() { 8 console.log('CONNECTED TO: ' + HOST + ':' + PORT); 9 // 建立連接后立即向服務器發送數據,服務器將收到這些數據 10 client.write('Hello TCP/IP! 老子終於通過 Electron 實現和你通信了!!!'); 11 12 }); 13 14 // 為客戶端添加“data”事件處理函數 15 // data是服務器發回的數據 16 client.on('data', function(data) { 17 console.log('DATA: ' + data); 18 // 完全關閉連接 19 client.destroy(); 20 }); 21 22 // 為客戶端添加“close”事件處理函數 23 client.on('close', function() { 24 console.log('Connection closed'); 25 });
然后通過網絡調試助手打開一個127.0.0.1:7899的TCP Server
服務,我們通過Electron
向網絡調試助手發送一條消息。
再次啟動我們的Electron
項目,項目一啟動就會自動向網絡調試助手發送一條信息,在網絡調試助手中我們就可以看到這條消息了
然后再通過網絡調試助手給我們的客戶端Electron
發送一條數據:
然后我們的Electron
也就可以收到來自TCP Server
服務端的消息了,至此,大功告成。
最后貼出我主進程background.js
的完整代碼:
1 'use strict' 2 3 import { 4 app, 5 protocol, 6 BrowserWindow, 7 ipcMain, 8 } from 'electron' 9 import { 10 createProtocol 11 } from 'vue-cli-plugin-electron-builder/lib' 12 import installExtension, { 13 VUEJS_DEVTOOLS 14 } from 'electron-devtools-installer' 15 const isDevelopment = process.env.NODE_ENV !== 'production' 16 17 const net = require('net'); 18 // Keep a global reference of the window object, if you don't, the window will 19 // be closed automatically when the JavaScript object is garbage collected. 20 let win 21 22 // Scheme must be registered before the app is ready 23 protocol.registerSchemesAsPrivileged([{ 24 scheme: 'app', 25 privileges: { 26 secure: true, 27 standard: true 28 } 29 }]) 30 31 function createWindow() { 32 // Create the browser window. 33 win = new BrowserWindow({ 34 width: 800, 35 height: 700, 36 // 設置窗口的透明屬性為true 37 // transparent:true, 38 // 禁用默認邊框,無法拖拽移動窗口,也無法最大化、最小化、關閉窗口 39 frame: false, 40 // 透明的窗口不可調整大小,所以將resizable屬性也設置為false 41 // resizable:false, 42 // 為了防止雙擊窗口可拖拽區域觸發最大化事件,將maximizable屬性也設置為false 43 // maximizable:false, 44 webPreferences: { 45 // nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION, 46 // 注意devTools可能會影響到transparent 47 // devTools: false, 48 nodeIntegration: true, 49 // enableRemoteModule:true 50 } 51 }) 52 53 if (process.env.WEBPACK_DEV_SERVER_URL) { 54 // Load the url of the dev server if in development mode 55 win.loadURL(process.env.WEBPACK_DEV_SERVER_URL) 56 if (!process.env.IS_TEST) win.webContents.openDevTools() 57 } else { 58 createProtocol('app') 59 // Load the index.html when not in development 60 win.loadURL('app://./index.html') 61 } 62 63 win.on('closed', () => { 64 win = null 65 }) 66 } 67 68 // Quit when all windows are closed. 69 app.on('window-all-closed', () => { 70 // On macOS it is common for applications and their menu bar 71 // to stay active until the user quits explicitly with Cmd + Q 72 if (process.platform !== 'darwin') { 73 app.quit() 74 } 75 }) 76 77 app.on('activate', () => { 78 // On macOS it's common to re-create a window in the app when the 79 // dock icon is clicked and there are no other windows open. 80 if (win === null) { 81 createWindow() 82 } 83 }) 84 85 // This method will be called when Electron has finished 86 // initialization and is ready to create browser windows. 87 // Some APIs can only be used after this event occurs. 88 app.on('ready', async () => { 89 if (isDevelopment && !process.env.IS_TEST) { 90 // Install Vue Devtools 91 try { 92 await installExtension(VUEJS_DEVTOOLS) 93 } catch (e) { 94 console.error('Vue Devtools failed to install:', e.toString()) 95 } 96 } 97 98 // 最小化窗體 99 ipcMain.on('minWindow', () => { 100 win.minimize() 101 }) 102 103 // 關閉窗體 104 ipcMain.on('closeWindow', () => { 105 win.close() 106 }) 107 108 // 最大化窗體 109 ipcMain.on('maxWindow', () => { 110 win.isMaximized() ? win.unmaximize() : win.maximize() 111 }) 112 113 createWindow() 114 }) 115 116 // Exit cleanly on request from parent process in development mode. 117 if (isDevelopment) { 118 if (process.platform === 'win32') { 119 process.on('message', (data) => { 120 if (data === 'graceful-exit') { 121 app.quit() 122 } 123 }) 124 } else { 125 process.on('SIGTERM', () => { 126 app.quit() 127 }) 128 } 129 } 130 131 132 // TCP/IP通訊 133 134 // 試了半天,才發現這是創建一個 TCP Server 的 135 // function createServer(port) { 136 // const HOST = '127.0.0.1'; 137 // // const port = 7899; 138 // if (server) { 139 // server.close(); 140 // } 141 142 // server = net.createServer(); 143 144 // server.listen(port, HOST, function () { 145 // console.log('Server listen on port:' + server.address().address); 146 // console.log('--------------------------------------服務正在監聽中---------------------------------------'); 147 // sendServerData('start-server', '服務正在監聽中,server is listening...'); 148 // }); 149 150 151 // server.on('connection', socket => { 152 // sendServerData('connect-server', 'Get conneciton from:' + socket.remoteAddress); 153 154 // socket.on('data', data => { 155 // sendServerData('data-server', 'Get data from socket:' + socket.remoteAddress + '. The data:' + data); 156 // socket.write('you said:' + data); 157 // }); 158 159 // socket.on('close', () => { 160 // sendServerData('close-server', 'Socket:' + socket.remoteAddress + " closed"); 161 // }) 162 // }); 163 164 // } 165 166 // let server; 167 // let client; 168 // let serverEvent, clientEvent; 169 // // 通過這個方法就可以建立一個TCP服務器,當收到前端發送的event,就可以創建了,這里前端發送的消息是 start-server 170 // ipcMain.on('start-server', (event, arg) => { 171 // serverEvent = event; 172 // // event.sender.send() 173 // console.log('+++++++++++++++++++++++++++ event ++++++++++++++++++++++++++++++++++++++++++++++') 174 // console.log(event) 175 // console.log('=================================== arg =============================================') 176 // console.log(arg) 177 // createServer(arg); 178 // }) 179 180 // 將消息返回給前端 181 // function sendServerData(channel, msg) { 182 // try { 183 // console.log(`server send event ${channel}, ${msg}`); 184 // if (serverEvent) { 185 // serverEvent.sender.send(channel, msg); 186 // } 187 // } catch (error) { 188 // console.error('gt error:' + error); 189 // } 190 // } 191 192 193 // 作為客戶端 194 var HOST = '127.0.0.1'; 195 var PORT = 7899; 196 197 var client = new net.Socket(); 198 client.connect(PORT, HOST, function() { 199 console.log('CONNECTED TO: ' + HOST + ':' + PORT); 200 // 建立連接后立即向服務器發送數據,服務器將收到這些數據 201 client.write('Hello TCP/IP! 老子終於通過 Electron 實現和你通信了!!!'); 202 }); 203 204 // 為客戶端添加“data”事件處理函數 205 // data是服務器發回的數據 206 client.on('data', function(data) { 207 console.log('DATA: ' + data); 208 // 完全關閉連接 209 client.destroy(); 210 }); 211 212 // 為客戶端添加“close”事件處理函數 213 client.on('close', function() { 214 console.log('Connection closed'); 215 });
文章會首發於我的微信公眾號:小笑殘虹,大家可以關注我,一起交流進步。