【初学与研发之NETTY】netty3之文件上传

7 篇文章 0 订阅

客户端:

package netty3.socket.client;

import static org.jboss.netty.channel.Channels.pipeline;

import java.io.File;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpClientCodec;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import org.jboss.netty.handler.codec.http.multipart.HttpDataFactory;
import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestEncoder;
import org.jboss.netty.handler.codec.http.multipart.InterfaceHttpData;
import org.jboss.netty.handler.stream.ChunkedWriteHandler;
import org.jboss.netty.util.CharsetUtil;

public class UploadFileClient
{
	private ClientBootstrap bootstrap = null;
	
	private ChannelFuture future = null;
	
	private HttpDataFactory factory = null;
	
	// 服务端处理完成后返回的消息
	private StringBuffer retMsg = new StringBuffer();

	public UploadFileClient()
	{
		bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
		bootstrap.setPipelineFactory(new UploadChannelFactory());

		// 连接超时时间为3s
		bootstrap.setOption("connectTimeoutMillis", 3000);
		
		future = bootstrap.connect(new InetSocketAddress("127.0.0.1", 2777));
		
		// 获得一个阈值,它是来控制上传文件时内存/硬盘的比值,防止出现内存溢出
		factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
	}
	
	/**
	 * 方法描述:关闭文件发送通道(为阻塞式)
	 */
	public void shutdownClient()
	{
		// 等待数据的传输通道关闭
		future.getChannel().getCloseFuture().awaitUninterruptibly();
		
		bootstrap.releaseExternalResources();
		
		// Really clean all temporary files if they still exist
		factory.cleanAllHttpDatas();
	}
	
	/**
	 * 方法描述:获取发送文件过程中服务端反馈的消息
	 * @return 服务端反馈的消息
	 */
	public String getRetMsg()
	{
		return retMsg.toString();
	}

	/**
	 * 方法描述:将文件上传到服务端
	 * @param file 待上传的文件
	 */
	public void uploadFile(File file)
	{
		if (!file.canRead())
		{
			return;
		}
		
		// Simple Post form: factory used for big attributes
		List<InterfaceHttpData> bodylist = formpost(file);
		if (bodylist == null)
		{
			return;
		}
		
		// Multipart Post form: factory used
		uploadFileToServer(file.getName(), factory, bodylist);
	}
	

	/**
	 * @param file
	 * @return
	 */
	private List<InterfaceHttpData> formpost(File file)
	{
		// Prepare the HTTP request.
		HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "");

		// Use the PostBody encoder
		HttpPostRequestEncoder bodyRequestEncoder = null;
		try
		{
			bodyRequestEncoder = new HttpPostRequestEncoder(factory, request, false);
			bodyRequestEncoder.addBodyAttribute("getform", "POST");
			bodyRequestEncoder.addBodyFileUpload("myfile", file, "application/x-zip-compressed", false);
		}
		catch(Exception e)
		{
			// should not be since args are not null
			e.printStackTrace();
			return null;
		}

		// Create the bodylist to be reused on the last version with Multipart support
		List<InterfaceHttpData> bodylist = bodyRequestEncoder.getBodyListAttributes();

		return bodylist;
	}
	
	/**
	 * Multipart example
	 */
	private void uploadFileToServer(String fileName, HttpDataFactory factory, List<InterfaceHttpData> bodylist)
	{
		// Wait until the connection attempt succeeds or fails.
		Channel channel = future.awaitUninterruptibly().getChannel();
		if (!future.isSuccess())
		{
			future.getCause().printStackTrace();
			bootstrap.releaseExternalResources();
			return;
		}

		// Prepare the HTTP request.
		HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, fileName);
		
		// 设置该属性表示服务端文件接收完毕后会关闭发送通道
		request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE);

		// Use the PostBody encoder
		HttpPostRequestEncoder bodyRequestEncoder = null;
		try
		{
			bodyRequestEncoder = new HttpPostRequestEncoder(factory, request, true);
			bodyRequestEncoder.setBodyHttpDatas(bodylist);
			bodyRequestEncoder.finalizeRequest();
		}
		catch(Exception e)
		{
			// should not be since no null args
			e.printStackTrace();
		}
		System.out.println("开始时间:"+System.currentTimeMillis());
		// send request
		channel.write(request);

		// test if request was chunked and if so, finish the write
		if (bodyRequestEncoder.isChunked())
		{
			channel.write(bodyRequestEncoder).awaitUninterruptibly();
		}

		// Now no more use of file representation (and list of HttpData)
		bodyRequestEncoder.cleanFiles();
	}

	private class UploadChannelFactory implements ChannelPipelineFactory
	{

		public ChannelPipeline getPipeline() throws Exception
		{
			ChannelPipeline pipeline = pipeline();

			pipeline.addLast("decoder", new HttpResponseDecoder());
			pipeline.addLast("encoder", new HttpRequestEncoder());
			pipeline.addLast("codec", new HttpClientCodec());
			pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
			pipeline.addLast("handler", new UploadClientHandler());			

			return pipeline;
		}
	}

	private class UploadClientHandler extends SimpleChannelUpstreamHandler
	{
		private boolean readingChunks;

		/**
		 * 方法描述:接收服务端返回的消息
		 * @param ctx 发送消息的通道对象
		 * @param e 消息发送事件对象
		 */
		public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception
		{
			if (!readingChunks)
			{
				HttpResponse response = (HttpResponse)e.getMessage();

				// 收到服务端反馈的消息,并且链接正常、且还有后续消息
				if (response.getStatus().getCode() == 200 && response.isChunked())
				{
					readingChunks = true;
				}
				else
				{
					// 服务端有反馈消息,但没有后续的消息了
					ChannelBuffer content = response.getContent();
					if (content.readable())
					{
						retMsg.append(content.toString(CharsetUtil.UTF_8));
					}
				}
			}
			else
			{
				HttpChunk chunk = (HttpChunk)e.getMessage();
				if (chunk.isLast())
				{
					// 服务端的消息接收完毕
					readingChunks = false;
				}
				else
				{
					// 连续接收服务端发过来的消息
					retMsg.append(chunk.getContent().toString(CharsetUtil.UTF_8));
				}
			}
		}

		/**
		 * 方法描述:消息接收或发送过程中出现异常
		 * @param ctx 发送消息的通道对象
		 * @param e 异常事件对象
		 */
		public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception
		{
			System.out.println("异常--:" + e.getCause());
			e.getChannel().close();
			
			// 有异常后释放客户端占用的通道资源
			shutdownClient();
		}
	}
}


 

服务端:

package netty3.socket.server;

import static org.jboss.netty.channel.Channels.pipeline;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.stream.ChunkedWriteHandler;

public class InitServer
{
	private static InitServer sockServer = null;

	private static ServerBootstrap bootstrap = null;

	public static InitServer getInstance()
	{
		if (sockServer == null)
		{
			sockServer = new InitServer();
		}
		return sockServer;
	}

	public InitServer()
	{
		bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));

		bootstrap.setPipelineFactory(new ChannelPipelineFactory()
		{
			public ChannelPipeline getPipeline() throws Exception
			{
				ChannelPipeline pipeline = pipeline();
				pipeline.addLast("decoder", new HttpRequestDecoder());
				pipeline.addLast("encoder", new HttpResponseEncoder());
				pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
				pipeline.addLast("handler", new ServerHandler());
				
				return pipeline;
			}

		});

		bootstrap.bind(new InetSocketAddress("127.0.0.1", 2777));
	}
	
	public void shutdownServer()
	{
		bootstrap.releaseExternalResources();
	}
}


 

package netty3.socket.server;

import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CACHE_CONTROL;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.DATE;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.EXPIRES;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.LAST_MODIFIED;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.TimeZone;

import javax.activation.MimetypesFileTypeMap;

import netty3.socket.client.SendMsgClient;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelFutureProgressListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.DefaultFileRegion;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.FileRegion;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.handler.codec.frame.TooLongFrameException;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.codec.http.multipart.Attribute;
import org.jboss.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import org.jboss.netty.handler.codec.http.multipart.DiskFileUpload;
import org.jboss.netty.handler.codec.http.multipart.FileUpload;
import org.jboss.netty.handler.codec.http.multipart.HttpDataFactory;
import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
import org.jboss.netty.handler.codec.http.multipart.InterfaceHttpData;
import org.jboss.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import org.jboss.netty.handler.ssl.SslHandler;
import org.jboss.netty.handler.stream.ChunkedFile;
import org.jboss.netty.util.CharsetUtil;

public class ServerHandler extends SimpleChannelHandler
{
    public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
    
    public static final String HTTP_DATE_GMT_TIMEZONE = "GMT";
    
    public static final int HTTP_CACHE_SECONDS = 60;
    
	private static final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); // Disk if size exceed MINSIZE
	
	private HttpPostRequestDecoder decoder;
	
	private HttpRequest request;
	
	private String receiveFileName = "";
	
	private Map<String, String> msgMap = new HashMap<String, String>();
	
	private boolean readingChunks = false;
	
	static
	{
		DiskFileUpload.baseDirectory = "/home/build1/file_test/";
	}
	
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception
	{
		if (e.getMessage() instanceof HttpRequest)
		{
			HttpRequest request = (DefaultHttpRequest)e.getMessage();
			String uri = sanitizeUri(request.getUri());
		
			System.out.println(request.isChunked());

			if (request.getMethod() == HttpMethod.POST)
			{
					// 接收客户端上传的文件
					receiveFileName = uri;
					this.request = request;
					
					// clean previous FileUpload if Any
					if (decoder != null)
					{
						decoder.cleanFiles();
						decoder = null;
					}
					
					// if GET Method: should not try to create a HttpPostRequestDecoder
					try
					{
						decoder = new HttpPostRequestDecoder(factory, request);
					}
					catch(Exception e1)
					{
						e1.printStackTrace();
						writeResponse(e.getChannel(), "接收文件信息时出现异常:" + e1.toString());
						Channels.close(e.getChannel());
						return;
					}
					
					if (!request.isChunked())
					{
		                             readHttpDataAllReceive(e.getChannel());
		                             writeResponse(e.getChannel(), "服务端文件接收完毕!");
		                        }
			}
		}
		else
		{
			// New chunk is received
			HttpChunk chunk = (HttpChunk)e.getMessage();
			// example of reading only if at the end
			if (!chunk.isLast())
			{
				try
				{
					decoder.offer(chunk);
				}
				catch(Exception e1)
				{
					e1.printStackTrace();
					writeResponse(e.getChannel(), "接收文件数据时出现异常:" + e1.toString());
					Channels.close(e.getChannel());
					return;
				}
	
				// example of reading chunk by chunk (minimize memory usage due to Factory)
				readHttpDataChunkByChunk();
			
			} else {
				readHttpDataAllReceive(e.getChannel());
				//writeResponse(e.getChannel(), "服务端数据接收完毕!");
				String sendMsg = msgMap.get("sendMsg");
				System.out.println("服务端收到消息:" + sendMsg);

				sendReturnMsg(ctx, HttpResponseStatus.OK, "服务端返回的消息!");
			}
		}
	}
	
	/**
	 * Example of reading all InterfaceHttpData from finished transfer
	 */
	private void readHttpDataAllReceive(Channel channel)
	{
		List<InterfaceHttpData> datas;
		try
		{
			datas = decoder.getBodyHttpDatas();
		}
		catch(Exception e1)
		{
			e1.printStackTrace();
			writeResponse(channel, "接收文件数据时出现异常:" + e1.toString());
			Channels.close(channel);
			return;
		}
		
		for (InterfaceHttpData data : datas)
		{
			writeHttpData(data);
		}
	}
	
	/**
	 * Example of reading request by chunk and getting values from chunk to chunk
	 */
	private void readHttpDataChunkByChunk()
	{
		try
		{
			while(decoder.hasNext())
			{
				InterfaceHttpData data = decoder.next();
				if (data != null)
				{
					// new value
					writeHttpData(data);
				}
			}
		}
		catch(EndOfDataDecoderException e1)
		{
			e1.printStackTrace();
		}
	}
	
	private void writeHttpData(InterfaceHttpData data)
	{
		if (data.getHttpDataType() == HttpDataType.FileUpload)
		{
			FileUpload fileUpload = (FileUpload)data;
			if (fileUpload.isCompleted())
			{
				try
				{
					Random r = new Random();
					StringBuffer fileNameBuf = new StringBuffer();
					fileNameBuf.append(DiskFileUpload.baseDirectory).append("U").append(System.currentTimeMillis());
					fileNameBuf.append(String.valueOf(r.nextInt(10))).append(String.valueOf(r.nextInt(10)));
					fileNameBuf.append(receiveFileName.substring(receiveFileName.lastIndexOf(".")));

					fileUpload.renameTo(new File(fileNameBuf.toString()));
				}
				catch(IOException e)
				{
					e.printStackTrace();
				}
				System.out.println("结束时间:"+System.currentTimeMillis());
			}
			else
			{
				System.out.println("\tFile to be continued but should not!\r\n");
			}
		}
		else if (data.getHttpDataType() == HttpDataType.Attribute) 
		{
			Attribute attribute = (Attribute)data;
			try
			{
				msgMap.put(attribute.getName(), attribute.getString());
			}
			catch(IOException e)
			{
				e.printStackTrace();
			}
		}
	}
	
	private void writeResponse(Channel channel, String retMsg)
	{
		// Convert the response content to a ChannelBuffer.
		ChannelBuffer buf = ChannelBuffers.copiedBuffer(retMsg, CharsetUtil.UTF_8);

		// Decide whether to close the connection or not.
		boolean close = HttpHeaders.Values.CLOSE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION))
				|| request.getProtocolVersion().equals(HttpVersion.HTTP_1_0)
				&& !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION));

		// Build the response object.
		HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
		response.setContent(buf);
		response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8");

		if (!close)
		{
			// There's no need to add 'Content-Length' header
			// if this is the last response.
			response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(buf.readableBytes()));
		}

		// Write the response.
		ChannelFuture future = channel.write(response);
		// Close the connection after the write operation is done if necessary.
		if (close)
		{
			future.addListener(ChannelFutureListener.CLOSE);
		}
	}
	
	private String sanitizeUri(String uri)
	{
		try
		{
			uri = URLDecoder.decode(uri, "UTF-8");
		}
		catch(UnsupportedEncodingException e)
		{
			try
			{
				uri = URLDecoder.decode(uri, "ISO-8859-1");
			}
			catch(UnsupportedEncodingException e1)
			{
				throw new Error();
			}
		}

		return uri;
	}

	/**
	 * 方法描述:设置请求响应的header信息
	 * @param response 请求响应对象
	 * @param fileToCache 下载文件
	 */
	private static void setContentTypeHeader(HttpResponse response, File fileToCache)
	{
		MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
		response.setHeader(CONTENT_TYPE, mimeTypesMap.getContentType(fileToCache.getPath()));
		
		SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
		dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));

		// Date header
		Calendar time = new GregorianCalendar();
		response.setHeader(DATE, dateFormatter.format(time.getTime()));

		// Add cache headers
		time.add(Calendar.SECOND, HTTP_CACHE_SECONDS);
		response.setHeader(EXPIRES, dateFormatter.format(time.getTime()));
		response.setHeader(CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS);
		response.setHeader(LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));
	}
	
	/**
	 * 方法描述:给客户端发送反馈消息
	 * @param ctx 发送消息的通道
	 * @param status 状态
	 * @param retMsg 反馈消息
	 */
	private static void sendReturnMsg(ChannelHandlerContext ctx, HttpResponseStatus status, String retMsg)
	{
		HttpResponse response = new DefaultHttpResponse(HTTP_1_1, status);
		response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8");
		response.setContent(ChannelBuffers.copiedBuffer(retMsg, CharsetUtil.UTF_8));

		// 信息发送成功后,关闭连接通道
		ctx.getChannel().write(response).addListener(ChannelFutureListener.CLOSE);
	}
	
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception
	{
		if (decoder != null)
		{
			decoder.cleanFiles();
		}
		System.out.println("连接断开:" + e.getChannel().getRemoteAddress().toString());
	}
	
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception
	{
		String remoteIp = e.getChannel().getRemoteAddress().toString();
		System.out.println(remoteIp.substring(1, remoteIp.indexOf(":")));
		System.out.println("收到连接:" + e.getChannel().getRemoteAddress().toString());
	}


	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception
	{
		Channel ch = e.getChannel();
		Throwable cause = e.getCause();
		if (cause instanceof TooLongFrameException)
		{
			return;
		}

		System.err.println("连接的通道出现异常:" + cause.toString());
		if (ch.isConnected())
		{
			System.out.println("连接还没有关闭!");
			ch.close();
		}
	}



}


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值