Java tutorial
/** * Copyright 2013 openteach * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.openteach.diamond.network.waverider.network; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.openteach.diamond.network.waverider.command.Command; import com.openteach.diamond.network.waverider.command.CommandFactory; import com.openteach.diamond.network.waverider.config.WaveriderConfig; /** * <p> * TCP/IP?Slave * </p> * * @author <a href="mailto:sihai@taobao.com">sihai</a> * */ public class DefaultNetWorkClient implements NetWorkClient, Runnable { private static final Log logger = LogFactory.getLog(DefaultNetWorkClient.class); private String hostName; // ?? private int port; // ?? private SocketChannel socketChannel; // Socket? private Selector selector; // ? private Thread thread; // ?, ? private AtomicInteger state; // ? private AtomicBoolean isWeakuped; // ? private SocketChannelOPSChangeRequest opsChangeRequest; // ? // Buffer private BlockingQueue<ByteBuffer> inputBuffer; // ? private BlockingQueue<Command> outputBuffer; // ? private byte[] waitMoreDataLock; // //============================================================= // //============================================================= public DefaultNetWorkClient() { this(null, WaveriderConfig.WAVERIDER_DEFAULT_PORT); } public DefaultNetWorkClient(String hostName) { this(hostName, WaveriderConfig.WAVERIDER_DEFAULT_PORT); } public DefaultNetWorkClient(String hostName, int port) { this.hostName = hostName; this.port = port; state = new AtomicInteger(NetworkStateEnum.NETWORK_STATE_DISCONNECT.value); isWeakuped = new AtomicBoolean(false); inputBuffer = new LinkedBlockingQueue<ByteBuffer>(); outputBuffer = new LinkedBlockingQueue<Command>(); waitMoreDataLock = new byte[0]; } //============================================================= // LifeCycle //============================================================= @Override public boolean init() { state.set(NetworkStateEnum.NETWORK_STATE_DISCONNECT.value); isWeakuped.set(false); thread = new Thread(this, NET_WORK_CLIENT_THREAD_NAME); thread.setDaemon(true); return true; } @Override public boolean start() { // ? if (hostName == null || port == 0) { throw new IllegalArgumentException("hostName and port must be supply"); } // ? while (!connect() && !Thread.currentThread().isInterrupted()) { try { logger.warn("Can not connect to server , so sleep 60s, then try again"); Thread.sleep(60 * 1000); } catch (InterruptedException e) { // logger.error("OOPSException", e); state.set(NetworkStateEnum.NETWORK_STATE_DISCONNECT.value); Thread.currentThread().interrupt(); } } // ?? thread.start(); return state.get() == NetworkStateEnum.NETWORK_STATE_CONNECTED.value; } @Override public boolean stop() { // ? thread.interrupt(); // ? disconnect(); inputBuffer.clear(); outputBuffer.clear(); thread = null; socketChannel = null; selector = null; isWeakuped.set(false); return true; } @Override public boolean restart() { return stop() && init() && start(); } @Override public boolean notifyRead(SocketChannel channel) { logger.debug("notifyRead"); if (state.get() == NetworkStateEnum.NETWORK_STATE_CONNECTED.value) { // opsChangeRequest.addOps(SelectionKey.OP_READ); // ?selector weakup(); return true; } return false; } @Override public boolean notifyWrite(SocketChannel channel) { logger.debug("notifyWrite"); if (state.get() == NetworkStateEnum.NETWORK_STATE_CONNECTED.value) { // opsChangeRequest.addOps(SelectionKey.OP_WRITE); // ?selector weakup(); return true; } return false; } @Override public void waitMoreData(long timeout) throws InterruptedException { synchronized (waitMoreDataLock) { waitMoreDataLock.wait(timeout); } } @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { dispatch(); } catch (IOException e) { logger.error("OOPSException", e); // ? logger.warn(String.format("Slave try to reconnect to %s", hostName)); reconnect(); } catch (InterruptedException e) { // logger.error("OOPSException", e); Thread.currentThread().interrupt(); } } logger.info(new StringBuilder("Waverider-NetWork-Client stoped ").toString()); } // ? private boolean connect() { try { state.set(NetworkStateEnum.NETWORK_STATE_CONNECTING.value); socketChannel = SocketChannel.open(); // socketChannel.connect(new InetSocketAddress(hostName, port)); // ?? socketChannel.configureBlocking(false); selector = Selector.open(); socketChannel.register(selector, SelectionKey.OP_READ); opsChangeRequest = new SocketChannelOPSChangeRequest(socketChannel, 0); // state.set(NetworkStateEnum.NETWORK_STATE_CONNECTED.value); logger.warn(String.format("Slave connected to %s", hostName)); return true; } catch (IOException e) { logger.error("OOPSException", e); } state.set(NetworkStateEnum.NETWORK_STATE_DISCONNECT.value); return false; } // private void disconnect() { state.set(NetworkStateEnum.NETWORK_STATE_DISCONNECT.value); try { selector.close(); } catch (IOException e) { logger.error("OOPSException", e); } try { socketChannel.close(); } catch (IOException e) { logger.error("OOPSException", e); } socketChannel = null; selector = null; opsChangeRequest = null; isWeakuped.set(false); } // ? private void reconnect() { // ? disconnect(); while (!connect() && !Thread.currentThread().isInterrupted()) { try { logger.warn("Can not connect to server , so sleep 60s, then try again"); Thread.sleep(60 * 1000); } catch (InterruptedException e) { // logger.error("OOPSException", e); Thread.currentThread().interrupt(); } } } /** * ?, ?, ?, ?? * @throws IOException * @throws InterruptedException */ private void dispatch() throws IOException, InterruptedException { logger.debug("try dispatch"); SelectionKey key = null; key = opsChangeRequest.getChannel().keyFor(selector); if (key != null) { if ((opsChangeRequest.getOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) { // key.interestOps(SelectionKey.OP_WRITE); opsChangeRequest.clearOps(SelectionKey.OP_WRITE); } else if ((opsChangeRequest.getOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) { key.interestOps(SelectionKey.OP_READ); opsChangeRequest.clearOps(SelectionKey.OP_READ); } } // ?, isWeakuped.set(false); if (selector.select(WaveriderConfig.WAVERIDER_DEFAULT_NETWORK_TIME_OUT) <= 0) { return; } // ? Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { key = (SelectionKey) iterator.next(); iterator.remove(); if (!key.isValid()) { continue; } else if (key.isReadable()) { onRead(key); } else if (key.isWritable()) { onWrite(key); } } } /** * ?? * @param key * @throws IOException */ private void onConnected(SelectionKey key) throws IOException { if (socketChannel.isConnectionPending()) { socketChannel.finishConnect(); key.interestOps(SelectionKey.OP_READ); // ? state.set(NetworkStateEnum.NETWORK_STATE_CONNECTED.value); logger.info(new StringBuilder("Connected to server:").append(hostName).append(":").append(port)); } } /** * ?, ?inputBuffer * @param key * @throws IOException * @throws InterruptedException */ private void onRead(SelectionKey key) throws IOException, InterruptedException { logger.debug("onRead"); // ? ByteBuffer readByteBuffer = ByteBuffer.allocate(NetWorkConstants.DEFAULT_NETWORK_BUFFER_SIZE); int ret = 0; do { ret = socketChannel.read(readByteBuffer); } while (ret > 0); // , ??? if (ret == -1) { throw new IOException("EOF"); } readByteBuffer.flip(); if (readByteBuffer.hasRemaining()) { inputBuffer.put(readByteBuffer); synchronized (waitMoreDataLock) { waitMoreDataLock.notifyAll(); } } //logger.info("Slave read " + readByteBuffer.remaining() + " bytes"); } /** * ?, outputBuffer * ? * @param key * @throws IOException */ private void onWrite(SelectionKey key) throws IOException { logger.debug("onWrite"); key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); Command command = null; Packet packet = null; ByteBuffer data = null; //int size = 0; while ((command = outputBuffer.poll()) != null) { //size = 0; // ??? packet = Packet.newDataPacket(command); // ???ByteBuffer data = packet.marshall(); //size = data.remaining(); while (data.hasRemaining()) { socketChannel.write(data); } socketChannel.socket().getOutputStream().flush(); //logger.info("Slave write one packet, " + size + " bytes"); } //key.interestOps(SelectionKey.OP_READ); } /** * ?selector, ?? */ private void weakup() { logger.debug("try to weakup"); if (isWeakuped.compareAndSet(false, true)) { this.selector.wakeup(); logger.debug("weakuped"); } } // parse network data, convert to command private Command _parse_() throws IOException, InterruptedException { return CommandFactory.unmarshallCommandFromPacket(Packet.parse(inputBuffer, this, socketChannel)); } @Override public void send(Command command) throws InterruptedException { outputBuffer.put(command); if (state.get() == NetworkStateEnum.NETWORK_STATE_CONNECTED.value) { notifyWrite(socketChannel); } else { //logger.warn("Not connected to server"); // ?? //restart(); // ? Thread.sleep(1000); } } @Override public Command receive() throws IOException, InterruptedException { if (state.get() == NetworkStateEnum.NETWORK_STATE_CONNECTED.value) { return _parse_(); } else { ///logger.warn("Not connected to server"); // ?? //restart(); // ? Thread.sleep(1000); } return null; } // ? private enum NetworkStateEnum { NETWORK_STATE_CONNECTING(0, "Connecting"), NETWORK_STATE_CONNECTED(1, "Connected"), NETWORK_STATE_DISCONNECT(2, "Disconnect"); private int value; private String desc; private NetworkStateEnum(int value, String desc) { this.value = value; this.desc = desc; } } }