WebSocket NIO服务端--连接部分

原创
2013/01/02 19:08
阅读数 8.1K

年前就写了点儿的东西,今天抽空把连接的部分给补完整了,之前写这个也是为了练练手,敲点儿代码。网上基本上都是C#、PHP或者socket写的版本,当然我也发现有用mina写的。其实用什么语言写都是一样的,只不过是对协议的解析。所以无论是用mina还是socket,代码量都是差不多的。没发现NIO版本的,就写了一些。

最早看见的文章来自IBM《使用 HTML5 WebSocket 构建实时 Web 应用》。这里面讲述的很清楚,什么是web socket。唯一遗憾的是,这篇文章中的协议版本不够新,现在的chrome已经支持到version 13了,而文章中的版本还是10。所以在编码和解码上有些差异。本文写的是13版本的代码。当然再文章最后有个JS的客户端还是可以拿来应用的。

首先当然是协议,请求端的协议格式如下:

GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 127.0.0.1:9020
Origin: null
Sec-WebSocket-Key: SN3OSin4/Zok8kmgrD8qxQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: x-webkit-deflate-frame

这里最重要的就是Sec-WebSocket-Key,服务器端需要通过这个key计算出相应的Accept值,这个值的计算步骤如下:

  1. 获取Sec-WebSocket-Key中的值,注意不要有空格
  2. 将Sec-WebSocket-Key的值加上“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”后进行SHA1摘要
  3. 将摘要后的值做BASE64编码,生成相应的Sec-WebSocket-Accept值。

简单的用代码模拟下:

/**Sec-WebSocket-Key*/
		int keyindex=request.indexOf("Sec-WebSocket-Key:");
		String key=request.substring(keyindex+19,keyindex+43);
		System.out.println("Sec-WebSocket-Key:"+key);
		
		/**计算结果*/
		String new_key=key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
		byte[] key_sha1=SHA1Utils.SHA1(new_key);
		String result=new String(Base64.encode(key_sha1));
		
		/**返回协议*/
		StringBuilder sb = new StringBuilder("HTTP/1.1 101 Switching Protocols\r\n");
		sb.append("Upgrade: websocket\r\n");
		sb.append("Connection: Upgrade\r\n");
		sb.append("Sec-WebSocket-Accept:"+result+"\r\n\r\n");

所以这样一来,返回协议的格式也就出来了:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept:Prqb8zq7hH8B5tU2ujXT9dM9Gio=
如果上述步骤都正确的话,连接部分就算是成功了,连接上后,这是一个长连接,浏览器和服务器之间就维持着这个连接,直到一方发生中断。后续的就是解析格式,获取通信的数据,这里需要了解web socket的协议格式:

当然这部分代码还没有写,不过有了协议,解析就是一个耐心和细心的工作了,至于更详细的相关web socket可以参考相关RFC文档:http://datatracker.ietf.org/doc/rfc6455/?include_text=1

最后贴上服务器端的代码:

package socketserver;

import it.sauronsoftware.base64.Base64;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class WebSocketServer {

	private ServerSocketChannel ssc;
	private Selector selector;
	private Set<SocketChannel> keylist = Collections
			.synchronizedSet(new HashSet<SocketChannel>());

	public WebSocketServer() throws IOException {
		init();
	}

	private void init() throws IOException {
		ssc = ServerSocketChannel.open();
		ServerSocket ss = ssc.socket();
		ss.bind(new InetSocketAddress(9020));
		selector = Selector.open();
		ssc.configureBlocking(false);
		ssc.register(selector, SelectionKey.OP_ACCEPT);

		if (ssc.isOpen() && selector.isOpen()) {
			System.out.println("listening port:" + 9020 + "....");
			polling();
		}
	}

	private void polling() throws IOException {

		while (true) {
			try {
				int n = selector.select();
				if (n == 0) {
					continue;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}

			Iterator<SelectionKey> keys = selector.selectedKeys().iterator();

			while (keys.hasNext()) {
				SelectionKey key = keys.next();
				keys.remove();
				if (key.isAcceptable()) {
					acceptOperation(key);

				} else if (key.isReadable()) {
					readOperation(key);
				}else if(key.isWritable()){
					writeOperation(key);
				}
			}
		}
	}

	private void writeOperation(SelectionKey key) {
		// TODO Auto-generated method stub
		
	}

	private void acceptOperation(SelectionKey key) throws IOException {

		ServerSocketChannel server = (ServerSocketChannel) key.channel();
		SocketChannel client = server.accept();
		client.configureBlocking(false);
		client.register(selector, SelectionKey.OP_READ);
		System.out.println("coming:  " + client.socket().getReuseAddress());
		
		// 保存连接上的client
		keylist.add(client);
		
		/**welcome the clients*/
		ByteBuffer dst = ByteBuffer.allocate(1024);
		StringBuilder sb = new StringBuilder("client:");
		sb.append(client + "arrival");
		dst.put(sb.toString().getBytes());
		dst.flip();
		broadcast(keylist, dst, client);
		
	}

	private void readOperation(SelectionKey key) throws IOException {
		
		SocketChannel client = (SocketChannel) key.channel();
		ByteBuffer dst = ByteBuffer.allocate(1024);
		int n = -1;
		try {
			n = client.read(dst);
		} catch (Exception ex) {
			System.err.println("client has disconnect...");
		}
		if (n == -1) {
			System.out.println(client.socket().getRemoteSocketAddress()+" leave.............");
			key.cancel();
			client.close();
			keylist.remove(client);
			return;
		}
		dst.flip();
		StringBuilder sb = new StringBuilder();
		if (dst.hasRemaining()) {
			while (dst.hasRemaining()) {
				sb.append((char) dst.get());
			}
			if (sb.toString().contains("HTTP/1.1")) {
				dst.flip();
				/**处理协议*/
				ByteBuffer buffer = processProtocol(dst);
				writeHTTP(keylist, buffer, client);
			} else {
				dst.flip();
				broadcast(keylist, dst, client);
			}
		}
		System.out.println("receive:-----------------\r\n"+sb.toString()+"\r\n");	
		
		dst.clear();
	}

	private ByteBuffer processProtocol(ByteBuffer dst) {
		StringBuilder request=new StringBuilder();
		
		while(dst.hasRemaining()){
			request.append((char)dst.get());
		}

		/**Sec-WebSocket-Key*/
		int keyindex=request.indexOf("Sec-WebSocket-Key:");
		String key=request.substring(keyindex+19,keyindex+43);
		System.out.println("Sec-WebSocket-Key:"+key);
		
		/**计算结果*/
		String new_key=key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
		byte[] key_sha1=SHA1Utils.SHA1(new_key);
		String result=new String(Base64.encode(key_sha1));
		
		/**返回协议*/
		StringBuilder sb = new StringBuilder("HTTP/1.1 101 Switching Protocols\r\n");
		sb.append("Upgrade: websocket\r\n");
		sb.append("Connection: Upgrade\r\n");
		sb.append("Sec-WebSocket-Accept:"+result+"\r\n\r\n");

		ByteBuffer buffer = ByteBuffer.allocate(1024);
		buffer.put(sb.toString().getBytes());
		buffer.flip();
		System.out.println(sb.toString());
		return buffer;
	}

	private void writeHTTP(Set<SocketChannel> set, ByteBuffer dst,
			SocketChannel currentClient) throws IOException {
		currentClient.write(dst);
	}

	private void broadcast(Set<SocketChannel> set, ByteBuffer dst,
			SocketChannel currentClient) throws IOException {

		System.out.println("now has-" + set.size() + "-clients");
		Iterator<SocketChannel> iter = set.iterator();
		while (iter.hasNext()) {
			SocketChannel sc = iter.next();
			if (sc == currentClient) {
				continue;
			}
			try {
				sc.write(dst);
			} catch (IOException e) {
				continue;
			}
			dst.flip();
		}
	}

	
	public static void main(String[] args) throws IOException {
		WebSocketServer wss = new WebSocketServer();
	}
}
sha1部分,其实没必要把这个抽出来:
package socketserver;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class SHA1Utils {
	
	private static final String ENCODE = "UTF-8";

	private static MessageDigest sha1MD;

	public static byte[] SHA1(String text) {
		if (null == sha1MD) {
			try {
				sha1MD = MessageDigest.getInstance("SHA-1");
			} catch (NoSuchAlgorithmException e) {
				return null;
			}
		}
		try {
			sha1MD.update(text.getBytes(ENCODE), 0, text.length());
		} catch (UnsupportedEncodingException e) {
			sha1MD.update(text.getBytes(), 0, text.length());
		}
		return sha1MD.digest();
	}
}

客户端的例子可以从IBM那篇文章中找到。

展开阅读全文
打赏
2
20 收藏
分享
加载中
yak
能连接上,但是解码错误 receive:-----------------
@?-\¢ M?'\\
2014/02/15 09:28
回复
举报
更多评论
打赏
1 评论
20 收藏
2
分享
返回顶部
顶部