WebSocket双端实践(iOS/ Golang)

秸秆青储液压打包机 · · 80 次点击 · · 开始浏览    

一、使用Starscream(swift)完成客户端长链需求

首先附上Starscream:GitHub地址

第一步:将Starsream导入到项目。

打开Podfile,加上:

pod'Starscream','~> 4.0.0'复制代码

接着pod install。

第二步:实现WebSocket能力。

导入头文件,import Starscream

初始化WebSocket,把一些请求头包装一下(与服务端对好)

privatefuncinitWebSocket(){// 包装请求头varrequest =URLRequest(url:URL(string:"ws://127.0.0.1:8000/chat")!)    request.timeoutInterval =5// Sets the timeout for the connectionrequest.setValue("some message", forHTTPHeaderField:"Qi-WebSocket-Header")    request.setValue("some message", forHTTPHeaderField:"Qi-WebSocket-Protocol")    request.setValue("0.0.1", forHTTPHeaderField:"Qi-WebSocket-Version")    request.setValue("some message", forHTTPHeaderField:"Qi-WebSocket-Protocol-2")    socketManager =WebSocket(request: request)    socketManager?.delegate =self}复制代码

同时,我用三个Button的点击事件,分别模拟了connect(连接)、write(通信)、disconnect(断开)。

// Mark - Actions// 连接@objcfuncconnetButtonClicked(){        socketManager?.connect()    }// 通信@objcfuncsendButtonClicked(){        socketManager?.write(string:"some message.")    }// 断开@objcfunccloseButtonCliked(){        socketManager?.disconnect()    }复制代码

第三步:实现WebSocket回调方法(接收服务端消息)

遵守并实现WebSocketDelegate。

extensionViewController:WebSocketDelegate{// 通信(与服务端协商好)funcdidReceive(event: WebSocketEvent, client: WebSocket){switchevent {case.connected(letheaders):            isConnected =trueprint("websocket is connected:\(headers)")case.disconnected(letreason,letcode):            isConnected =falseprint("websocket is disconnected:\(reason)with code:\(code)")case.text(letstring):print("Received text:\(string)")case.binary(letdata):print("Received data:\(data.count)")case.ping(_):breakcase.pong(_):breakcase.viablityChanged(_):breakcase.reconnectSuggested(_):breakcase.cancelled:            isConnected =falsecase.error(leterror):            isConnected =false// ...处理异常错误print("Received data:\(String(describing: error))")        }    }}复制代码

分别对应的是:

publicenumWebSocketEvent{caseconnected([String:String])//!< 连接成功casedisconnected(String,UInt16)//!< 连接断开casetext(String)//!< string通信casebinary(Data)//!< data通信casepong(Data?)//!< 处理pong包(保活)caseping(Data?)//!< 处理ping包(保活)caseerror(Error?)//!< 错误caseviablityChanged(Bool)//!< 可行性改变casereconnectSuggested(Bool)//!< 重新连接casecancelled//!< 已取消}复制代码

这样一个简单的客户端WebSocket demo就算完成了。

客户端成功,日志截图:https://www.jianshu.com/u/8c785c6be781

Demo源码

二、使用Golang完成简单服务端长链需求

仅仅有客户端也无法验证WebSocket的能力。

因此,接下来我们用Golang简单做一个本地的服务端WebSocket服务。

PS:最近,正好在学习Golang,参考了一些大神的作品。

直接上代码了:

packagemainimport("crypto/sha1""encoding/base64""errors""io""log""net""strings")funcmain(){ln, err := net.Listen("tcp",":8000")iferr !=nil{log.Panic(err)}for{log.Println("wss")conn, err := ln.Accept()iferr !=nil{log.Println("Accept err:", err)}for{handleConnection(conn)}}}funchandleConnection(conn net.Conn){content :=make([]byte,1024)_, err := conn.Read(content)log.Println(string(content))iferr !=nil{log.Println(err)}isHttp :=false// 先暂时这么判断ifstring(content[0:3]) =="GET"{isHttp =true}log.Println("isHttp:", isHttp)ifisHttp {headers := parseHandshake(string(content))log.Println("headers", headers)secWebsocketKey := headers["Sec-WebSocket-Key"]// NOTE:这里省略其他的验证guid :="258EAFA5-E914-47DA-95CA-C5AB0DC85B11"// 计算Sec-WebSocket-Accepth := sha1.New()log.Println("accept raw:", secWebsocketKey+guid)io.WriteString(h, secWebsocketKey+guid)accept :=make([]byte,28)base64.StdEncoding.Encode(accept, h.Sum(nil))log.Println(string(accept))response :="HTTP/1.1 101 Switching Protocols\r\n"response = response +"Sec-WebSocket-Accept: "+string(accept) +"\r\n"response = response +"Connection: Upgrade\r\n"response = response +"Upgrade: websocket\r\n\r\n"log.Println("response:", response)iflenth, err := conn.Write([]byte(response)); err !=nil{log.Println(err)}else{log.Println("send len:", lenth)}wssocket := NewWsSocket(conn)for{data, err := wssocket.ReadIframe()iferr !=nil{log.Println("readIframe err:", err)}log.Println("read data:",string(data))err = wssocket.SendIframe([]byte("good"))iferr !=nil{log.Println("sendIframe err:", err)}log.Println("send data")}}else{log.Println(string(content))// 直接读取}}typeWsSocketstruct{MaskingKey []byteConn      net.Conn}funcNewWsSocket(conn net.Conn)*WsSocket{return&WsSocket{Conn: conn}}func(this *WsSocket)SendIframe(data []byte)error{// 这里只处理data长度<125的iflen(data) >=125{returnerrors.New("send iframe data error")}lenth :=len(data)maskedData :=make([]byte, lenth)fori :=0; i < lenth; i++ {ifthis.MaskingKey !=nil{maskedData[i] = data[i] ^ this.MaskingKey[i%4]}else{maskedData[i] = data[i]}}this.Conn.Write([]byte{0x81})varpayLenBytebyteifthis.MaskingKey !=nil&&len(this.MaskingKey) !=4{payLenByte =byte(0x80) |byte(lenth)this.Conn.Write([]byte{payLenByte})this.Conn.Write(this.MaskingKey)}else{payLenByte =byte(0x00) |byte(lenth)this.Conn.Write([]byte{payLenByte})}this.Conn.Write(data)returnnil}func(this *WsSocket)ReadIframe()(data []byte, err error){err =nil//第一个字节:FIN + RSV1-3 + OPCODEopcodeByte :=make([]byte,1)this.Conn.Read(opcodeByte)FIN := opcodeByte[0] >>7RSV1 := opcodeByte[0] >>6&1RSV2 := opcodeByte[0] >>5&1RSV3 := opcodeByte[0] >>4&1OPCODE := opcodeByte[0] &15log.Println(RSV1, RSV2, RSV3, OPCODE)payloadLenByte :=make([]byte,1)this.Conn.Read(payloadLenByte)payloadLen :=int(payloadLenByte[0] &0x7F)mask := payloadLenByte[0] >>7ifpayloadLen ==127{extendedByte :=make([]byte,8)this.Conn.Read(extendedByte)}maskingByte :=make([]byte,4)ifmask ==1{this.Conn.Read(maskingByte)this.MaskingKey = maskingByte}payloadDataByte :=make([]byte, payloadLen)this.Conn.Read(payloadDataByte)log.Println("data:", payloadDataByte)dataByte :=make([]byte, payloadLen)fori :=0; i < payloadLen; i++ {ifmask ==1{dataByte[i] = payloadDataByte[i] ^ maskingByte[i%4]}else{dataByte[i] = payloadDataByte[i]}}ifFIN ==1{data = dataBytereturn}nextData, err := this.ReadIframe()iferr !=nil{return}data =append(data, nextData...)return}funcparseHandshake(contentstring)map[string]string{headers :=make(map[string]string,10)lines := strings.Split(content,"\r\n")for_, line :=rangelines {iflen(line) >=0{words := strings.Split(line,":")iflen(words) ==2{headers[strings.Trim(words[0]," ")] = strings.Trim(words[1]," ")}}}returnheaders}复制代码

完成后,在本地执行:

gorun WebSocket_demo.go复制代码

即可开启本地服务。

这时候访问ws://127.0.0.1:8000/chat接口,即可调用长链服务。

作者:奇舞647

链接:https://juejin.im/post/5e45050ae51d45270c27741d

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自:简书

感谢作者:秸秆青储液压打包机

查看原文:WebSocket双端实践(iOS/ Golang)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:muxilin131420 备注:入群;或加QQ群:977810755

80 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传