大家好,我是公众号「线下聚会游戏」作者,开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏。其中的核心技术就是WebSocket,我会分享如何用Go实现WebSocket服务,文章写在专栏《Go WebSocket》里,关注专栏跟我一起学习吧!
背景
在专栏《Go WebSocket》里,有一些前置文章:
第一篇文章:《为什么我选用Go重构Python版本的WebSocket服务?》,介绍了我的目标。
第二篇文章:《你的第一个Go WebSocket服务: echo server》,介绍了一下怎么写一个WebSocket server。
第三篇文章:《你的第二个Go WebSocket服务: 聊天室》,介绍了如何实现一个单房间的聊天室。
第四篇文章:《你的第三个Go WebSocket服务: 多房间的聊天室(上:思考篇)》,介绍了实现一个多房间的聊天室的思路。
第五篇文章:《你的第三个Go WebSocket服务: 多房间的聊天室(下:实践篇)》,介绍了实现一个多房间的聊天室的代码。
第六篇文章:《你的第三个Go WebSocket服务: 多房间的聊天室(自动清理无人房间)》,介绍了如何清理无人的房间,避免内存无限增长的问题。
第七篇文章:《你的第三个Go WebSocket服务: 多房间的聊天室(黑天鹅事件)》,介绍了如何避免并发导致的资源竞争的问题,是通过悲观锁解决的。
温馨提示:阅读本文不需要阅读前面的文章。但最好先读完前三篇。
本文介绍了一个gorilla/websocket官方提供的简易版的web shell案例。
代码
见这里: github.com/gorilla/web…
体验
然后浏览器打开 127.0.0.1:8080,就可以输入ls
、pwd
等命令体验了。
但是这是个简易的 Web Shell,所以不支持vim这种命令。只能处理简单的stdin和stdout。
另外,它有一个参数,刚才我们传入的是sh
,你也可以传入其它可执行命令,例如echo
,会开启echo的交互命令。执行go run main.go echo
后,在浏览器内,你输入什么,它返回什么。
从main函数开始
阅读一段go代码,应该从外到里,一层一层拨开她的衣。
flag.Parse()
是在处理参数,这里要求必须有1个参数,后续会执行这个参数对应的命令,就可以在Web中交互了。
关于exec.LookPath(flag.Args()[0])
:这是在环境变量中寻找PATH,返回一个字符串(可能是绝对路径或相对路径)。
随后启动了http服务(用serveHome
处理),和websocket服务(用serveWs处理)。前者是展示html,后者处理websocket。
阅读serveWs
建立ws连接
上面是建立ws连接,以前聊过,不多说了。
创建用于标准输出的Pipe
Pipe returns a connected pair of Files; reads from r return bytes written to w. It returns the files and an error, if any.
上面是新建管道os.Pipe,之后用于连接标准输出。
创建用于标准输入的Pipe
上面是新建管道os.Pipe,之后用于连接标准输出。
让操作系统执行PATH对应的命令(启动了新的进程)
刚刚我们LookPath找到了具体要执行的命令,现在让操作系统执行它,通过os.StartProcess
:
StartProcess会启动一个进程,flag.Args()作为它的参数。
为了在Go这个进程中跟另一个进程交互,需要通过Pipe连接,就是我们刚才定义的2个。程序都有标准输入、标准输出、异常输出,所以定义了os.ProcAttr
,这里我们把异常输出也输出到了标准输出了。
启动其它goroutine,处理输入输出
pumpStdout处理进程的输出;pumpStdin处理进程的输入。ping只是为了跟客户端保持持久的连接。
stdoutDone
是进程结束的标志,结束后,ws连接也要断开(毕竟连着也没法交互,没意义了)。
先阅读简单的ping
是个死循环,每隔一段时间(pingPeriod),都会主动个PingMessage,保持连接。浏览器收到后会自动回复Pong消息。通过这种方式,双方都知道彼此还连着。
当然,如果done了,进程结束,就可以停止ping了。相反也是一样,ws断开连接时,进程也可以结束了。
阅读pumpStdin
注意,pumpStdin不是在serveWs用go开启的goroutine。所以到这里时,其实serveWs就阻塞在pumpStdin里的死循环了。
主要就是读取ws
消息,然后把消息写入w
(即inw
这个Pipe),之后,上面说的新启动的进程会收到这个消息。
阅读pumpStdout
新启动的进程有标准输出或异常输出时,会发送到Pipe,我们代码中通过outr
可获取到输出,即本函数的参数r
。
这是个死循环,不断读取s.Scan()
进程输出,然后通过ws发给客户端。
直到ws断开,就结束了进程close(donw)
。
写在最后
我是HullQin,独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费无广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这2个专栏里分享:《教你做小游戏》、《极致用户体验》。
本文正在参加技术专题18期-聊聊Go语言框架。