Java网络编程——使用NIO实现非阻塞Socket通信


       除了普通的Socket与ServerSocket实现的阻塞式通信外,java提供了非阻塞式通信的NIO API。先看一下NIO的实现原理。

 

       从图中可以看出,服务器上所有Channel(包括ServerSocketChannel和SocketChannel)都需要向Selector注册,而该Selector则负责监视这些Socket的IO状态,当其中任意一个或者多个Channel具有可用的IO操作时,该Selector的select()方法将会返回大于0的整数,该整数值就表示该Selector上有多少个Channel具有可用的IO操作,并提供了selectedKeys()方法来返回这些Channel对应的SelectionKey集合。正是通过Selector,使得服务器端只需要不断地调用Selector实例的select()方法即可知道当前所有Channel是否有需要处理的IO操作。

看个demo

NClient.java

 

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;

public class NClient {
	//定义检测SocketChannel的Selector对象
	private Selector selector=null;
	//定义处理编码和解码的字符集
	private Charset charset=Charset.forName("UTF-8");
	//客户端SocketChannel
	private SocketChannel sc=null;
	public void init() throws IOException{
		selector=Selector.open();
		InetSocketAddress isa=new InetSocketAddress("127.0.0.1",30000);
		//调用open静态方法创建连接到指定主机的SocketChannel
		sc=SocketChannel.open(isa);
		//设置该sc以非阻塞方式工作
		sc.configureBlocking(false);
		//将Socketchannel对象注册到指定Selector
		sc.register(selector, SelectionKey.OP_READ);
		//启动读取服务器端数据的线程
		new ClientThread().start();
		//创建键盘输入流
		Scanner scan=new Scanner(System.in);
		while(scan.hasNextLine()){
			//读取键盘输入
			String line=scan.nextLine();
			//将键盘输入的内容输出到SocketChannel中
			sc.write(charset.encode(line));
		}
	}
	//定义读取服务器数据的线程
	private class ClientThread extends Thread{
		public void run(){
			try{
				while(selector.select()>0){
					//遍历每个有可用IO操作Channel对应的SelectionKey
					for(SelectionKey sk:selector.selectedKeys()){
						//删除正在处理的SelectionKey
						selector.selectedKeys().remove(sk);
						//如果该SelectionKey对应的Channel中有可读的数据
						if(sk.isReadable()){
							//使用NIO读取channel中的数据
							SocketChannel sc=(SocketChannel) sk.channel();
							ByteBuffer buff=ByteBuffer.allocate(1024);
							String content="";
							while(sc.read(buff)>0){
								//sc.read(buff);
								buff.flip();
								content+=charset.decode(buff);
							}
							//打印输出读取的内容
							System.out.println("聊天信息"+content);
							//为下一次读取做准备
							sk.interestOps(SelectionKey.OP_READ);
						}
					}
				}
			}catch(IOException ex){
				ex.printStackTrace();
			}
		}
	}
	public static void main(String[]args) throws IOException{
		new NClient().init();
	}
}

NServer.java

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
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;

public class NServer {
	//用于检测所有Channel状态的Selector
	private Selector selector=null;
	//定义实现编码、解码的字符集对象
	private Charset charset=Charset.forName("UTF-8");
	public void init() throws IOException{
		selector=Selector.open();
		//通过open方法来打开一个未绑定的ServerSocketChannel实例
		ServerSocketChannel server=ServerSocketChannel.open();
		InetSocketAddress isa=new InetSocketAddress("127.0.0.1",30000);
		//将该ServerSocketChannel绑定到指定ip地址
		server.socket().bind(isa);
		//设置ServerSocket以非阻塞方式工作
		server.configureBlocking(false);
		//将server注册到指定Selector对象
		server.register(selector, SelectionKey.OP_ACCEPT);
		while(selector.select()>0){
			//依次处理selector上的每个已选择的SelectionKey
			for(SelectionKey sk:selector.selectedKeys()){
				//从selector上的已选择Key集中删除正在处理的SelectionKey
				selector.selectedKeys().remove(sk);
				//如果sk对应的通信包含客户端的连接请求
				if(sk.isAcceptable()){
					//调用accept方法接受连接,产生服务器端对应的SocketChannel
					SocketChannel sc=server.accept();
					//设置采用非阻塞模式
					sc.configureBlocking(false);
					sc.register(selector, SelectionKey.OP_READ);
					//将sk对应的Channel设置成准备接受其他请求
					sk.interestOps(SelectionKey.OP_ACCEPT);
				}
				//如果sk对应的通道有数据需要读取
				if(sk.isReadable()){
					//获取该SelectionKey对应的Channel,该Channel中有可读的数据
					SocketChannel sc=(SocketChannel) sk.channel();
					//定义准备之星读取数据的ByteBuffer
					ByteBuffer buff=ByteBuffer.allocate(1024);
					String content="";
					//开始读取数据
					try{
						while(sc.read(buff)>0){
							buff.flip();
							content+=charset.decode(buff);
						}
						//打印从该sk对应的Channel里读到的数据
						System.out.println("=========="+content);
						//将sk对应的Channel设置成准备下一次读取
						sk.interestOps(SelectionKey.OP_READ);
						//如果捕捉到该sk对应的channel出现异常,即表明该channel对应的client出现了
						//异常,所以从selector中取消sk的注册
					}catch(IOException e){
						//从Selector中删除指定的SelectionKey
						sk.cancel();
						if(sk.channel()!=null){
							sk.channel().close();
						}
					}
					//如果content的长度大于0,即聊天信息不为空
					if(content.length()>0){
						//遍历该selector里注册的所有SelectKey
						for(SelectionKey key:selector.keys()){
							//选取该key对应的Channel
							Channel targetChannel=key.channel();
							//如果该channel是SocketChannel对象
							if(targetChannel instanceof SocketChannel){
								//将独到的内容写入该Channel中
								SocketChannel dest=(SocketChannel) targetChannel;
								dest.write(charset.encode(content));
							}
						}
					}
				}
			}
		}
	}
	public static void main(String[]args) throws IOException{
		new NServer().init();
	}
}


          通过java提供的NIO实现非阻塞Socket通信,大大提高了网络服务器的性能。

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
看到很多人都分卷打包的,下载很是不方便,还浪费积分,我就整合压缩打包到一个包里面,里面包含全部源码 源码目录如下: 第1部分(2个程序包) HelloWorld.zip 01.Java入门样例HelloWorld demo.zip 03.Eclipse入门样例demo 第2部分(3个程序包) javasyntax.zip 05.Java核心语法详解 javaoop.zip 06.Java面向对象编程(计算器) javaexception.zip 07.Java面向对象编程扩展(计算器异常捕捉实例) 第3部分(5个程序包) javaio.zip 09.Java输入/输出流 javavi.zip 09.Java输入/输出流上机作业参考(文本编辑器) javathread.zip 10.Java多线程编程(线程池、生产者消费者、存取款实例) javautil.zip 11.Java常用实体类 javaxml.zip 14.XML属性文件 第4部分(6个程序包) javagui.zip 15.Java GUI库对比实例 javaawt.zip 16.AWT图形界面开发——Java文本编辑器 javamedia.zip 17.AWT多媒体编程——录音机、MP3播放器、视频拍照、视频播放器 javaswing.zip 18.Swing图形界面开发——Java文本编辑器 javaswt.zip 19.SWT图形界面开发——Java文本编辑器 javajface.zip 20.SWT增强组件库JFace 第5部分(5个程序包) javaapplet.zip 21.Applet网络组件——电子相册 javanet.zip 22.Java网络编程详解(Socket/UDP实例、简单聊天系统) javanio.zip 23.NIO非阻塞通信Socket/UDP实例、简单聊天系统) javarmi.zip 24.RMI编程(HelloWorld例、计算器实例) javacorba.zip 25.Corba编程(HelloWorld例、计算器实例) 第6部分(4个程序包) javareflection.zip 26.Java反射机制与动态代理 javageneric.zip 27.Java泛型编程 javaannotation.zip 28.Java注释符编程 javafeature.zip 29.Java5.0语言新特性
以下是使用Spring Boot和Netty实现非阻塞socket的完整源码。这个例子会创建一个简单的Echo服务器,它会接收客户端发送的消息并将其返回给客户端。 ```java import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.stereotype.Component; @SpringBootApplication public class NettyServerApplication { public static void main(String[] args) { SpringApplication.run(NettyServerApplication.class, args); } @Component public static class NettyServer { @Value("${netty.port}") private int port; public void start() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new EchoServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } } public static class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ctx.write(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } } } ``` 在这个例子中,我们创建了一个`NettyServer`组件,它会在Spring应用程序启动时自动启动。`NettyServer`类中的`start()`方法会创建一个Netty服务器,并将其绑定到指定的端口。 在`ServerBootstrap`构造函数中,我们设置了事件循环组(boss和worker),服务器通道类型(NioServerSocketChannel),并设置了一个`ChannelInitializer`,它会在每个新连接被接受时创建一个新的`EchoServerHandler`实例。 `EchoServerHandler`类是我们的Echo服务器的核心部分。它会在收到客户端发送的消息时将其返回给客户端。`channelRead()`方法会被自动调用,当有新的消息被接收到时。在`channelReadComplete()`方法中,我们调用了`ctx.flush()`,它会将所有待发送的消息一次性发送到远程节点。在`exceptionCaught()`方法中,我们打印出异常信息并关闭连接。 最后,我们在`application.properties`文件中设置了服务器端口号: ``` netty.port=8888 ``` 使用此代码,您可以创建一个简单的Echo服务器,并使用Netty在Spring Boot应用程序中执行非阻塞socket通信

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值