使用nio基本api处理tcp/ip服务端编程

一.示例代码

package nio.base.socket.server;

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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class WebServer {
	public static void main(String[] args) {
		try {
			ServerSocketChannel ssc = ServerSocketChannel.open();
			ssc.socket().bind(new InetSocketAddress("192.168.31.198", 8000));
			ssc.configureBlocking(false);

			Selector selector = Selector.open();
			// 注册 channel,作为服务端唯一感兴趣的事件是 Accept
			ssc.register(selector, SelectionKey.OP_ACCEPT);

			ByteBuffer readBuff = ByteBuffer.allocate(1024);

			while (true) {
				int nReady = selector.select();
				if (nReady == 0)
					continue;
				Set<SelectionKey> keys = selector.selectedKeys();
				Iterator<SelectionKey> it = keys.iterator();
				while (it.hasNext()) {
					SelectionKey key = it.next();
					it.remove();
					// 处理OP_ACCEPT事件
					if (key.isAcceptable()) {
						SocketChannel socketChannel = ssc.accept();
						socketChannel.configureBlocking(false);
						System.out.println("来自" + socketChannel.getRemoteAddress() + "的新连接接入");
						// 把连接注册到selector上,指定这个channel只对读操作感兴趣
						socketChannel.register(selector, SelectionKey.OP_READ);
						continue;
					}

					// 处理OP_READ事件
					if (key.isReadable()) {
						SocketChannel socketChannel = (SocketChannel) key.channel();
						readBuff.clear();
						if (socketChannel.read(readBuff) == -1) {
							key.cancel();
							continue;
						}
						readBuff.flip();
						System.out.println("received : " + new String(readBuff.array()));
						key.interestOps(SelectionKey.OP_WRITE);
						continue;
					}

					// 处理OP_WRITE事件
					if (key.isWritable()) {
						SocketChannel socketChannel = (SocketChannel) key.channel();
						socketChannel.write(ByteBuffer.wrap("received".getBytes()));
						key.interestOps(SelectionKey.OP_READ);
						continue;
					}

				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

二.详细说明

1.创建ServerSocketChannel

			ServerSocketChannel ssc = ServerSocketChannel.open();
			ssc.socket().bind(new InetSocketAddress("192.168.31.198", 8000));
			ssc.configureBlocking(false);

首先新建ServerSocketChannel ssc,配置非阻塞,基操

2.创建selector注册事件

			Selector selector = Selector.open();
			// 注册 channel,作为服务端唯一感兴趣的事件是 Accept
			ssc.register(selector, SelectionKey.OP_ACCEPT);

然后是nio的重点,selector

selector其实就是一个线程,帮助你轮询你注册的通道有没有产生相应事件,产生了就会给你自动插入一条对应的key

这里把我们的服务端通道ssc和OP_ACCEPT注册到selector上,这样selector会在ssc可以accept时会插入一条key

那么这里的重点是关于如何注册事件
selector如何知道指定的通道对什么事件感兴趣,这里我们看到是通过注册方法来指定的
具体实现是改变了通道对应的key的interest set的位值
这个位值不只是在注册时可以指定,也可以通过key.interestOps(SelectionKey.OP_XX)来改变,但这个改变需要再下一次调用selector.select()后才会起作用

3.接收Selector通知的key

			while (true) {
				int nReady = selector.select();
				if (nReady == 0)
					continue;
				Set<SelectionKey> keys = selector.selectedKeys();
				Iterator<SelectionKey> it = keys.iterator();
				
				while (it.hasNext()) {
					SelectionKey key = it.next();
					it.remove();
					...
				}
				
			}

然后我们开始写while,在循环里调用selector.select(),如果返回值不为0就表示有一个或多个key需要处理了,然后去调用
selector.selectedKeys();获得需要处理的key的集合

获得key之后,就在集合里把这个remove掉,表示已经处理过了,不然会一直需要你处理

4.处理SelectionKey事件

下面分别处理各种key

1.OP_ACCEPT
	if (key.isAcceptable()) {
		SocketChannel socketChannel = ssc.accept();
		socketChannel.configureBlocking(false);
		System.out.println("来自" + socketChannel.getRemoteAddress() + "的新连接接入");
		// 把连接注册到selector上,指定这个channel只对读操作感兴趣
		socketChannel.register(selector, SelectionKey.OP_READ);
		continue;
	}

ssc通道关于accept的事件我们注册到selector里了,所以这里是selector通知我们有新连接接入了,那么就accept这个新连接socketChannel ,并且把socketChannel和OP_READ注册到selector上,就是说selector在这个socketChannel有新消息可读时会插入一条对应的key通知我们

2.OP_READ
	if (key.isReadable()) {
		SocketChannel socketChannel = (SocketChannel) key.channel();
		readBuff.clear();
		if (socketChannel.read(readBuff) == -1) {
			key.cancel();
			continue;
		}
		readBuff.flip();
		System.out.println("received : " + new String(readBuff.array()));
		key.interestOps(SelectionKey.OP_WRITE);
		continue;
	}

socketChannel通道可读时,我们需要判断一下读出来是不是-1
-1是约定的中止值,如果通道在该selector的位值包含有read事件,并且被客户端中断了,那么selector会无限通知我们有可读事件,其实读出来的是中止值-1,那这里需要处理一下,是-1的时候就把key对应的通道直接cancel掉了

在读完数据之后,我想对他发送一条回包,那么此时调用key.interestOps(SelectionKey.OP_WRITE); 通知selector对于这个通道帮我们只监听可写事件,一般来说通道99%的时间都是可写的,不出意外selector在下次调用selector.select();后会马上插入关于此通道可写的key通知我们

2.OP_WRITE
	if (key.isWritable()) {
		SocketChannel socketChannel = (SocketChannel) key.channel();
		socketChannel.write(ByteBuffer.wrap("received".getBytes()));
		key.interestOps(SelectionKey.OP_READ);
		continue;
	}

此时我们发送对应的回包然后马上调用key.interestOps(SelectionKey.OP_READ);通知selector对于这个通道帮我们只监听可读事件,因为通道99%的时间都是可写的,如果不置回去的话selector会不停地通知我们该通道可写,死循环调用这一方法

三.总结

以上就是调用nio基本的api编写服务端的基本流程,重点在于要理解selector到底是怎么工作的,才会减少困惑;实际上nio基本的api还是太简略了,一般还是用框架,下面是我关于用springboot+netty实现服务端的另一个demo
https://blog.csdn.net/qq_24516549/article/details/88985217
有疑惑或者我哪里写错了的欢迎评论交流

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值