package cn.ac.yangge.service;

import cn.ac.yangge.unity.AnalysisData;
import cn.ac.yangge.unity.ByteBufferPrint;
import org.springframework.stereotype.Service;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * Created by yangge on 2017/5/26 0026.
 */
@Service("tcpService")
public class TcpService {
    // 主入口
    public static void main() throws IOException {
        sockectChannelSever();
    }


    //使用serversocketchannel 非程阻塞
    public static void sockectChannelSever() {
        int port = 8888;//端口

        ServerSocketChannel serverSocketChannel;
        Selector selector;
        try {
            serverSocketChannel = ServerSocketChannel.open();
            ServerSocket ss = serverSocketChannel.socket();
            InetSocketAddress address = new InetSocketAddress(port);
            ss.bind(address);
            serverSocketChannel.configureBlocking(false);
            selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        while (true) {
            try {
                selector.select();
            } catch (IOException e) {
                e.printStackTrace();
                break;
            }
            Set<SelectionKey> readyKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext()) {

                SelectionKey key = iterator.next();
                iterator.remove();
                try {
                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        System.out.println("Accepted connection from " + client);
                        client.configureBlocking(false);
                        SelectionKey clientKey = client.register(selector, SelectionKey.OP_READ);
                        ByteBuffer buffer = ByteBuffer.allocate(100);
                        clientKey.attach(buffer);
                    }
                    if (key.isReadable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer output = (ByteBuffer) key.attachment();
                        client.read(output);
                        output.flip();
                        byte[] byteArray=output.array();
                        for(byte b:byteArray){
                            System.out.println(b);
                        }
                        System.out.println(ByteBufferPrint.getString(output));

                    }
                } catch (IOException ex) {
                    key.cancel();
                    try {
                        key.channel().close();
                    } catch (IOException e) {
                    }
                }
            }
        }

    }

}

Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel:

  1. 打开一个SocketChannel并连接到互联网上的某台服务器。
  2. 一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。


打开 SocketChannel

下面是SocketChannel的打开方式:

1
SocketChannel socketChannel = SocketChannel.open();
2
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

关闭 SocketChannel

当用完SocketChannel之后调用SocketChannel.close()关闭SocketChannel:

socketChannel.close();



从 SocketChannel 读取数据

要从SocketChannel中读取数据,调用一个read()的方法之一。以下是例子:

1
ByteBuffer buf = ByteBuffer.allocate(48);
2
int bytesRead = socketChannel.read(buf);



首先,分配一个Buffer。从SocketChannel读取到的数据将会放到这个Buffer中。

然后,调用SocketChannel.read()。该方法将数据从SocketChannel 读到Buffer中。read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了流的末尾(连接关闭了)。

写入 SocketChannel

写数据到SocketChannel用的是SocketChannel.write()方法,该方法以一个Buffer作为参数。示例如下:

01
String newData = "New String to write to file..." + System.currentTimeMillis();
02
 
03
ByteBuffer buf = ByteBuffer.allocate(48);
04
buf.clear();
05
buf.put(newData.getBytes());
06
 
07
buf.flip();
08
 
09
while(buf.hasRemaining()) {
10
    channel.write(buf);
11
}

注意SocketChannel.write()方法的调用是在一个while循环中的。Write()方法无法保证能写多少字节到SocketChannel。所以,我们重复调用write()直到Buffer没有要写的字节为止。

非阻塞模式

可以设置 SocketChannel 为非阻塞模式(non-blocking mode).设置之后,就可以在异步模式下调用connect(), read() 和write()了。

connect()

如果SocketChannel在非阻塞模式下,此时调用connect(),该方法可能在连接建立之前就返回了。为了确定连接是否建立,可以调用finishConnect()的方法。像这样:

1
socketChannel.configureBlocking(false);
2
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
3
 
4
while(! socketChannel.finishConnect() ){
5
    //wait, or do something else...
6
}

write()

非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()。前面已经有例子了,这里就不赘述了。

read()

非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值,它会告诉你读取了多少字节。

调试的时候想要转化byteBuffer?

提供给你两个方法

String 转换 ByteBuffer:

public static ByteBuffer getByteBuffer(String str) {
 return ByteBuffer.wrap(str.getBytes());
 }


ByteBuffer 转换 String:

public static String getString(ByteBuffer buffer) {
 Charset charset = null;
 CharsetDecoder decoder = null;
 CharBuffer charBuffer = null;
 try {
 charset = Charset.forName("UTF-8");
 decoder = charset.newDecoder();
                        //用这个的话,只能输出来一次结果,第二次显示为空
 // charBuffer = decoder.decode(buffer);
 charBuffer = decoder.decode(buffer.asReadOnlyBuffer());
 return charBuffer.toString();
 } catch (Exception ex) {
 ex.printStackTrace();
 return "error";
 }
 }


想要直接转化为byte[] ?

可以直接使用byteBuffer.array();


遇到的坑:

SelectionKey clientKey = client.register(selector, SelectionKey.OP_READ);

在配置selectionKey的时候,之前是用了Selection.OP_WRITE的,这导致keys的事件集合中一直存在一个isWritable事件,原因是:

key.isWritable()是表示Socket可写,网络不出现阻塞情况下,一直是可以写的,所认一直为true.一般我们不注册OP_WRITE事件.