@@ -129,224 +129,6 @@ this.ws.onclose = () => {
129129* [ Xterm.js v3] ( https://github.com/lflxp/gin-xterm/blob/master/src/views/pty/xterm3.vue )
130130* [ Xterm.js v4] ( https://github.com/lflxp/gin-xterm/blob/master/src/views/pty/index.vue )
131131
132- ## Golang Websocket
133-
134- 1 . 不需要解析Quit操作,那怎么判断程序结束去杀掉线程呢
135-
136- 解答:根据` exec.Command ` 的` cmd.Wait() ` 去判断,退出自动触发` quitChan <- true ` 去结束线程
137-
138- 2 . 后端websocket使用的什么项目
139-
140- 解答: ` github.com/gorilla/websocket `
141-
142- 3 . 如何实现golang命令行exec.Command的结果实时联动操作的?
143-
144- 解答:通过pty/tty在linux启动一个pid线程实现的,具体资料如下:
145-
146- * [ Linux中tty、pty、pts的概念区别] ( https://blog.csdn.net/fuhanghang/article/details/83691158 )
147- * [ golang pty pkg] ( github.com/creack/pty )
148-
149- 4 . gin或者go http如何做到websocket连接的切换?
150-
151- 解答:` http ` 升级为[ websocket] ( github.com/gorilla/websocket )
152-
153- ``` golang
154- ...
155-
156- import " github.com/gorilla/websocket"
157-
158- var upGrader = websocket.Upgrader {
159- ReadBufferSize : 1024 * 1024 ,
160- WriteBufferSize : 1024 * 1024 * 10 ,
161- CheckOrigin : func (r *http.Request ) bool {
162- return true
163- },
164- }
165-
166- func ping (c *gin .Context ) {
167- // 升级get请求为webSocket协议
168- ws , err := upGrader.Upgrade (c.Writer , c.Request , nil )
169- if err != nil {
170- return
171- }
172- defer ws.Close ()
173-
174- // todo
175- ...
176- }
177- ```
178-
179- 5 . 如何处理每个websocket的多个goroutine正常退出
180-
181- 解答:通过quit Channel + for select
182-
183- ``` golang
184- // 发送命令的执行结果
185- // 不执行具体任务
186- func (this *ClientContext ) Send (quitChan chan bool ) {
187- defer setQuit (quitChan)
188-
189- buf := make ([]byte , 1024 )
190-
191- for {
192- select {
193- case <- quitChan:
194- log.Info (" Close Send Channel" )
195- return
196- default :
197- // 读取命令执行结果并通过ws反馈给用户
198- size , err := this.Pty .Read (buf)
199- if err != nil {
200- log.Errorf (" %s 命令执行错误退出: %s " , this.Request .RemoteAddr , err.Error ())
201- return
202- }
203- log.Infof (" Send Size: %d buf: %s buf[:size]: %s \n " , size, string (buf), string (buf[:size]))
204- if err = this.write (buf[:size]); err != nil {
205- log.Error (err.Error ())
206- return
207- }
208- }
209- }
210- }
211- ```
212-
213- 6 . 如何判断各种操作类型以及怎么执行
214-
215- 解答:
216-
217- * 预先设计数据格式
218- * for select + switch
219-
220- ``` golang
221- // 判断命令
222- // @Params msg:message
223- switch message[0 ] {
224- case Input :
225- // TODO: 用户是否能写入
226- if !this.Xtermjs .Options .PermitWrite {
227- break
228- }
229-
230- // base64解码
231- decode , err := utils.DecodeBase64Bytes (string (message[1 :]))
232- if err != nil {
233- log.Error (err.Error ())
234- break
235- }
236-
237- // 向pty中传入执行命令
238- _, err = this.Pty .Write (decode)
239- if err != nil {
240- log.Error (err.Error ())
241- return
242- }
243- case Ping :
244- this.write ([]byte (" pong" ))
245- case ResizeTerminal :
246- // @数据格式 type+rows:cols
247- // base64解码
248- decode , err := utils.DecodeBase64 (string (message[1 :]))
249- if err != nil {
250- log.Error (err.Error ())
251- break
252- }
253-
254- tmp := strings.Split (decode, " :" )
255- rows , err := strconv.Atoi (tmp[0 ])
256- if err != nil {
257- log.Error (err.Error ())
258- this.write ([]byte (err.Error ()))
259- break
260- }
261- cols , err := strconv.Atoi (tmp[1 ])
262- if err != nil {
263- log.Error (err.Error ())
264- this.write ([]byte (err.Error ()))
265- break
266- }
267- window := struct {
268- row uint16
269- col uint16
270- x uint16
271- y uint16
272- }{
273- uint16 (rows),
274- uint16 (cols),
275- 0 ,
276- 0 ,
277- }
278- syscall.Syscall (
279- syscall.SYS_IOCTL ,
280- this.Pty .Fd (),
281- syscall.TIOCSWINSZ ,
282- uintptr (unsafe.Pointer (&window)),
283- )
284- default :
285- this.write ([]byte (fmt.Sprintf (" Unknow Message Type %s " , string (message[0 ]))))
286- log.Error (" Unknow Message Type %v " , message[0 ])
287- }
288- ```
289-
290- 7 . 如何设置exec.Command在pty终端的窗口大小
291-
292- 解答:
293-
294- ``` golang
295- window := struct {
296- row uint16
297- col uint16
298- x uint16
299- y uint16
300- }{
301- uint16 (rows),
302- uint16 (cols),
303- 0 ,
304- 0 ,
305- }
306- syscall.Syscall (
307- syscall.SYS_IOCTL ,
308- this.Pty .Fd (),
309- syscall.TIOCSWINSZ ,
310- uintptr (unsafe.Pointer (&window)),
311- )
312- ```
313-
314- 8 . 还需要注意什么
315-
316- * 明确命令下发渠道和作用
317-
318- > this.Pty.Write([ ] byte(*** ))
319-
320- * 明确websocket下发渠道和作用
321-
322- > this.WsConn.Write([ ] byte(*** ))
323-
324- * 如何操作正在执行中的cmd程序
325-
326- > this.Cmd.Process.Signal(syscall.Signal(this.Xtermjs.Options.CloseSignal))
327-
328- * 明确一个exec.Command和一个webscoket.Conn如何对一个http请求保持长连接请求的
329-
330- 一个http.Request请求由cmd.Wait() + go Send() + go Receive() + Channel 将一个完整链路进行串联起来
331-
332- * 如何下手?设计一个符合场景的struct
333-
334- ``` golang
335- // 服务端内部处理对象
336- type ClientContext struct {
337- Xtermjs *XtermJs // 前端配置
338- Request *http.Request // http客户端请求
339- WsConn *websocket.Conn // websocket连接
340- Cmd *exec.Cmd // exec.Command实例
341- Pty *os.File // 命令行pty代理
342- Cache *bytes.Buffer // 命令缓存
343- CacheMutex *sync.Mutex // 缓存并发锁
344- WriteMutex *sync.Mutex // 并发安全 通过ws发送给客户
345- }
346- ```
347-
348132## 参考
349133
350- * https://github.com/yudai/gotty @release-1 .0
351- * https://github.com/creack/pty
352134* https://mojotv.cn/2019/05/27/xtermjs-go
0 commit comments