java NIOSocket 通信简要举例

39 篇文章 0 订阅
2 篇文章 0 订阅

基于Java使用NIOSocket(java.nio.*)通信的举例:

首先创建NIOSocket的服务端;其次创建NIOSocket的客户端。

通信过程如下:

客户端(C)向服务端(S)发送任意数据(包括用户直接从控制台输入数据,使用Scanner),服务端接受到来自客户端的数据并展示,同时客户端发过来的数据原封不动的再发给客户端;客户端接受来自服务端的数据并展示。

说明:通信过程如上。下面看代码如何实现:

服务端:

package socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
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;
import java.util.Iterator;

public class NIOService {

	public String IP = "127.0.0.1";// 10.50.200.120
	public static final int PORT = 4444;
	private static final int SIZE = 256;

	// 对于以字符方式读取和处理的数据必须要进行字符集编码和解码
	String encoding = System.getProperty("file.encoding");
	// 加载字节编码集
	Charset charse = Charset.forName(encoding);

	public NIOService() throws IOException {
		// NIO的通道channel中内容读取到字节缓冲区ByteBuffer时是字节方式存储的,
		// 分配两个字节大小的字节缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(SIZE);
		SocketChannel ch = null;
		Selector selector = null;
		ServerSocketChannel serverChannel = null;

		try {
			// 打开通道选择器
			selector = Selector.open();
			// 打开服务端的套接字通道
			serverChannel = ServerSocketChannel.open();
			// 将服务端套接字通道连接方式调整为非阻塞模式
			serverChannel.configureBlocking(false);
			// serverChannel.socket().setReuseAddress(true);
			// 将服务端套接字通道绑定到本机服务端端口
			serverChannel.socket().bind(new InetSocketAddress(IP, PORT));
			// 将服务端套接字通道OP_ACCEP事件注册到通道选择器上
			serverChannel.register(selector, SelectionKey.OP_ACCEPT);
			System.out.println("Server on port:" + PORT);
			while (true) {
				// 通道选择器开始轮询通道事件
				selector.select();
				Iterator
    
    
     
      it = selector.selectedKeys().iterator();
				while (it.hasNext()) {
					// 获取通道选择器事件键
					SelectionKey skey = (SelectionKey) it.next();
					it.remove();
					// 服务端套接字通道发送客户端连接事件,客户端套接字通道尚未连接
					if (skey.isAcceptable()) {
						// 获取服务端套接字通道上连接的客户端套接字通道
						ch = serverChannel.accept();
						System.out.println("Accepted connection from:" + ch.socket());
						// 将客户端套接字通过连接模式调整为非阻塞模式
						ch.configureBlocking(false);
						// 将客户端套接字通道OP_READ事件注册到通道选择器上
						ch.register(selector, SelectionKey.OP_READ);
					} 
					// 如果sk对应的Channel有数据需要读取
					if (skey.isReadable()) {
						// 获取该SelectionKey对银行的Channel,该Channel中有刻度的数据
						SocketChannel sc = (SocketChannel) skey.channel();
						String content = "";
						// 开始读取数据
						try {
							content = receiverFromClient(sc,buffer);
							// 将sk对应的Channel设置成准备下一次读取
							skey.interestOps(SelectionKey.OP_READ);
						} catch (IOException e) {// 如果捕获到该sk对银行的Channel出现了异常,表明
							// Channel对应的Client出现了问题,所以从Selector中取消
							// 从Selector中删除指定的SelectionKey
							skey.cancel();
							if (skey.channel() != null) {
								skey.channel().close();
							}
						}
						// 如果content的长度大于0,则处理信息返回给客户端
						if (content.length() > 0) {
							System.out.println("接受客户端数据:" + content);
							// 处理信息返回给客户端
							sendToClient(selector,content);
						} 
						//ch.write((ByteBuffer)buffer.rewind());
						//buffer.clear();
					}
					if(skey.isWritable()){
						
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (ch != null)
				ch.close();
			serverChannel.close();
			selector.close();
		}
	}

	/**
	 * 向客户端发送数据 
	 * @param selector
	 * @param content
	 * @throws IOException
	 */
	public void sendToClient(Selector selector,String content) throws IOException{
		// 遍历selector里注册的所有SelectionKey
		for (SelectionKey key1 : selector.keys()) {
			// 获取该key对应的Channel
			Channel targerChannel = key1.channel();
			// 如果该Channel是SocketChannel对象
			if (targerChannel instanceof SocketChannel) {
				// 将读取到的内容写入该Channel中
				SocketChannel dest = (SocketChannel) targerChannel;
				sendToClient(dest,content);
			}
		}
	}
	
	/**
	 * 向指定频道发送数据
	 * @param channel
	 * @param data
	 * @throws IOException
	 */
	public void sendToClient(SocketChannel channel, String data) throws IOException {
		channel.write(charse.encode(data));
		//channel.socket().shutdownOutput();
	}

	/**
	 * 接受来自客户端数据
	 * @param channel
	 * @param buffer
	 * @return
	 * @throws Exception
	 */
	private String receiverFromClient(SocketChannel channel,ByteBuffer buffer) throws Exception {
		String content = "";
		//* 取客户端发送的数据两个方法任选其一即可
		// 开始读取数据
		// 法一
		channel.read(buffer);
		CharBuffer cb = charse.decode((ByteBuffer) buffer.flip());
		content = cb.toString();
		// 法二
		/*
		while (sc.read(buffer) > 0) {
			buffer.flip();
			content += charse.decode(buffer);
		}//*/
		buffer.clear();
		return content;
	}

	public static void main(String[] args) {
		try {
			new NIOService();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

    
    


客户端:

package socket;

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;

/**
 * 
 * NIOClient 
* * @author 王俊伟 wjw.happy.love@163.com * @date 2016年7月21日 下午5:26:25 */ public class NIOClient { private static final int SIZE = 1024; private static NIOClient instance = new NIOClient(); public String IP = "127.0.0.1";// 10.50.200.120 public int CLIENT_PORT = 4444;// 4444 9666 private SocketChannel channel; private Selector selector = null; String encoding = System.getProperty("file.encoding"); Charset charset = Charset.forName(encoding); private NIOClient() { } public static NIOClient getInstance() { return instance; } public void send(String content) throws IOException { selector = Selector.open(); channel = SocketChannel.open(); // channel = SocketChannel.open(new InetSocketAddress(IP,CLIENT_PORT)); InetSocketAddress remote = new InetSocketAddress(IP, CLIENT_PORT); channel.connect(remote); // 设置该sc以非阻塞的方式工作 channel.configureBlocking(false); // 将SocketChannel对象注册到指定的Selector // SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT channel.register(selector, SelectionKey.OP_READ);//这里注册的是read读,即从服务端读数据过来 // 启动读取服务器数据端的线程 new ClientThread().start(); channel.write(charset.encode(content)); // 创建键盘输入流 Scanner scan = new Scanner(System.in);//这里向服务端发送数据,同时启动了一个键盘监听器 while (scan.hasNextLine()) { System.out.println("输入数据:\n"); // 读取键盘的输入 String line = scan.nextLine(); // 将键盘的内容输出到SocketChanenel中 channel.write(charset.encode(line)); } scan.close(); } /** * 从服务端读入数据的线程
* * @author 王俊伟 wjw.happy.love@163.com * @date 2016年10月20日 下午9:59:11 */ private class ClientThread extends Thread { @Override 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(); String content = ""; ByteBuffer bff = ByteBuffer.allocate(SIZE); while (sc.read(bff) > 0) { sc.read(bff); bff.flip(); content += charset.decode(bff); } // 打印读取的内容 System.out.println("服务端返回数据:" + content); // 处理下一次读 sk.interestOps(SelectionKey.OP_READ); } } } } catch (IOException io) { io.printStackTrace(); } } } /** * TCP 处理 线程
*/ class TCPClientReadThread implements Runnable { private Selector selector; public TCPClientReadThread(Selector selector) { this.selector = selector; new Thread(this).start(); } @Override public void run() { try { channel.configureBlocking(false); // selector.select(3000); channel.register(selector, SelectionKey.OP_READ); while (true) { if (selector.select(1000) > 0) { // 遍历每个有可用IO操作Channel对应的SelectionKey for (SelectionKey sk : selector.selectedKeys()) { // 如果该SelectionKey对应的Channel中有可读的数据 if (sk.isReadable()) { // 使用NIO读取Channel中的数据 SocketChannel sc = (SocketChannel) sk.channel(); // 将字节转化为为UTF-8的字符串 receiveData(sc); // 为下一次读取作准备 sk.interestOps(SelectionKey.OP_READ); } else if (sk.isWritable()) { // 取消对OP_WRITE事件的注册 ByteBuffer buffer = ByteBuffer.allocate(1024); sk.interestOps(sk.interestOps() & (~SelectionKey.OP_WRITE)); SocketChannel sc = (SocketChannel) sk.channel(); // 此步为阻塞操作,直到写入操作系统发送缓冲区或者网络IO出现异常 // 返回的为成功写入的字节数,若缓冲区已满,返回0 int writeenedSize = sc.write(buffer); // 若未写入,继续注册感兴趣的OP_WRITE事件 if (writeenedSize == 0) { sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE); } } else if (sk.isConnectable()) { SocketChannel sc = (SocketChannel) sk.channel(); sc.configureBlocking(false); // 注册感兴趣的IO事件,通常不直接注册写事件,在发送缓冲区未满的情况下 // 一直是可写的,所以如果注册了写事件,而又不写数据,则很容易造成CPU消耗100% // SelectionKey sKey = sc.register(selector, // SelectionKey.OP_READ); // 完成连接的建立 sc.finishConnect(); } // 删除正在处理的SelectionKey selector.selectedKeys().remove(sk); } } if (selector.select(1000) <= 0) { Thread.sleep(1000); continue; } } } catch (Exception ex) { ex.printStackTrace(); } } } /** * 客户端发送数据 * * @param channel * @param bytes * @throws Exception */ protected void sendData(SocketChannel channel, byte[] bytes) throws Exception { ByteBuffer buffer = ByteBuffer.wrap(bytes); channel.write(buffer); //channel.socket().shutdownOutput(); } protected void sendData(SocketChannel channel, String data) throws Exception { this.sendData(channel, data.getBytes()); } /** * 接受服务端的数据 * * @param channel * @return * @throws Exception */ protected void receiveData(SocketChannel channel) throws Exception { ByteBuffer buffer = ByteBuffer.allocateDirect(1024); int count = 0; while ((count = channel.read(buffer)) != -1) { if (count == 0) { Thread.sleep(100); // 等等一下 continue; } // 转到最开始 buffer.flip(); while (buffer.remaining() > 0) { System.out.print((char) buffer.get()); } buffer.clear(); } } public static void main(String[] args) { try { NIOClient nio = new NIOClient(); nio.send("test\n");//向服务端发送数据 //nio.send("metrics:memory: swap: cpu: network i/o: disks i/o: tcp:\n"); } catch (IOException e) { e.printStackTrace(); } } }

如上两个类代码注释很全了。

运行:
首先运行服务端,然后执行客户端,返回数据如下:

服务端:

Server on port:4444
Accepted connection from:Socket[addr=/127.0.0.1,port=58153,localport=4444]
接受客户端数据:test

接受客户端数据:你好啊!

客户端:

服务端返回数据:test

输入数据:

你好啊!
输入数据:

服务端返回数据:你好啊!



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

junehappylove

急急如律令,buibui~~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值