`

利用NIO建立Socket服务器

    博客分类:
  • Java
阅读更多

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



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代码如下:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerMain {
	
	public static void main(String[] args) {

		class SocketThread extends Thread{
			
			private Socket socket;
			private byte[] buf;
			private int len = 0;
			public SocketThread(Socket socket) { 
				this.socket = socket;
				buf = new byte[1024];
			}

			@Override
			public void run() {
				try {   
					DataInputStream dis = new DataInputStream(socket.getInputStream());
					DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); 
					
					//String command = dis.readUTF(); 
					len = dis.read(buf);
					String command = new String(buf,0,len);
					
					System.out.println("command=="+command);
					
					String[] temp =command.split(" ");
					command = temp[0];  //命令  是put还是get
					String filename = temp[1];  //文件名
					
					File file = new File("C:\\",filename);//假设放在C盘
					if(command.equals("get")){
						if(!file.exists()){
							//dos.writeUTF("notexists");
							dos.write("notexists".getBytes());
							dos.flush();
							System.out.println("没有这个文件,无法提供下载!");
							dis.close();
							dos.close();
							socket.close();
							return;
						}
						//dos.writeUTF("DownloadReady "+file.length()); 
						dos.write("准备下载".getBytes());
						dos.flush();
						
						System.out.println("正在接受文件下载...");
						DataInputStream fis = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); 
  
		                while ((len = fis.read(buf))!= -1) { 
		                    dos.write(buf, 0, len);
		                }
		                dos.flush();
		                
		                fis.close();     
		                System.out.println("文件传输完成");
					}
					else { 
						//dos.writeUTF("UploadReady"); 
						dos.write("UploadReady".getBytes());
						dos.flush();
						
						System.out.println("正在接受文件上传...");
						DataOutputStream fileOut = 
							new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
 
						while ((len = dis.read(buf))!=-1) {   
			                fileOut.write(buf, 0, len);
			            }
						System.out.println("上传完毕!");
						fileOut.close(); 
					}
					dis.close();
	                dos.close();
	                socket.close(); 
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
		}
		
		System.out.println("等待客户端连接....");
		int index = 0;
		try {
			ServerSocket server = new ServerSocket(9527,300); //端口号9527  允许最大连接数300
			while (true) {
				Socket socket = server.accept();
				System.out.println("收到第"+(++index)+"个连接");
				new SocketThread(socket).start(); //对每个连接创建一个线程
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

 

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

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer; 
import java.nio.CharBuffer;
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.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;

public class NewSocketServer {
 
	private static final int port = 9527;
	private Selector selector;
	private ByteBuffer clientBuffer = ByteBuffer.allocate(1024);  
	private CharsetDecoder decoder = Charset.forName("GB2312").newDecoder();
	private CharsetEncoder encoder = Charset.forName("GB2312").newEncoder(); 
	//编码解码格式设置成GBK也行.UTF-8不行,中文乱码  (前提都是客户端没有设置任何编码解码格式)
	
	public void setListener() throws Exception{
		
		selector = Selector.open(); //打开选择器   
		
		ServerSocketChannel server = ServerSocketChannel.open();  //定义一个 ServerSocketChannel通道
        server.socket().bind(new InetSocketAddress(port));  //ServerSocketChannel绑定端口  
        server.configureBlocking(false);   //配置通道使用非阻塞模式
        server.register(selector, SelectionKey.OP_ACCEPT); //该通道在selector上注册  接受连接的动作
        
        while(true)
        {    
            selector.select();   //select() 会阻塞,直到在该selector上注册的channel有对应的消息读入
            Iterator iter = selector.selectedKeys().iterator();   
            while (iter.hasNext()) {    
                SelectionKey key = (SelectionKey) iter.next();   
                iter.remove();  // 删除此消息 
                process(key);   // 当前线程内处理。(为了高效,一般会在另一个线程中处理此消息)
            }   
        }   
	}
	
	 private void process(SelectionKey key) throws IOException {   
	        if (key.isAcceptable()) { // 接收请求   
	            ServerSocketChannel server = (ServerSocketChannel) key.channel();   
	            SocketChannel channel = server.accept();//类似于io的socket,ServerSocketChannel的accept函数返回 SocketChannel
	            channel.configureBlocking(false);   //设置非阻塞模式   
	            SelectionKey sKey = channel.register(selector, SelectionKey.OP_READ); 
	            sKey.attach("read_command"); //这儿接收到连接请求之后可以为每个连接设置一个ID
	        } 
	        else if (key.isReadable()) { // 读信息    
	            SocketChannel channel = (SocketChannel) key.channel();   
	            String name = (String) key.attachment(); 
	            if(name.equals("read_command")){
	            	int count = channel.read(clientBuffer); 
		            if (count > 0) {   
		                clientBuffer.flip();   
		                CharBuffer charBuffer = decoder.decode(clientBuffer);   
		                String command = charBuffer.toString();   
		                
		                //command形如:get abc.png 或者  put aaa.png
		                System.out.println("command===="+command);  //得到客户端传来的命令 
		                
		                String[] temp =command.split(" ");
						command = temp[0];  //命令  是put还是get
						String filename = temp[1];  //文件名
		                
		                SelectionKey sKey = channel.register(selector,SelectionKey.OP_WRITE);   
		                if(command.equals("put"))sKey.attach("UploadReady#"+filename);  //要保护该通道的文件名
		                else if(command.equals("get")){ 
		                	if(!new File("C:\\",filename).exists()){ //假设文件都是在C盘根目录
								System.out.println("没有这个文件,无法提供下载!");
								sKey.attach("notexists"); 
							}
		                	else sKey.attach("DownloadReady#"+filename); //要保护该通道的文件名
		                }
		            } else {   
		                channel.close();   
		            }   
	            }
	            else if(name.startsWith("read_file")){//这儿可以新开一个线程     文件操作也可以用NIO 
	            	DataOutputStream fileOut = 
						new DataOutputStream(
								new BufferedOutputStream(
										new FileOutputStream(
												new File("C:\\",name.split("#")[1]))));
 
					int passlen = channel.read(clientBuffer);  
					while (passlen>=0) {   
						clientBuffer.flip();  
		                fileOut.write(clientBuffer.array(), 0, passlen); 
		                passlen = channel.read(clientBuffer);
		            }
					System.out.println("上传完毕!");
					fileOut.close(); 
					channel.close();
	            }
	            clientBuffer.clear();   
	        } 
	        else if (key.isWritable()) { // 写事件   
	            SocketChannel channel = (SocketChannel) key.channel();   
	            String flag = (String) key.attachment();    
	            if(flag.startsWith("downloading")){//这儿可以新开一个线程   文件操作也可以用NIO
	            	DataInputStream fis = new DataInputStream(
	            			new BufferedInputStream(
	            					new FileInputStream(
	            							new File("C:\\",flag.split("#")[1])))); 
	            	 
	                byte[] buf = new byte[1024];
                    int len =0; 
	                while ((len = fis.read(buf))!= -1) { 
	                    channel.write(ByteBuffer.wrap(buf, 0, len));  
	                }  
	                fis.close();     
	                System.out.println("文件传输完成");
	            	channel.close();
	            }
	            else if(flag.equals("notexists")){ 
	            	//channel.write(encoder.encode(CharBuffer.wrap(flag)));   
	            	channel.write(ByteBuffer.wrap(flag.getBytes())); //不用编码也行    客户端直接接收    中文也不是乱码
	            	channel.close();
	            }
	            else if(flag.startsWith("UploadReady")){ 
	            	channel.write(encoder.encode(CharBuffer.wrap("UploadReady"))); 
	            	
	            	//这儿如果不重新注册该通道的读操作    selector选择到该通道的将继续永远是写操作,也就无法跳转到上面的接受上传的处理
	            	SelectionKey sKey =channel.register(selector, SelectionKey.OP_READ);//register是覆盖的????!!!
	            	sKey.attach("read_file#"+flag.split("#")[1]);
	            	//key.attach("read_file#"+flag.split("#")[1]); //select不到读操作
	            }
	            else if(flag.startsWith("DownloadReady")){ 
	            	channel.write(ByteBuffer.wrap("准备下载".getBytes())); 
	            	//channel.write(encoder.encode(CharBuffer.wrap("准备下载")));   
	            	key.attach("downloading#"+flag.split("#")[1]);
	            } 
	        }  
	    }   
	
	public static void main(String[] args) {
		
        try {
        	 System.out.println("等待来至" + port + "端口的客户端连接....."); 
			new NewSocketServer().setListener();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

}

 客户端代码如下:

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

public class ClientMain {

	private   int ServerPort = 9527;
	private   String ServerAddress = "192.168.1.154";
	private   String GetOrPut = "get";   
	private   String local_filename = ""; 
	private   String remote_filename  = ""; 
	private   byte[] buf;
	private   int len;
	class SocketThread extends Thread{
		
		@Override
		public void run() {
			 try {
				
				File file = new File("C:\\",local_filename); //假设文件放在C盘
				if(!file.exists()&&GetOrPut.equals("put")){ 
					System.out.println("本地没有这个文件,无法上传!"); 
					return;
				} 
				
				InetAddress loalhost = InetAddress.getLocalHost();
				Socket socket = new Socket(ServerAddress,ServerPort,loalhost,44);
											//服务器IP地址  端口号   本机IP 本机端口号
				DataInputStream dis = new DataInputStream(socket.getInputStream());
				DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
				 
				//dos.writeUTF(GetOrPut+" "+remote_filename);//服务器端如果是io的socket,writeUTF和writeUTF对接
				dos.write((GetOrPut+" "+remote_filename).getBytes());
				dos.flush(); 
			  
				//String tempString = dis.writeUTF(); 
				buf = new byte[1024];
				len = dis.read(buf);
				String tempString = new String(buf,0,len);//服务器反馈的信息
				
				//System.out.println(tempString); 
				if(tempString.equals("notexists")){
					System.out.println("服务器没有这个文件,无法下载!"); 
					dos.close();
					dis.close();
					socket.close();
					return;
				}
				
				if(tempString.startsWith("准备下载")){  
					DataOutputStream fileOut = 
						new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
 
					while ((len = dis.read(buf))!=-1) {   
		                fileOut.write(buf, 0, len);
		            }
					System.out.println("下载完毕!");
					fileOut.close();
					dos.close();
					dis.close();
					socket.close();
				}
				else if(tempString.equals("UploadReady")){  
					System.out.println("正在上传文件.......");
					DataInputStream fis = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); 
					  
	                while ((len = fis.read(buf))!= -1) {  
	                    dos.write(buf, 0, len);
	                }
	                dos.flush();
	                System.out.println("上传完毕!");
	                fis.close();
	                dis.close();
	                dos.close();
	                socket.close();
				}
				
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
	}
	
	public boolean checkCommand(String command)
	{ 
		if(!command.startsWith("put")&&!command.startsWith("get")){
			System.out.println("输入命令错误");
			return false;
		}
		
		int index = -1;
		String temp = "";
		String[] tempStrings = null;
		
		if((index=command.indexOf("-h"))>0){
			temp = command.substring(index+3);
			temp = temp.substring(0, temp.indexOf(' '));
			ServerAddress = temp;
		}
		if((index=command.indexOf("-p"))>0){
			temp = command.substring(index+3);
			temp = temp.substring(0, temp.indexOf(' '));
			ServerPort = Integer.valueOf(temp);
		}
		
		tempStrings = command.split(" ");
		if(command.startsWith("put")){
			GetOrPut = "put";
			local_filename = tempStrings[tempStrings.length-2];
			remote_filename = tempStrings[tempStrings.length-1];
		}
		else if(command.startsWith("get")){
			GetOrPut = "get";
			local_filename = tempStrings[tempStrings.length-1];
			remote_filename = tempStrings[tempStrings.length-2];
		}
		
		return true;
	}
	
	public static void main(String[] args) {
		ClientMain thisC= new ClientMain(); 
		Scanner sc = new Scanner(System.in);
		String commandString = "";
		do {
			System.out.println("请输入命令:"); 
			commandString = sc.nextLine();
		} while (!thisC.checkCommand(commandString)); 
		
		ClientMain.SocketThread a = thisC.new SocketThread();
		a.start();
	 
	}

}

 

 

 

  • 大小: 45 KB
分享到:
评论
2 楼 lishuoying 2013-10-08  
读取命令有小小问题

int count = channel.read(clientBuffer);   
                    if (count > 0) { 
//命令解释
 }


channel.read(clientBuffer) 有可能接收到命令字节的一部分,命令解释会出问题
1 楼 chinesejie 2012-11-07  
同学,您的程序有bug,我帮你调了几天了,终于搞定。
用nio写的server这段。
while (passlen>=0) {    
                        clientBuffer.flip();   
                        fileOut.write(clientBuffer.array(), 0, passlen);  
                        passlen = channel.read(clientBuffer); 
                    }

应嘎是这样的逻辑:
while (passlen>=0) {  
if(passlen!=0) { 
                        clientBuffer.flip();
}  
                        fileOut.write(clientBuffer.array(), 0, passlen);  
                        passlen = channel.read(clientBuffer); 
                    }   
这样传大文件也不都会错了。

相关推荐

    《NIO与Socket编程技术指南》_高洪岩

    通过阅读《NIO与Socket编程技术指南》,读者不仅可以理解NIO和Socket的基本原理,还能学习到如何在实际项目中有效地利用这些技术,提升网络应用的性能和可扩展性。这本书对于希望深入学习Java网络编程的开发者来说,...

    java NIO socket聊天

    Java NIO(Non-blocking Input/Output)是一种在...在设计聊天服务器时,利用NIO可以有效地处理大量并发用户,降低CPU压力,提高系统整体性能。同时,单线程模型简化了并发控制的复杂性,使得程序更易于理解和维护。

    java socket nio 研究

    总的来说,Java Socket结合NIO,能够构建出高性能的网络应用,尤其适用于需要处理大量并发连接的场景,如聊天服务器、流媒体服务等。`NonBlockingServer.java`和`Client.java`的代码分析和实践,可以帮助我们更好地...

    socket通信NIO代理模式demo实例

    本实例"socket通信NIO代理模式demo"将展示如何利用NIO来构建一个高性能的代理服务器。 代理模式是一种设计模式,它允许我们创建一个代理对象来控制对原对象的访问。在NIO代理模式中,代理服务器作为客户端与目标...

    android-socket-nio-master.zip

    NIO(非阻塞I/O)是Java提供的一个替代传统I/O的API,主要特点是允许程序在无需等待数据准备就绪时执行其他任务,从而提高系统资源利用率和整体性能。在Android中,NIO适用于服务器端需要处理大量短连接或长连接的...

    Mina NIO Socket

    Mina,全称为“Java Network Application Architecture”,是一个高度可扩展的网络通信框架,它利用Java NIO(New I/O)API来实现非阻塞I/O,从而提高服务器处理大量并发连接的能力。Mina的目标是简化网络服务开发,...

    从Jetty、Tomcat和Mina中提炼NIO构架网络服务器的经典模式

    3. **处理请求**:为了防止业务处理阻塞Selector,使用多线程分离监听和处理任务,最大化利用NIO的异步特性。 接下来是Tomcat的NIO实现: 1. **NioEndpoint**:同样负责组件的组合,包括Acceptor和Poller。 2. **...

    Java Socket学习---nio实现阻塞多线程通信

    1. 连接服务器:客户端通过`SocketChannel.open()`创建通道,并调用`connect()`方法与服务器建立连接。 2. 发送数据:使用`ByteBuffer`包装要发送的数据,然后通过`SocketChannel.write()`方法发送。 3. 接收数据:...

    netty5实现的socket服务器

    总的来说,Netty 5 实现的 Socket 服务器利用其高效的 I/O 模型和灵活的架构,能够有效地支持公司的实际项目,为 APP 提供稳定可靠的长连接服务。尽管在本示例中没有具体提及 `netty5socket` 压缩包的文件内容,但...

    net.rar_java 服务器_net_socket 服务器_分布式_多客户端

    总的来说,"net.rar_java 服务器_net_socket 服务器_分布式_多客户端"的主题涵盖了网络服务器开发的核心要素,包括用Java实现的Socket服务器、处理多客户端的能力以及在分布式环境中的应用。理解并掌握这些知识点...

    用Netty实现NIO Socket聊天系统示例

    在本示例中,我们将深入探讨如何利用Netty实现一个基于NIO(非阻塞I/O)的Socket聊天系统。NIO是一种I/O模型,它允许Java程序在不阻塞线程的情况下处理多个输入/输出流,从而提高并发性能。 首先,Netty的核心组件...

    Java_Socket开发高并发小型服务器

    Java Socket 开发高并发小型服务器涉及的核心概念是网络编程中的Socket技术,以及如何利用Java语言构建能够处理大量并发连接的服务端。首先,Socket是网络通信中的一个基础概念,它为两台计算机之间的通信提供了接口...

    Java基于Socket文件传输示例:服务器端和客户端

    在Java编程中,Socket是网络通信的基础,它允许两个网络应用程序之间进行双向通信。...这个示例展示了如何在服务器端创建一个接收连接并发送文件的服务器,以及如何在客户端建立连接并接收文件的简单流程。

    socket多线程支持客户端服务端长连接互发消息

    - 创建一个`Socket`实例,指定服务器的IP地址和端口号,调用`connect()`方法建立连接。 - 获取`Socket`的输入和输出流(`InputStream`和`OutputStream`),使用它们进行数据的读写操作。 3. **双向通信**: - ...

    Android-AndroidAsync异步SocketHTTP(客户端服务器)WebSocket和socket.io库

    AndroidAsync库就利用了NIO来优化Socket通信,减少了内存消耗和CPU占用。 HTTP是互联网上应用最广泛的一种网络协议,通常用于客户端与服务器之间的数据交换。在Android中,我们可以通过HttpURLConnection或第三方库...

    基于NIO的Java聊天室2

    在本项目"基于NIO的Java聊天室2"中,我们深入探讨了如何利用Java的非阻塞I/O(New Input/Output,NIO)框架来实现一个多人在线聊天室。NIO是一种高效的I/O模型,它允许程序在不阻塞主线程的情况下处理输入和输出操作...

    socket-nio-single-reactor.zip

    这使得NIO在资源利用率和性能上有了显著提升。 在"socket-nio-single-reactor.zip"中的代码示例,我们可以预期以下几个关键组件: 1. **ServerBootstrap**:这是Netty框架(或者可能是自定义实现)中的一个类,...

    基于NIO非阻塞的java聊天demo(支持单聊和群聊)

    在这个基于NIO非阻塞的Java聊天demo中,我们将会看到如何利用NIO实现一个支持单聊和群聊的应用。 首先,NIO的核心组件包括Channel、Buffer、Selector和Pipe。在传统的IO模型中,数据是从流的一端流向另一端,而在...

    java_Nio_server_and_j2me_client.rar_J2ME SERVER_NIO_j2me_j2me ni

    学习这个项目可以帮助开发者理解如何在资源受限的环境中利用NIO提高网络通信效率,以及如何在J2ME客户端和Java NIO服务器之间进行数据交换。对于Java和J2ME开发者来说,这是一个很好的实战案例,能够深入理解NIO机制...

Global site tag (gtag.js) - Google Analytics