Java NIO(四)开发基于帧的ping-pong服务器

TCP有个特性是粘包,也就是一个包后面可能跟着下一个包的部分或者全部数据,帧格式就是用来解决粘包问题的。帧格式一般都是在包的前面增加一个包头,包头里的一个字段会保存包的大小。所以基于帧的服务器的每条连接的读取数据状态机都是在“读包头”和“读包体”之间转化。

具体服务器的代码如下:

public class FramedServer {

	final public static int DEFAULT_PORT = 30303;

	public static class Packet {
		ByteBuffer bb;

		public Packet() {
			bb = null;
			//ByteBuffer.allocate(0);
		}

		public static Packet createPacket(String s) {
			Packet p = new Packet();
			byte[] bytes = s.getBytes();
			p.bb = ByteBuffer.allocate(4+bytes.length);
			p.bb.putInt(-1);
			p.bb.put(bytes);
			p.bb.putInt(0, p.bb.limit() - 4);
			p.bb.rewind();
			return p;
		}
	}

	public static class Connection {
		SocketChannel sc;
		SelectionKey scKey;

		List<Packet> outgoingQueue = new LinkedList<Packet>();

		ByteBuffer lenBuf = ByteBuffer.allocate(4);
		ByteBuffer incomingBuf = lenBuf;
		
		boolean readingHeader = true;

		public Connection(SocketChannel sc, SelectionKey scKey) {
			this.sc = sc;
			this.scKey = scKey;
		}
	}

	ServerSocketChannel serverChannel;
	Selector selector;

	public void start() throws IOException {
		selector = Selector.open();
		serverChannel = ServerSocketChannel.open();
		serverChannel.bind(new InetSocketAddress(DEFAULT_PORT));
		serverChannel.configureBlocking(false);
		serverChannel.register(selector, SelectionKey.OP_ACCEPT);
		while (true) {
			selector.select();
			Set<SelectionKey> readyKeys = selector.selectedKeys();
			Iterator<SelectionKey> it = readyKeys.iterator();
			while (it.hasNext()) {
				SelectionKey key = it.next();
				it.remove();
				if (key.isAcceptable()) {
					ServerSocketChannel server = (ServerSocketChannel) key.channel();
					SocketChannel sc = server.accept();
					if (sc != null) {
						sc.configureBlocking(false);
						SelectionKey scKey = sc.register(selector, SelectionKey.OP_READ);
						scKey.attach(new Connection(sc, scKey));
					} else {
						System.out.println("WARN: accept null.");
					}
				} else if (key.isReadable()) {
					doRead((Connection) key.attachment());
				} else if (key.isWritable()) {
					doWrite((Connection) key.attachment());
				}
			}
		}
	}

	public void doRead(Connection conn) {
		try {
			if (conn.readingHeader) {
				//reading header
				int ret = conn.sc.read(conn.lenBuf);
				if (ret < 0) {
					conn.scKey.cancel();
					conn.sc.close();
                                        return;
				}
				System.out.println("ret="+ret);
				if (conn.lenBuf.hasRemaining()) {
					return;
				} else {
					conn.lenBuf.rewind();
					int len = conn.lenBuf.getInt();
					System.out.println("header: "+len);
					conn.incomingBuf = ByteBuffer.allocate(len);
					conn.readingHeader = false;
				}
			} else {
				//reading body
				int ret = conn.sc.read(conn.incomingBuf);
				if (ret < 0) {
					conn.scKey.cancel();
					conn.sc.close();
                                        return;
				}
				if (conn.incomingBuf.hasRemaining()) {
					return;
				} else {
					conn.incomingBuf.rewind();
					System.out.println("body: "+conn.incomingBuf);
					String s = new String(conn.incomingBuf.array(), 0, conn.incomingBuf.limit());
					System.out.println("received: " + s);
					conn.incomingBuf = null;
					conn.lenBuf.rewind();
					conn.readingHeader = true;
					
					Packet p = Packet.createPacket("pong");
					conn.outgoingQueue.add(p);
					doWrite(conn);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
			conn.scKey.cancel();
			try {
				conn.sc.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			return;
		}
	}

	public void doWrite(Connection conn) {
		while (conn.outgoingQueue.size() > 0) {
			try {
				Packet p = conn.outgoingQueue.get(0);
				conn.sc.write(p.bb);
				if (p.bb.hasRemaining()) {
					enableWrite(conn.scKey);
					return;
				} else {
					conn.outgoingQueue.remove(0);
				}
			} catch (IOException e) {
				e.printStackTrace();
				conn.scKey.cancel();
				try {
					conn.sc.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				return;
			}
		}

		disableWrite(conn.scKey);
	}

	protected void enableWrite(SelectionKey scKey) {
		int ops = scKey.interestOps();
		scKey.interestOps(ops | SelectionKey.OP_WRITE);
	}

	protected void disableWrite(SelectionKey scKey) {
		int ops = scKey.interestOps();
		scKey.interestOps(ops & (~SelectionKey.OP_WRITE));
	}

	public static void main(String args[]) throws IOException {
		FramedServer server = new FramedServer();
		server.start();
	}
}

客户端代码如下:

public class FramedClient {
	
	public void readPacket(SocketChannel sc) throws IOException {
		ByteBuffer lenBuf = ByteBuffer.allocate(4);
		sc.read(lenBuf);
		lenBuf.rewind();
		int len = lenBuf.getInt();
		ByteBuffer incomingBuf = ByteBuffer.allocate(len);
		sc.read(incomingBuf);
		System.out.println(new String(incomingBuf.array(), 0, incomingBuf.limit()));
	}
	
	public void run() throws IOException {
		SocketChannel sc = SocketChannel.open();
		sc.connect(new InetSocketAddress(FramedServer.DEFAULT_PORT));
		Packet p = Packet.createPacket("ping");
		sc.write(p.bb);
		
		readPacket(sc);
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		p.bb.rewind();
		sc.write(p.bb);
		
		readPacket(sc);
		
		sc.close();
	}
	
	public static void main(String args[]) {
		FramedClient client = new FramedClient();
		try {
			client.run();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
posted @ 2020-11-12 17:38  ralgo  阅读(342)  评论(0编辑  收藏  举报