基于阻塞模式的简易javaHTTP服务器

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

// 此类实现 IP 套接字地址(IP 地址 + 端口号)。SocketAddress
public class SimpleHttpServer {
	private int port = 80;
	private ServerSocketChannel serverSocketChannel = null;
	private ExecutorService executorService;
	private static final int POOL_MULTIPLE = 4;

	public SimpleHttpServer() throws IOException {

		executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
				.availableProcessors() * POOL_MULTIPLE);
		serverSocketChannel = ServerSocketChannel.open();
		// 确保socket关闭之后,端口可以立即被另一个程序使用
		serverSocketChannel.socket().setReuseAddress(true);
		// 将服务器程序绑定到一个端口
		serverSocketChannel.socket().bind(new InetSocketAddress(port));
		System.out.println("服务器已经启动!");

	}

	public void service() {
		while (true) {
			SocketChannel socketChannel = null;
			try {
				socketChannel = serverSocketChannel.accept();
				executorService.execute(new Handler(socketChannel));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String args[]) throws IOException {
		new SimpleHttpServer().service();
	}

	class Handler implements Runnable {
		private SocketChannel socketChannel;

		public Handler(SocketChannel socketChannel) {
			this.socketChannel = socketChannel;
		}

		@Override
		public void run() {
			handle(socketChannel);
		}

		public void handle(SocketChannel socketChannel) {
			try {
				Socket socket = socketChannel.socket();
				System.out.println("接收到用户连接,来自:" + socket.getInetAddress()
						+ ":" + socket.getPort());
				ByteBuffer buffer = ByteBuffer.allocate(1024);
				socketChannel.read(buffer);// 接收HTTP请求,假定其长度不超过1024个字节
				buffer.flip();// 将极限设置为位置,再把位置设置为0
				String request = decode(buffer);
				System.out.println(request);// 打印HTTP请求

				// 生成HTTP响应结果
				StringBuffer sb = new StringBuffer("HTTP/1.1 200 OK\r\n");
				sb.append("Content-Type:text/html\r\n\r\n");
				socketChannel.write(encode(sb.toString()));// 输出响应头

				FileInputStream in;
				// 获得HTTP请求的第一行 解析请求

				String firstLineOfRequest = request.substring(0,
						request.indexOf("\r\n"));
				if (firstLineOfRequest.indexOf("login.htm") != -1) {
					System.out.println("hehe");
					in = new FileInputStream(new File("D://root/login.htm"));
				}
				LinkedList 
				else
					in = new FileInputStream(new File("D://root/hello.htm"));
				// 通道Channel用来连接缓冲区与数据源或者数据目的地
				// FileChannel类是Channel的实现类,代表这一个与文件相连的通道。
				// 该类实现了ByteChannel、ScatteringByteChannel和GatheringByteChannel接口,支持读写
				// 、分散读和集中写操作,FIleChannel没有公开的构造方法,因此客户程序不能用new来创建它的实例
				// 不过在FileInputStream和FileOutStream当中提供了getChannel()返回FileChannel的实例
				FileChannel fileChannel = in.getChannel();
				// 将字节从此通道的文件传输到给定的可写入字节通道。
				/*
				 * public abstract long transferTo(long position, long count,
				 * WritableByteChannel target) throws IOException
				 */

				/*
				 * 将字节从此通道的文件传输到给定的可写入字节通道。
				 * 
				 * 试图读取从此通道的文件中给定 position 处开始的 count
				 * 个字节,并将其写入目标通道。此方法的调用不一定传输所有请求的字节;是否传输取决于通道的性质和状态。如果此通道的文件从给定的
				 * position 处开始所包含的字节数小于 count 个字节,或者如果目标通道是非阻塞的并且其输出缓冲区中的自由空间少于
				 * count 个字节,则所传输的字节数要小于请求的字节数。
				 * 
				 * 此方法不修改此通道的位置。如果给定的位置大于该文件的当前大小,则不传输任何字节。如果目标通道中有该位置,则从该位置开始写入各字节
				 * ,然后将该位置增加写入的字节数。
				 * 
				 * 与从此通道读取并将内容写入目标通道的简单循环语句相比,此方法可能高效得多。很多操作系统可将字节直接从文件系统缓存传输到目标通道
				 * ,而无需实际复制各字节。
				 * 
				 * 参数: position - 文件中的位置,从此位置开始传输;必须为非负数 count -
				 * 要传输的最大字节数;必须为非负数 target - 目标通道 返回: 实际已传输的字节数,可能为零
				 */
				long num = fileChannel.transferTo(0, fileChannel.size(),
						socketChannel);
				System.out.println(num);
				// fileChannel.close();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					if (socketChannel != null)
						socketChannel.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		/*
		 * java.nio.Charset类的每个实例代表特定的字符编码类型。 将字节序列转换为字符串的过程称之为解码;decode
		 * 将字符串转换为字节序列的过程称之为编码 ;encode Charset的静态forName(String
		 * encode)方法返回一个Charset对象 代表encode指定的编码类型
		 */
		private Charset charset = Charset.forName("GBK");

		public String decode(ByteBuffer buffer) {
			// decode(ByteBuffer buffer) 将ByteBuffer构成的字节序列进行解码 生成CharBuffer
			CharBuffer charBuffer = charset.decode(buffer);
			return charBuffer.toString();
		}

		public ByteBuffer encode(String str) { // 编码
			return charset.encode(str);
		}
	}
}

通过输入http://localhost:80/login.htm可以访问本地的这个htm文件,

这个编程是基于阻塞模式的,ServerSocketChannel是继承与SelecteableChannel的子类,默认是阻塞式的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章