Socket的Channel在Selector上注册某一种动作,Selector通过select操作,监视所有在该Selector注册过的Channel的对应的动作,如果监测到某一对应的动作,则返回selectedKeys,自己手动取到各个SelectionKey进行相应的处理。当然NIO不仅可以接受Socket的Channel,还有文件操作等其他IO操作。

传统的Java 的IO,利用Socket建立服务器,接收客户端连接,一般都是为每一个连接建立一个线程,如果连接数巨大,那么服务器开销也将巨大。。NIO的原理,可以参照图:

java 开发 创建邮件服务器_java

Socket的Channel在Selector上注册某一种动作,Selector通过select操作,监视所有在该Selector注册过的Channel的对应的动作,如果监测到某一对应的动作,则返回selectedKeys,自己手动取到各个SelectionKey进行相应的处理。当然NIO不仅可以接受Socket的Channel,还有文件操作等其他IO操作。

作业的要求:

使用socket编程实现一个简单的文件服务器。客户端程序实现put功能(将一个文件从本地传到文件服务器)和get功能(从文件服务器取一远程文件存为本地文件)。客户端和文件服务器不在同一台机器上。



put [-h hostname] [-p portname] local_filename remote_filename
get [-h hostname] [-p portname] remote_filename local_filename


服务器端不使用nio,直接使用io的socket代码如下:


1. import java.io.*; 
2. import .ServerSocket; 
3. import .Socket; 
4.  
5. public class ServerMain { 
6.      
7. public static void main(String[] args) { 
8.  
9. class SocketThread extends Thread{ 
10.              
11. private Socket socket; 
12. private byte[] buf; 
13. private int len = 0; 
14. public SocketThread(Socket socket) {  
15. this.socket = socket; 
16. new byte[1024]; 
17.             } 
18.  
19. @Override 
20. public void run() { 
21. try {    
22. new DataInputStream(socket.getInputStream()); 
23. new DataOutputStream(socket.getOutputStream());  
24.                      
25. //String command = dis.readUTF();  
26.                     len = dis.read(buf); 
27. new String(buf,0,len); 
28.                      
29. "command=="+command); 
30.                      
31. " "); 
32. 0];  //命令  是put还是get 
33. 1];  //文件名 
34.                      
35. new File("C:\\",filename);//假设放在C盘 
36. if(command.equals("get")){ 
37. if(!file.exists()){ 
38. //dos.writeUTF("notexists"); 
39. "notexists".getBytes()); 
40.                             dos.flush(); 
41. "没有这个文件,无法提供下载!"); 
42.                             dis.close(); 
43.                             dos.close(); 
44.                             socket.close(); 
45. return; 
46.                         } 
47. //dos.writeUTF("DownloadReady "+file.length());  
48. "准备下载".getBytes()); 
49.                         dos.flush(); 
50.                          
51. "正在接受文件下载..."); 
52. new DataInputStream(new BufferedInputStream(new FileInputStream(file)));  
53.    
54. while ((len = fis.read(buf))!= -1) {  
55. 0, len); 
56.                         } 
57.                         dos.flush(); 
58.                          
59.                         fis.close();      
60. "文件传输完成"); 
61.                     } 
62. else {  
63. //dos.writeUTF("UploadReady");  
64. "UploadReady".getBytes()); 
65.                         dos.flush(); 
66.                          
67. "正在接受文件上传..."); 
68.                         DataOutputStream fileOut =  
69. new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); 
70.   
71. while ((len = dis.read(buf))!=-1) {    
72. 0, len); 
73.                         } 
74. "上传完毕!"); 
75.                         fileOut.close();  
76.                     } 
77.                     dis.close(); 
78.                     dos.close(); 
79.                     socket.close();  
80. catch (Exception e) { 
81.                     e.printStackTrace(); 
82.                 } 
83.             } 
84.              
85.         } 
86.          
87. "等待客户端连接...."); 
88. int index = 0; 
89. try { 
90. new ServerSocket(9527,300); //端口号9527  允许最大连接数300 
91. while (true) { 
92.                 Socket socket = server.accept(); 
93. "收到第"+(++index)+"个连接"); 
94. new SocketThread(socket).start(); //对每个连接创建一个线程 
95.             } 
96. catch (Exception e) { 
97.             e.printStackTrace(); 
98.         } 
99.     } 
100. }


使用NIO建立的Socket服务器,代码如下:


1. import java.io.BufferedInputStream; 
2. import java.io.BufferedOutputStream; 
3. import java.io.DataInputStream; 
4. import java.io.DataOutputStream; 
5. import java.io.File; 
6. import java.io.FileInputStream; 
7. import java.io.FileOutputStream; 
8. import java.io.IOException; 
9. import .InetSocketAddress; 
10. import java.nio.ByteBuffer;  
11. import java.nio.CharBuffer; 
12. import java.nio.channels.SelectionKey; 
13. import java.nio.channels.Selector; 
14. import java.nio.channels.ServerSocketChannel; 
15. import java.nio.channels.SocketChannel; 
16. import java.nio.charset.Charset; 
17. import java.nio.charset.CharsetDecoder; 
18. import java.nio.charset.CharsetEncoder; 
19. import java.util.Iterator; 
20.  
21. public class NewSocketServer { 
22.   
23. private static final int port = 9527; 
24. private Selector selector; 
25. private ByteBuffer clientBuffer = ByteBuffer.allocate(1024);   
26. private CharsetDecoder decoder = Charset.forName("GB2312").newDecoder(); 
27. private CharsetEncoder encoder = Charset.forName("GB2312").newEncoder();  
28. //编码解码格式设置成GBK也行.UTF-8不行,中文乱码  (前提都是客户端没有设置任何编码解码格式) 
29.      
30. public void setListener() throws Exception{ 
31.          
32. //打开选择器    
33.          
34. //定义一个 ServerSocketChannel通道 
35. new InetSocketAddress(port));  //ServerSocketChannel绑定端口   
36. false);   //配置通道使用非阻塞模式 
37. //该通道在selector上注册  接受连接的动作 
38.          
39. while(true) 
40.         {     
41. //select() 会阻塞,直到在该selector上注册的channel有对应的消息读入 
42.             Iterator iter = selector.selectedKeys().iterator();    
43. while (iter.hasNext()) {     
44.                 SelectionKey key = (SelectionKey) iter.next();    
45. // 删除此消息  
46. // 当前线程内处理。(为了高效,一般会在另一个线程中处理此消息) 
47.             }    
48.         }    
49.     } 
50.      
51. private void process(SelectionKey key) throws IOException {    
52. if (key.isAcceptable()) { // 接收请求    
53.                 ServerSocketChannel server = (ServerSocketChannel) key.channel();    
54. //类似于io的socket,ServerSocketChannel的accept函数返回 SocketChannel 
55. false);   //设置非阻塞模式    
56.                 SelectionKey sKey = channel.register(selector, SelectionKey.OP_READ);  
57. "read_command"); //这儿接收到连接请求之后可以为每个连接设置一个ID 
58.             }  
59. else if (key.isReadable()) { // 读信息     
60.                 SocketChannel channel = (SocketChannel) key.channel();    
61.                 String name = (String) key.attachment();  
62. if(name.equals("read_command")){ 
63. int count = channel.read(clientBuffer);  
64. if (count > 0) {    
65.                         clientBuffer.flip();    
66.                         CharBuffer charBuffer = decoder.decode(clientBuffer);    
67.                         String command = charBuffer.toString();    
68.                          
69. //command形如:get abc.png 或者  put aaa.png 
70. "command===="+command);  //得到客户端传来的命令  
71.                          
72. " "); 
73. 0];  //命令  是put还是get 
74. 1];  //文件名 
75.                          
76.                         SelectionKey sKey = channel.register(selector,SelectionKey.OP_WRITE);    
77. if(command.equals("put"))sKey.attach("UploadReady#"+filename);  //要保护该通道的文件名 
78. else if(command.equals("get")){  
79. if(!new File("C:\\",filename).exists()){ //假设文件都是在C盘根目录 
80. "没有这个文件,无法提供下载!"); 
81. "notexists");  
82.                             } 
83. else sKey.attach("DownloadReady#"+filename); //要保护该通道的文件名 
84.                         } 
85. else {    
86.                         channel.close();    
87.                     }    
88.                 } 
89. else if(name.startsWith("read_file")){//这儿可以新开一个线程     文件操作也可以用NIO  
90.                     DataOutputStream fileOut =  
91. new DataOutputStream( 
92. new BufferedOutputStream( 
93. new FileOutputStream( 
94. new File("C:\\",name.split("#")[1])))); 
95.   
96. int passlen = channel.read(clientBuffer);   
97. while (passlen>=0) {    
98.                         clientBuffer.flip();   
99. 0, passlen);  
100.                         passlen = channel.read(clientBuffer); 
101.                     } 
102. "上传完毕!"); 
103.                     fileOut.close();  
104.                     channel.close(); 
105.                 } 
106.                 clientBuffer.clear();    
107.             }  
108. else if (key.isWritable()) { // 写事件    
109.                 SocketChannel channel = (SocketChannel) key.channel();    
110.                 String flag = (String) key.attachment();     
111. if(flag.startsWith("downloading")){//这儿可以新开一个线程   文件操作也可以用NIO 
112. new DataInputStream( 
113. new BufferedInputStream( 
114. new FileInputStream( 
115. new File("C:\\",flag.split("#")[1]))));  
116.                       
117. byte[] buf = new byte[1024]; 
118. int len =0;  
119. while ((len = fis.read(buf))!= -1) {  
120. 0, len));   
121.                     }   
122.                     fis.close();      
123. "文件传输完成"); 
124.                     channel.close(); 
125.                 } 
126. else if(flag.equals("notexists")){  
127. //channel.write(encoder.encode(CharBuffer.wrap(flag)));    
128. //不用编码也行    客户端直接接收    中文也不是乱码 
129.                     channel.close(); 
130.                 } 
131. else if(flag.startsWith("UploadReady")){  
132. "UploadReady")));  
133.                      
134. //这儿如果不重新注册该通道的读操作    selector选择到该通道的将继续永远是写操作,也就无法跳转到上面的接受上传的处理 
135. //register是覆盖的????!!! 
136. "read_file#"+flag.split("#")[1]); 
137. //key.attach("read_file#"+flag.split("#")[1]); //select不到读操作 
138.                 } 
139. else if(flag.startsWith("DownloadReady")){  
140. "准备下载".getBytes()));  
141. //channel.write(encoder.encode(CharBuffer.wrap("准备下载")));    
142. "downloading#"+flag.split("#")[1]); 
143.                 }  
144.             }   
145.         }    
146.      
147. public static void main(String[] args) { 
148.          
149. try { 
150. "等待来至" + port + "端口的客户端连接.....");  
151. new NewSocketServer().setListener(); 
152. catch (Exception e) { 
153.             e.printStackTrace(); 
154.         } 
155.  
156.     } 
157. }


客户端代码如下:

1. import java.io.*; 
2. import .InetAddress; 
3. import .Socket; 
4. import java.util.Scanner; 
5.  
6. public class ClientMain { 
7.  
8. private   int ServerPort = 9527; 
9. private   String ServerAddress = "192.168.1.154"; 
10. private   String GetOrPut = "get";    
11. private   String local_filename = "";  
12. private   String remote_filename  = "";  
13. private   byte[] buf; 
14. private   int len; 
15. class SocketThread extends Thread{ 
16.          
17. @Override 
18. public void run() { 
19. try { 
20.                  
21. new File("C:\\",local_filename); //假设文件放在C盘 
22. if(!file.exists()&&GetOrPut.equals("put")){  
23. "本地没有这个文件,无法上传!");  
24. return; 
25.                 }  
26.                  
27.                 InetAddress loalhost = InetAddress.getLocalHost(); 
28. new Socket(ServerAddress,ServerPort,loalhost,44); 
29. //服务器IP地址  端口号   本机IP 本机端口号 
30. new DataInputStream(socket.getInputStream()); 
31. new DataOutputStream(socket.getOutputStream()); 
32.                   
33. //dos.writeUTF(GetOrPut+" "+remote_filename);//服务器端如果是io的socket,writeUTF和writeUTF对接 
34. " "+remote_filename).getBytes()); 
35.                 dos.flush();  
36.                
37. //String tempString = dis.writeUTF();  
38. new byte[1024]; 
39.                 len = dis.read(buf); 
40. new String(buf,0,len);//服务器反馈的信息 
41.                  
42. //System.out.println(tempString);  
43. if(tempString.equals("notexists")){ 
44. "服务器没有这个文件,无法下载!");  
45.                     dos.close(); 
46.                     dis.close(); 
47.                     socket.close(); 
48. return; 
49.                 } 
50.                  
51. if(tempString.startsWith("准备下载")){   
52.                     DataOutputStream fileOut =  
53. new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); 
54.   
55. while ((len = dis.read(buf))!=-1) {    
56. 0, len); 
57.                     } 
58. "下载完毕!"); 
59.                     fileOut.close(); 
60.                     dos.close(); 
61.                     dis.close(); 
62.                     socket.close(); 
63.                 } 
64. else if(tempString.equals("UploadReady")){   
65. "正在上传文件......."); 
66. new DataInputStream(new BufferedInputStream(new FileInputStream(file)));  
67.                        
68. while ((len = fis.read(buf))!= -1) {   
69. 0, len); 
70.                     } 
71.                     dos.flush(); 
72. "上传完毕!"); 
73.                     fis.close(); 
74.                     dis.close(); 
75.                     dos.close(); 
76.                     socket.close(); 
77.                 } 
78.                  
79. catch (Exception e) { 
80.                 e.printStackTrace(); 
81.             } 
82.         } 
83.          
84.     } 
85.      
86. public boolean checkCommand(String command) 
87.     {  
88. if(!command.startsWith("put")&&!command.startsWith("get")){ 
89. "输入命令错误"); 
90. return false; 
91.         } 
92.          
93. int index = -1; 
94. ""; 
95. null; 
96.          
97. if((index=command.indexOf("-h"))>0){ 
98. 3); 
99. 0, temp.indexOf(' ')); 
100.             ServerAddress = temp; 
101.         } 
102. if((index=command.indexOf("-p"))>0){ 
103. 3); 
104. 0, temp.indexOf(' ')); 
105.             ServerPort = Integer.valueOf(temp); 
106.         } 
107.          
108. " "); 
109. if(command.startsWith("put")){ 
110. "put"; 
111. 2]; 
112. 1]; 
113.         } 
114. else if(command.startsWith("get")){ 
115. "get"; 
116. 1]; 
117. 2]; 
118.         } 
119.          
120. return true; 
121.     } 
122.      
123. public static void main(String[] args) { 
124. new ClientMain();  
125. new Scanner(System.in); 
126. ""; 
127. do { 
128. "请输入命令:");  
129.             commandString = sc.nextLine(); 
130. while (!thisC.checkCommand(commandString));  
131.          
132. new SocketThread(); 
133.         a.start(); 
134.     } 
135. }