NIO实现网络通信TCP Server

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;

public class NioTCPServer {	
	//缓冲区长度
	private static final int BUFSIZE = 1024;
	//接受数据的缓冲区
	private static ByteBuffer byteBuffer;
	
	public static void tcpServer()  throws Exception{
		System.out.println("服务器启动");
		//创建一个选择器
		Selector selector = Selector.open();
		//实例化一个通道
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		//将通道绑定到指定端口(6789)
		serverSocketChannel.socket().bind(new InetSocketAddress(6789));
		//配置通道为非阻塞模式
		serverSocketChannel.configureBlocking(false);
		//将选择器注册到通道上
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		//初始化缓冲区的大小
		byteBuffer = ByteBuffer.allocateDirect(BUFSIZE);
		//不断轮询select方法,获取准备好的通道关联的key集
		while(true) {
			//一直等待,直到有通道准备好了数据的传输,在此处异步执行其他任务(3000为select方法等待信道准备好的最长时间)
			if (selector.select(3000) == 0) {
				//异步执行其他任务
				continue;
			}
			//获取准备好的通道中关联的Key集合的Iterator
			Iterator<SelectionKey> selectionKeyIter = selector.selectedKeys().iterator();
			//循环获取集合中的键值
			while(selectionKeyIter.hasNext()) {
				SelectionKey key = selectionKeyIter.next();
				//服务端对哪种信号感兴趣就执行那种操作
				if(key.isAcceptable()) {
					System.out.println("accept");
					
					//连接好了,然后将读注册到选择器中
					readRegister(selector,key);
				}
				//上一部将读注册到选择器中之后,如果客户端发送数据,就可以读取到数据,还可以将发送到客户端
				if(key.isReadable()) {
					readDataFromSocket(key);
				}
				if (key.isValid() && key.isWritable()) {
					System.out.println("write");
				}
				//需要手动从键集中移除当前key
				selectionKeyIter.remove();
			}
		}
	}
	
	//将读注册到选择器中
	private static void readRegister(Selector selector, SelectionKey key) throws IOException {
		//从key中获取关联的通道(此处是ServerSocketChannel,因为需要将服务器的检测模式注册到选择器中)
		ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
		//获取通道实例
		SocketChannel channel = serverSocketChannel.accept();
		//设置为非阻塞模式
		channel.configureBlocking(false);
		//将读注册到选择器中
		channel.register(selector, SelectionKey.OP_READ);
	}
	
	private static void readDataFromSocket(SelectionKey key) throws Exception {
		//从与key关联的通道中获取数据,首先获取关联的通道(此处是SocketChannel,因为与客户端通信是通过SocketChannel,数据都放在其中)
        SocketChannel socketChannel = (SocketChannel) key.channel();
        int count;
        //清除缓冲区(此处清除不能实际擦出buffer中的数据,而是回归各个标志位)
        byteBuffer.clear();
        //从通道中读取数据到缓冲区中,读到最后没有数据则返回-1
        while ((count = socketChannel.read(byteBuffer)) > 0) {
        	//向客户端发送数据(hasRemaining告知当前位置和限制之间是否存在任何元素)
            while (byteBuffer.hasRemaining()) {
            	//将buffer切换到读状态
                byteBuffer.flip();
                //下面是将buffer转换成字符串
                CharBuffer charBuffer = null;
            	Charset charset = Charset.forName("UTF-8");
            	/** 
            	 * 使用注释会出现的问题:当客户端发送汉字,或者16进制数据时,会报异常
            	 * CharsetDecoder decoder = charset.newDecoder();
            	   charBuffer = decoder.decode(byteBuffer);
            	 */
            	//使用此方法不会出现异常,但是汉字会显示成?号(可能是编码不对应),但是发送数据的时候,发送汉字不会出现编码问题
            	charBuffer = charset.decode(byteBuffer);
            	//打印客户端发送的数据
            	System.out.println("接受的数据:" + charBuffer.toString());
            	//保存至数据库的数据
            	String data = charBuffer.toString();
            	//将数据保存数据库
            	//ConnectionMysql.insert(data);
            	//查询所有数据
            	//ConnectionMysql.select();
            	}
            byteBuffer.clear();
        }
        //发送数据给客户端
        //sentDataClient(socketChannel);
        
        if (count < 0) {
            socketChannel.close();
        }
    }
	
	//向客户端发送数据
	public static void sentDataClient(SocketChannel socketChannel) throws IOException {
        /**
         * 将自定义的数据发送给客户端
         */
        ByteBuffer sentBuffer = ByteBuffer.allocateDirect(20);
        sentBuffer.put("456e我".getBytes());
        sentBuffer.flip();
        //在向通道写数据的时候,需要将buffer给flip()
        socketChannel.write(sentBuffer);
	}
	
	public static void main(String[] args) throws Exception{
		tcpServer();
	}
}

用以上方式解析发送过来的数据:数据是以十进制的方式发送,因此接受之后能正常显示;如果以16进制发送过来,数据就会被转换成补码的形式(如:客户端发送:FE 服务端接受显示:-2),然后在将此数返回给客户端(客户端以16进制数显示):又会显示成其他的数。因此以上方式解析buffer会出现很严重的问题。
用以下方式可以解决(摘自: https://blog.csdn.net/linlzk/article/details/6566124):
/**
	  * 字节数组转成16进制表示格式的字符串
	  * 
	  * @param byteArray
	  *            需要转换的字节数组
	  * @return 16进制表示格式的字符串
	  **/
	 public static String toHexString(byte[] byteArray) {
	  if (byteArray == null || byteArray.length < 1)
	   throw new IllegalArgumentException("this byteArray must not be null or empty");
	 
	  final StringBuilder hexString = new StringBuilder();
	  for (int i = 0; i < byteArray.length; i++) {
	   if ((byteArray[i] & 0xff) < 0x10)//0~F前面不零
	    hexString.append("0");
	   hexString.append(Integer.toHexString(0xFF & byteArray[i]));
	  }
	  return hexString.toString().toLowerCase();
	 }
	
	/**
	  * 16进制的字符串表示转成字节数组
	  * 
	  * @param hexString
	  *            16进制格式的字符串
	  * @return 转换后的字节数组
	  **/
	 public static byte[] toByteArray(String hexString) {
	  if (hexString.isEmpty())
	   throw new IllegalArgumentException("this hexString must not be empty");
	 
	  hexString = hexString.toLowerCase();
	  final byte[] byteArray = new byte[hexString.length() / 2];
	  int k = 0;
	  for (int i = 0; i < byteArray.length; i++) {//因为是16进制,最多只会占用4位,转换成字节需要两个16进制的字符,高位在先
	   byte high = (byte) (Character.digit(hexString.charAt(k), 16) & 0xff);
	   byte low = (byte) (Character.digit(hexString.charAt(k + 1), 16) & 0xff);
	   byteArray[i] = (byte) (high << 4 | low);
	   k += 2;
	  }
	  return byteArray;
	 }


猜你喜欢

转载自blog.csdn.net/qq_37978623/article/details/80780761