使用 NIO 完成网络通信的三个核心:
-
通道(Channel):负责连接
java.nio.channels.Channel 接口: |--SelectableChannel |--SocketChannel |--ServerSocketChannel |--DatagramChannel |--Pipe.SinkChannel |--Pipe.SourceChannel
-
缓冲区(Buffer):负责数据的存取
-
选择器(Selector):是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 状况
基于通道的普通阻塞式网络通信演示
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class NetSocketTest {
@Test
public void client(){
SocketChannel sc = null;
FileChannel fc = null;
try {
sc = SocketChannel.open(new InetSocketAddress("192.168.0.102",18888));
fc = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
ByteBuffer buf = ByteBuffer.allocate(1024*1024);
while (fc.read(buf)!=-1){
buf.flip();
sc.write(buf);
buf.clear();
}
sc.shutdownOutput();
} catch (IOException e) {
}finally {
try {
fc.close();
sc.close();
} catch (IOException e) {
}
}
}
@Test
public void Server(){
ServerSocketChannel ssc = null;
FileChannel fc = null;
ServerSocketChannel sscPort = null;
try {
ssc = ServerSocketChannel.open();
sscPort = ssc.bind(new InetSocketAddress(18888));
fc = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
ByteBuffer buf =ByteBuffer.allocate(1024*1024);
//阻塞式方法
SocketChannel accept = sscPort.accept();
while (accept.read(buf) != -1) {
buf.flip();
fc.write(buf);
buf.clear();
}
accept.shutdownInput();
} catch (IOException e) {
}finally {
try {
fc.close();
sscPort.close();
ssc.close();
} catch (IOException e) {
}
}
}
}
基于选择器的无阻塞式网络通信
public class NetSocketTest2 {
@Test
public void Client() {
SocketChannel sc = null;
FileChannel file = null;
try {
sc = SocketChannel.open(new InetSocketAddress("192.168.0.103", 18888));
file = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
//切换为非阻塞式
sc.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024 * 1024);
while (file.read(buf) != -1) {
buf.flip();
sc.write(buf);
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
file.close();
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void Server() {
ServerSocketChannel ssc = null;
FileChannel file = null;
try {
//将客户端的数据存储到本地
file = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//获取服务端通道
ssc = ServerSocketChannel.open();
//设置为非阻塞式
ssc.configureBlocking(false);
//绑定服务监听端口号
ssc.bind(new InetSocketAddress(18888));
//获取选择器
Selector selector = Selector.open();
//选择器监控通道,因此
//将通道注册选择器上,并设置选择键,选择键是明确选择器监控的是那种状态
ssc.register(selector, SelectionKey.OP_ACCEPT);
//迭代选择器上已经准备好了的事件(OP_ACCEPT)
while (selector.select() > 0) {//如果选择器上准备就绪的事件大于0
//如果有就绪的事件,就把它们拿出来迭代,
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()){
SelectionKey selectionKey = it.next();
//判断具体什么事件准备就绪,
if (selectionKey.isAcceptable()) {//如果是接收事件准备就绪
//则获取客户端的套接字通道
SocketChannel accept = ssc.accept();
//并把套接字通道设置为非阻塞式
accept.configureBlocking(false);
//得到了客户端的通道后,也注册到选择器上,并监听他的读事件是否就绪
//同个下面的代码,就明确了无论是客户端还是服务端获取的通道,
// 都可以放在选择器中进行监听状态
accept.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
//如果当前选择键位读就绪,就获取他的套接字通道,
SocketChannel sc = (SocketChannel) selectionKey.channel();
ByteBuffer buf = ByteBuffer.allocate(1024 * 1024);
//开始读写数据
while (sc.read(buf) > 0) {
buf.flip();
file.write(buf);
buf.clear();
}
}
//如果不是上面if else 语句中的事件,或已经执行完成了的,就可以清空掉。
it.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
file.close();
ssc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
UDP版无阻塞式
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Scanner;
public class UDPNetSocketTest {
public static void main(String[] args) {
new UDPNetSocketTest().Cline();
}
@Test
public void Cline() {
DatagramChannel dc = null;
Scanner sc = null;
try {
dc = DatagramChannel.open();
dc.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
sc = new Scanner(System.in);
String str = null;
while ((str = sc.next()) != null) {
buf.put(str.getBytes());
buf.flip();
dc.send(buf, new InetSocketAddress("127.0.0.1", 18888));
buf.clear();
}
} catch (IOException e) {
} finally {
if (sc != null) {
sc.close();
}
try {
if (dc != null) {
dc.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void Server() {
DatagramChannel dc = null;
try {
dc = DatagramChannel.open();
dc.bind(new InetSocketAddress(18888));
dc.configureBlocking(false);
Selector selector = Selector.open();
dc.register(selector, SelectionKey.OP_READ);
while (selector.select() > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isReadable()) {
ByteBuffer buf = ByteBuffer.allocate(1024);
dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
buf.clear();
}
//一定要注意清空
iterator.remove();
}
}
} catch (IOException e) {
} finally {
try {
if (dc != null) {
dc.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}