JavaNio编程实现phonegap插件websocket

分清协议和socket的关系。

想一想,如果单纯的使用socket实现通信的话,那么在接受消息的时候,我们需要read一下,如果在主线程中使用这个方法,那么如果没有消息发过来的时候,主线程就会阻塞,直到消息发过来为止。这显然是不能满足我们的需求的,很明显我们需要一直监听看看是不是有客户端发过来消息,而且要有实时性。如果你在主线程中使用while循环,确实能实现,但是还是做不了其他事情。明显自从出现线程编程的概念之后我们就能使用线程来实现这个效果。new一个线程出来,然后通过在run函数中加入while循环体,就可以实现。

但是新的问题会出现。传统的客户端服务器程序就是使用这种方法的,每增加一个客户端request连接请求,便会new一个线程出来来和这个客户端进行通信。但是线程不能无限增加,因为会消耗资源,导致系统性能下降,而且如果在每一个线程中read都处于一个阻塞状态,那么显然又造成了资源的浪费。于是从这个问题出发,有人提出了java nio这个概念,目的是减少线程的使用,实现“非阻塞”效果。那么nio是怎么做到减少线程数呢?

Java NIO非堵塞技术实际是采取Reactor模式,或者说是Observer模式为我们监察I/O端口,如果有内容进来,会自动通知我们,这样,我们就不必开启多个线程死等,从外界看,实现了流畅的I/O读写,不堵塞了。

Java NIO出现不只是一个技术性能的提高,你会发现网络上到处在介绍它,因为它具有里程碑意义,从JDK1.4开始,Java开始提高性能相关的功能,从而使得Java在底层或者并行分布式计算等操作上已经可以和C或Perl等语言并驾齐驱。

如果你至今还是在怀疑Java的性能,说明你的思想和观念已经完全落伍了,Java一两年就应该用新的名词来定义。从JDK1.5开始又要提供关于线程、并发等新性能的支持,Java应用在游戏等适时领域方面的机会已经成熟,Java在稳定自己中间件地位后,开始蚕食传统C的领域。

本文主要简单介绍NIO的基本原理,在下一篇文章中,将结合Reactor模式和著名线程大师Doug Lea的一篇文章深入讨论。

NIO主要原理和适用。

NIO 有一个主要的类Selector,这个类似一个观察者,只要我们把需要探知的socketchannel告诉Selector,我们接着做别的事情,当有事件发生时,他会通知我们,传回一组SelectionKey,我们读取这些Key,就会获得我们刚刚注册过的socketchannel,然后,我们从这个Channel中读取数据,放心,包准能够读到,接着我们可以处理这些数据。

Selector内部原理实际是在做一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会站起来报告,交出一把钥匙,让我们通过这把钥匙来读取这个channel的内容。

电子书包中的websocket协议需要在android中使用,但是android中目前还不支持,所以我们需要自己写一个phonegap插件。如何做呢?网上找了一个已经实现的代码:
/*
 * Copyright (c) 2010 Animesh Kumar  (https://github.com/anismiles)
 * Copyright (c) 2010 Strumsoft  (https://strumsoft.com)
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *  
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *  
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *  
 */
package com.strumsoft.websocket.phonegap;


import java.net.URI;
import java.util.Random;


/**
 * The <tt>WebSocketFactory</tt> is like a helper class to instantiate new
 * WebSocket instaces especially from Javascript side. It expects a valid
 * "ws://" URI.
 * 
 * @author Animesh Kumar
 */
public class WebSocketFactory {


/** The app view. */
//WebView appView;


/**
* Instantiates a new web socket factory.

* @param appView
*            the app view
*/
public WebSocketFactory() {

}


public WebSocket getInstance(String url) {
// use Draft75 by default
return getInstance(url, WebSocket.Draft.DRAFT75);
}


public WebSocket getInstance(String url, WebSocket.Draft draft) {
WebSocket socket = null;
Thread th = null;
try {
socket = new WebSocket(new URI(url), draft, getRandonUniqueId());
th = socket.connect();
return socket;
} catch (Exception e) {
//Log.v("websocket", e.toString());
if(th != null) {
th.interrupt();
}

return null;
}


/**
* Generates random unique ids for WebSocket instances

* @return String
*/
private String getRandonUniqueId() {
return "WEBSOCKET." + new Random().nextInt(100);
}


}





Webocket类
/*
 * Copyright (c) 2010 Nathan Rajlich (https://github.com/TooTallNate)
 * Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles)
 * Copyright (c) 2010 Strumsoft (https://strumsoft.com)
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
package com.strumsoft.websocket.phonegap;


import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;


//import android.util.Log;
//import android.webkit.WebView;


/**
 * The <tt>WebSocket</tt> is an implementation of WebSocket Client API, and
 * expects a valid "ws://" URI to connect to. When connected, an instance
 * recieves important events related to the life of the connection, like
 * <var>onOpen</var>, <var>onClose</var>, <var>onError</var> and
 * <var>onMessage</var>. An instance can send messages to the server via the
 * <var>send</var> method.
 * 
 * @author Animesh Kumar
 */
public class WebSocket implements Runnable {


/**
* Enum for WebSocket Draft
*/
public enum Draft {
DRAFT75, DRAFT76
}


// CONSTANT
/**
* The connection has not yet been established.
*/
public final static int WEBSOCKET_STATE_CONNECTING = 0;
/**
* The WebSocket connection is established and communication is possible.
*/
public final static int WEBSOCKET_STATE_OPEN = 1;
/**
* The connection is going through the closing handshake.
*/
public final static int WEBSOCKET_STATE_CLOSING = 2;
/**
* The connection has been closed or could not be opened.
*/
public final static int WEBSOCKET_STATE_CLOSED = 3;


/**
* An empty string
*/
private static String BLANK_MESSAGE = "";
/**
* The javascript method name for onOpen event.
*/
private static String EVENT_ON_OPEN = "onopen";
/**
* The javascript method name for onMessage event.
*/
private static String EVENT_ON_MESSAGE = "onmessage";
/**
* The javascript method name for onClose event.
*/
private static String EVENT_ON_CLOSE = "onclose";
/**
* The javascript method name for onError event.
*/
private static String EVENT_ON_ERROR = "onerror";
/**
* The default port of WebSockets, as defined in the spec.
*/
public static final int DEFAULT_PORT = 80;
/**
* The WebSocket protocol expects UTF-8 encoded bytes.
*/
public static final String UTF8_CHARSET = "UTF-8";
/**
* The byte representing Carriage Return, or \r
*/
public static final byte DATA_CR = (byte) 0x0D;
/**
* The byte representing Line Feed, or \n
*/
public static final byte DATA_LF = (byte) 0x0A;
/**
* The byte representing the beginning of a WebSocket text frame.
*/
public static final byte DATA_START_OF_FRAME = (byte) 0x00;
/**
* The byte representing the end of a WebSocket text frame.
*/
public static final byte DATA_END_OF_FRAME = (byte) 0xFF;


// INSTANCE Variables
/**
* The WebView instance from Phonegap DroidGap
*/
//private final WebView appView;
/**
* The unique id for this instance (helps to bind this to javascript events)
*/
private String id;
/**
* The URI this client is supposed to connect to.
*/
private URI uri;
/**
* The port of the websocket server
*/
private int port;
/**
* The Draft of the WebSocket protocol the Client is adhering to.
*/
private Draft draft;
/**
* The <tt>SocketChannel</tt> instance to use for this server connection.
* This is used to read and write data to.
*/
private SocketChannel socketChannel;
/**
* The 'Selector' used to get event keys from the underlying socket.
*/
private Selector selector;
/**
* Keeps track of whether or not the client thread should continue running.
*/
private boolean running;
/**
* Internally used to determine whether to recieve data as part of the
* remote handshake, or as part of a text frame.
*/
private boolean handshakeComplete;
/**
* The 1-byte buffer reused throughout the WebSocket connection to read
* data.
*/
private ByteBuffer buffer;
/**
* The bytes that make up the remote handshake.
*/
private ByteBuffer remoteHandshake;
/**
* The bytes that make up the current text frame being read.
*/
private ByteBuffer currentFrame;
/**
* Queue of buffers that need to be sent to the client.
*/
private BlockingQueue<ByteBuffer> bufferQueue;
/**
* Lock object to ensure that data is sent from the bufferQueue in the
* proper order
*/
private Object bufferQueueMutex = new Object();
/**
* Number 1 used in handshake
*/
private int number1 = 0;
/**
* Number 2 used in handshake
*/
private int number2 = 0;
/**
* Key3 used in handshake
*/
private byte[] key3 = null;
/**
* The readyState attribute represents the state of the connection.
*/
private int readyState = WEBSOCKET_STATE_CONNECTING;


private final WebSocket instance;


/**
* Constructor.

* Note: this is protected because it's supposed to be instantiated from {@link WebSocketFactory} only.

* @param appView
*            {@link android.webkit.WebView}
* @param uri
*            websocket server {@link URI}
* @param draft
*            websocket server {@link Draft} implementation (75/76)
* @param id
*            unique id for this instance
*/
protected WebSocket( URI uri, Draft draft, String id) {
//this.appView = appView;
this.uri = uri;
this.draft = draft;


// port
port = uri.getPort();
if (port == -1) {
port = DEFAULT_PORT;
}


// Id
this.id = id;


this.bufferQueue = new LinkedBlockingQueue<ByteBuffer>();
this.handshakeComplete = false;
this.remoteHandshake = this.currentFrame = null;
this.buffer = ByteBuffer.allocate(1);


this.instance = this;
}


// //
// /// WEB SOCKET API Methods
// ///
// //
/**
* Starts a new Thread and connects to server

* @throws IOException
*/
public Thread connect() throws IOException {
this.running = true;
this.readyState = WEBSOCKET_STATE_CONNECTING;
// open socket
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
// set address
socketChannel.connect(new InetSocketAddress(uri.getHost(), port));
// start a thread to make connection


// More info:
// http://groups.google.com/group/android-developers/browse_thread/thread/45a8b53e9bf60d82
// http://stackoverflow.com/questions/2879455/android-2-2-and-bad-address-family-on-socket-connect
System.setProperty("java.net.preferIPv4Stack", "true");
System.setProperty("java.net.preferIPv6Addresses", "false");


selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
//Log.v("websocket", "Starting a new thread to manage data reading/writing");


Thread th = new Thread(this);
th.start();
// return thread object for explicit closing, if needed
return th;
}


public void run() {
while (this.running) {
try {
_connect();
} catch (IOException e) {
this.onError(e);
}
}
}


/**
* Closes connection with server
*/
public void close() {
this.readyState = WebSocket.WEBSOCKET_STATE_CLOSING;


// close socket channel
try {
this.socketChannel.close();
} catch (IOException e) {
this.onError(e);
}
this.running = false;
selector.wakeup();


// fire onClose method
this.onClose();


this.readyState = WebSocket.WEBSOCKET_STATE_CLOSED;
}


/**
* Sends <var>text</var> to server

* @param text
*            String to send to server
*/
public void send(final String text) {
new Thread(new Runnable() {
@Override
public void run() {
if (instance.readyState == WEBSOCKET_STATE_OPEN) {
try {
instance._send(text);
} catch (IOException e) {
instance.onError(e);
}
} else {
instance.onError(new NotYetConnectedException());
}
}
}).start();
}


/**
* Called when an entire text frame has been received.

* @param msg
*            Message from websocket server
*/
public void onMessage(final String msg) {
System.out.println(msg);
}


public void onOpen() {
this.send("{'msg_type':1,'msg_content':'201030258','msg_time':'10.23'}");
System.out.println("open");
}


public void onClose() {
System.out.println("close");
}


public void onError(final Throwable t) {
System.out.println(t);
}


public String getId() {
return id;
}


/**
* @return the readyState
*/
public int getReadyState() {
return readyState;
}


/**
* Builds text for javascript engine to invoke proper event method with
* proper data.

* @param event
*            websocket event (onOpen, onMessage etc.)
* @param msg
*            Text message received from websocket server
* @return
*/
private String buildJavaScriptData(String event, String msg) {
String _d = "javascript:WebSocket." + event + "(" + "{" + "\"_target\":\"" + id + "\"," + "\"data\":'" + msg
+ "'" + "}" + ")";
return _d;
}


// //
// /// WEB SOCKET Internal Methods
// //
// //


private boolean _send(String text) throws IOException {
if (!this.handshakeComplete) {
throw new NotYetConnectedException();
}
if (text == null) {
throw new NullPointerException("Cannot send 'null' data to a WebSocket.");
}


// Get 'text' into a WebSocket "frame" of bytes
byte[] textBytes = text.getBytes(UTF8_CHARSET);
ByteBuffer b = ByteBuffer.allocate(textBytes.length + 2);
b.put(DATA_START_OF_FRAME);
b.put(textBytes);
b.put(DATA_END_OF_FRAME);
b.rewind();


// See if we have any backlog that needs to be sent first
if (_write()) {
// Write the ByteBuffer to the socket
this.socketChannel.write(b);
}


// If we didn't get it all sent, add it to the buffer of buffers
if (b.remaining() > 0) {
if (!this.bufferQueue.offer(b)) {
throw new IOException("Buffers are full, message could not be sent to"
+ this.socketChannel.socket().getRemoteSocketAddress());
}
return false;
}
return true;
}


// actual connection logic
private void _connect() throws IOException {
// Continuous loop that is only supposed to end when "close" is called.


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


while (i.hasNext()) {
SelectionKey key = i.next();
i.remove();
if (key.isConnectable()) {
if (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();

}
socketChannel.register(selector, SelectionKey.OP_READ);
_writeHandshake();
}
if (key.isReadable()) {
try {
_read();
} catch (NoSuchAlgorithmException nsa) {
this.onError(nsa);
}
}
}


}


private void _writeHandshake() throws IOException {
String path = this.uri.getPath();
if (path.indexOf("/") != 0) {
path = "/" + path;
}


String host = uri.getHost() + (port != DEFAULT_PORT ? ":" + port : "");
String origin = "my socket"; 
String request = "GET " + path + " HTTP/1.1\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n"
+ "Host: " + host + "\r\n" + "Sec-WebSocket-Origin: " + origin + "\r\n";
String key1,key2;
key1 = this._randomKey();
key2 = this._randomKey();
String key = key1 + key2;
this.key3 = getAuthKey(key);
request = request + key + "\r\n";

byte[] bToSend = request.getBytes(UTF8_CHARSET);
// Now we can send all keys as a single frame
_write(bToSend);
return;

}


private boolean _write() throws IOException {
synchronized (this.bufferQueueMutex) {
ByteBuffer buffer = this.bufferQueue.peek();
while (buffer != null) {
this.socketChannel.write(buffer);
if (buffer.remaining() > 0) {
return false; // Didn't finish this buffer. There's more to
// send.
} else {
this.bufferQueue.poll(); // Buffer finished. Remove it.
buffer = this.bufferQueue.peek();
}
}
return true;
}
}


private void _write(byte[] bytes) throws IOException {
this.socketChannel.write(ByteBuffer.wrap(bytes));
}


private void _read() throws IOException, NoSuchAlgorithmException {
this.buffer.rewind();


int bytesRead = -1;
try {
bytesRead = this.socketChannel.read(this.buffer);
} catch (Exception ex) {
}


if (bytesRead == -1) {
close();
} else if (bytesRead > 0) {
this.buffer.rewind();


if (!this.handshakeComplete) {
_readHandshake();
} else {
_readFrame();
}
}
}


private void _readFrame() throws UnsupportedEncodingException {
byte newestByte = this.buffer.get();


if (newestByte == DATA_START_OF_FRAME) { // Beginning of Frame
this.currentFrame = null;


} else if (newestByte == DATA_END_OF_FRAME) { // End of Frame
String textFrame = null;
// currentFrame will be null if END_OF_FRAME was send directly after
// START_OF_FRAME, thus we will send 'null' as the sent message.
if (this.currentFrame != null) {
textFrame = new String(this.currentFrame.array(), UTF8_CHARSET.toString());
}
// fire onMessage method
this.onMessage(textFrame);


} else { // Regular frame data, add to current frame buffer
ByteBuffer frame = ByteBuffer.allocate((this.currentFrame != null ? this.currentFrame.capacity() : 0)
+ this.buffer.capacity());
if (this.currentFrame != null) {
this.currentFrame.rewind();
frame.put(this.currentFrame);
}
frame.put(newestByte);
this.currentFrame = frame;
}
}


private void _readHandshake() throws IOException, NoSuchAlgorithmException {
ByteBuffer ch = ByteBuffer.allocate((this.remoteHandshake != null ? this.remoteHandshake.capacity() : 0)
+ this.buffer.capacity());
if (this.remoteHandshake != null) {
this.remoteHandshake.rewind();
ch.put(this.remoteHandshake);
}
ch.put(this.buffer);
this.remoteHandshake = ch;
this.remoteHandshake.rewind();

this.buffer.rewind();
char c = (char)this.buffer.get();
//System.out.println(c);

if(c == '\n'){
byte[] arr = this.remoteHandshake.array();
String tem= "";
for(int i=0;i<arr.length;i++){
tem += (char)arr[i];
}
this.remoteHandshake = null;
_readHandshake(tem);
}
}


private void _readHandshake(String tem) throws IOException, NoSuchAlgorithmException {
this.handshakeComplete = true;
boolean isConnectionReady = true;


if()


if (isConnectionReady) {
this.readyState = WEBSOCKET_STATE_OPEN;
// fire onOpen method
this.onOpen();
} else {
close();
}
}


private String _randomKey() {
Random r = new Random();
long maxNumber = 4294967295L;
long spaces = r.nextInt(12) + 1;
int max = new Long(maxNumber / spaces).intValue();
max = Math.abs(max);
int number = r.nextInt(max) + 1;
if (this.number1 == 0) {
this.number1 = number;
} else {
this.number2 = number;
}
long product = number * spaces;
String key = Long.toString(product);
int numChars = r.nextInt(12);
for (int i = 0; i < numChars; i++) {
int position = r.nextInt(key.length());
position = Math.abs(position);
char randChar = (char) (r.nextInt(95) + 33);
// exclude numbers here
if (randChar >= 48 && randChar <= 57) {
randChar -= 15;
}
key = new StringBuilder(key).insert(position, randChar).toString();
}
for (int i = 0; i < spaces; i++) {
int position = r.nextInt(key.length() - 1) + 1;
position = Math.abs(position);
key = new StringBuilder(key).insert(position, "\u0020").toString();
}
return key;
}

//获得服务器返回的密钥
private byte[] getAuthKey(String key){
key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
MessageDigest sha1 = MessageDigest.getInstance();
}
}



这是我改过的代码,不要按照我的去看,因为我想实现的是websocket草案10的代码。但是这个类原来是用来实现7.5和7.6的,我该了很多,而且没有标准化。草案10的还没出来,但是在这里完全没有必要使用socketchannel,个人观点,因为在这个类的selector中进行注册的只可能是这个类对象的socketchannel,只有一个,而且注册的时候中connecable那个只会在开始链接的时候执行一次,其他的工作其实还是监听消息,和while差不多,而且它在这里每一次读取一个字节,确实效率很低,我不知道这样的代码居然还能声明说要加入官方类库中。其实由于我们的目标使用负载并不大,完全可以使用传统的thread per request开发模式来写我们的协议。关注我的博客看代码。或发邮件至yaboxu@whu.edu.cn



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值