Netty底层实现是由NIO实现的;
以下实现基于版本:
Maven 3.3
Netty 4.1.22
进程(简单理解成一个应用) 它要去读写数据的时候,它会去做一个系统调用(简单理解:API的调用),
到内核以后,它会去准备数据,数据准备过来之后把数据复制过去,(为什么要复制,因为此时数据是在内存的缓冲区里面的,要复制要用户缓冲区打到网卡的buf上面去)数据没有复制好的话,整个进程的阻塞的;
unix处理过程:首先有一个进程通过系统调用到了内核,此时系统调用是不阻塞的,不管内核怎么处理,内核处理好数据了告之数据准备好了,通知去取数据时候有可能这个数据还在复制,还会去阻塞,这个过程复制结束的话,这段也是没有阻塞的;所以它这种模型叫同步非阻塞模型;
IO复用模型:进行经过系统调用进入内核,等待数据准备这段过程是阻塞的,数据准备好的之后告之,在经过
系统调用,它是用一个线程复用了整个事件,这个事件既可以为A客户端去服务,也可以为B客户端去服务,
这个线程轮询一个事件是非常短的,很快就结束了,性能瓶颈还谈不上,对程序来说完全感知不到;
这个是普及知识点,
什么是信号驱动的IO模型呢》》
首先有系统调用到内核,数据准备好了就主动通知,这个进程知道数据准备好了之后经过系统调用但是这个过程会
阻塞,等待数据复制完返回;
异步IO模型:
首先有个系统调用, 到内核,只要这个过程调用完,这个进程就返回了,返回之后它会注册一个事件,或者注册一个回调
然后告诉内核,数据复制完了之后才告之,数据复制完了之后它就会去取数据;
异步IO模型 非阻塞
Acceptor相当于门卫,Client1,2….N相当于你的朋友,Thread1,2,,,,N就类比于你的分身,来接待这些朋友;
这样的模型会发生什么问题:主要问题是要分配的线程太多;
加了线程池之后有些改善,一个客户请求过来处理时,可以不用再创建连接,节省了时间和性能,放在线程池中,用完之后归还
问题:
线程池满了,后面的请求就需要等待;
读和写的操作都是同步的,同步:比如一个客户端建立一个连接,发了一条数据过去,服务端在处理,该客户端是不能再做别的事情,等待结果返回;
I/O线程和业务线程它们的速度是不匹配的,也就是说速度差会存在,这样会导致它的可靠性非常差;
日志的依赖:
org.projectlombok
lombok
1.16.20
org.slf4j
slf4j-api
1.7.5
@Slf4j
publicclassServer{
..
}
这样就不用LoggerFactory了
package com.yz.study.bio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* BIO 服务端编码
*/
public class Server {
private static Logger log = LoggerFactory.getLogger(Server.class);
//默认的端口号
private static int DEFAULT_POST = 7777;
//单例的ServerSocket
private static ServerSocket serverSocket;
//
public static void start() throws IOException {
//
start(DEFAULT_POST);
}
//这个方法不会被大量并发访问,不太需要考虑效率问题,直接进行方法同步就行了
private synchronized static void start(int port) throws IOException {
//做一个容错处理
if (serverSocket != null) {
return;
}
try {
/*
* 通过构造函数创建ServerSocket
* 如果端口合法且空闲,服务端就监听成功
*/
serverSocket = new ServerSocket(port);
log.info("服务端已启动!端口号:" + port);
System.out.println("服务端已启动!端口号:" + port);
/*
* 自旋
* 通过无线循环监听客户端连接
* 如果没有客户端接入,将阻塞在accept操作上。
*/
for(;;){
/*
* 源码
*
protected synchronized void accept(SocketImpl s) throws IOException {
if (s instanceof PlainSocketImpl) {
// pass in the real impl not the wrapper.
SocketImpl delegate = ((PlainSocketImpl)s).impl;
delegate.address = new InetAddress();
delegate.fd = new FileDescriptor();
impl.accept(delegate);
// set fd to delegate's fd to be compatible with older releases
s.fd = delegate.fd;
} else {
impl.accept(s);
}
}
protected void accept(SocketImpl s) throws IOException {
acquireFD();
try {
socketAccept(s);
} finally {
releaseFD();
}
}
void releaseFD() {
synchronized (fdLock) {
fdUseCount--;
if (fdUseCount == -1) {
if (fd != null) {
try {
socketClose();
} catch (IOException e) {
} finally {
fd = null;
}
}
}
}
}
*/
Socket socket = serverSocket.accept();
new Thread(new ServerHandler(socket)).start();
}
} finally {
//一些必要的清理工作
if (serverSocket != null) {
log.info("服务端已关闭");
System.out.println("服务端已关闭");
serverSocket.close();
serverSocket = null;
}
}
}
}
package com.yz.study.bio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class ServerHandler implements Runnable {
private static Logger log = LoggerFactory.getLogger(ServerHandler.class);
private Socket socket;
public ServerHandler() {
}
public ServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
String expression;
String result;
while (true) {
if ((expression = in.readLine()) == null) {
break;
}
log.info("服务端收到信息:"+expression);
System.out.println("服务端收到信息:"+expression);
result = Calculator.cal(expression);
out.println(result);
}
} catch (IOException e) {
e.printStackTrace();
log.error(e.getLocalizedMessage());
System.out.println(e.getLocalizedMessage());
} finally {
if(in !=null){
try{
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if(out !=null){
out.close();
out = null;
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket =null;
}
}
}
}
package com.yz.study.bio;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
private static org.slf4j.Logger logger = LoggerFactory.getLogger(Client.class);
private static int DEFAULT_SERVER_PORT = 7777;
private static String DEFAULT_SERVER_IP = "127.0.0.1";
public static void send(String expression) {
send(DEFAULT_SERVER_PORT, expression);
}
public static void send(int port, String expression) {
logger.info("算术表达式为:" + expression);
System.out.println("算术表达式为:" + expression);
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket(DEFAULT_SERVER_IP, port);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println(expression);
String str = in.readLine();
logger.info("_结果为:" + str);
System.out.println("_结果为:" + str);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if (out != null) {
out.close();
out = null;
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket = null;
}
}
}
}
package com.yz.study.bio;
public class Calculator {
public static String cal(String expression){
return expression;
}
}
package com.yz.study.bio;
import io.netty.util.internal.ThreadLocalRandom;
import java.io.IOException;
import java.util.Random;
public class BioTest {
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
try{
Server.start();
}catch (IOException e) {
e.printStackTrace();
}
}
}).start();
//为了避免客户端比服务端先启动
Thread.sleep(1000);
final char[] op = {'+','-','*','/'};
final Random random = new Random(System.currentTimeMillis());
new Thread(()-> {
while(true){
String expression = random.nextInt(10)+
""+op[random.nextInt(4)]+
(random.nextInt(10)+1);
Client.send(expression);
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
4.0.0
com.yz
netty-study
1.0-SNAPSHOT
org.apache.maven.plugins
maven-compiler-plugin
8
io.netty
netty-all
4.1.22.Final
org.projectlombok
lombok
1.16.20
org.slf4j
slf4j-api
1.7.5
NIO同步非阻塞,客户端提交请求给反应堆(Reactor),由它做请求的分配,它会分配多个线程,acceptor分配过程中,它怎样的解决BIO的问题呢?用一个线程轮询各种事件,netty就是异步基于事件的通信模型,假设是读的事件到了,数据到了内核区以后系统调用内核API去获取数据,它会把数据准备好,准备好之后它会通知好上层的应用,比如说当有连接进来以后,操作系统也会告之某一个端口有一个连接进来了,产生connected事件这样的一个事件,有了这个事件之后NIO才会把一个客户端或者一个socket接进来,这是acceptor干的事情;
整个读过程:客户发送连接请求-->操作系统底层产生了一个连接事件--->acceptor就会把这个连接请求进来---->它会分配一个线程出去处理这个请求;
acceptor会有一些问题,首先它是单线程的,现在的计算都是多核的CPU,这个线程既要处理accept事件,还要处理,read,decode,compute,encode,send等各种事件,若其中decode,业务处理非常的慢,服务端没有办法即时的响应,客户端会等待,虽然它是非阻塞的,但是它是同步的,为什么它是非阻塞的呢?因为Reactor专门有一个线程轮询事件,它可以接很多的请求,acceptor是非阻塞的,当然没有更多的资源可以分配时,acceptor也会拒绝新的请求,
整个写的过程:把一个任务完成后将结果返回---->通过操作系统传到客户端上;
acceptor:是一个接收器,Reactor是一个抽象的模型;
多线程的Reactor让acceptor只做接收的事情,read,decode,compute,encode等事件操作放入到线程池队列里面,有任务过程来时,分配线程去处理请求,性能是可以有所提高的,但是这样处理过程还会存在一些问题的,acceptor是单线程的它就不能并行的响应多个客户端;
终极的解决方案,首先有个主的Reactor,和sub Reactor(netty里面叫work线程),
处理过程:
首先请求过来以后,通过主的Reactor去接收,把这个socket交给子的反应堆(subReactor)然后它去分配线程去处理,整个过程分成两个反应堆去处理了,
这里面还是有些问题的,mainReactor和subReactor怎么去判断分配多少个线程合适呢?
小小的总结下:
什么是同步?什么是异步,同步和异步的差别?
同步和异步的差别在于数据访问的时候,进程是否阻塞;
阻塞和非阻塞,同步和异步的差别?
阻塞和非阻塞是对应于应用程序来说的,同步和异步是对于服务端数据处理来说的,这是两个不同的维度,
阻塞和非阻塞的区别在于看应用程序调用完了之后,会不会立即返回,没有直接返回的即是阻塞的,反之是
非阻塞的;
流什么?
流是一种抽象,一段数据通过网络发送出去,要发送到操作系统中来,怎么去模拟呢?因为流是二进制的,把
它比作是二进制的流水通过管道不停的发送出去,也是通过这种方式不停的接收数据;接、收两端分别看成是
socket,BIO是面向流的,NIO是面向buffer的即缓存的,这也是BIO和NIO最大的区别;
用户空间和内核空间交互对一个文件进行读写的时候,它分为两个阶段:
一个是数据准备阶段:为什么要进行数据准备,原因是可能这个数据是要从硬盘上取的,读硬盘到内存的过程
当中,它是一个过程的,还有就是这个数据是从网络上下载下来的,它这个是有一个过程;
当把数据准备好之后,发送给用户进程那边去,这时候
第二个阶段数据复制,若第一个阶段直接返回的话就不会阻塞,就叫非阻塞模型,
对一个IO操作有两个过程:
第一个过程就是发起IO阶段,第一个阶段不能直接返回的它一定是阻塞的,反之它是非阻塞的;第二个过程是IO读取阶段;不能立即返回的就是同步的,反之它是异步的;
java是最外圈,应用程序,系统调用:应用程序去调内核API的时候,需要用到系统调用,
用户态运行的上层应用程序在系统调用的时候会调用内核态中的资源,系统调用发过去的时候,CPU会把
这个进程比如现在java程序在运行的过程当中,会调用它,java程序调用系统内核API函数,它会被操作系
统挂起(所谓挂起:没有权限去使用CPU这样一个昂贵的资源的时候会被挂起),CPU会去调用内核的API
,在调用内核api的过程中,用户态运行的上层应用程序是阻塞不可用的;
内核态中的资源:可以直接去操作操作系统管理的机器的硬件比如说硬盘,还有一部分是内存区域,甚至键盘;
内核空间:是操作系统管理的那个区域,这个区域主要是用来进行和硬件打交道的,
还有去操作操作系统内部函数用的,不如说要去访问某一块地址空间的时候
用户空间:应用程序那个范围;
内核缓冲区: 内核开辟的一块内存空间;
例如:从硬盘读取一个文件的时候,把文件放入内核缓冲区,为什么要放入内核缓冲区呢?
这样可以减少IO操作;
进程缓冲区:
例如:当一个文件收到以后放入内核缓冲区,通过网络的方式传输到服务器上的网卡,有了
端口可以在网卡找到对应的这个端口对应的进程,这个进程也有缓存区,因为也需要把一些
数据放入到这个进程缓存区,进程缓冲区(socket缓冲区)拿到这个数据之后,然后进行相应的处理;
外设都是由内核态去调用的;内核是管理内存,操作CPU,管理文件系统的,这样一套程序;
外围的应用程序是来调内核的,通过系统调用的方式来调用,
系统调用的理解可以看成,内核提供给应用程序去用的一些API函数,
为什么要分用户态和内核态?
不能让应用程序随意的去访问整片内存区域,因为有些内存区域是操作系统本身要去用的;
channel对应到BIO中的socket,
channel和socket有什么差别呢?
channel是面向buffer的,一次操作可以读取一块内容;BIO是面向字节的,面向流的,每次只能够操作一个字节;
两个channel之间的交互是通过buffer,流程如下:
channelA发送数据buffer中,ChannelB往buffer中读取数据,之间是通过网络传输,也可能是这两个channel是在本地的,通过内存交互;
channel: 它是一个通道,可以把它理解传递数据的时候,获得网络通讯的时候,需要channel这样的
一个东西,拿到channel之后,得有一个地方放置,放置在buffer里面,两个channel就可以去交互了;
selector:它是单线程轮询模式,去和操作系统打交道问问刚刚发送的IO操作的数据是否已经准备好了,基于
事件操作;
selector:buffer中肯定要进行数据读写,需要告之channel什么时候可以读什么时候可以写,第一阶段因为
进程是马上返回干别的事情,selector只需要注册事件,告之channel什么时候可以读或者写,是一个
非阻塞的模型;
unsafe:本身跟NIO体系关系不是很大,那它的重要性在于,在很多时候NIO并不是通过JVM去操作的,
大部分的情况下,它会使用堆外内存,在使用堆外内存时,java是不能直接操作底层硬件的,不能操作内存,
它没有指针等一些概念,在jdk里面它提供unsafe这样的类,就可以去操作底层的硬件资源,包括内存;
unsafe: 第一种它是用来操作内存区域的,还有一种是它来操作java本身内库无法操作的区域比如CAS的操作,
buffer: 它其实就是一个数组,它相当于内存的一块区域;
channel它是面向buffer的通道,channel可以往buffer里面写数据,同时channel
也是可以从buffer中取数据,它跟传统的BIO最大的差别在于它是双向的,而流是单向的;
在channel中会有四个类:
ServerSocketChannel :它是服务器端的channel ,基于TCP协议;
SocketChannel:客户端的的channel,简单的理解即客户端要接入一个客户之后真正去
去工作的就是socket做一些相应的事情, 基于TCP协议;
DatagramChannel 不是面向连接的通道 面向UDP的;
FileChannel 如果没有buffer,只要有一个channel是FileChannel,两个channel一定是可以交互的
因为FileChannel是面向文件的,有一种说法叫内存映射文件,把磁盘的东西映射到内存中,原理
是相通的;
BufferedReader:是采用的装饰者模式包装了底层的字节流的带缓冲功能的流;它和buffer是完全
是两个概念,buffer可以理解成它是内存的一块或者更简单的理解就是一个数组;
Buffer新开辟的缓冲区有几个属性:
mark: 用来做标记的,默认的情况下它是-1的,
position :可以把理解为指针,指针初始化的时候会在0号位置,它是来标志未写区域是从哪里开始的;
limit :限制,比如现在写了10个,最大的capacity也是10就需要去扩容,只能读取到limit之前的数据
limit可以做一个可读区域是多少;
capacity :总的数组的容量,capacity是可读区域和可写区域是多大;
写数据是用put方法,写了a,b,c,d,e五个字母,当写到4号位置的时候position就指向5了,默认limit和
capacity都是在数组中index最大的位置,
当需要读的时候,position的位置通过flip()操作,position位置归置为0,这样就可以从0号位置开始读取数据了,
然后,limit位置归置到position原来的位置,读取数据的长度限制;
跟flip()操作类似,唯一的区别在于limit的位置还是原来的位置,也就是说读取数据时可以读到limit的位置,
即可以读取到未写区域,为什么要做这步操作呢?原因是可能想去重复读;之前position位置可以记录下最
大可读的范围,重复读的时候,未写入的区域也可以通过并发的写入进来,可提高它的并发性能。
Buffer 中有两种模式:
写的模式:position在0号位置,开始写,limit和capacity在同一个位置,这种模式叫写的模型;
读的模式: 假设写了5个数据的时候,读的时候limit归置到原来的position的位置,position的位置
归零,
JAVA NIO通道之间的数据传输
- 在Java NIO中,如果两个通道中有一个是FileChannel,那可以直接将数据从一个channel传输到
另一个channel
它有两个API:transferFrom()和transferTo()
直接内存可以理解为堆外内存,
多路复用肯定需要一个selector,就有了注册中心,然后把channel注册在selector中,注册事件是因为这个过程
是非阻塞模型,IO操作完立即返回,通过事件机制通知。
通道可以注册到选择器上面,selector是用一个线程来轮询注册在Channel上的IO事件是否已经完成;
Channel为什么要注册到Selector?即为什么要注册呢?
注册的目的是如果channel上的一个IO事件准备好了,告之Selector
client端注册自己感兴趣的事件,感兴趣的事件如:connect ,read,write…,假设注册connect事件以后,
selector会去检测每个client上面的事件是否已经就绪,若检测到了有读写事件到达之后,就会去通知client可以去
读写了,处理的过程一定是按照顺序去处理的;
为什么选择器和多路复用联系在一起呢为什么要用单线程的模式?
如果用多线程的方式去做选择器的功能的话,那它势必带来频繁的线程的调度,上下文切换,操作系统
在进行上下文切换是非常耗费资源的,并且在selector要轮询的事件的流量是非常小的,也就是说这样的
场景是非常适合单线程去处理的;
什么叫多路复用呢?
所谓多路是指在操作系统注册了多个client,并且每个client都是用同一个选择器去处理的,那这样在处理
过程当中按照先后顺序先处理A的,B到了就处理B的,这样就实现了选择器的复用;
选择器模型:
有一个连接进来acceptor会去接收,选择器去轮询相应的事件,read,decode,encode,compute等事件,
轮询到了该事件告之客户端,这些客户端可以理解为注册在选择器上的channel,当拿到一个事件的时候,会把
这个事件告诉到客户端,客户端进行判断是什么事件,就拿到对应的client对应的channel,把数据读取过来,
Reactor模型:两点
一个是线程,这个线程它就是来轮询事件,轮询到事件告诉对应的客户端;
组件:acceptor组件,dispatch组件
如果真的是并发量太大了,可以实现多路的acceptor;
Reactor:可以理解成IO事件的派发者;对应到java里面就是Selector
事件:事件是由操作系统产生的;read事件,write事件,close事件
组件Acceptor :建立连接的
接收一个连接,并且建立对应client的Handler(处理IO事件的),它会往Reactor注册Handler,
Handler:客户端通讯的实体;
Acceptor建立连接完成接着就是派发事件,事件准备好了,才有第三步,有了数据之后进行处理;
buffer文件夹中:
package com.yz.study.nio.buffer;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
/**
* channel之间传输的编解码
* 编解码是因为在网络传输过程中都需要二进制方式,或者说可传输的字节流的方式去处理的
*/
public class BufferDemo {
public static void decode(String str) throws UnsupportedEncodingException {
/*
* 创建了128个字节的buffer
*/
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
byteBuffer.put(str.getBytes("UTF-8"));
//读的时候需要反转
byteBuffer.flip();
/*
* 获取utf-8的编解码器
*/
Charset utf8 = Charset.forName("UTF-8");
/*
* 对bytebuffer中的内容解码
*/
CharBuffer charBuffer = utf8.decode(byteBuffer);
/*
* array()返回的就是内部数组引用,编码以后的有效长度是0~limit
* 转成数组的方式会很方便的
*/
char[] charArr = Arrays.copyOf(charBuffer.array(),charBuffer.limit());
System.out.println(charArr);
}
public static void encode(String str){
CharBuffer charBuffer = CharBuffer.allocate(128);
charBuffer.append(str);
charBuffer.flip();
/*
* 对获取utf8的编解码器
*/
Charset utf8 = Charset.forName("UTF-8");
/*
* 对charbuffer中的内容解码
*/
ByteBuffer byteBuffer = utf8.encode(charBuffer);
/*
* array()返回的就是内部的数组引用,编码以后的有效长度为0~limit
*/
byte[] bytes = Arrays.copyOf(byteBuffer.array(),byteBuffer.limit());
System.out.println(Arrays.toString(bytes));
}
public static void main(String[] args) throws UnsupportedEncodingException {
BufferDemo.decode("NIO channel buffer");
BufferDemo.encode("NIO channel buffer");
}
}
package com.yz.study.nio.buffer;
import java.nio.ByteBuffer;
public class Buffers {
private ByteBuffer readBuffer ;
private ByteBuffer writeBuffer;
public Buffers(int readCapacity,int writeCapacity){
this.readBuffer= ByteBuffer.allocate(readCapacity);
this.writeBuffer = ByteBuffer.allocate(writeCapacity);
}
public ByteBuffer getReadBuffer() {
return readBuffer;
}
public ByteBuffer getWriteBuffer() {
return writeBuffer;
}
}
channel文件夹
package com.yz.study.nio.channel;
import com.yz.study.nio.buffer.Buffers;
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.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
/**
* 客户端:客户端每隔1-2秒自动向服务器发送数据,接收
* 服务器接收到的数据并显示
*/
public class ClientSocketChannelDemo {
public static class TCPEnchoClient implements Runnable{
/*客户端线程名*/
private String name;
private Random rnd = new Random();
/*服务器的ip地址+端口port*/
private InetSocketAddress remoteAddress;
public TCPEnchoClient(String name,InetSocketAddress remoteAddress){
this.name = name;
this.remoteAddress = remoteAddress;
}
@Override
public void run() {
/*创建解码器*/
Charset utf8 = Charset.forName("UTF-8");
Selector selector ;
try{
/*创建TCP通道
* 创建客户端的channel*/
SocketChannel sc = SocketChannel.open();
/*设置通道为非阻塞*/
sc.configureBlocking(false);
/*创建选择器*/
selector = Selector.open();
/*注册感兴趣的事件
* 既要接收服务器端发送的事件,也要向服务器端发送事件,所以需要两个事件*/
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
/*向选择器注册通道*/
sc.register(selector,interestSet,new Buffers(256,256));
/*向服务器发起连接,一个通道代表一条tcp链接*/
sc.connect(remoteAddress);
/*等待三次握手完成*/
while(!sc.finishConnect()){
;
}
System.out.println(name + " "+ "finished connection");
}catch (IOException e) {
System.out.println("client connect failed");
return;
}
/*与服务器端断开或线程被中断则结束线程*/
try {
int i = 1;
while(!Thread.currentThread().isInterrupted()){
/*阻塞等待*/
selector.select();
/*Set中的每个key代表一个通道*/
Set keySet = selector.selectedKeys();
Iterator it = keySet.iterator();
/*遍历每个已就绪的通道,处理这个通道已就绪的事件*/
while(it.hasNext()){
SelectionKey key = it.next();
/*防止下次select方法返回已处理过的通道*/
it.remove();
/*通过SelectionKey获取对应的通道*/
Buffers buffers = (Buffers)key.attachment();
ByteBuffer readBuffer = buffers.getReadBuffer();
ByteBuffer writeBuffer = buffers.getWriteBuffer();
/*通过SelectionKey 获取通道对应的缓冲区*/
SocketChannel sc = (SocketChannel) key.channel();
/*表示底层socket的读缓冲区有数据可读*/
if(key.isReadable()){
/*从socket的读缓冲区读取到程序定义的缓冲区中*/
sc.read(readBuffer);
readBuffer.flip();
/*字节到utf8解码*/
CharBuffer cb = utf8.decode(readBuffer);
/*显示接收到由服务器发送的信息*/
System.out.println(cb.array());
readBuffer.clear();
}
/*socket的写缓冲区可写*/
if (key.isWritable()){
writeBuffer.put((name + " " +i).getBytes("UTF-8"));
writeBuffer.flip();
/*将程序定义的缓冲区中的内容写入到socket的写缓冲区中*/
sc.write(writeBuffer);
writeBuffer.clear();
i++;
}
}
Thread.sleep(1000 + rnd.nextInt(1000));
}
} catch (IOException e) {
System.out.println(name +" is interrupted");
} catch (InterruptedException e) {
System.out.println(name + " encounter a connect error");
}finally {
try {
selector.close();
} catch (IOException e) {
System.out.println(name + " close selector failed");
} finally {
System.out.println(name + " closed");
}
}
}
}
public static void main(String[] args) throws InterruptedException {
InetSocketAddress remoteAddress = new InetSocketAddress("127.0.0.1",8080);
Thread ta = new Thread(new TCPEnchoClient("thread a",remoteAddress));
Thread tb = new Thread(new TCPEnchoClient("thread b",remoteAddress));
Thread tc = new Thread(new TCPEnchoClient("thread c",remoteAddress));
Thread td = new Thread(new TCPEnchoClient("thread d",remoteAddress));
ta.start();
tb.start();
tc.start();
Thread.sleep(5000);
/*结束客户端*/
ta.interrupt();
/*开始客户端d*/
td.start();
}
}
package com.yz.study.nio.channel;
import com.yz.study.nio.buffer.Buffers;
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.util.Iterator;
import java.util.Random;
import java.util.Set;
/**
* 服务器端:接收客户端发送过来的数据并显示
* 服务器把上面接收到的数据加上"echo from service:"再发送回去
*
*/
public class ServiceSocketChannelDemo {
public static class TCPEchoServer implements Runnable {
/*
* 服务器地址
* InetSocketAddress
是一个封装了IP地址的类
*/
private InetSocketAddress localAddress;
public TCPEchoServer(int port) {
this.localAddress = new InetSocketAddress(port);
}
/**
* ServerSocket
的channel
* channel
绑定ip和端口
* 注册一个SelectionKey.OP_READ
读的事件
* channel
注册到selector
上;
* {@link Buffers }
*/
@Override
public void run() {
Charset utf8 = Charset.forName("UTF-8");
ServerSocketChannel ssc = null;
Selector selector = null;
Random rnd = new Random();
try {
/*
* 创建选择器
*/
selector = Selector.open();
/*
* 创建服务器通道
* 创建的是 ServerSocket
的channel
* 并且把它设置为非阻塞模型
*/
ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
//接下来就是要把选择器和Channel关联起来
/*
* 设置舰艇服务器的端口,设置最大连接缓冲数为100
* channel
绑定ip和端口
* backlog
:后端排队的队列可能客户端有很多个,用它来设置最大
* 缓冲区的大小
*/
ssc.bind(localAddress, 100);
/*
* 服务器通道只能对tcp连接事件感兴趣
* 把channel
注册到选择器上,
* SelectionKey.OP_ACCEPT
:注册的类型为accept
* 先注册accept事件是因为需要接入客户端进来
*/
ssc.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
System.out.println("server start failed");
e.printStackTrace();
}
System.out.println("server start with address :" + localAddress);
/*
* 服务器线程中断后会退出
*/
try {
while (!Thread.currentThread().isInterrupted()) {
int n = selector.select();
if (n == 0) continue;
/*
* selector.selectedKeys()
通过这个key可以找到
* 对应的事件的
*/
Set keySet = selector.selectedKeys();
/*
* 接下来是轮询这些事件
*/
Iterator it = keySet.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
/*
* 防止下次select方法返回已处理过的通道
* 简单的理解:处理之前已处理过的事件把它删除,因为我们的事件
* 是顺序处理的,如果不删除的话会出现一些问题的;
*/
it.remove();
/*
* 若发现异常,说明客户端连接出现问题,但服务器要保持正常
*/
try {
/*
* ssc通道只能对连接事件感兴趣
* 判断是否是可接收的
*/
if (key.isAcceptable()) {
/*
* accept方法会返回一个普通通道,每个通道在内核中都会对应一个
* socket缓冲区;
* 如果是可接收的,把这个channel接进来,并且设置成非阻塞的;
*/
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
/*
* 向选择器注册这个通道和普通通道感兴趣的事件,同时提供这个新通道
* 相关的缓冲区;
*
* 接入连接进来之后,注册一个SelectionKey.OP_READ
读的事件
* channel
注册到selector
上;
* 写一个Buffers 可读的大小是256,可写的大小也是256;
*/
int interestSet = SelectionKey.OP_READ;
sc.register(selector, interestSet, new Buffers(256, 256));
System.out.println("accept from " + sc.getRemoteAddress());
}
/*
* (普通) 通道感兴趣读事件且有数据可读
* 写事件已经注册好了,需要去判断读事件是否准备就绪
*/
if (key.isReadable()) {
/*
* 通过 SelectionKey 获取通道对应的缓冲区
* 如果准备好了,把它的复件拿出看它的readBuffer,writeBuffer里面的东西
*/
Buffers buffers = (Buffers) key.attachment();
ByteBuffer readBuffer = buffers.getReadBuffer();
ByteBuffer writeBuffer = buffers.getWriteBuffer();
/*
* 通过SelectionKey
获取对应的通道
*/
SocketChannel sc = (SocketChannel) key.channel();
/*
* 从底层socket读取缓冲区中写入的写入数据
*/
sc.read(readBuffer);
readBuffer.flip();
/*
* 解码显示,客户端发送来的信息
*/
CharBuffer cb = utf8.decode(readBuffer);
System.out.println(cb.array());
/*
* 需要能够重复的读取
*/
readBuffer.rewind();
/*
* 准备好向客户端发送的消息
* 先写入"echo:",再写入收到的信息
*/
writeBuffer.put("echo from service:".getBytes("UTF-8"));
writeBuffer.put(readBuffer);
readBuffer.clear();
/*
* 设置通道写事件
*/
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
}
/*
* 通道感兴趣写事件且底层缓冲区有空间
*/
if (key.isWritable()) {
Buffers buffers = (Buffers) key.attachment();
ByteBuffer writeBuffer = buffers.getWriteBuffer();
writeBuffer.flip();
SocketChannel sc = (SocketChannel) key.channel();
int len = 0;
while (writeBuffer.hasRemaining()) {
len = sc.write(writeBuffer);
/*
* 说明底层的socket写缓冲已满
*/
if (len == 0) break;
}
//对数据进行压缩
writeBuffer.compact();
/*
* 说明数据全部写入到底层的socket写缓冲区
*/
if (len != 0) {
/*
* 取消通道写的事件
*/
key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
}
}
} catch (IOException e1) {
System.out.println("service encounter client error");
/*
* 若客户端连接出现异常,从Selector中移除这个key
*/
key.cancel();
key.channel().close();
}
}
Thread.sleep(rnd.nextInt(500));
}
} catch (IOException e){
System.out.println("serverThread selector error");
} catch (InterruptedException e) {
System.out.println("serverThread is interrupted");
}finally {
try {
selector.close();
} catch (IOException e) {
System.out.println("selector close failed");
} finally {
System.out.println("server close");
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new TCPEchoServer(8080));
thread.start();
Thread.sleep(100000);
/*
* 中断服务器线程
*/
Thread.interrupted();
}
}
package com.yz.study.nio.channel;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 由原来的字节流的处理方式转成NIO FileChannel处理方式
*/
public class FileChannelDemo {
/**
*
* @param args
*/
public static void main(String[] args) {
/*
* 创建文件,向文件中写入数据
*/
try {
File file = new File("f:/noi_utf.data");
if (!file.exists()) {
file.createNewFile();
}
/*
* 根据文件输出流创建与这个文件相关的通道
* 从底层的字节流里面把FileChannel取出来
*/
FileOutputStream fos = new FileOutputStream(file);
FileChannel fc = fos.getChannel();
/*
* 创建ByteBuffer对象,position = 0,limit = 64
*/
ByteBuffer bb = ByteBuffer.allocate(64);
/*
* 向ByteBuffer中放入字符串UTF-8的字节,Position = 17 ,limit = 64
*/
bb.put("Hello,World 123 \n".getBytes("UTF-8"));
/*
* flip方法 position = 0,limit = 17
*/
bb.flip();
/*
* write方法使得ByteBuffer的position到limit中的元素写入通道中
*/
fc.write(bb);
/*
* clear方法使得position = 0,limit = 64
*/
bb.clear();
} catch (IOException e) {
e.printStackTrace();
}
/*
* 从刚才的文件中读取字符序列
*/
try {
/*
* 通过Path 对象创建文件通道
*/
Path path = Paths.get("f:/noi_utf-8.data");
FileChannel fc = FileChannel.open(path);
ByteBuffer bb = ByteBuffer.allocate((int)fc.size()+1);
Charset utf8 = Charset.forName("UTF-8");
/*
* 阻塞模式,读取完成才能返回
*/
fc.read(bb);
bb.flip();
CharBuffer cb = utf8.decode(bb);
System.out.println(cb.toString());
bb.clear();
fc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}