Java NIO编写Socket服务器的一个例子

来源:互联网 发布:后台调用前台js方法 编辑:程序博客网 时间:2024/06/06 01:20

Java中编写Socket服务器,通常有一下几种模式:

1. 一个链接一个线程;优点:程序编写简单; 缺点:如果链接非常多,分配的线程会非常多,机器可能资源耗尽而崩溃。

2.把每一个新链接,交接给一个拥有固定数量线程的连接池;优点:程序编写相对简单,可以处理大量的链接。确定:线程的开销非常大,链接很多的情况,排队现象会比较严重。

3. 使用Java中NIO,用异步IO方式处理。这种模式,可以用一个线程,处理大量的链接。


下面使用java中NIO,编写一个Socket服务器程序。要使用java中NIO,必须掌握下面几个概念: ByteBuffer, Channel, Selector和SelectionKey。

这里就不介绍这些基本概念了,网上资料很多。


下面程序接收客户端输入一行文本(以“\r\n”)结束,并向客户端回显输入的文本。如果输入的文本是 “get file:xxxx”模式,则把“xxxx” 解析为服务器class path下的一个文件,如果找到该文件,则回显该文件的内容,如果找不到文件,回显文件不能找到的消息。

import java.io.IOException;import java.net.InetSocketAddress;import java.net.URL;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.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.util.Iterator;import java.util.Set;public class NIOServer {private static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;private static final int BUFFER_SIZE = 1024;private int port = 8081;public NIOServer(int port) {this.port = port;}public NIOServer() {}public void start() {ServerSocketChannel ssc = null;try {ssc = ServerSocketChannel.open();ssc.configureBlocking(false);ssc.bind(new InetSocketAddress(this.port));Selector sel = Selector.open();ssc.register(sel, SelectionKey.OP_ACCEPT);while (true) {Set<SelectionKey> keySet = null;try {sel.select();keySet = sel.selectedKeys();} catch (Exception e) {e.printStackTrace();break;}for (Iterator<SelectionKey> it = keySet.iterator(); it.hasNext();) {SelectionKey sKey = it.next();it.remove();try {if (sKey.isAcceptable()) {ServerSocketChannel serChannel = (ServerSocketChannel) sKey.channel();SocketChannel clientChannel = serChannel.accept();clientChannel.configureBlocking(false);SelectionKey k2 = clientChannel.register(sel, SelectionKey.OP_READ);k2.attach(ByteBuffer.allocate(BUFFER_SIZE));} else if (sKey.isWritable()) {SocketChannel clientChannel = (SocketChannel) sKey.channel();ByteBuffer[] bfs = (ByteBuffer[]) sKey.attachment();if (bfs[bfs.length - 1].hasRemaining()) {clientChannel.write(bfs);} else {clientChannel.close();}} else if (sKey.isReadable()) {SocketChannel clientChannel = (SocketChannel) sKey.channel();ByteBuffer bf = (ByteBuffer) sKey.attachment();String msg = "";boolean clientEnd = false;if (bf.hasRemaining()) {int len = clientChannel.read(bf);if (len != -1 && bf.position() > 1) {char lastChar = (char) bf.get(bf.position() - 1);char last2Char = (char) bf.get(bf.position() - 2);if (String.valueOf(new char[] { last2Char, lastChar }).equals("\r\n")) {System.out.println("client inupt end.");clientEnd = true;}}if (len == -1) {System.out.println("client closed.");clientEnd = true;}} else {System.out.println("buff is full.");msg = "You can only enter " + BUFFER_SIZE + " chars\r\n";clientEnd = true;}if (clientEnd) {ByteBuffer[] att = processInput(bf, msg);clientChannel.register(sel, SelectionKey.OP_WRITE, att);}}} catch (Exception e) {sKey.cancel();e.printStackTrace();}}}} catch (IOException e) {e.printStackTrace();} finally {if (ssc != null) {try {ssc.close();} catch (IOException e) {e.printStackTrace();}}}}private ByteBuffer[] processInput(ByteBuffer bf, String msg) throws Exception {bf.flip();ByteBuffer promptMsg = ByteBuffer.wrap((msg + "You just input:\r\n").getBytes(DEFAULT_CHARSET));String inputMsg = new String(bf.array(), bf.position(), bf.limit(), DEFAULT_CHARSET).trim();ByteBuffer[] att = new ByteBuffer[] { promptMsg, bf };if (inputMsg.indexOf("get file:") >= 0) {String fileName = inputMsg.substring("get file:".length()).trim();System.out.println("fileName=" + fileName);URL fileURL = this.getClass().getClassLoader().getResource(fileName);if (fileURL != null) {Path path = Paths.get(fileURL.toURI());System.out.println(path);ByteBuffer fileData = ByteBuffer.wrap(Files.readAllBytes(path));ByteBuffer info = ByteBuffer.wrap(("The content of file " + fileName + ":\r\n").getBytes(DEFAULT_CHARSET));att = new ByteBuffer[] { promptMsg, bf, info, fileData };} else {String errMsg = "fileName: " + fileName + " not found in the classpath.";System.out.println(errMsg);att = new ByteBuffer[] { promptMsg, bf, ByteBuffer.wrap(errMsg.getBytes(DEFAULT_CHARSET)) };}}return att;}public static void main(String[] args) {new NIOServer().start();}}


运行上面的程序。


然后在 window的命令行,用telnet 测试:

telnet localhost 8081

然后 输入: 

get file:test.txt

注意:在我的class path 下有 test.txt 这个文件(放到eclipse src/test.txt). 

然后看到下面的输出:

You just input:get file:test.txtThe content of file test.txt:Hello World 1!Hello World 2!Hello World 3!Hello World 4!Connection to host lost.


常见错误1:

在写缓冲区的时候这样:

     if (sKey.isWritable()){   while((buffer.hasRemaining())) {clientChannel.write(bfs);          }  }


读缓冲区的时候这样:


   if (sKey.isReadable()) {while(buffer.hasRemaining()) {int len = clientChannel.read(bf);if (len != -1 ) break;}}

上面的的操作,只有一个channel数据处理完后,才会处理其他channel。这样就会阻塞其他channel。


常见错误2:


把所有代码都放到,一个try-catch中,如下代码:

        try {ssc = ServerSocketChannel.open();ssc.configureBlocking(false);ssc.bind(new InetSocketAddress(this.port));Selector sel = Selector.open();ssc.register(sel, SelectionKey.OP_ACCEPT);while (true) {Set<SelectionKey> keySet = null;sel.select();        keySet = sel.selectedKeys();for (Iterator<SelectionKey> it = keySet.iterator(); it.hasNext();) {SelectionKey sKey = it.next();it.remove();if (sKey.isAcceptable()) {....} else if (sKey.isWritable()) {....} else if (sKey.isReadable()) {....}}}} catch (Exception e) {e.printStackTrace();} finally {if (ssc != null) {try {ssc.close();} catch (IOException e) {e.printStackTrace();}}}


这样,在处理某一客户端请求,如果出现了运行时异常,就会让整个服务器程序挂掉。






0 0
原创粉丝点击