写这些东西的原因,因为本人14年毕业,从事工作也有几年了,中间关于书,一直看了很多,有的甚至看过很多遍,但是总是觉得自己没有熟透一门技术,所以在学习使用技术之余,把自己所学的东西记录下来,便于加深印象,提升自我!
目录
1 传统BIO socket通信
2 NIO 编程
一:传统socket通信
在NIO编程没出来之前,java使用的socket编程时
socket 服务端:
public class OldSocketServer {
private void start(int port) throws IOException {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
Socket socket = null;
while (true){
socket = serverSocket.accept();
new Thread(new OldSocketServerHandler(socket)).start();
}
}finally {
System.out.println("关闭服务端...");
serverSocket.close();
}
}
public static void main(String[] args) throws IOException {
new OldSocketServer().start(8090);
}
}
对应的handler处理:
public class OldSocketServerHandler implements Runnable {
private Socket socket;
public OldSocketServerHandler(Socket socket) {
this.socket = socket;
}
public void run() {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
String body = null;
while ((body = in.readLine())!=null){
System.out.println(" 服务端收到消息 : " + body);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(socket != null){
try {
if(in != null){
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
socket客户端:
public class OldSocketClient {
private void connect(String ip, int port) throws IOException {
Socket socket = null;
PrintWriter out = null;
try {
socket = new Socket(ip, port);
out = new PrintWriter(socket.getOutputStream(), true);
out.write("this is socket client to server msg.");
}catch (Exception e){
e.printStackTrace();
}finally {
if(out !=null){
out.close();
}
if(socket != null){
socket.close();
}
}
}
public static void main(String[] args) throws IOException {
new OldSocketClient().connect("localhost",8090);
}
}
通过上面简单的一个socket通信问题,可以找出几个问题:
问题1. 对于上面的服务端,当每接一个客户端请求,服务端必须new一个线程处理新的请求,对于java这种线程资源非常珍 贵的语言中,这种设计显示是有问题的。
问题2:对于IO数据的读取上,对于InputStream输入流,当对socket的输入流进行读取操作的时候,线程会一直阻塞,直到读取到数据,或数据读取完毕,又或者发生空指针或I/O异常时。 同样输出流输出数据,OutputStream输出流会把所有的数据全部发送出去或者发生异常才会停止阻塞。显然,同步阻塞这种设计是十分浪费系统资源的。
二:nio编程
NIO是JDK1.4引入的,通过快的形式处理数据。NIO常用的几个概念:
1、Buffer(缓冲区)
在传统的面向流的I/O中,数据是直接写入或读取到流对象中的,而在NIO中,所有的数据都是用缓冲区来处理的。缓冲区实质上就是一个数组,通过定义数据的结构,是的缓冲区能被重复利用。
NIO定义的Buffer的继承关系图:
缓冲区定义四个属性来提供关于其所包含的数据元素的信息:
1).容量 ( Capacity )
缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能 被改变。
2).上界 ( Limit )
缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。
3).位置 ( Position )
下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新。
4).标记 ( Mark )
一个备忘位置。调用 mark( )来设定 mark = postion。调用 reset( )设定 position = mark。标记在设定前是未定义的 (undefined)。 这四个属性之间总是遵循以下关系:
0 <= mark <= position <= limit <= capacity
新初始化的Buffer图:
2、通道Channel
Channel是一个通道,是全双工的,就像是自来水管一样,网络数据通过Channel读取和写入。与流IO的不同之处在意,流IO只能进行读InputStream 或者写OutputStream, 而Channel可读可写。
Channel继承关系类图如下,主要有:ServerSocketChannel, SocketChannel, DatagramChannel
3、多路复用器Selector
多路复用器Selector提供选择已经就绪的任务的能力。selector会不断的轮询注册在其上的Channel, 如果某个Channel发生读或者写事件,则这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey获取就绪Channel的集合,进行后续的I/O操作。
在JDK中selector使用了epoll()来实现select, 故此没有轮询Channel数量限制。
一个NIO的案例:
服务端代码:
public class NioServer {
private ByteBuffer readBuf = ByteBuffer.allocate(1024);
private void start(int port) throws IOException {
ServerSocketChannel serverSocketChannel = null;
try{
// 1、打开ServerSocketChannel,用于监听客户端连接,是所有客户端连接的父管道
serverSocketChannel = ServerSocketChannel.open();
// 2、绑定监听端口,设置连接为非阻塞模式
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
// 3、创建Reactor线程,创建多路复用器并启动线程
Selector selector = Selector.open();
// 4、将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println(" 服务端开始工作.....................");
run(selector);
}finally {
if(serverSocketChannel!=null){
serverSocketChannel.close();
}
}
}
private void run(Selector selector) {
while(true){
try {
//1.让多路复用器开始监听
selector.select();
//2.返回多路复用器已经选择的结果集
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()){
SelectionKey key = keys.next();
keys.remove();
if(key.isValid()){
if(key.isConnectable()){
System.out.println("connectable....");
}
if(key.isWritable()){
System.out.println("writable.........");
}
if(key.isAcceptable()){
System.out.println("acceptable....");
accept(key,selector);//这里的key就是服务器端的Channel的key
}
if(key.isReadable()){
System.out.println("readable....");
read(key);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void read(SelectionKey key) {
try {
// 1. 清空旧的缓冲区
readBuf.clear();
//2.获取之前注册的socket通道对象
SocketChannel sc = (SocketChannel) key.channel();
//3.读取数据
int count = sc.read(readBuf);
//4.如果没有数据
if(count == -1){
key.channel().close();
key.cancel();
System.out.println("已无可读数据");
return;
}
//5.有数据则进行读取,读取之前需要进行复位方法(把position和limit进行复位)
readBuf.flip();
//6.根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据
byte[] bytes = new byte[readBuf.remaining()];
//7.接收缓冲区数据
readBuf.get(bytes);
//8.打印结果
String body = new String(bytes).trim();
System.out.println("Server: " + body);
} catch (IOException e) {
e.printStackTrace();
}
}
private void accept(SelectionKey key, Selector selector) {
try {
//1.获取服务端通道
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//2.执行客户端Channel的阻塞方法
SocketChannel sc = ssc.accept();
//3.设置阻塞模式
sc.configureBlocking(false);
//4.注册到多路复用器上,并设置读取标识
sc.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
new NioServer().start(8090);
}
}
客户端代码:
public class NioClient {
public static void main(String[] args) {
SocketChannel sc = null;
ByteBuffer buf = ByteBuffer.allocate(1024);
try {
//打开通道
sc = SocketChannel.open();
//进行连接
sc.connect(new InetSocketAddress("127.0.0.1", 8090));
//把数据放到缓冲区
buf.put("fangyouyun".getBytes());
//复位
buf.flip();
//写出数据
sc.write(buf);
//清空缓冲区数据
buf.clear();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
总结:NIO以多路复用的设计方式,以及Buffer缓冲区的设计,都使得性能得到很大的提升,但是API太复杂了,所以选择Netty,因为Netty是基于NIO再次封装,便于开发。
博客中案例代码:https://download.csdn.net/download/qq_22871607/11072379