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;
}