`
m635674608
  • 浏览: 4939531 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

如何为可扩展系统进行Socket编程

    博客分类:
  • java
 
阅读更多

从简单I/O到异步非阻塞channel的Java Socket模型演变之旅 

上世纪九十年代后期,我在一家在线视频游戏工资工作,在哪里我主要的工作就是编写Unix Unix Berkley Socket和Windows WinSock代码。我的任务是确保视频游戏客户端和一个游戏服务器通信。很幸运有这样的机会写一些Java Socket代码,我对Java流式网络编程和简洁明了的API着迷。这一点都不让人惊讶,Java最初就是设计促进智能设备之间的通信,这一点很好的转移到了桌面应用和服务器应用。 

1996年,JavaWorld刊登了Qusay H. Mahmoud的文章《Sockets programming in Java: A tutorial》。文章概述了Java的Socket编程模型。从那以后的18年,这个模型少有变化。这篇文章依然是网络系统Java socket编程的入门经典。我将在此基础之上,首先列出一个简单的客户端/服务器例子,开启Java I/O谦卑之旅。此例展示来自java.io包和NIO——Java1.4引起的新的非阻塞I/O API的特性,最后一个例子会涉及Java 7引入的 NIO2 某些特性。 
Java的Socket编程:TCP和UDP 
Socket编程拆分为两个系统之间的相互通信,网络通信有两种方式:ransport Control Protocol(TCP)和User Datagram Protocol(UDP)。TCP和UDP用途不一,并且有各自独特的约束: 

  • TCP协议相对简单稳定,可以帮助客户端与一台服务器建立连接,这样两个系统就可以通信。在TCP协议中,每个实体都能保证其通信载荷(communication payload)会被接受。
  • UDP是一种非连接协议,适用于那些无需保证每个包都能抵达终点的场景,比如流媒体。


如何区分这两者的差异?试想,倘若你在自己喜欢的网站上观看流媒体视频,这时掉帧会发生什么。你是倾向于客户端放缓视频接收丢失的帧,还是继续观看视频呢?典型的流媒体协议采用UDP协议,因为TCP协议保障传输,HTTP、FTP、SMTP、POP3等协议会选择TCP。 
以往的Socket编程 

早在NIO以前,Java TCP客户端socket代码主要由java.net.Socket类来实现。下面的代码开启了一个对服务器的连接: 

Java代码 
  1. Socket socket = new Socket( server, port );  


一旦Socket实例与服务器相连,我们就可以获得服务器端的输入输出流。输入流用来读取服务器端的数据,输出流用来将数据写回到服务器端。可以执行以下的方法获取输入输出流: 

Java代码 
  1. InputStream in = socket.getInputStream();  
  2. OutputStream out = socket.getOutputStream();  


这是基本的流——用来读取或者写入一个文件的流是相同的,所以我们能够将其转换成最好的形式服务于用例中。比如,我们可以用一个PrintStream 包装 OutputStream,这样我们就能轻易地用println()等方法对文本进行写的操作。再比如,我们用BufferedReader包装 InputStream,再通过InputStreamReader可以很容易的用readLine()等方法对文本进行读操作。 

Java I/O示例第一部分:HTTP客户端 

通过一个简短的例子来看如何执行HTTP GET获取一个HTTP服务。HTTP比本例更加复杂成熟,在我们只写一个客户端代码去处理简单案例。发出一个请求,从服务器端获取一个资源,同时服务器端返回响应,并关闭流。本案例所需的步骤如下: 

创建端口为80的网络服务器所对应的客户端Socket。 
从服务器端获取一个PrintStream,同时发送一个GET PATH HTTP/1.0请求,其中PATH就是服务器上的请求资源。比如,假设你想打开一个网站根目录,那么path就是 / 。 
获取服务器端的InputStream,用一个BufferedReader将其包装,然后按行读取响应。 
列表1、 SimpleSocketClientExample.java 

Java代码 
  1. package com.geekcap.javaworld.simplesocketclient;  
  2.    
  3. import java.io.BufferedReader;  
  4. import java.io.InputStreamReader;  
  5. import java.io.PrintStream;  
  6. import java.net.Socket;  
  7.    
  8. public class SimpleSocketClientExample  
  9. {  
  10.     public static void main( String[] args )  
  11.     {  
  12.         if( args.length < 2 )  
  13.         {  
  14.             System.out.println( "Usage: SimpleSocketClientExample <server> <path>" );  
  15.             System.exit( 0 );  
  16.         }  
  17.         String server = args[ 0 ];  
  18.         String path = args[ 1 ];  
  19.    
  20.         System.out.println( "Loading contents of URL: " + server );  
  21.    
  22.         try  
  23.         {  
  24.             // 创建与端口为80的网络服务器对应的客户端socket  
  25.             Socket socket = new Socket( server, 80 );  
  26.    
  27.             //从服务器端获取一个PrintStream  
  28.             PrintStream out = new PrintStream( socket.getOutputStream() );  
  29.             //获取服务器端的InputStream,用一个BufferedReader将其包装  
  30.             BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );  
  31.    
  32.             //发送一个GET PATH HTTP/1.0请求到服务器端  
  33.             out.println( "GET " + path + " HTTP/1.0" );  
  34.             out.println();  
  35.    
  36.             //按行的读取服务器端的返回的响应数据  
  37.             String line = in.readLine();  
  38.             while( line != null )  
  39.             {  
  40.                 System.out.println( line );  
  41.                 line = in.readLine();  
  42.             }  
  43.    
  44.             // 关闭流  
  45.             in.close();  
  46.             out.close();  
  47.             socket.close();  
  48.         }  
  49.         catch( Exception e )  
  50.         {  
  51.             e.printStackTrace();  
  52.         }  
  53.     }  
  54. }  


列表1接受两个命令行参数:需要连接的服务器,需要取回的资源。创建一个Socket指向服务器端,并且显式地为其指定端口号80,接着程序会指向这个命令: 

Java代码 
  1. GET PATH HTTP/1.0  


比如 

Java代码 
  1. GET / HTTP/1.0  


这个过程中发生了什么? 

当你准备从一个web服务器获取一个网页,比如 www.google.com, HTTP client利用DNS服务器去获取服务器地址:从最高域名服务器开始查询com域名,哪里存有 www.google.com 的权威域名服务器,接着 HTTP client询问域名服务器 www.google.com 的IP地址。接下来,它会打开一个Socket通向端口80的服务器。最后, HTTP Client执行特定的HTTP方法,比如GET、POST、PUT、DELETE、HEAD 或者OPTI/ONS。每种方法都有自己的语法,如上述的代码列表中,GET方法后面依次需要一个path、HTTP/版本号、一个空行。如果想加入 HTTP headers,我们必须在进入新的一行之前完成。 

在列表1中,获取了一个 OutputStream,并用 PrintStream 包装了它,这样我们就能容易的执行基于文本的命令。 同样,从 InputStream 获取的代码,InputStreamReader 包装之后,流被转化成一个Reader,再用 BufferedReader 包装。这样我们就能用PrintStream执行GET方法,用BufferedReader 按行读取响应直到获取的响应为 null 时结束,最后关闭Socket。 

现在我们执行这个类,传入以下的参数: 

Java代码 
  1. java com.geekcap.javaworld.simplesocketclient.SimpleSocketClientExample www.javaworld.com /  


你应该能够看到类似下面的输出: 

Java代码 
  1. Loading contents of URL: www.javaworld.com  
  2. HTTP/1.1 200 OK  
  3. Date: Sun, 21 Sep 2014 22:20:13 GMT  
  4. Server: Apache  
  5. X-Gas_TTL: 10  
  6. Cache-Control: max-age=10  
  7. X-GasHost: gas2.usw  
  8. X-Cooking-With: Gasoline-Local  
  9. X-Gasoline-Age: 8  
  10. Content-Length: 168  
  11. Last-Modified: Tue, 24 Jan 2012 00:09:09 GMT  
  12. Etag: "60001b-a8-4b73af4bf3340"  
  13. Content-Type: text/html  
  14. Vary: Accept-Encoding  
  15. Connection: close  
  16.    
  17. <!DOCTYPE html>  
  18. <html lang="en">  
  19. <head>  
  20.     <meta charset="utf-8" />  
  21.     <title>Gasoline Test Page</title>  
  22. </head>  
  23. <body>  
  24.    
  25. <center>Success</center>  
  26. </body>  
  27. </html>  


本输出显示了JavaWorld网站测试页面,网页HTTP version 1.1,响应200 OK. 
Java I/O示例第二部分:HTTP服务器 

刚才我们说了客户端,幸运的是,服务器端的通信也是很容易。从一个简单的视角看,处理过程如下: 

1.创建一个ServerSocket,并指定一个监听端口。 
2.调用 ServerSocket的 accept() 方法监听来自客户端的连接。 
3.一旦有客户端连接服务器,accept() 方法通过服务器与客户端通信,返回一个Socket。在客户端用过同样的Socket类,那么处理过程相同,获取 InputStream 读取客户端信息,OutputStream 写数据到客户端。 
4.如果服务器需要扩展,你需要将Socket传给其他的线程去处理,因此服务器可以持续的监听后来的连接。 
5.再次调用 ServerSocket的 accept() 方法监听其它连接。 
正如你所看到的,NIO处理此场景略有不同。可以直接创建ServerSocket,并将一个端口号传给它用于监听(关于 ServerSocketFactory 的更多信息会在后面讨论): 

Java代码 
  1. ServerSocket serverSocket = new ServerSocket( port );  


通过 accept() 方法接收传入的连接: 

Java代码 
  1. Socket socket = serverSocket.accept();  
  2. // 处理连接……  


多线程Socket编程 

在如下的列表2中,所有的服务器代码放在一起组成一个更加健壮的例子,本例中线程处理多个请求。服务器是一个ECHO服务器,就是说会将所有接收到的消息返回。 

列表2中的例子不是很复杂,但已经提前介绍了一部分NIO的内容。在线程代码上花费一些精力,是为了构建一个处理多并发请求的服务器。 

列表2、SimpleSocketServer.java 

Java代码 
  1. package com.geekcap.javaworld.simplesocketclient;  
  2.    
  3. import java.io.BufferedReader;  
  4. import java.io.I/OException;  
  5. import java.io.InputStreamReader;  
  6. import java.io.PrintWriter;  
  7. import java.net.ServerSocket;  
  8. import java.net.Socket;  
  9.    
  10. public class SimpleSocketServer extends Thread  
  11. {  
  12.     private ServerSocket serverSocket;  
  13.     private int port;  
  14.     private boolean running = false;  
  15.    
  16.     public SimpleSocketServer( int port )  
  17.     {  
  18.         this.port = port;  
  19.     }  
  20.    
  21.     public void startServer()  
  22.     {  
  23.         try  
  24.         {  
  25.             serverSocket = new ServerSocket( port );  
  26.             this.start();  
  27.         }  
  28.         catch (I/OException e)  
  29.         {  
  30.             e.printStackTrace();  
  31.         }  
  32.     }  
  33.    
  34.     public void stopServer()  
  35.     {  
  36.         running = false;  
  37.         this.interrupt();  
  38.     }  
  39.    
  40.     @Override  
  41.     public void run()  
  42.     {  
  43.         running = true;  
  44.         while( running )  
  45.         {  
  46.             try  
  47.             {  
  48.                 System.out.println( "Listening for a connection" );  
  49.    
  50.                 // 调用 accept() 处理下一个连接  
  51.                 Socket socket = serverSocket.accept();  
  52.    
  53.                 // 向 RequestHandler 线程传递socket对象进行处理  
  54.                 RequestHandler requestHandler = new RequestHandler( socket );  
  55.                 requestHandler.start();  
  56.             }  
  57.             catch (I/OException e)  
  58.             {  
  59.                 e.printStackTrace();  
  60.             }  
  61.         }  
  62.     }  
  63.    
  64.     public static void main( String[] args )  
  65.     {  
  66.         if( args.length == 0 )  
  67.         {  
  68.             System.out.println( "Usage: SimpleSocketServer <port>" );  
  69.             System.exit( 0 );  
  70.         }  
  71.         int port = Integer.parseInt( args[ 0 ] );  
  72.         System.out.println( "Start server on port: " + port );  
  73.    
  74.         SimpleSocketServer server = new SimpleSocketServer( port );  
  75.         server.startServer();  
  76.    
  77.         // 1分钟后自动关闭  
  78.         try  
  79.         {  
  80.             Thread.sleep( 60000 );  
  81.         }  
  82.         catch( Exception e )  
  83.         {  
  84.             e.printStackTrace();  
  85.         }  
  86.    
  87.         server.stopServer();  
  88.     }  
  89. }  
  90.    
  91. class RequestHandler extends Thread  
  92. {  
  93.     private Socket socket;  
  94.     RequestHandler( Socket socket )  
  95.     {  
  96.         this.socket = socket;  
  97.     }  
  98.    
  99.     @Override  
  100.     public void run()  
  101.     {  
  102.         try  
  103.         {  
  104.             System.out.println( "Received a connection" );  
  105.    
  106.             // 获取输入和输出流  
  107.             BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );  
  108.             PrintWriter out = new PrintWriter( socket.getOutputStream() );  
  109.    
  110.             // 向客户端写出头信息  
  111.             out.println( "Echo Server 1.0" );  
  112.             out.flush();  
  113.    
  114.             // 向客户端回写信息,直到客户端关闭连接或者收到空行  
  115.             String line = in.readLine();  
  116.             while( line != null && line.length() > 0 )  
  117.             {  
  118.                 out.println( "Echo: " + line );  
  119.                 out.flush();  
  120.                 line = in.readLine();  
  121.             }  
  122.    
  123.             // 关闭自己的连接  
  124.             in.close();  
  125.             out.close();  
  126.             socket.close();  
  127.    
  128.             System.out.println( "Connection closed" );  
  129.         }  
  130.         catch( Exception e )  
  131.         {  
  132.             e.printStackTrace();  
  133.         }  
  134.     }  
  135. }  


在列表2中,我们创建了一个新的 SimpleSocketServer 实例,并开启了这个服务器。继承 Thread 的 SimpleSocketServer 创建一个新的线程,处理存在于 run() 方法中的阻塞方法 accept() 调用。 

run() 方法中存在一个循环,用来接收客户端请求,并创建RequestHandler线程去处理这些请求。再次强调,这是一个相对简单的编程,但涉及了相当的线程编程。 

RequestHandler 处理客户端通信代码与列表1相似:PrintStream 包装后的 OutputStream 更容易进行写操作。同 样,BufferedReader 包装后的InputStream 更易于读取。只要服务器在跑,RequestHandler 就会将客户端的信息按行读取,并将它们返回给客户端。如果客户端发过来的是空行,那对话就结束了,RequestHandler 关闭Socket 。 

NIO、NIO2 Socket编程 

对于多数应用而言,Java基础的Socket编程,我们已经做了充分的探讨。对于涉及到高强度的 I/O 或者异步输入输出,大家就有了熟悉Java NIO和NIO.2中非阻塞API的需要。 

JDK1.4 NIO包提供了如下重要特性: 

  • Channel 被设计用来支持块(bulk)转移,从一个NIO转到另一个NIO。
  • Buffer 提供了连续的内存块,由一组简单的操作提供接口。
  • 非阻塞I/O 是一组class文件,它们可以将 Channel 开放给普通的I/O资源,比如文件和Socket。


用NIO编码时,你可以打开一个到目的地的Channel,接着从目的地读取数据到一个buffer中;写入数据到一个buffer中,接着将其发送到目的地。我会创建一个Socket,并为此获取一个Channel。但首先让我们回顾一下buffer的处理流程: 

  • 写数据到一个buffer中。
  • 调用buffer的 flip() 方法准备读的操作。
  • 从buffer中读取数据。
  • 调用buffer中的 clear() 或者 compact() 方法准备读取更多的数据。


当数据写入buffer后,buffer知道写入其中的数据量。它维护了三个属性,在读模式和写模式中其含义不尽相同。 

  • Position:在写模式中,初始position值为0,它存储的是写入buffer后的当前位置;一旦flip一个buffer使其进入读模式,它会将位置的值重置为0,然后存储读取buffer后的当前位置。
  • Capacity:指的是buffer的固定大小。
  • Limit:在写模式中,limit定义了写入buffer的数据大小;在读模式中,limit定义了可以从buffer中读取的数据大小。


Java I/O示例第三部分:基于NIO.2的ECHO服务器 

JDK 7引入的NIO.2添加了非阻塞I/O库去支持文件系统任务,比如 java.nio.file 包和 java.nio.file.Path 类,并提供了一个 新的文件系统API。记住,我们采用IO.2 AsynchronousServerSocketChannel 写一个新的ECHO服务器。 

”NIO在提供处理性能方法大放异彩,但NIO的结果跟底层平台紧密相连。比如,或许你会发现,NIO加速应用性能不光取决于OS,还跟特定的JVM有关,主机的虚拟化上下文、大存储特性、甚至数据……” 
——摘自”Five ways to maximize Java NIO and NIO.2 

AsynchronousServerSocketChannel 提供了一个非阻塞异步Channel作为流定向监听的Socket。为了用这个Channel,首先需要执行它的 open() 静态方法。然后调用 bind() 为其绑定一个端口号。接着,将一个实现CompletionHandler接口的类传给 accept() 并执行。多数时候,你会发现 handler作为匿名内部类被创建。 
列表3显示新的异步ECHO服务器源码。 

列表3、SimpleSocketServer.java 

Java代码 
  1. package com.geekcap.javaworld.nio2;  
  2.    
  3. import java.io.I/OException;  
  4. import java.net.InetSocketAddress;  
  5. import java.nio.ByteBuffer;  
  6. import java.nio.channels.AsynchronousServerSocketChannel;  
  7. import java.nio.channels.AsynchronousSocketChannel;  
  8. import java.nio.channels.CompletionHandler;  
  9. import java.util.concurrent.ExecutionException;  
  10. import java.util.concurrent.TimeUnit;  
  11. import java.util.concurrent.TimeoutException;  
  12.    
  13. public class NioSocketServer  
  14. {  
  15.     public NioSocketServer()  
  16.     {  
  17.         try  
  18.         {  
  19.             // 创建一个 AsynchronousServerSocketChannel 侦听 5000 端口  
  20.             final AsynchronousServerSocketChannel listener =  
  21.                     AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(5000));  
  22.    
  23.             // 侦听新的请求  
  24.             listener.accept( nullnew CompletionHandler<AsynchronousSocketChannel,Void>() {  
  25.    
  26.                 @Override  
  27.                 public void completed(AsynchronousSocketChannel ch, Void att)  
  28.                 {  
  29.                     // 接受下一个连接  
  30.                     listener.accept( nullthis );  
  31.    
  32.                     // 向客户端发送问候信息  
  33.                     ch.write( ByteBuffer.wrap( "Hello, I am Echo Server 2020, let's have an engaging conversation!n".getBytes() ) );  
  34.    
  35.                     // 分配(4K)字节缓冲用于从客户端读取信息  
  36.                     ByteBuffer byteBuffer = ByteBuffer.allocate( 4096 );  
  37.                     try  
  38.                     {  
  39.                         // Read the first line  
  40.                         int bytesRead = ch.read( byteBuffer ).get( 20, TimeUnit.SECONDS );  
  41.    
  42.                         boolean running = true;  
  43.                         while( bytesRead != -1 && running )  
  44.                         {  
  45.                             System.out.println( "bytes read: " + bytesRead );  
  46.    
  47.                             // 确保有读取到数据  
  48.                             if( byteBuffer.position() > 2 )  
  49.                             {  
  50.                                 // 准备缓存进行读取  
  51.                                 byteBuffer.flip();  
  52.    
  53.                                 // 把缓存转换成字符串  
  54.                                 byte[] lineBytes = new byte[ bytesRead ];  
  55.                                 byteBuffer.get( lineBytes, 0, bytesRead );  
  56.                                 String line = new String( lineBytes );  
  57.    
  58.                                 // Debug  
  59.                                 System.out.println( "Message: " + line );  
  60.    
  61.                                 // 向调用者回写  
  62.                                 ch.write( ByteBuffer.wrap( line.getBytes() ) );  
  63.    
  64.                                 // 准备缓冲进行写操作  
  65.                                 byteBuffer.clear();  
  66.    
  67.                                 // 读取下一行  
  68.                                 bytesRead = ch.read( byteBuffer ).get( 20, TimeUnit.SECONDS );  
  69.                             }  
  70.                             else  
  71.                             {  
  72.                                 // 在我们的协议中,空行表示会话结束  
  73.                                 running = false;  
  74.                             }  
  75.                         }  
  76.                     }  
  77.                     catch (InterruptedException e)  
  78.                     {  
  79.                         e.printStackTrace();  
  80.                     }  
  81.                     catch (ExecutionException e)  
  82.                     {  
  83.                         e.printStackTrace();  
  84.                     }  
  85.                     catch (TimeoutException e)  
  86.                     {  
  87.                         // 用户达到20秒超时,关闭连接  
  88.                         ch.write( ByteBuffer.wrap( "Good Byen".getBytes() ) );  
  89.                         System.out.println( "Connection timed out, closing connection" );  
  90.                     }  
  91.    
  92.                     System.out.println( "End of conversation" );  
  93.                     try  
  94.                     {  
  95.                         // 如果需要,关闭连接  
  96.                         if( ch.isOpen() )  
  97.                         {  
  98.                             ch.close();  
  99.                         }  
  100.                     }  
  101.                     catch (I/OException e1)  
  102.                     {  
  103.                         e1.printStackTrace();  
  104.                     }  
  105.                 }  
  106.    
  107.                 @Override  
  108.                 public void failed(Throwable exc, Void att) {  
  109.                     ///...  
  110.                 }  
  111.             });  
  112.         }  
  113.         catch (I/OException e)  
  114.         {  
  115.             e.printStackTrace();  
  116.         }  
  117.     }  
  118.    
  119.     public static void main( String[] args )  
  120.     {  
  121.         NioSocketServer server = new NioSocketServer();  
  122.         try  
  123.         {  
  124.             Thread.sleep( 60000 );  
  125.         }  
  126.         catch( Exception e )  
  127.         {  
  128.             e.printStackTrace();  
  129.         }  
  130.     }  
  131. }  


在列表3中,我们首先创建了一个新的AsynchronousServerSocketChannel,然后为其绑定端口号5000: 

Java代码 
  1. final AsynchronousServerSocketChannel listener =  
  2.     AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(5000));  


调用 AsynchronousServerSocketChannel 的 accept(),通知其监听一个连接,并将一个典型的CompletionHandler传给它。一旦调用 accept(),结果会立即返回。注意,本例不同于列表2中的ServerSocket类;除非一个客户端与ServerSocket相连,否则accept()会被阻塞。AsynchronousChannelGroup 的 accept() 会为我们解决这个问题。 

完整的Handler处理 

接 下来的主要任务就是创建一个 CompletionHandler 类,并实现 completed() 和 failed() 方法。当 AsynchronousServerSocketChannel 接收一个客户端连接,这个连接包含一个连接客户端的 AsynchronousSocketChannel,completed()方法就会被调用。completed()方法第一次被调用从AsynchronousServerSocketChannel 处接收连接,开始与客户端进行通信。首先它做的事情向客户端写入一个“hello”消息:建立一个字符串,并将其转换成字节数组并将其传给 ByteBuffer.wrap(),完了构造一个ByteBuffer。接着ByteBuffer传给 AsynchronousSocketChannel的 write() 方法。 

为了更够从客户端那里读取数据,我们创建了一个新的ByteBuffer,并调用它的allocate(4096)。接 着我们调用了AsynchronousSocketChannel的 read() 方法,此方法会返回一个 Future<Integer>,调用后者的 get() 方法可以获取读自客户端的字节数。在本例中,我们传递了20秒的timeout参数给 get();如果20分钟没有得到响应,那 get() 就会抛出一个TimeoutException。本回响服务器的应对策略是,如果20秒没有响应,就终止这个对话。 

异步计算中的Future 
“The Future<V>接口显示一个异步计算的结果,此结果作为一个Future,因为它直到未来的某个时刻才存在。你可以调用它的方法去取消一个任务,返回任务的结果——如果任务没有完成,无限等待或者超时退出——并且决定任务是否已取消或者完成……”。 
——摘自”Java concurrency without the pain, Part 1 

接下来我们会检测buffer的position,它会定位到最后一个来自客户端的byte。倘若客户端发来的是一个空行,接收两个字节:一个回车和一个换行。检测确保客户端发出一个空白行,我们以此作为客户端对话结束的信号。如果我们拥有有意义的数据,那我们就调用ByteBuffer的 flip() 方法去进入读的状态。我们可以创建一个临时byte数组去存储读自客户端的数据,然后调用ByteBuffer的 get() 加载数据到byte数组中。最后,我们通过创建一个新的String对象将数组转换成一行字符串。我们将这行字符串返回给客户端:将字符串line转换成一个byte数组,作为参数传递给 ByteBuffer.wrap(),然后调用 AsynchronousSocketChannel的write() 方法。接着调用ByteBuffer的clear(),这样position被重置为0并将ByteBuffer置于写的模式,接着我们读取客户端下一行。 

需要注意的是 main() 方法。它 创建了服务器,同时创建了一个让应用跑60秒的计时器。这是因为AsynchronousSocketChannel的 accept() 会理解返回,如果线程 Thread.sleep() 不执行,应用将会立即停止。为了进行测试,启动服务器后用telnet客户端进行连接: 

Java代码 
  1. telnet localhost 5000  


发送少量的字符串给服务器,观察它们向你返回结果,然后发送一个空行结束对话。 

结语 

本文展示了两种Socket Java编程方式:传统的Java 1.0引入的编写方式,Java 1.4和Java 7中分别引入的非阻塞 NIO 和 NIO.2 方式。采用客户端服务器几次迭代的例子,展示了基本 Java I/O的使用,以及一些场景下非阻塞I/O对Java socket编程模型的改进和简化。利用非阻塞I/O,你可以编写网络应用来处理多并发连接,而无需管理多线程集合。同样,你也可以利用构建在NIO和 NIO.2上新的服务器扩展特性。 

原文链接:javaworld翻译: ImportNew.com - 乔永琪 
译文链接:http://www.importnew.com/15996.html

 

http://www.iteye.com/news/30572

分享到:
评论

相关推荐

    c_socket编程入门

    Microsoft.Net Framework为应用程序访问Internet提供了分层的、可扩展的以及受管辖的网络服务,其名字空间System.Net和System.Net.Sockets包含丰富的类可以开发多种网络应用程序。.Net类采用的分层结构允许应用程序...

    C# Socket 编程基础

    Microsoft.Net Framework 为应用程序访问Internet 提供了分层的、可扩展的以及受管辖的网 络服务,其名字空间System.Net 和System.Net.Sockets 包含丰富的类可以开发多种网络应用 程序。.Net 类采用的分层结构允许...

    Linux环境下C语言Socket编程——客户端向服务端单次发送一个或多个文件的功能

    网上很多有关socket文件传输的源码都是每次只能发送一个文件,如果想传输多个文件,不但需要多次交互,还需要把每个文件的文件名都输入进去,很不方便,所以进行了一个小扩展:即客户端可以将打算传送的多个文件都先...

    Android Socket服务器客户端编程

    Android Socket的编程实例,Android端即当服务器又当客户端,既简单又好学好用,即基础功能强大,可扩展性强,物超所值

    可扩展多线程异步Socket服务器框架EMTASS 2.0

    在程序设计与实际应用中,Socket数据包接收服务器够得上一个经典问题了:需要计算机与网络编程知识(主要是Socket),与业务处理逻辑密切(如:包组成规则),同时还要兼顾系统运行的稳定、效率、安全与管理等。...

    C#.Net Socket网络聊天室编程实例附教程

    C#.Net Socket网络聊天室编程实例附教程 是利用套接字编程的入门教程,源码也可以直接使用 入门者的一个好的范例,可扩展,简直就是一个内网中的即时通。有

    计算机网络Socket编程大作业实现了一个基于局域网通信的简易微信。使用到的技术有Java的Socket编程,数据库.zip

    对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同...

    HP-SOCKET加易语言数据库,全源码制作的网络验证开发,可运营,可自行扩展 最新

    HP-SOCKET加易语言数据库,全源码制作的网络验证开发,可运营,可自行扩展 最新 好不容易找到的哦

    C#.Net网络程序开发-Socket篇

    c#网络编程经典入门,示例,原理的精辟讲解,学习的好材料 &lt;br&gt; Microsoft.Net Framework为应用程序访问Internet提供了分层的、可扩展的以及受管辖的网络服务,其名字空间System.Net和System.Net.Sockets包含...

    学习OC的Socket编程,本demo是基于C语言原生API的.zip

    对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同...

    Java高并发异步Socket编程

    DougLee可扩展的网络服务事件驱动Reactor模式基础版多线程版其他变体java.io包中分阻塞IOAPI一览Web服务器,分布式对象系统等等它们的共同特点Read请求解码请求报文业务处理编码响应报文发送响应实际应用中每一个...

    基恩士固定扫码枪 获取条码信息 基于TCP协议 基于串口com协议 基恩士基于Socket协议获取条码信息 源代码

    可扩展性:Socket通信支持多对多的通信模式,可以同时与多个设备进行通信,具有较高的可扩展性。 大数据传输能力:Socket通信支持大数据传输,可以满足基恩士固定扫码枪对于大数据量实时传输的需求。 源代码,在多...

    通用基恩士 海康机器人 新大陆固定扫码枪通过Socket、TCP、串口协议获取条码信息 C#实现获取扫码枪扫码信息源代码

    可扩展性:Socket通信支持多对多的通信模式,可以同时与多个设备进行通信,具有较高的可扩展性。 大数据传输能力:Socket通信支持大数据传输,可以满足基恩士固定扫码枪对于大数据量实时传输的需求。 ...

    java实现的聊天工具,可扩展性强。.zip

    java实现的聊天工具,无需数据库,实现私聊、群聊功能,可以通过该项目对socket编程、多线程并发server有新的认识,代码架构清晰,可扩展性强。.zip 软件开发设计:应用软件开发、系统软件开发、移动应用开发、网站...

    多线程的Socket网络应用

    运用了Socket,Dns,多线程的网络技术,可以打开多个客户端应用程序和服务端相应(上限20,可扩展)。此程序要求用户输入一个字符串,服务器返回大写的字符串给用户,用户输入“exit”退出登录,服务端有相应的显示。 ...

    linux编程白皮书

    本书对Linux操作系统及其编程作了整体的介绍,以支持用于开发软件的公开源码模型。对内存管理、进程及其通信机制、PCI、内核模块编程及内核系统结构作了详细的解释,且附有很多程序代码实例。对深入研究Linux下的...

    Linux编程从入门到精通

    8.4.2 为INET BSD Socket绑定地址 99 8.4.3 建立INET BSD Socket连接 99 8.4.4 INET BSD Socket侦听 100 8.4.5 接受连接请求 100 8.5 IP层 100 8.5.1 套接字缓冲区 100 8.5.2 接收IP报文 101 8.5.3 发送IP报文 102 ...

    Linux编程资料

    8.4.2 为INET BSD Socket绑定地址 99 8.4.3 建立INET BSD Socket连接 99 8.4.4 INET BSD Socket侦听 100 8.4.5 接受连接请求 100 8.5 IP层 100 8.5.1 套接字缓冲区 100 8.5.2 接收IP报文 101 8.5.3 发送IP报文 102 ...

Global site tag (gtag.js) - Google Analytics