基于http请求以拉的方式去作服务器的推送,不管是实时性和有效字节都是差强人意的效果。web
公司的im系统在与客户端的交互上实际上借助了websocket来实现服务器与客户端的事实消息推送,今天就来简单了解下这个协议,而且本身实现对websocket的响应。服务器
能够看到在理解了tcp和http以后,websocket的设计其实并不复杂,再最开始创建连接的时候客户端实际上会进行一次http请求,只不过请求头的内容有些特别,这里咱们来看下:websocket
GET /chat HTTP/1.1socket
Host: server.example.comtcp
Upgrade: websocketide
Connection: Upgrade测试
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==this
Origin: http://example.com编码
Sec-WebSocket-Protool: chat,superchat加密
Sec-WebSocket-Version:13
能够看到这个报文里包含了一些附加头信息。其中附加头信息"Upgrade: WebSocket" ,代表这是一个申请协议升级的http请求。"Sec-WebSocket-Key"是随机的,服务端会用这些数据构造出
一个SHA-1的信息摘要,把"Sec-WebSocket-Key"加上一个魔幻字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"。使用SHA-1加密,而后进行BASE-64编码,将结果做为"Sec-WebSocket-Accept"头的值,返回给客户端:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protool: chat
实现对websocket请求的响应:
public class WebsocketServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket=new ServerSocket(8080); Socket socket= serverSocket.accept(); new Thread(new Handle(socket)).start(); } }
public class Handle implements Runnable{ private Socket socket; Handle(Socket socket){ this.socket=socket; } @Override public void run() { try { BufferedReader reader=new BufferedReader(new InputStreamReader(socket.getInputStream())); OutputStreamWriter streamWriter=new OutputStreamWriter(socket.getOutputStream()); BufferedWriter bufferedWriter=new BufferedWriter(streamWriter); String key=null; //读报文 while (true){ String s= reader.readLine(); System.out.println(s); if (s.equals("")){ break; }else{ if (s.contains("Sec-WebSocket-Key")){ String keyValue[]=s.split(":"); key=keyValue[1].trim()+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; } } } //写报文 MessageDigest messageDigest=DigestUtils.getSha1Digest(); byte[] digest=messageDigest.digest(key.getBytes()); Base64 base64 = new Base64(); String finalKey=base64.encodeToString(digest); bufferedWriter.write("HTTP/1.1 101 Switching Protocols\r\n"); bufferedWriter.write("Upgrade: websocket\r\n"); bufferedWriter.write("Connection: Upgrade\r\n"); bufferedWriter.write("Sec-WebSocket-Accept: "+finalKey+"\r\n"); bufferedWriter.write("Sec-WebSocket-Protool: chat\r\n"); bufferedWriter.write("\r\n"); bufferedWriter.write("test"); bufferedWriter.write("\r\n"); bufferedWriter.flush(); //接收数据 System.out.println("响应报文已经发送"); while (true){ String s=reader.readLine(); System.out.println(s==null); } } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }
创建链接的时候要注意 Sec-WebSocket-Key在作sha-1哈希时,取得时摘要 而后拿着摘要去作base64遍嘛获得Sec-WebSocket-Accept,上面代码还有问题就是链接创建以后 BufferedReader的readLine()方法在遇到\r,\r\n,\n以前会等到填满缓冲区才会被唤醒,找的不少测试用的客户端都会把换行符去掉致使线程在填满缓冲区8KB以前一直阻塞,另外一个问题就是字符流的编码问题。这里更多的关注链接创建过程,链接创建以后其实就是直接用tcp传输数据了,这里很少作赘述