• Java Socket IO(BIO、NIO)


    总结下Java socket IO。首先是各种IO的定义,这个定义似乎也是众说纷纭。我按照stackoverflow上面的解释:

    IO有两种分法:按照阻塞或者按照同步。按照阻塞,有阻塞IO和非阻塞IO。按照同步就是同步IO或者异步IO。我们可以认为阻塞IO和同步IO相等,而非阻塞IO和异步IO不同。

    阻塞IO或者同步IO是指:IO的请求发出去之后,请求者一直在等待回复,当IO的数据回来到来之后,请求者就开始接受数据。阻塞的意思就是:IO请求发出去之后请求线程就停止在那里,一直等待数据到来。

    非阻塞则是:IO请求发出去之后,会立刻收到回复,这个回复可能是IO可以立即进行,或者是无法进行IO的错误。所以非阻塞IO请求者必须一直调用那个API,一直到API返回可以进行IO的信号。

    异步IO:IO请求发出之后,后台会建立一个进程,处理IO,当IO都处理完之后,把处理完的数据交给IO的请求者。

    本文用Java socket实现阻塞和非阻塞IO(这里大部分内容都学习自千与的专栏,这是位大牛,居然hadoop源码分析写了19篇博客)。然后本文章的所有代码我都放在Github上了。

    阻塞IO

    阻塞IO比较简单,就是用普通的socket去写,因为没有什么太复杂的处理。建立一个socket,然后,获取它的inputstream和outputstream,然后进行读写操作。

    Server端主要代码,handleSocket就是从socket里面读取数据,然后向client写数据:

    	ServerSocket serverSocket = new ServerSocket(port);
    	while (true) {
    		socket = serverSocket.accept();
    		handleSocket(socket);
    	}

    Client只是简单的发送一句消息:

    	socket = new Socket(host, port);
    	out = socket.getOutputStream();
    	out.write(data);
    	out.flush();
    	in = socket.getInputStream();
    	byte[] buffer = new byte[128];
    	int receivedBytes;
    	if((receivedBytes = in.read(buffer))!=-1){
    		System.out.println("Client: received msg from server: " + new String(buffer, 0, receivedBytes));
    	}

    同时client5000个,使用了大概5s。然后还可以使用多线程Server,就是在每一个client到的时候分配一个线程来处理这个IO,理论上可以增加效率,但是似乎是因为每次处理时间太短,效果不明显。主要代码:

    	boolean flag = false;
    	int count = 0;
    	ServerSocket serverSocket = new ServerSocket(port);
    	Date start = null;
    	while (true) {
    		socket = serverSocket.accept();
    		if (!flag) {
    			start = new Date();
    			flag = true;
    		}
    		pool.execute(new RequestHandler(socket));
    		if(++count== threadCount){
    			flag = false;
    			Date end = new Date();
    			System.out.println(threadCount+" client requests spends: " + (end.getTime() -start.getTime()));
    		}
    	}

    其中的pool是ExecutorService,提供线程池,然后RequestHandler是一个Runnable类,用来处理一个socket连接。简单讲来,就是把前面server处理socket的代码放到了这个handler里面。

    NIO,非阻塞IO

    我认定Java的NIO包实现的是同步非阻塞IO,也有人说不是,至少我这里这么认为。在server端,首先打开一个channel,然后向其中注册一个selector,这个selector会在channel中轮询注册的事件,然后根据事件的类型作出处理。一般事件的类型有Accept、read、write。

    在Client端也可以是一样的注册,然后通过和server同样的处理方式处理事件(但是没有accept事件,因为只有server才能accept的)。但是我的例子里面只是简单的发送了一条消息。

    Server的主要代码:

    	Selector selector = Selector.open();
    	ServerSocketChannel serverSocketChannel = ServerSocketChannel
    			.open();
    	serverSocketChannel.configureBlocking(false);
    	serverSocketChannel.socket().bind(address);
    	serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    	log.info("Server: socket server started!");
    	while (true) {
    		int nKeys = selector.select();
    		if (nKeys > 0) {
    			Set selectedKeys = selector.selectedKeys();
    			Iterator it = selectedKeys.iterator();
    			while (it.hasNext()) {
    				SelectionKey key = (SelectionKey) it.next();
    				if (key.isAcceptable()) {
    					log.info("Server: Selection key is acceptable");
    					handler.handleAccept(key);
    				} else if (key.isReadable()) {
    					log.info("Server: Selection key is readable");
    					handler.handleRead(key);
    				} else if (key.isWritable()) {
    					log.info("Server: Selection key is writable");
    					handler.handleWrite(key);
    				}
    				it.remove();
    			}
    		}
    	}

    其中的handler就是处理每种类型的事件的类,举个Read的例子:

    public void handleRead(SelectionKey key) throws IOException {
    	ByteBuffer byteBuffer = ByteBuffer.allocate(512);
    	SocketChannel socketChannel = (SocketChannel) key
    			.channel();
    	while (true) {
    		int readBytes = socketChannel.read(byteBuffer);
    		if (readBytes > 0) {
    			log.info("Server: readBytes = " + readBytes);
    			log.info("Server: data = " + new String(byteBuffer.array(), 0, readBytes));
    			byteBuffer.flip();
    			socketChannel.write(byteBuffer);
    			break;
    		}
    	}
    	socketChannel.close();
    }

    Client就比较简单了,就是用channel直接发送了一句消息:

    public void send(String data) {
    	try {
    		SocketChannel socketChannel = SocketChannel.open(inetSocketAddress);
    		socketChannel.configureBlocking(false);
    		ByteBuffer byteBuffer = ByteBuffer.allocate(512);
    		socketChannel.write(ByteBuffer.wrap(data.getBytes()));
    		while (true) {
    			byteBuffer.clear();
    			int readBytes = socketChannel.read(byteBuffer);
    			if (readBytes > 0) {
    				byteBuffer.flip();
    				log.info("Client: readBytes = " + readBytes);
    				log.info("Client: data = " + byteBuffer.toString());
    				socketChannel.close();
    				break;
    			}
    		}
    	} catch (IOException e) {
    		e.printStackTrace();
    	}
    }

    最后,这些Java的socket编程在实际工程中已经很少使用了,由于Unix的poll等功能的出现,select相比较之下,有点弱了。接下来可以考虑研究下。

    NIO包的API介绍:

    http://wufan0023.iteye.com/blog/198722

    http://wufan0023.iteye.com/blog/198710

      分享到:

    This entry was posted in Java topic and tagged  by 柳浪闻莺. Bookmark the permalink.

  • 相关阅读:
    使用SHA256WithRSA来签名和验签(.NET/C#)
    对2个hex(16进制)字符串进行异或操作
    Java DESede 加解密("DESede/ECB/PKCS5Padding")
    获取公钥证书的DN(Distinguished Name)
    Java DES 加解密("DES/EBC/NoPadding")
    Porting .Net RSA xml keys to Java
    Linux使用Shell脚本实现ftp的自动上传下载
    Lombok 安装、入门
    一段对16进制字符串进行异或的代码
    一个封装的使用Apache HttpClient进行Http请求(GET、POST、PUT等)的类。
  • 原文地址:https://www.cnblogs.com/wnlja/p/4368127.html
Copyright © 2020-2023  润新知