socket 非阻塞 java_Java NIO Socket 非阻塞通信

相对于非阻塞通信的复杂性,通常客户端并不需要使用非阻塞通信以提高性能,故这里只有服务端使用非阻塞通信方式实现

0b9c1fdc8a7f1b772d228380cbe3b596.png

客户端:

package com.test.client;

import java.io.DataInputStream;

import java.io.DataOutputStream;

import java.io.IOException;

import java.net.InetAddress;

import java.net.InetSocketAddress;

import java.nio.channels.SocketChannel;

import org.apache.log4j.Logger;

import com.test.util.SocketIO;

public class Client {

static Logger logger = Logger.getLogger(Client.class);

private int port = 10000;

private SocketChannel socketChannel;

public Client(){

try {

socketChannel = SocketChannel.open();

InetAddress host = InetAddress.getLocalHost();

InetSocketAddress addr = new InetSocketAddress(host, port);

socketChannel.connect(addr);

logger.debug("***");

logger.debug("client ip:"+socketChannel.socket().getLocalAddress());

logger.debug("client port:"+socketChannel.socket().getLocalPort());

logger.debug("server ip:"+socketChannel.socket().getInetAddress());

logger.debug("server port:"+socketChannel.socket().getPort());

logger.debug("***");

} catch (IOException e) {

e.printStackTrace();

logger.error("Cilent socket establish failed!");

}

logger.info("Client socket establish success!");

}

public void request(String request){

try{

DataInputStream input = SocketIO.getInput(socketChannel.socket());

DataOutputStream output = SocketIO.getOutput(socketChannel.socket());

if(null != request && !request.equals("")){

byte[] bytes = request.getBytes("utf-8");

output.write(bytes);

bytes = new byte[64];

int num = input.read(bytes);

byte[] answer = new byte[num];

System.arraycopy(bytes, 0, answer, 0, num);

if(num > 0){

logger.info("server answer:"+new String(answer,"utf-8"));

}else{

logger.info("No server answer.");

}

}

}catch(Exception e){

e.printStackTrace();

logger.error("client request error");

}finally{

if(null != socketChannel){

try{

socketChannel.close();

}catch(Exception e){

e.printStackTrace();

logger.error("socket close error");

}

}

}

}

public static void main(String[] args){

Client client1 = new Client();

//Client client2 = new Client();

client1.request("your name?");

//client2.request("your name?");

}

}

服务端:

package com.test.server;

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.nio.charset.Charset;

import java.util.Iterator;

import java.util.Set;

import org.apache.log4j.Logger;

public class Server {

static Logger logger = Logger.getLogger(Server.class);

private Selector selector;

private ServerSocketChannel serverSocketChannel;

private int queueNum = 10;

private int bindPort = 10000;

private int step = 0;

private Charset charset = Charset.forName("utf-8");

private ByteBuffer buffer = ByteBuffer.allocate(64);

public Server(){

try{

//为ServerSocketChannel监控接收连接就绪事件

//为SocketChannel监控连接就绪事件、读就绪事件以及写就绪事件

selector = Selector.open();

//作用相当于传统通信中的ServerSocket

//支持阻塞模式和非阻塞模式

serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().setReuseAddress(true);

//非阻塞模式

serverSocketChannel.configureBlocking(false);

//serverSocketChannel.socket()会获得一个和当前信道相关联的socket

serverSocketChannel.socket().bind(new InetSocketAddress(bindPort),queueNum);

//注册接收连接就绪事件

//注册事件后会返回一个SelectionKey对象用以跟踪注册事件句柄

//该SelectionKey将会放入Selector的all-keys集合中,如果相应的事件触发

//该SelectionKey将会放入Selector的selected-keys集合中

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

}catch(Exception e){

e.printStackTrace();

logger.error("Server establish error!");

}

logger.info("Server start up!");

}

public void service() throws Exception{

//判断是否有触发事件

while(selector.select() > 0){

Set selectedKeys = selector.selectedKeys();

Iterator iterator = selectedKeys.iterator();

while(iterator.hasNext()){

SelectionKey selectionKey = iterator.next();

//处理事件后将事件从Selector的selected-keys集合中删除

iterator.remove();

try{

if(selectionKey.isAcceptable()){

this.Acceptable(selectionKey);

}else if(selectionKey.isReadable()){

this.Readable(selectionKey);

}else if(selectionKey.isWritable()){

this.Writable(selectionKey);

}

}catch(Exception e){

e.printStackTrace();

logger.error("event deal exception!");

}

}

}

}

private void Acceptable(SelectionKey selectionKey) throws Exception{

logger.info("accept:"+(++step));

ServerSocketChannel ssc = (ServerSocketChannel)selectionKey.channel();

SocketChannel sc = (SocketChannel)ssc.accept();

sc.configureBlocking(false);

sc.register(selector, SelectionKey.OP_READ);

logger.info(selectionKey.hashCode());

}

private void Readable(SelectionKey selectionKey) throws Exception{

logger.info("read:"+(++step));

SocketChannel sc = (SocketChannel)selectionKey.channel();

buffer.clear();

int num = sc.read(buffer);

String request = "";

if(num > 0){

buffer.flip();

request = charset.decode(buffer).toString();

sc.register(selector, SelectionKey.OP_WRITE,request);

}else{

sc.close();

}

logger.info(selectionKey.hashCode()+":"+request);

}

private void Writable(SelectionKey selectionKey) throws Exception{

logger.info("write:"+(++step));

String request = (String)selectionKey.attachment();

SocketChannel sc = (SocketChannel)selectionKey.channel();

String answer = "not supported";

if(request.equals("your name?")){

answer = "server";

}

logger.info(selectionKey.hashCode()+":"+answer);

buffer.clear();

buffer.put(charset.encode(answer));

buffer.flip();

while(buffer.hasRemaining())

sc.write(buffer);

sc.close();

}

public static void main(String[] args) {

Server server = new Server();

try{

server.service();

}catch(Exception e){

e.printStackTrace();

logger.error("Server run exception!");

}

}

}

IO工具类:

package com.test.util;

import java.io.DataInputStream;

import java.io.DataOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.Socket;

public class SocketIO{

public static DataInputStream getInput(Socket socket) throws IOException{

//接收缓存区大小,socket获取输入流之前设置

socket.setReceiveBufferSize(10);

InputStream input = socket.getInputStream();

return new DataInputStream(input);

}

public static DataOutputStream getOutput(Socket socket) throws IOException{

//发送缓存区大小,socket获取输出流之前设置

socket.setSendBufferSize(10);

OutputStream output = socket.getOutputStream();

return new DataOutputStream(output);

}

}

log4j日志配置文件:

log4j.rootLogger=debug,logOutput

log console out put

log4j.appender.logOutput=org.apache.log4j.ConsoleAppender

log4j.appender.logOutput.layout=org.apache.log4j.PatternLayout

log4j.appender.logOutput.layout.ConversionPattern=%p%d{[yy-MM-dd HH:mm:ss]}[%c] -> %m%n

server端的运行结果:

INFO[13-10-16 11:40:41][com.test.server.Server] -> Server start up!

INFO[13-10-16 11:40:53][com.test.server.Server] -> accept:1

INFO[13-10-16 11:41:14][com.test.server.Server] -> 20469344

INFO[13-10-16 11:41:21][com.test.server.Server] -> read:2

INFO[13-10-16 11:41:37][com.test.server.Server] -> 11688861:your name?

INFO[13-10-16 11:43:00][com.test.server.Server] -> write:3

INFO[13-10-16 11:43:00][com.test.server.Server] -> 11688861:server

可以看到readable方法中的SelectionKey和writable方法中的SelectionKey的哈希码是完全相同的,是同一个SelectionKey

SelectionKey是在SocketChannel类或ServerSocketChannel类注册要监控的事件时产生的,这两个类本身并没有register方法,需要查看它们共同父类AbstractSelectableChannel(只有关键代码):

public abstract class AbstractSelectableChannel

extends SelectableChannel{

......

// Keys that have been created by registering this channel with selectors.

// They are saved because if this channel is closed the keys must be

// deregistered. Protected by keyLock.

private SelectionKey[] keys = null;

public final SelectionKey register(Selector sel, int ops, Object att)

throws ClosedChannelException{

if (!isOpen())

throw new ClosedChannelException();

if ((ops & ~validOps()) != 0)

throw new IllegalArgumentException();

synchronized (regLock) {

if (blocking)

throw new IllegalBlockingModeException();

SelectionKey k = findKey(sel);

if (k != null) {

k.interestOps(ops);

k.attach(att);

}

if (k == null) {

// New registration

k = ((AbstractSelector)sel).register(this, ops, att);

addKey(k);

}

return k;

}

}

private SelectionKey findKey(Selector sel) {

synchronized (keyLock) {

if (keys == null)

return null;

for (int i = 0; i < keys.length; i++)

if ((keys[i] != null) && (keys[i].selector() == sel))

return keys[i];

return null;

}

}

void removeKey(SelectionKey k) {// package-private

synchronized (keyLock) {

for (int i = 0; i < keys.length; i++)

if (keys[i] == k) {

keys[i] = null;

keyCount--;

}

((AbstractSelectionKey)k).invalidate();

}

}

......

}

ServerSocketChannel和Socketchannel向Selector中注册了特定事件,Selector就会监控这些事件是否发生。ServerSocketChannel和Socketchannel都为AbstractSelectableChannel类的子类,AbstractSelectableChannel类的register方法负责注册事件,该方法会返回一个SelectionKey对象,该对象用于跟踪被注册事件

public abstract class SelectionKey {

protected SelectionKey() { }

public abstract SelectableChannel channel();

public abstract Selector selector();

......

}

一个Selector对象中包含了3种类型的键集(即SelectionKey集合,SelectionKey在以下部分被称为“键”)

1,all-keys:所有注册至该Selector的事件键集(selector.keys())

2,selected-keys:相关事件已经被Selector捕获的键集(selector.selectedKeys())

3,cancel-keys:已被取消的键集(无法访问该集合)

selected-keys和cancel-keys都为all-keys的子集,对于一个新建的Selector,这3个键集都为空

注册事件时会将相应的SelectionKey加入Selector的all-keys键集中

取消SelectionKey或者关闭了SelectionKey相关联的Channel,则会将相应的SelectionKey加入cancel-keys键集中

当执行选择器的选择操作时(selector.select(),对于选择器来说,这个方法应该是相当重要的):

1,将cancel-keys中的每个SelectionKey从3个键集中移除(如果存在的话),并注销SelectionKey所关联的Channel,cancel-keys键集变为空集。

2,如果监控的事件发生,Selector会将相关的SelectionKey加入selected-keys键集中

以下为对源代码的分析、推测:

Selector作为选择器,保存了所有的Selectionkey(注册的,取消的,触发的),通过上面的AbstractSelectableChannel类的源代码,发现Channel本身也保存了一个自身关联的SelectionKey数组,这看起来是完全没有必要的,但是仔细看一下register方法,能看出些许端倪:

Selector本身维护了3个集合,all-keys,selected-keys和cancel-keys,频繁的注册操作、取消注册将会导致这3个集合频繁的变化,伴随频繁变化的是频繁的加锁,这会严重的降低Selector的性能,毕竟一个Selector会被多个Channel作为选择器使用,本身非阻塞的实现方式就是提高性能的一种解决方式

当注册新的事件时,如果存在该通道相关的SelectionKey,则更新该SelectionKey所关注的事件以及其携带的附加信息,如果不存在,则添加新的SelectionKey

这样做的好处是,比起删除以前的SelectionKey,添加新的SelectionKey,修改SelectionKey所关注的事件以及其携带的附加信息显然是更好的选择,毕竟不需要修改Selector所维护的键集,当然也不需要频繁加锁(通过查看Selector类的api,SelectionKey并不是thread-safe的,显然并没有加锁,但是并没有什么问题),能够提供更好的性能

总之,SelectionKey的哈希码会重复是很正常的,毕竟不是单纯的注册时新建、触发后删除方式,java实现时进行了优化

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值