使用Java的nio实现高效能的网络通信

在使用Java的API构建网络通信的应用时(尤其是基于移动的网络),有两种技术可供选择。第一种为直接使用Socket/Server Socket+输入输出流来构建,另外一种使用ServerSocketChannel+Selector。前者为阻塞式通信,需要服务端启动很多线程监听每一个客户端的Socket,通信效率低,且服务端容易受客户端的影响。比如当某个客户端当掉之后,服务的向这个客户端写数据的线程就会一直阻塞,直到写数据超时。所以我们在开发网络通信的应用时,首选后者。如下的代码实现了网络聊天/的功能,在加上语音或视频的录制功能又可实现网络的音频会议或视屏会议。网上和书上有很多类似的例子,但大多没有经过很好的测试,比如有一个客户端突然退出或者当掉,会影响服务端的运行。而我们的实际情况是要求服务端不应该受客户端的影响。

 

代码如下:

 

服务端:

package com.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
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;

/**
 * 非阻塞通信的服务端
 * @author Alex
 *
 */
public class NioServer {
	private class NSServerThread extends Thread {
		private String ip;
		private int port;

		public NSServerThread(String ip, int port) {
			this.ip = ip;
			this.port = port;
		}

		public void run() {
			try {
				init(this.ip, this.port);
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public void init(String ip, int port) throws IOException { // 用于检测所有Channel状态的Selector
		System.out.println("打开NioPTTServer 端口为:" + port);
		Selector selector = null;
		// 定义实现编码、解码的字符集对象
		selector = Selector.open();
		// 通过open方法来打开一个未绑定的ServerSocketChannel实例
		ServerSocketChannel server = ServerSocketChannel.open();
		// InetSocketAddress isa = new InetSocketAddress(ip, port);
		InetSocketAddress isa = new InetSocketAddress(ip, port);
		// 将该ServerSocketChannel绑定到指定IP地址
		server.socket().bind(isa);
		// 设置ServerSocket以非阻塞方式工作
		server.configureBlocking(false);
		// 将server注册到指定Selector对象
		server.register(selector, SelectionKey.OP_ACCEPT);
		while (true) {
			int keys = selector.select();
			// System.out.println("keys:"+keys);
			if (keys > 0) {
				// 依次处理selector上的每个已选择的SelectionKey
				// SelectionKey removeReadSk=null;
				try {
					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);
							// 将该SocketChannel也注册到selector
							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);
							// 开始读取数据
							int len = 0;
							try {
								if ((len = sc.read(buff)) > 0) {
									buff.flip();// 缓存 2指针复位 准备下次读取数据
									System.out.println("读取数据:" + buff.array());
									sk.interestOps(SelectionKey.OP_READ);
								}else{
									System.out.println("没有数据读取,关闭通道");
									sk.cancel();
									if (sk.channel() != null) {
										sk.channel().close();
									}
								}
								// 将sk对应的Channel设置成准备下一次读取
								
							}
							// 如果捕捉到该sk对应的Channel出现了异常,即表明该Channel
							// 对应的Client出现了问题,所以从Selector中取消sk的注册
							catch (IOException ex) {
								// 从Selector中删除指定的SelectionKey
								ex.printStackTrace();
								sk.cancel();
								if (sk.channel() != null) {
									sk.channel().close();
								}
								System.out.println("关闭一个客户端");
							}
							// 如果content的长度大于0,即聊天信息不为空
							if (len > 0) {
								// 遍历该selector里注册的所有SelectKey
								SelectionKey writeKey = null;
								for (SelectionKey key : selector.keys()) {
									// 获取该key对应的Channel

									Channel targetChannel = key.channel();
									// 如果该channel是SocketChannel对象
									if (targetChannel instanceof SocketChannel) {
										// && key != sk) {
										// 将读到的内容写入该Channel中 ,返回到客户端
										if (targetChannel instanceof SocketChannel
												&& key != sk) {
											SocketChannel dest = null;
											try {
												// 将读到的内容写入该Channel中 ,返回到客户端
												dest = (SocketChannel) targetChannel;
												System.out.println("写数据:"
														+ buff.array());
												dest.write(buff);
											} catch (Exception e) {
												// 写异常,关闭通道
												e.printStackTrace();
												if (dest != null) {
													dest.close();
												}
												targetChannel.close();
												writeKey = key;
											}
										}
									}
								}
								if (writeKey != null) {
									writeKey.cancel();
									if (writeKey.channel() != null) {
										writeKey.channel().close();
									}
								}
							}
						}
					}
				} catch (Exception e) {
					e.printStackTrace();
					// selector.close();
					// server.close();
					// System.out.println("发生异常,重新打开端口");
					// init(ip,port);
					// throw new RuntimeException(e);
				}
			}
		}
	}

	public static void main(String[] args) throws IOException {
		// System.out.println(InetAddress.getLocalHost().getHostAddress());
		String host = InetAddress.getLocalHost().getHostAddress();
		int port = 30000;
		new NioServer().new NSServerThread(host, port).start();
		// new NServer().init(InetAddress.getLocalHost().getHostAddress(),
		// 30000);
		System.out.println("Nio服务端启动了,host:" + host);
	}
}

 

 

客户端代码:

package com.nio;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
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;

/**
 * 非阻塞通信的服务端
 * @author Alex
 *
 */
public class NioClient {

	public void init(String ip, int port) throws IOException {
		// 定义检测SocketChannel的Selector对象
		Selector selector = null;
		// 定义处理编码和解码的字符集
		//Charset charset = Charset.forName("UTF-8");
		// 客户端SocketChannel
		SocketChannel sc = null;
		selector = Selector.open();
		InetSocketAddress isa = new InetSocketAddress(ip, port);
		// 调用open静态方法创建连接到指定主机的SocketChannel
		sc = SocketChannel.open(isa);
		// 设置该sc以非阻塞方式工作
		sc.configureBlocking(false);
		// 将SocketChannel对象注册到指定Selector
		sc.register(selector, SelectionKey.OP_READ);
		// 启动读取服务器端数据的线程------------
		new ReadDataThread(selector).start();
		new SendDataThread(sc,selector).start();
	}

	private class SendDataThread extends Thread{
		SocketChannel sc;
		Selector selector;
		public SendDataThread(SocketChannel sc,Selector selector){
			this.sc=sc;
			this.selector=selector;
		}
		public void run(){
			// 创建键盘输入流-----------
			Scanner scan = new Scanner(System.in);
			ByteBuffer byteBuf=ByteBuffer.allocate(152);
			File file=new File("D:/未命名2.jpg");
			try{
				InputStream in=new FileInputStream(file);
				int len=0;
				while (scan.hasNextLine()) {
					// 读取键盘输入
					String line = scan.nextLine();
					
					// 将键盘输入的内容输出到SocketChannel中
					if("send".equals(line.trim())){
						byte[] buf=new byte[152];
						if((len=in.read(buf))>-1){
							byteBuf.put(buf);
							byteBuf.flip();
							sc.write(byteBuf);
							byteBuf.clear();
						}
						byteBuf.put(buf);
						byteBuf.flip();
						sc.write(byteBuf);
						byteBuf.clear();
					}else if("quit".equals(line.trim())){
						sc.close();
						selector.selectedKeys().clear();
						selector.close();
					}
					//sc.write(charset.encode(line));
				}
			}catch(Exception e){
				e.printStackTrace();
			}
			
		}
	}
	//读取服务器数据的----客户端监听线程
	private class ReadDataThread extends Thread {
		Selector selector;

		public ReadDataThread(Selector selector)
				throws IOException {
			this.selector = selector;
		}
		public void run() {
			try {
				ByteBuffer buff = ByteBuffer.allocate(152);
				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();
							String content = "";
							if (sc.read(buff) > 0) {
								sc.read(buff);
								buff.flip();
								content = new String(buff.array());// charset.decode(buff);
								buff.clear();
							}
							// 打印输出读取的内容
							System.out.println("接收的数据:" + content);
							// 为下一次读取作准备
							sk.interestOps(SelectionKey.OP_READ);
						}
					}
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
	}

	public static void main(String[] args) throws IOException {
		
		new NioClient().init("192.168.42.128", 30000);
	}
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值