NIO(二)--CS架构的非阻塞通信
来源:互联网 发布:怎么看天猫店铺数据 编辑:程序博客网 时间:2024/06/01 10:22
Socket服务器编程范例
1. 阻塞服务器
(1)方法 : 用主线程处理接受客户端连接, 在主线程中开启线程池, 用池子中的每个线程处理客户端请求
(2)弊端 :
1)JVM为每个线程分配自己独立的堆栈空间 (每个线程中变量的值是主线程变量值的拷贝), 导致线程间同步过程变得复杂(eg:valitile), 也会增加
死锁的可能性, 增加JVM进行线程调度的开销
2)服务器在处理客户端请求时, 会惊醒大量的I/O操作. 而I/O操作不需CPU进行控制, 且会使处理线程阻塞, JVM会释放阻塞线程的CPU时间片给
其他非阻塞的线程, 这些CPU时间片的轮转会降低响应效率 (所以处理线程并非越多越好)
(3)步骤 :
1) ServerSocketChannel.open()打开服务器的nio
2) 设置服务器段的socket地址重用
3) 把serverSocketChannel的socket绑定到本地的一个端口上
4) while(true)循环内, 用ServerSocketChannel用阻塞的accept()方法, 接收客户端连接, 每接收一个客户端连接, 就用线程池中的线程处理请求
(1)方法 :
1) 先在Selector中注册SelectionKey.OP_ACCEPT监听事件
2) 当accept事件发生 ,再注册读写事件
3) 读事件 : 非阻塞模式遵循能读多少读多少, 所以一次读并非用户的一行数据, 先用一个readbuffer接受传来的数据, 在复制到整个大Buffer
写事件 : 写之前先确定大Buffer中已经有了一行数据(截取"\r\n")
非阻塞模式下的写遵循能写多少写多少, 所以先把"echo"+这行数据复制到一个outputBuffer , 用while(hasRemaining)循环写
接着删除大Buffer中已经发送的数据:用tempBuffer存储这行数据(不带echo), 在把大Buffer的position设为tempBuffer的limit, 再把大Buffer, compact()删 除了已发送的数据
(2)Selector的select()方法是阻塞方法,知道阻塞的事件到来, 返回新发生的注册事件个数 (循环打印一次,可减小到0)
Selector的select(long timeout)方法是阻塞方法, 可被Selector的wakeip()方法唤醒
Selectionkey的channel()方法, 返回SelectableChannel
(3)SocketChannel的read(Buffer b),write(Buffer b)都会使Buffer的positon变为原先位置+读/写的数据, 但是Buffer的limit不变
int read(Buffer b) -- 返回从channel中读到的字节数, 返回-1表示读到channel的末尾
阻塞版的read方法会去读满Buffer中的剩余容量(buffer. remaining()的数量), 若未读满且未达到channel的末尾, 就会阻塞
非阻塞版的read方法奉行有多少读多少, 即使未读满buffer, 也立即返回
int write(Buffer b) -- 返回写出的字节数
阻塞版的write()方法一次输出buffer的全部数据, 当底层soket输出缓冲区不能容纳buffer从position到limit的数据时, 就会阻塞
非阻塞版的write()能帮读多少数据就读多少数据, 会改变positon的值, 使得可以继续从该位置读
(4)key.isAcceptable()为true, key.channel()得到的是服务器端的ServerSocket
key.isReadable()与key.isWritable()为true, 得到的是客户端socket
(1) 方法 : 当客户端请求数量变大时, 可以用2个线程完成服务
1)用阻塞模式接受连接请求
2)用费阻塞模式完成业务处理
(2)产生的同步问题 :
Selector.select()一开始处于阻塞状态,当有连接时唤醒,同时防止socketChannel.register()和selector.select()一起阻塞
(1)阻塞版客户端 : 用户输入一句话, 接收到返回的echo字符串后才能输入下一句话
(2)非阻塞版客户端 :
1)准备sendBuffer和recieveBuffer, sendBuffer负责把用户输入的数据存储并发往服务器,recieveBuffer负责接收服务器发来的echo字符串
2) 用阻塞的SocketChannel连接服务器, 之后设置为非阻塞形式来发送接受字符串
3) 接受键盘输入和发送字符串为2个线程
1. 阻塞服务器
(1)方法 : 用主线程处理接受客户端连接, 在主线程中开启线程池, 用池子中的每个线程处理客户端请求
(2)弊端 :
1)JVM为每个线程分配自己独立的堆栈空间 (每个线程中变量的值是主线程变量值的拷贝), 导致线程间同步过程变得复杂(eg:valitile), 也会增加
死锁的可能性, 增加JVM进行线程调度的开销
2)服务器在处理客户端请求时, 会惊醒大量的I/O操作. 而I/O操作不需CPU进行控制, 且会使处理线程阻塞, JVM会释放阻塞线程的CPU时间片给
其他非阻塞的线程, 这些CPU时间片的轮转会降低响应效率 (所以处理线程并非越多越好)
(3)步骤 :
1) ServerSocketChannel.open()打开服务器的nio
2) 设置服务器段的socket地址重用
3) 把serverSocketChannel的socket绑定到本地的一个端口上
4) while(true)循环内, 用ServerSocketChannel用阻塞的accept()方法, 接收客户端连接, 每接收一个客户端连接, 就用线程池中的线程处理请求
public class EchoServerBlock {private ServerSocketChannel serverSocketChannel = null;//nio获取服务器端口通道private ExecutorService executorService = null;//线程池处理客户端请求//构造函数实例化服务器端口 + 线程池public EchoServerBlock() throws IOException {serverSocketChannel = ServerSocketChannel.open();/* serverSocketChannel.socket()获取服务器打开的socket, Socket的setReuseAddress(true)让服务器可以快速重连*/serverSocketChannel.socket().setReuseAddress(true); serverSocketChannel.socket().bind(new InetSocketAddress(12121)); //绑定serverSocketChannel到本机的端口int num = Runtime.getRuntime().availableProcessors()*4;//让每个核心运行4个线程executorService = Executors.newFixedThreadPool(num);System.out.println("阻塞版服务器启动 . . .");}//客户端请求加入线程池处理public void service(){while(true){try {//SocketChannel获取客户端的socket流, accept()方法是阻塞的, 没有请求的时候会阻塞SocketChannel socketChannel = this.serverSocketChannel.accept();executorService.execute(new Handler(socketChannel));} catch (IOException e) {e.printStackTrace();}}}class Handler implements Runnable{private SocketChannel socketChannel;private Handler(SocketChannel socketChannel){this.socketChannel = socketChannel;}public void run() {handle(socketChannel);}public void handle(SocketChannel socketChannel){Socket socket = socketChannel.socket();//阻塞模式从nio中获取socket与serversocketSystem.out.println("接收客户端"+socket.getInetAddress()+":"+socket.getPort());try {InputStream in = socket.getInputStream();OutputStream out = socket.getOutputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(in,"utf-8"));PrintWriter writer = new PrintWriter(new OutputStreamWriter(out,"utf-8"), true);//自动flushString msg = null;while((msg=reader.readLine())!=null){System.out.println("客户端发来:"+msg);writer.println("echo:"+msg);//把数据发回客户端if(msg.equals("bye")){break;}}} catch (IOException e) {e.printStackTrace();}finally{try {if(socketChannel!=null) socketChannel.close();System.exit(0);} catch (IOException e) {e.printStackTrace();}}}}public static void main(String[] args) throws IOException {new EchoServerBlock().service();}}2. 非阻塞服务器
(1)方法 :
1) 先在Selector中注册SelectionKey.OP_ACCEPT监听事件
2) 当accept事件发生 ,再注册读写事件
3) 读事件 : 非阻塞模式遵循能读多少读多少, 所以一次读并非用户的一行数据, 先用一个readbuffer接受传来的数据, 在复制到整个大Buffer
写事件 : 写之前先确定大Buffer中已经有了一行数据(截取"\r\n")
非阻塞模式下的写遵循能写多少写多少, 所以先把"echo"+这行数据复制到一个outputBuffer , 用while(hasRemaining)循环写
接着删除大Buffer中已经发送的数据:用tempBuffer存储这行数据(不带echo), 在把大Buffer的position设为tempBuffer的limit, 再把大Buffer, compact()删 除了已发送的数据
(2)Selector的select()方法是阻塞方法,知道阻塞的事件到来, 返回新发生的注册事件个数 (循环打印一次,可减小到0)
Selector的select(long timeout)方法是阻塞方法, 可被Selector的wakeip()方法唤醒
Selectionkey的channel()方法, 返回SelectableChannel
(3)SocketChannel的read(Buffer b),write(Buffer b)都会使Buffer的positon变为原先位置+读/写的数据, 但是Buffer的limit不变
int read(Buffer b) -- 返回从channel中读到的字节数, 返回-1表示读到channel的末尾
阻塞版的read方法会去读满Buffer中的剩余容量(buffer. remaining()的数量), 若未读满且未达到channel的末尾, 就会阻塞
非阻塞版的read方法奉行有多少读多少, 即使未读满buffer, 也立即返回
int write(Buffer b) -- 返回写出的字节数
阻塞版的write()方法一次输出buffer的全部数据, 当底层soket输出缓冲区不能容纳buffer从position到limit的数据时, 就会阻塞
非阻塞版的write()能帮读多少数据就读多少数据, 会改变positon的值, 使得可以继续从该位置读
(4)key.isAcceptable()为true, key.channel()得到的是服务器端的ServerSocket
key.isReadable()与key.isWritable()为true, 得到的是客户端socket
//主线程同时处理客户端tcp连接与数据处理public class EchoServerNoBlock {private ServerSocketChannel serverSocketChannel = null;private Selector selector;private Charset charSet = Charset.forName("utf-8");public EchoServerNoBlock() throws IOException {selector = Selector.open();this.serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.socket().setReuseAddress(true);//服务器可立即重启serverSocketChannel.configureBlocking(false);//服务器配置成非阻塞模式serverSocketChannel.socket().bind(new InetSocketAddress(12121)); //地址一旦绑定,服务器开启System.out.println("服务器开启. . . ");}public void service() throws IOException {//服务器端注册监听的事件this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//System.out.println(selector.select()); //打印1 //System.out.println(selector.select()); // 打印0 所以不能直接打印发生事件的个数while(selector.select()>0){ // selector.select():返回发生的事件个数Set<SelectionKey> readyKeys = selector.selectedKeys(); //该方法返回的集合是"单例的,公用的",下次循环得到的还是上次循环的集合for(SelectionKey key:readyKeys){try{ //因为下面取客户端时serverSocketChannel.accept()在非阻塞模式下会立刻返回,如果没有客户端连接就返回null //并且serverSocketChannel.accept()总是返回新连接的客户端,已经接收的客户端就不去返回readyKeys.remove(key); //集合中必须删除处理过的事件,否则在下次循环中还需处理一遍if(key.isAcceptable()){ //连接事件触发ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();SocketChannel socketChannel = serverSocketChannel.accept();socketChannel.configureBlocking(false);Socket socket = socketChannel.socket();System.out.println("请求来自" + socket.getInetAddress() + ":" + socket.getPort());ByteBuffer buffer = ByteBuffer.allocate(1024);//存放读来的数据socketChannel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE , buffer);}if(key.isReadable()){ //服务器读事件触发recieve(key);}if(key.isWritable()){ //服务器写事件触发 (客户端不发来信息,会在此处无限循环)send(key);}}catch(IOException e){e.printStackTrace();try{if(key != null){key.cancel();key.channel().close();}}catch(IOException e1){e1.printStackTrace();}}}}}private void send(SelectionKey key) throws IOException {//从大Buffer中取得一行string,编码后放到发送BufferByteBuffer buffer = (ByteBuffer)key.attachment();SocketChannel socketChannel = (SocketChannel) key.channel();buffer.flip();CharBuffer charBuffer = charSet.decode(buffer);String data = charBuffer.toString();if(data.indexOf("\r\n")==-1) // 若此时的Buffer中不是一整行数据,就返回,先不去写return;data = data.substring(0,data.indexOf("\n")+1);System.out.print(""+data);ByteBuffer outputBuffer = charSet.encode("echo:"+data);while(outputBuffer.hasRemaining()){socketChannel.write(outputBuffer);}//发送后在大Buffer中删除已发送完毕的数据 : 大Buffer中前面的data已被发送,先致positionByteBuffer compareBuffer = charSet.encode(data);buffer.position(compareBuffer.limit());buffer.compact();//compact会把position前面的数据清除,并把后面的数据从Buffer的0位置放置//最后发现bye结束,则释放key,关闭channelif(data.equals("bye\r\n")){key.cancel();socketChannel.close();System.out.println("客户端退出:"+socketChannel.socket().getInetAddress());}}private void recieve(SelectionKey key) throws IOException {//从key里面拿到连接的socketChannel和bufferByteBuffer buffer = (ByteBuffer)key.attachment();SocketChannel socketChannel = (SocketChannel)key.channel();//非阻塞模式下,read(Buffer)方法读到多少数据是不确定的,不一定是一行, 所以在写事件中,应该截取读buffer中的一行数据ByteBuffer readBuffer = ByteBuffer.allocate(64); //假设一次read不会导致buffer溢出socketChannel.read(readBuffer);readBuffer.flip(); //一次read完毕后,把readBuffer中的数据复制到大Buffer中buffer.limit(buffer.capacity());buffer.put(readBuffer);}public static void main(String[] args) throws IOException {new EchoServerNoBlock().service();}}3. 阻塞与非阻塞混合服务器
(1) 方法 : 当客户端请求数量变大时, 可以用2个线程完成服务
1)用阻塞模式接受连接请求
2)用费阻塞模式完成业务处理
(2)产生的同步问题 :
Selector.select()一开始处于阻塞状态,当有连接时唤醒,同时防止socketChannel.register()和selector.select()一起阻塞
public class EchoServerMix {private ServerSocketChannel serversoketChannel = null;private Selector selector = null;private Charset charSet = Charset.forName("utf-8");private Object obj = new Object();public EchoServerMix() throws IOException{this.selector = Selector.open();this.serversoketChannel = ServerSocketChannel.open();this.serversoketChannel.configureBlocking(true);this.serversoketChannel.socket().setReuseAddress(true);this.serversoketChannel.socket().bind(new InetSocketAddress(12121));System.out.println("服务器启动. . .");}public void accept(){while(true){try {SocketChannel socketChannel = this.serversoketChannel.accept(); //阻塞socketChannel.configureBlocking(false); //非阻塞Socket socket = socketChannel.socket();System.out.println("请求来自" + socket.getInetAddress() + ":" + socket.getPort());ByteBuffer buffer = ByteBuffer.allocate(1024);synchronized (obj) {selector.wakeup(); // 业务处理的Selector.select()一开始处于阻塞状态,有连接时唤醒,同时防止socketChannel.register() // 和selector.select()一起阻塞socketChannel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE,buffer);;}} catch (IOException e) { e.printStackTrace(); }}}private void service() throws IOException{while(true){synchronized (obj) {}; //里面可以不放任何代码int i = selector.select();if(i==0) continue; //没有事件发生就继续循环Set<SelectionKey> readyKeys = selector.selectedKeys();for(SelectionKey key:readyKeys){readyKeys.remove(key);if(key.isReadable()){ recieve(key);}if(key.isWritable()){send(key);}}}}private void send(SelectionKey key) throws IOException {//从大Buffer中取得一行string,编码后放到发送BufferByteBuffer buffer = (ByteBuffer)key.attachment();SocketChannel socketChannel = (SocketChannel) key.channel();buffer.flip();CharBuffer charBuffer = charSet.decode(buffer);String data = charBuffer.toString();if(data.indexOf("\r\n")==-1) // 若此时的Buffer中不是一整行数据,就返回,先不去写return;data = data.substring(0,data.indexOf("\n")+1);System.out.print(""+data);ByteBuffer outputBuffer = charSet.encode("echo:"+data);while(outputBuffer.hasRemaining()){socketChannel.write(outputBuffer);}//发送后在大Buffer中删除已发送完毕的数据 : 大Buffer中前面的data已被发送,先致positionByteBuffer compareBuffer = charSet.encode(data);buffer.position(compareBuffer.limit());buffer.compact();//compact会把position前面的数据清除,并把后面的数据从Buffer的0位置放置//最后发现bye结束,则释放key,关闭channelif(data.equals("bye\r\n")){key.cancel();socketChannel.close();System.out.println("客户端退出:"+socketChannel.socket().getInetAddress());}}private void recieve(SelectionKey key) throws IOException {//从key里面拿到连接的socketChannel和bufferByteBuffer buffer = (ByteBuffer)key.attachment();SocketChannel socketChannel = (SocketChannel)key.channel();//非阻塞模式下,read(Buffer)方法读到多少数据是不确定的,不一定是一行, 所以在写事件中,应该截取读buffer中的一行数据ByteBuffer readBuffer = ByteBuffer.allocate(64); //假设一次read不会导致buffer溢出socketChannel.read(readBuffer);readBuffer.flip(); //一次read完毕后,把readBuffer中的数据复制到大Buffer中buffer.limit(buffer.capacity());buffer.put(readBuffer);}public static void main(String[] args) throws IOException {final EchoServerMix server = new EchoServerMix();new Thread(){@Overridepublic void run() {server.accept();}}.start();server.service();}}客户端 :
(1)阻塞版客户端 : 用户输入一句话, 接收到返回的echo字符串后才能输入下一句话
public class EchoClientBlock {private SocketChannel socketChannel = null;public EchoClientBlock() throws IOException{this.socketChannel = SocketChannel.open();InetSocketAddress serverAddress = new InetSocketAddress(12121);socketChannel.connect(serverAddress);//客户端链接服务器System.out.println("与服务器建立连接");}public void talk(){Socket server = this.socketChannel.socket();try{BufferedReader reader = new BufferedReader(new InputStreamReader(System.in,"utf-8"));OutputStream out = server.getOutputStream();InputStream in = server.getInputStream();BufferedReader sreader = new BufferedReader(new InputStreamReader(in,"utf-8"));PrintWriter swriter = new PrintWriter(new OutputStreamWriter(out,"utf-8"), true);//自动flushString msg = null;while((msg=reader.readLine())!=null){swriter.println(msg);System.out.println(sreader.readLine());if(msg.equals("bye")){break;}}}catch(IOException e){e.printStackTrace();}finally{try {socketChannel.close();} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) throws IOException {new EchoClientBlock().talk();}}
(2)非阻塞版客户端 :
1)准备sendBuffer和recieveBuffer, sendBuffer负责把用户输入的数据存储并发往服务器,recieveBuffer负责接收服务器发来的echo字符串
2) 用阻塞的SocketChannel连接服务器, 之后设置为非阻塞形式来发送接受字符串
3) 接受键盘输入和发送字符串为2个线程
public class EchoClientNoBlock {private SocketChannel socketaChannel = null;private Charset charset = Charset.forName("utf-8");private Selector selector = null;private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);//发往服务器private ByteBuffer recieveBuffer = ByteBuffer.allocate(1024);//接受服务器发来的数据public EchoClientNoBlock() throws IOException{this.socketaChannel = SocketChannel.open();this.socketaChannel.connect(new InetSocketAddress(12121)); //阻塞连接this.socketaChannel.configureBlocking(false);this.selector = Selector.open();System.out.println("服务器连接成功");}public void recieveFromUser() throws IOException{BufferedReader reader = new BufferedReader(new InputStreamReader(System.in,"utf-8"));String msg = "";while(true){msg = reader.readLine();synchronized (sendBuffer) {this.sendBuffer.put(this.charset.encode(msg+"\r\n")); //encode("str")返回ByteBuffer}if(msg.equals("bye"))break;}}public void sendAndRecieve() throws IOException {this.socketaChannel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);while(selector.select()>0){Set<SelectionKey> readyKeys = this.selector.selectedKeys();for(SelectionKey key:readyKeys){readyKeys.remove(key);if(key.isReadable())recieve(key);if(key.isWritable())send(key);}}}private void send(SelectionKey key) throws IOException {//从sendBuffer里面拿出数据,删除已经发送的数据SocketChannel socketaChannel = (SocketChannel) key.channel();synchronized (sendBuffer) {sendBuffer.flip(); //准备读socketaChannel.write(sendBuffer);sendBuffer.compact(); //删除发送完毕的数据}}private void recieve(SelectionKey key) throws IOException {SocketChannel socketaChannel = (SocketChannel) key.channel();socketaChannel.read(recieveBuffer);recieveBuffer.flip();String recieveStr = this.charset.decode(recieveBuffer).toString();if(recieveStr.indexOf("\n")==-1) return;String outputStr = recieveStr.substring(0,recieveStr.indexOf("\n")+1);System.out.print(outputStr);ByteBuffer temp = this.charset.encode(outputStr); recieveBuffer.position(temp.limit());recieveBuffer.compact();}public static void main(String[] args) throws IOException {final EchoClientNoBlock client = new EchoClientNoBlock();new Thread(){ //先开启接受输入数据的线程,否则阻塞@Overridepublic void run() {try {client.recieveFromUser();} catch (IOException e) {e.printStackTrace();}}}.start();client.sendAndRecieve();}}
0 0
- NIO(二)--CS架构的非阻塞通信
- NIO的非阻塞通信
- NIO实现TCP的非阻塞通信
- socket nio非阻塞通信
- Java NIO-非阻塞通信
- Java NIO-非阻塞通信
- 使用NIO实现非阻塞式的网络通信
- 使用NIO实现非阻塞Socket通信
- 使用NIO实现非阻塞Socket通信
- java NIO 实现非阻塞socket通信
- NIO Socket实现非阻塞通信示例
- NIO实现非阻塞Socket通信
- Java NIO 非阻塞socket通信案例
- NIO非阻塞通信服务端部分代码
- java-非阻塞异步通信-NIO初探
- NIO实现的简单的客户端与服务端通信(非阻塞)
- 使用 JSSE 和 NIO 实现非阻塞通信的一种快速方法
- java中使用nio包实现非阻塞的UDP通信
- cdc 的部署 同步模式
- 图解Java 开发教程
- OA开发的基本流程——写在开始
- MySQL索引的创建、删除和查看
- Oracle 索引 详解
- NIO(二)--CS架构的非阻塞通信
- 决定写博客
- 风火轮 – 动画效果
- 详解Java解析XML的四种方法
- 常用类第三十三课,file类的使用
- Data 语意学 —— 数据成员的绑定、布局与存取
- UIResponder类的作用与方法介绍
- poj 3258 River Hopscotch(二分+贪心)
- SAP HANA SPS 08新特性资料汇总