client客户端 | server服务端 |
---|---|
通过socket对象请求服务端 | 通过serverSocket注册端口 |
从Socket中输入或输出 字节流 进行读写 | 调用accept方法监听socket请求 |
从soket 获取 字节 流 读写操作 |
public static void main(String[] args) {
try {
//创建套接字发送请求
Socket socket = new Socket("127.0.0.1",9999);
//获取 字节 输出流
OutputStream os = socket.getOutputStream();
//包装成打印流
PrintStream ps = new PrintStream(os);
//发送一行消息
ps.print("长日将尽");
//刷新容器
ps.flush();
} catch ( Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
System.out.println("+++ 启动+++");
try {
//创建 套接字
ServerSocket ss = new ServerSocket(9999);
//调用监听
Socket socket = ss.accept();
//获取字节输入流
InputStream is = socket.getInputStream();
//获取一个缓冲 输入流 -> 需要先转型
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//输出内容
if ( br.readLine() != null ) {
System.out.println("服务端接收数据->"+br.readLine());
}
} catch (Exception e) {
e.printStackTrace();
}
}
核心API构成。此外Pipe,FileLock ,工具类
stream 是单向的
Channel 是双向的(inputstream,ouputstream)可以读,也可写
NIO主要实现类:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel。这里分别对应,文件IO、UDP、TCP(Server和Client)
channel实现是依赖缓冲区的(Buffer)
不会将字节直接写入通道中,相反,是将数据写入包含一个或多个字节的缓冲区。
也不会从通道中读取字节,而是将数据从通道读入缓冲区,在从缓冲区去读取这个字节
channel 通道可以异步读写。
通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入
java.nio.channels
return | Method | describe |
---|---|---|
int | read(ByteBuffer dst) | 从Channel中读取数据到 ByteBuffer |
long | read(ByteBuffer[] dets) | 将Channel中的数据“分散”到ByteBuffer[] |
int | Write(ByteBuffer src) | 将ByteBuffer中的数据写入到Channel |
long | write(ByteBuffer[] srcs) | 将ByteBuffer中的数据“聚焦”到Channel |
long | postition() | 获取通道文件位置 |
FileChannel | position(long p) | 设置通道文件位置 |
long | size() | 返回此通道文件到当前大小 |
FileChannel | truncate(long s) | 将此通道的文件截取为给定大小 |
void | force(boolean metaDate) | 强制将所有对此通道的文件更新写入到储存设备中 |
filechannel 是无法直接使用的,需要通过 InputStream、OutputStream、RandomAccessFile 来获取FileChannel 实例
写入
buf.clear();
buf.put("写入".getBytes()).flip();
channel.write(buf);
//创建 filechannel
RandomAccessFile rw = new RandomAccessFile("nio_bio_aio/a/a.TEXT", "rw");
FileChannel channel = rw.getChannel();
//创建buffer
ByteBuffer buf = ByteBuffer.allocate(1024);
//数据读取 -1 读取不到内容
int byteRead = channel.read(buf);
while( byteRead != -1){
System.out.println( byteRead );
buf.flip();
while( buf.hasRemaining() ){
System.out.println( (char) buf.get());
}
buf.clear();
//再次读取
byteRead = channel.read(buf);
}
rw.close();
public static void main(String[] args) throws IOException {
//transferFrom() 通道之间的数据传输
RandomAccessFile aw = new RandomAccessFile("nio_bio_aio/a/a.TEXT", "rw");
RandomAccessFile bw = new RandomAccessFile("nio_bio_aio/a/b.text", "rw");
FileChannel ca = aw.getChannel();
FileChannel cb = bw.getChannel();
//a 文件里的流传输到 b文件里面
long size = ca.size();
cb.transferFrom(ca,0L,size);
aw.close();
bw.close();
}
新的socket通道类可以运行非阻塞模式并且是可选择的,可以激活大程序(网络服务的中间组件)巨大的可伸缩性和灵活性。socket避免大量线程管理的上下文切换的开销。
java.nio.channels.spi.AbstractSelectableChannel
- DatagramChannel
- SocketChannel
- ServSocketChannel
可以使用一个selecto对象来操作 socket通道的就绪(readiness selection)
注意:DatagramChannel 和 SockeyChannel 实现定义读写功能
ServerSocketChannel不实现。此类负责监听传入的连接和创建新的SocketChannel对象,它本身从不传输数据socket和socket通道之间的关系,通道是一个连接I/O服务导管并提供与该服务交互的方法。就某个socket而言,它不会在此实现与之对应socket通道类中socket协议API,而java.net中已经存在的socket通道都可以被大多数协议重复使用。
全部 sockey通道类(DatagramChannel、SocketChannel、ServerSocketChannel)在被实例化时都会创建一个对等的socket对象,这些是我门熟悉的来自java.net类。他们已经被更新以识别通道。对等socket可以通过调用socket()方法从一个通道上获取此外,这个三个 java.net类现在都**getChannel()**方法
要把一个sockey通置于非阻塞模式,需要依靠所有的socket通道类的共有超级累:SelectableChannel。就绪选择(readiness selection)是一种可以用来查询通道的机制,该查询可以判断通道是否准备好执行一个目标操作,如读或写。非组赛I/O和可选择性是紧密相连的,那夜正是管理组赛模式的APi代码要在 SelectableChannel超级类中定义的原因。
调用 configureBlocking(),传参数为ture 设置为阻赛模式,false非阻塞。
isBlocking()来获取 sockey通道的模式
基于通道的socket监听器。和java.net.ServerSocket执行相同的任务,不过它增加了通道语义,因此能够在非阻赛模式下运行。
ServerSocketChannel中没有 bind()方法,因此需要取出对等的socket并使用它来绑定到一个端口以开始监听连接。通过使用对等的ServerSocket的API来根据需要设置其他的socket选项
ServerSocketChannel中有accept()方法,一旦创建一个ServerSocketChannel并用对等socket绑定了它,然后您就可以在其中一个上调用accept(),
当ServerSocketChannel以非阻赛模式调用时,没有等待连接传入,调用 accept()会返回null
int port = 9999;
//buffer
ByteBuffer buffer = ByteBuffer.wrap("心若自由,身沐长风".getBytes());
//serverSocketChannel 获取
ServerSocketChannel ssc = ServerSocketChannel.open();
//绑定监听
ssc.socket().bind(new InetSocketAddress(port));
//设置非组赛
ssc.configureBlocking(false);
//监听是否有连接传入
while (true) {
System.out.println(" 监听连接汇总");
SocketChannel sc = ssc.accept();//连接进来 获取一个 单独socket
if (sc == null) {
System.out.println("null");
Thread.sleep(5000);
}else {
System.out.println( "" + sc.socket().getRemoteSocketAddress());//获取连接对接口
buffer.rewind();//指针0
sc.write(buffer);
sc.close();//关闭单独的socket
}
}
Java.nio 中一个连接到TCP网络的套接字通道,是一种面向流连接的sockets套接字的可以选择通道。
方法 | 描述 |
---|---|
isOpen() | 是否为open状态 |
isConnected() | 是否被连接 |
isConnectionPending() | 是否正在进行连接 |
finishConnect() | 校验正在进行套接字连接SocketChannel是否已经完成连接 |
configureBlocking() | fasle表示非阻赛 |
//获取sokcet
SocketChannel sc = SocketChannel.open(new InetSocketAddress("www.baidu.com", 80));
//设置非阻塞
sc.configureBlocking(false);
//读写操作
ByteBuffer bb = ByteBuffer.allocate(16);//allocate 缓冲区的数组长度
sc.read(bb);
sc.close();
System.out.println("非阻塞状态能打印");
每一个DataGramChannel对象夜又一个关联的DatagramSocket对象,
DatagramChannel模拟包导向的无连接协议(UDP/IP)。每个数据报(datagram)都是一个自包含的实体,拥有它自己的目的地址及不依赖其他数据报的数据负载。与面向流的socket不同,DatagramChannel可以发送单独的数据报给不同的地址。同样,DatagramChannel对象也可以接受来自任意地址的数据包,每个到达的数据报都包含有关它来自何处的信息(源地址)
//发送实现
static void sendDatagram() throws IOException, InterruptedException {
DatagramChannel sc = DatagramChannel.open();
InetSocketAddress sadd = new InetSocketAddress("127.0.0.1", 9999);
//发送
while (true) {
ByteBuffer buff = ByteBuffer.wrap("数据到达".getBytes("UTF-8"));
sc.send(buff, sadd);
System.out.println("数据发出");
Thread.sleep(2000);
}
}
//接收端实现
static void receiveDatagram() throws IOException {
DatagramChannel dc = DatagramChannel.open();
InetSocketAddress ia = new InetSocketAddress(9999);
//绑定
dc.bind(ia);
ByteBuffer bye = ByteBuffer.allocate(1024);
//接收
while (true) {
bye.clear();
//获取地址
SocketAddress socketAddress = dc.receive(bye);
//反转
bye.flip();
System.out.println(socketAddress.toString() );
System.out.println(Charset.forName("UTF-8").decode(bye) );
}
}
// read write
static void testConnedct() throws IOException {
DatagramChannel dc = DatagramChannel.open();
//绑定
dc.bind( new InetSocketAddress(9999));
//连接
dc.connect(new InetSocketAddress("127.0.0.1", 9999));
dc.write(ByteBuffer.wrap("发送目标的数据".getBytes("UTF-8")));
ByteBuffer bye = ByteBuffer.allocate(1024);
while (true ){
bye.clear();
dc.read(bye);
bye.flip();
System.out.println( Charset.forName("UTF-8").decode(bye) );
}
}
Java.nio 中支持 scatter/gether 来描述 channel中读取或者写入到channel的操作
分散 scatter 从 Channel中读取是指在读操作时将读取的数据写入多个buffer中,Channel从Channel中读取的数据“分散 scatter ”到多个Buffer
聚集 gether写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel将多个Buffer中的数据“聚集 gather” 后发送到Channel。
分散聚合经常用于需要将传输的数据 分开处理的场合,例如传输一个有消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的buffer中。
分散过程中,buffer被插入到数组,然后再将数组作为channel.read()的输入参数
read()按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,当一个buffer被写满后,channel 紧接着向另一个buffer中写。
Scattering Reads 在移动下一个buffer前,必须填满当前的buffer,这也意味着它不是用与动态消息(消息大小不固定)。如果存消息头和消息体,消息头必须完成填充(例如:128byte),Scattering Reads 才能正常工作
NIO中关键Buffer实现:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer
NIO中的所有数据基本都是通过buffer处理的,buffer本质是数组
基本使用
xxxBuffer.allocate(1024);//分配 1024的容量
Channel.read(buffer);//通过chaanel 写入数据
buffer.put( data );//直接put 写数据
Chaaenl.write( buffer );// channel 读
buffer.get();//直接读
当向buffer写入数据时,buffer会记录写了多少数据,一旦读取数据,需要通过flip 方法将buffer从写模式切换读模式。读完之前写的数据,需要清空缓冲区,恢复到能写入clear、compact
capacity 的含义一直不变
position 和 limit 的含义取决于buffer处在读写模式。
内存块,buffer有一个固定的大小值,也叫‘capacity’你只能往里写capacity 个byte,long,char 等类型。一旦buffer满了,需要将其清空(通过读数据或者清空数据)才能继续写入数据
在NIO中,除了可以分配或者包装一个缓冲区对象外,还可以根据现有的缓冲区对象来创建一个子缓冲区,即在现有缓冲区上切处一片来作为一个新的缓冲区,但现有的缓冲区来创建子缓冲区在底层数组层面上时数据共享的,也就是说,子缓冲区相当于现有缓冲区的一个视图窗口。调用slice()方法可以创建一个子缓冲区。
static void slice0(){
ByteBuffer bf = ByteBuffer.allocate(10);
for (int i = 0; i < 10; i++) {
bf.put((byte)i);
}
//创建子缓冲区 3-7的位子
bf.limit(7);
bf.position(3);
ByteBuffer slice = bf.slice();
//操作子缓冲区
for (int i = 0; i < slice.capacity(); i++) {
byte b = slice.get(i);
slice.put(i,b*=10);
}
//恢复 属性
bf.position(0);
bf.limit(bf.capacity());
while ( bf.remaining() > 0) System.out.print( bf.get()+" " );
}
结果: 0 1 2 30 40 50 60 7 8 9
目前的分片缓冲区就是 移动 limit 和 position 的临时区域
只读缓区可以读取它们,但不能向它们写入数据,可以通过调用缓冲区的asReadOnlyBuffer()方法,将任何常规缓冲区转换为制度缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区,并与源缓冲区共享数据,只不过它时只读的。原缓冲区发生变化,只读缓冲区的内容也随之发生变化。
static void read01() {
ByteBuffer buffer = ByteBuffer.allocate(10);
for (int i = 0; i < buffer.capacity(); i++) {
buffer.put((byte)i);
}
//创建只读缓冲区
ByteBuffer readonly = buffer.asReadOnlyBuffer();
for (int i = 0; i < buffer.capacity(); i++) {
byte b = buffer.get(i);
b *=10;
buffer.put(i,b);
}
readonly.position(0);
readonly.limit(buffer.capacity());
while (readonly.remaining()>0) {
System.out.println(readonly.get());
}
}
直接缓冲区时为了加快I/O速度,使用一种特殊的方式为其分配内存的缓冲区,JDK文档中描述:给定一个直接字节缓冲区,java虚拟机将尽最大努力直接对它执行本机I/O操作,每次调用底层操作系统的本机I/O操作之前,尝试避免将缓冲区的内容拷贝到一个中间缓冲区或者一个中间缓冲区中拷贝数据,分配直接缓冲区,使用 allocateDirect()
内存映射文件I/O是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的I/O快的多,内存映射文件I/O是通过使文件中的数据出现为内存数组内容来完成的,这并没有把整个文件读到内存中,只有文件实际读和写的部分才会映射到内存中。速度比流 和通道更快
static void mapped() throws IOException {
RandomAccessFile rw = new RandomAccessFile("nio_bio_aio/a", "rw");
FileChannel fc = rw.getChannel();
MappedByteBuffer map = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
map.put(0,(byte)97);
rw.close();
}
Selector 运行单线程处理多个Channel,如果你的应用打开了多个通道,单每个连接的流量都很低,使用selector就会很方便,
使用channel.register(Selector sel,int ops) 将一个通道注册到一个选择器
提供选择查询的通道操作,从类型分为四中
selector也可以用 位或 操作符来实现 例如:int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE
选择器查询的不是通道的操作,而是通道的某个操作的一种就绪状态。通道一旦具备完成某个操作的条件,就表示通道已经就绪,可以被selector查询到,程序可以对通道进行对应的操作。比方说,某个socketChannel通道可以连接到一个服务器,则处于“连接就绪状态”(OP_CONNECT).再比方说,一个serverSocketChannel服务器通道准备好接收新的连接,“接收就绪状态”(OP_AC CEPT)状态,可读数据通道“读就绪”(OP_READ),“写就绪”(OP_WRITE)
使用Selector时,Channel必须非阻塞模式,否则抛出IllegalBlockingModeExcepion(FileChannel 没有非阻塞模式)
channel通validOps()方法,获取特定通道下所支持的操作集合
selector通过slect(),查询出已经就绪的 channel,存在一个元素 selectionkey对象 set集中
选择器执行过程,系统底层会一次询问每个通道是否已经就绪,这个过程可能会造成调用线程进入阻赛模式
wakeup(),阻赛状态的select() 立刻返回
colse(), 关闭 selector :注销 selector中的所有channel,channel本身不会关闭
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
//设置非阻赛
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(9999));
//通道注册到选择器上
ssc.register(selector, SelectionKey.OP_ACCEPT);
//查询就绪通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//遍历集合
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
//判断状态 isAcceptable 、 isConnectable 、 isReadable 、 isWritable
if( key.isAcceptable() ){}
iterator.remove();
}
}
两个线程之间的单向数据连接,pipe有一个 source和一个sink通道,数据会被写到sink通道,从source通道读取
Pipe pipe = Pipe.open();//打开通道
Pipe.SinkChannel sinkChannel = pipe.sink();//需要访问的通道
sinkChannel.write("写入数据");
sinkChannel.read("读取数据");
文件锁,在多个程序访问、修改同一个文件,会因为文件不同而出现问题,给文件加个锁,同时只能有一个程序修改此文件,或者程序都只能读取此文件。
文件锁是进程级别,不是线程级别。文件锁可以解决多个进程并发访问、修改。但不能解决多个线程并发访问、修改。
文件锁是当前程序所属的JVM实例持有,一旦获取到文件锁,需要调用release(),或者关闭对应的 FileChannel对象,或者当前JVM退出,才会释放这个锁
一个进程对某个文件加锁,则在释放这个锁之前,次进程benign在对次文件加锁,JVM实例在同一个文件上的文件锁是不重叠的。
文件锁: 排他锁,共享锁
FileChannel fileChannel = new FileOutputStream("源文件").getChannel();
//上锁 排他锁 阻塞其他线程
FileLock lock = fileChannel.lock();
// fileChannel.tryLock(); 非阻赛,其他线程获取不到锁 返回null
lock.relesase(); //解锁
锁判断方法
fileLock.isShared() ;//文件是否是共享锁
fileLock.isValid();//文件锁是否还有效
是java7 更新到 java.nio.file 包中的
path表示文件系统中的路径,一个路径可以指向一个文件或一个目录。路径可以是绝对路径,也可以是相对路径。
在许多方面 java.niofile.path 接口类似于 java.io.file类,但有一些差别,不过在许多情况下,可以使用Path接口来替换File类
Path path = Paths.get("文件绝对路径");//通过实例工厂创建
Paths.get("C:/绝对/路径","a/a.text");// C:/绝对/路径/a/a.text
java.nio.file.files
提供了几种操作文件系统中的文件方法,
方法 | 描述 |
---|---|
createDirectory() | 根据Path实例创建一个新目录–>目录存在就抛出异常 |
copy() | 从一个路径拷贝一个文件到另一个目录 |
move() | 移动一个文件 |
walkFileTree() | 递归遍历目录 |
遍历寻找指定文件
public static void main(String[] args) {
//搜索的目录
Path path = Paths.get("/Applications/0utils/note/project");
//需要寻找的文件
String file1 = File.separator+"c.text";
try {
Files.walkFileTree(path,new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String s = file.toAbsolutePath().toString();
if( s.endsWith(file1) ){
//匹配到 文件,输出完整路径
System.out.println( file.toAbsolutePath() );
return FileVisitResult.TERMINATE;
}
//没匹配到就继续
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
//读取文件
static void readAsyncFileChannelFuture() throws IOException {
//创建 AsynchronousFileChannel
Path path = Paths.get("nio_bio_aio/a/b.text");
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
ByteBuffer allocate = ByteBuffer.allocate(1024);
Future<Integer> future = fileChannel.read(allocate, 0);
//判断异步是否完成 等待结束
while(! future.isDone());
//读取数据到buffer
allocate.flip();
byte[] data = new byte[allocate.limit()];
allocate.get(data);
System.out.println( new String(data));
allocate.clear();
}
默认覆盖的的方式,按字节覆盖
static void writeAsyncFileFunture() throws IOException {
//创建 AsynchronousFileChannel
Path path = Paths.get("nio_bio_aio/a/c.text");
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);
ByteBuffer byt = ByteBuffer.allocate(1024);
byt.put("浊酒".getBytes());
byt.flip();
Future<Integer> future = fileChannel.write(byt, 0);
//等待异步完成
while (! future.isDone());
byt.clear();
}
java.nio.charset.Charset
表示字符集编码对象
常用方法
方法 | 描述 |
---|---|
forName(String charsetName) | 通过编码类型获取charset对象 |
isSupported(String charsetName) | 判断是否支持编码类型 |
availableCharsets() | 获取系统支持的所有编码方式 |
defaultCharsert() | 获得虚拟机默认编码方式 |
name() | 获取当前对象编码类型 |
newEncoder() | 获取编码器对象 |
newDecoder() | 获取解码器对象 |
public static void main(String[] args) throws CharacterCodingException {
//获取对象
Charset charset = Charset.forName("UTF-8");
//获取编码器
CharsetEncoder charsetEncoder = charset.newEncoder();
//解码器
CharsetDecoder charsetDecoder = charset.newDecoder();
//数据
CharBuffer charbuff = CharBuffer.allocate(1024);
charbuff.put("半醉半醒").flip();
//编码结果 -27 -115 -118 -23 -122 -119 -27 -115 -118 -23 -122 -110
ByteBuffer byteBuffer = charsetEncoder.encode(charbuff);
// byteBuffer.flip();
//解码 TODO
CharBuffer charBuffer1 = charsetDecoder.decode(byteBuffer);
System.out.println( charBuffer1.toString() );//半醉半醒
//遍历对象
// for (int i = 0; i < byteBuffer.limit(); i++) {
// System.out.print( byteBuffer.get() +" ");
// }
}