本篇将介绍WebSocket的双端实战(Client、Server)。
分为两部分:
1.Client:使用Starscream(swift)完成客户端长链需求。
2.Server:使用Golang完成服务端长链需求。
一、使用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/54868bdc2bd6
二、使用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接口,即可调用长链服务。
有疑问加站长微信联系(非本文作者)