Netty 图片服务器搭建
Netty 是一个基于 Java 的异步事件驱动的网络应用框架,旨在快速开发可维护的高性能协议服务器和客户端,本文将详细介绍如何使用 Netty 搭建一个能够处理图片上传和下载请求的 HTTP 服务器。
一、准备工作
我们需要创建一个 Maven 项目,并在pom.xml
文件中添加 Netty 依赖,这里我们使用 Netty 的稳定版本 4.1.52.Final:
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.52.Final</version> </dependency>
二、服务端启动类
服务端的启动类主要负责初始化线程模型、配置网络参数并绑定端口,以下是一个简单的示例:
import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.*; import io.netty.util.CharsetUtil; import java.net.InetSocketAddress; public class ImageHttpServer { public void start(int port) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); // 主线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 从线程组 try { ServerBootstrap bootstrap = new ServerBootstrap(); // 创建引导器 bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 推荐 Netty 服务端采用 NioServerSocketChannel 作为 Channel 的类型 .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline() .addLast("codec", new HttpServerCodec()) // HTTP 编解码 .addLast("compressor", new HttpContentCompressor()) // HttpContent 压缩 .addLast("aggregator", new HttpObjectAggregator(65536)) // HTTP 消息聚合 .addLast("handler", new ImageHttpServerHandler()); // 自定义处理器 } }).childOption(ChannelOption.SO_KEEPALIVE, true); // 设置为 true 代表启用了 TCP SO_KEEPALIVE 属性,TCP 会主动探测连接状态,即连接保活 ChannelFuture f = bootstrap.bind().sync(); // 绑定端口并等待同步完成 System.out.println("Image HTTP Server started, listening on " + port); f.channel().closeFuture().sync(); // 关闭通道并释放所有资源 } catch (Exception e) { e.printStackTrace(); } finally { workerGroup.shutdownGracefully(); // 优雅地关闭工作线程组 bossGroup.shutdownGracefully(); // 优雅地关闭主线程组 } } public static void main(String[] args) throws Exception { new ImageHttpServer().start(8080); // 设置监听端口为 8080 } }
三、自定义处理器
在自定义处理器中,我们将处理 HTTP 请求,并根据请求类型(GET 或 POST)来响应相应的图片数据,以下是一个简单的示例:
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.*; import io.netty.util.CharsetUtil; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.net.URLDecoder; import java.nio.channels.FileChannel; public class ImageHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> { private static final String IMAGE_DIRECTORY = "path/to/your/image/directory"; // 指定图片存储目录 @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { if (HttpUtil.is100ContinueExpected(msg)) { send100Continue(ctx); } // 处理 GET 请求,返回图片文件内容 if (HttpUtil.isGet(msg)) { String uri = msg.uri(); if (uri.startsWith("/images/")) { String imagePath = IMAGE_DIRECTORY + URLDecoder.decode(uri.substring(7), "UTF-8"); File imageFile = new File(imagePath); if (imageFile.exists() && !imageFile.isDirectory()) { sendImage(ctx, imageFile); } else { sendError(ctx, NOT_FOUND); } } else { sendError(ctx, FORBIDDEN); } } else if (HttpUtil.isPost(msg)) { // 处理 POST 请求,保存上传的图片文件 HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), msg); FileUpload fileUpload = null; while (decoder.hasNext()) { InterfaceHttpData data = decoder.next(); if (data instanceof FileUpload) { fileUpload = (FileUpload) data; break; } } if (fileUpload != null) { String fileName = fileUpload.getFilename(); File directory = new File(IMAGE_DIRECTORY); if (!directory.exists()) { directory.mkdirs(); } File targetFile = new File(directory, fileName); try (FileChannel fileChannel = new FileOutputStream(targetFile).getChannel(); FileChannel sourceChannel = new RandomAccessFile(fileUpload.getFile(), "r").getChannel()) { sourceChannel.transferTo(0, sourceChannel.size(), fileChannel); } catch (IOException e) { e.printStackTrace(); sendError(ctx, BAD_REQUEST); return; } sendResponse(ctx, OK); } else { sendError(ctx, BAD_REQUEST); } } else { sendError(ctx, METHOD_NOT_ALLOWED); } } private static void send100Continue(ChannelHandlerContext ctx) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE); ctx.write(response); } private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } private static void sendResponse(ChannelHandlerContext ctx, HttpResponseStatus status) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } private static void sendImage(ChannelHandlerContext ctx, File imageFile) throws IOException { byte[] content = new byte[(int) imageFile.length()]; try (FileInputStream fis = new FileInputStream(imageFile)) { fis.read(content); } DefaultFullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "image/jpeg"); response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.length); response.content().writeBytes(content); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } }
四、测试与验证
启动服务器后,可以使用浏览器或 curl 命令来测试图片上传和下载功能。
上传图片:使用表单提交方式上传图片到/images/
目录。
下载图片:访问http://localhost:8080/images/{imageName}
以下载指定图片。
五、FAQs问答环节
Q1: Netty中的HttpRequest和HttpResponse由哪些部分组成?
A1: Netty中的HttpRequest由请求行、请求头和请求体三部分组成,HttpResponse由状态行、消息报头和响应正文三部分组成,具体如下表所示:
组件 | 描述 |
HttpRequest | |
请求行 | 以一个方法符开头,以空格分开,后面跟着请求的URI和协议的版本,格式为:Method Request-URI HTTP-Version CRLF,Method表示请求方法,Request-URI是一个统一资源标识符,HTTP-Version表示请求的HTTP协议版本,CRLF表示回车和换行。 |
请求头 | 包含多个键值对,用于传递客户端信息和服务器端配置。 |
请求体 | 可选部分,包含请求的主体数据,如POST请求中的表单数据。 |
HttpResponse | |
状态行 | 格式为:HTTP-Version Status-Code Reason-Phrase CRLF,其中HTTP-Version表示HTTP协议的版本,Status-Code表示服务器返回的响应状态码。 |
消息报头 | 包含多个键值对,用于传递服务器端信息和客户端配置。 |
响应正文 | 可选部分,包含响应的主体数据,如HTML页面、图片等。 |
Q2: Netty如何实现图片的上传和下载?
A2: Netty通过自定义处理器来实现图片的上传和下载,对于上传请求,Netty接收客户端发送的图片数据并保存到指定目录;对于下载请求,Netty读取指定目录下的图片文件并将其内容作为HTTP响应返回给客户端,具体实现细节如上文所述。