服务器开发-概述

想做一个简单的服务器,用来更好的了解web交互模式。


http1.0 简单服务器 :三个文件

package com.httpserver;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

import javax.swing.JTextArea;
import javax.swing.JTextField;


public class HttpResponse extends Thread {
	Socket socket;
	JTextArea ta;
	PrintStream pout;
	boolean isHttp1 = false;
	String path = null;
	HttpResponse(Socket socket, JTextArea ta,JTextField path)
	{
		this.socket = socket;
		this.ta = ta;
		this.path = path.getText();
	}
	
	public void run()
	{
		try {
		
			BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			pout = new PrintStream(socket.getOutputStream());
			String requestLine = br.readLine();
			//String requestCmds = ""+requestLine;
			if(requestLine==null){
				error(400,"Empty Request");
			}
			else{
				ta.append("Http请求,来自:["+socket.getInetAddress()+":"+socket.getPort()+"]   "+requestLine+"\n");
			}
			if(requestLine.toLowerCase().indexOf("http/1.")!=-1){
				isHttp1 = true;
			}
			String[] request = requestLine.split(" ");
			if(request.length<2)
			{
				error(400,"Bad Request");
			}
			String str1 = request[0];
			if(str1.equals("GET")){
				serveFile(request[1]);
			}
			else{
				error(400, "Bad Request");
				ta.append("Bad Request");
			}
			
			socket.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
			return;
		}
		
	}
	
	
	private void error(int erorcd, String erortx) {
		erortx = "<html><h1>" + erortx + "</h1></html>";
		if (isHttp1) {
			pout.println("HTTP/1.0 " + erorcd + " " + erortx);
			pout.println("Content-type: text/html");
			pout.println("Content-length: " + erortx.length() + "\n");
		}
		pout.println(erortx);
	}
	
	private void serveFile(String requestPath)  {
		if (requestPath.equals("/")){
			/**
			 * 取首页文件,首页文件可以为index.html或index.htm
			 */
			requestPath = "/index.html";
			if(path==null){
				path=new File("").getAbsolutePath();
			}
			if(!new File(path+requestPath).exists()){
				requestPath="/index.htm";
			}			
		}				
		try {
			sendFileData(requestPath);
			ta.append("文件传输成功 !       "+requestPath+"\n");
		} catch (Exception e) {
			error(404, "");
			ta.append("请求文件不存在\n");
		}		
	}
	
	private void sendFileData(String requestPath) throws IOException,FileNotFoundException {
		InputStream inputstream = new FileInputStream(path+requestPath);
		if (inputstream == null)
			throw new FileNotFoundException(requestPath);		
		if (isHttp1) {
			pout.println("HTTP/1.0 200 Document follows");
			pout.println("Content-length: " + inputstream.available());
			
			if (requestPath.endsWith(".gif"))
				pout.println("Content-type: image/gif");
			else if (requestPath.endsWith(".jpg"))
				pout.println("Content-type: image/jpeg");
			else if (requestPath.endsWith(".png") || requestPath.endsWith(".htm"))
				pout.println("Content-Type: text/png");
			
			else if (requestPath.endsWith(".css") || requestPath.endsWith(".htm"))
				pout.println("Content-Type: text/css");
			else if (requestPath.endsWith(".js") || requestPath.endsWith(".htm"))
				pout.println("Content-Type: text/javascript");

			else if (requestPath.endsWith(".html") || requestPath.endsWith(".htm"))
				pout.println("Content-Type: text/html");

			else
				pout.println("Content-Type: application/octet-stream");
			
			pout.println("Connection: Keep-Alive");//println 表示写完自带换行
			/*****下面这个换行回车,必须要有 这个是报头 格式******/
			pout.println();
			/***** 但是这个 只需要一个换行回车就行了 **/
			//pout.println();

           
			
		}
		/*缓冲区设为8K*/
		byte[] is = new byte[8*1024];
		/*实际测试 缓冲区 太小了,请求一张 大于 8K 的图片就 无法传输了,好像不是这个问题*/
		//byte[] is = new byte[300*1024];

		
		int length=0;
		while((length=inputstream.read(is))!=-1){
			pout.write(is, 0, length);
		}
		inputstream.close();
		pout.flush();
	}

}
package com.httpserver;

import java.net.*;

import javax.swing.*;

public final class ServerThread extends Thread {
	int port;
	JTextArea display;
	JTextField status;
	JTextField direc;
	//JTextField ipAdd;
	ServerSocket listener = null;
	ServerThread (int port, JTextArea display, JTextField status,JTextField direc,JTextField ipAdd) throws Exception
	{
		this.port = port;
		this.display = display;
		this.status = status;
		this.direc = direc;
		byte[] ip = new byte[4];
		String ipp = ipAdd.getText();
		//System.out.println(ipp);
		String[] ip1 = ipp.split("\\.");
		for(int i = 0;i<ip1.length;i++){
			try{
				ip[i] = (byte) Integer.parseInt(ip1[i]);
			}
			catch(Exception e){
				ip[i] = -1;
			}
			//System.out.println(ip1[i]);
		}
		//System.out.println(ip[0]+" "+ip[1]+" "+ip[2]+" "+ip[3]);
		//this.ipAdd = ipAdd;
		try{
			listener = new ServerSocket(port,20, InetAddress.getByAddress(ip));
		}
		catch(Exception e){
			
			listener = new ServerSocket(port,20);
			display.append("无效的IP地址,已使用默认地址:"+InetAddress.getLocalHost().getHostAddress()+"\n");
			ipAdd.setText(InetAddress.getLocalHost().getHostAddress());
		}
		display.append("服务器已开启,端口:"+port+"\n");
		status.setText("服务器已开启。 "+ipAdd.getText()+":"+port+"  "+direc.getText());
	}
	
	public void run()
	{
		
		
		while(true)
		{
			try{
				Socket socket = listener.accept();
				/*for(String s = new BufferedReader(new InputStreamReader(socket.getInputStream())).readLine();s!="\n";
						s = new BufferedReader(new InputStreamReader(socket.getInputStream())).readLine()){
					display.append(new BufferedReader(new InputStreamReader(socket.getInputStream())).readLine());
				}
				*/
				/*BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				for(String s = br.readLine();s!=null;s = br.readLine()){
					display.append(s+"\n");
				}*/
				new HttpResponse(socket, display, direc).start();
			}
			catch(Exception e){
				display.append(e+"\n");
			}
		}
	}
	
}
package com.httpserver;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.plaf.synth.SynthStyle;

public class Surface implements ActionListener {

	JFrame jf = new JFrame("Web Server");
	JLabel iP = new JLabel("IP: ");
	JLabel port = new JLabel("Port: ");
	JTextField ipAdd = new JTextField(15);
	JTextField portNum = new JTextField(4);
	JButton start = new JButton("Start");
	JButton over = new JButton("Stop");
	JLabel direct = new JLabel("Main Directory: ");
	JTextField directory = new JTextField(24);
	JButton br = new JButton("...");
	// JLabel diary = new JLabel("Diary:");
	JTextArea ta = new JTextArea(10, 40);
	// JTextField outDirct = new JTextField(15);
	// JTextField outIp = new JTextField(8);
	JTextField outStatus = new JTextField(40);
	// JLabel status = new JLabel("Status:");
	ServerThread listener = null;
	boolean hasStarted = false;
	String ipp;

	public void init() {
		JPanel jp1 = new JPanel();
		jp1.add(iP);
		jp1.add(ipAdd);
		jp1.add(port);
		jp1.add(portNum);
		jp1.add(start);
		jp1.add(over);
		JPanel jp2 = new JPanel();
		jp2.add(direct);
		jp2.add(directory);
		jp2.add(br);
		// JPanel jp3 = new JPanel();
		// jp3.add(diary);
		// JScrollPane jsp = new JScrollPane();
		// jsp.add(ta);
		Border bb = BorderFactory.createEtchedBorder();
		Border tb1 = BorderFactory.createTitledBorder(bb, "Console");
		Border tb2 = BorderFactory.createTitledBorder(bb, "Diary");
		JScrollPane jsp = new JScrollPane(ta);
		JPanel jp4 = new JPanel();
		jp4.add(jsp);
		jp4.setBorder(tb2);
		ta.setLineWrap(true);
		ta.setEditable(false);
		start.addActionListener(this);
		over.addActionListener(this);
		br.addActionListener(this);
		Box jb1 = Box.createVerticalBox();
		jb1.add(jp1);
		jb1.add(jp2);
		jb1.setBorder(tb1);
		directory.setText("/Users/hello/Sites/testwebsites");
		// Box jb2 = Box.createVerticalBox();
		// jb2.add(jp3);
		// jb2.add(jp4);
		Box jb = Box.createVerticalBox();
		// jb.setBorder(bb);
		outStatus.setBackground(jf.getBackground());
		outStatus.setBorder(null);
		outStatus.setEditable(false);
		JPanel jp5 = new JPanel();
		jp5.setBorder(bb);
		// jp5.add(status);
		jp5.add(outStatus);
		outStatus.setText("服务器已停止");
		try {
			ipAdd.setText(InetAddress.getLocalHost().getHostAddress());
		} catch (Exception e) {
			ipAdd.setText("0.0.0.0");
		}
		jb.add(jb1);
		jb.add(jp4);
		jb.add(jp5);
		// jb.add(jp3);
		// jb.add(jp4);
		jf.add(jb);
		// jf.add(jp1);
		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jf.setResizable(false);
		jf.pack();
		jf.setVisible(true);

	}

	public void startServer() {
		start.setEnabled(false);
		over.setEnabled(true);
		int port = 8080;
		try {
			port = Integer.parseInt(portNum.getText());
		} catch (Exception e) {
			ta.append("端口号异常,已使用8080端口。\n");
			portNum.setText("8080");
		}
		if (hasStarted && port == listener.port) {
			listener.resume();
			ta.append("服务器已开启,端口:" + listener.port + "\n");
			outStatus.setText("服务器已开启。" + ipp + ":" + port + "  " + directory.getText());
			// ipAdd.setText(InetAddress.getLocalHost().getHostAddress());
			return;
		}

		try {
			listener = new ServerThread(port, ta, outStatus, directory, ipAdd);
			ipp = ipAdd.getText();
		} catch (Exception e) {
			ta.append("端口已被其他程序占用,请重试。\n");
			listener = null;
			hasStarted = false;
		}
		if (listener == null) {
			start.setEnabled(true);
		} else {
			hasStarted = true;
			listener.start();
		}
	}

	public void exitServer() {
		ta.append("服务器关闭。\n");
		if (listener != null) {
			listener.stop();
		}
		System.exit(0);
	}

	public void stopServer() {
		start.setEnabled(true);
		over.setEnabled(false);
		listener.suspend();
		ta.append("服务器已停止。\n");
		outStatus.setText("服务器已停止。");
	}

	public void selectPath() {
		String str = "";
		String loc = "";
		JFileChooser chooser = new JFileChooser();
		chooser.setCurrentDirectory(new java.io.File("."));
		chooser.setDialogTitle("主目录");
		chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		chooser.setAcceptAllFileFilterUsed(false);
		if (chooser.showOpenDialog(jf) == JFileChooser.APPROVE_OPTION) {
			// str += chooser.getCurrentDirectory();
			str += chooser.getSelectedFile();
			loc = str.replaceAll("\\\\", "/"); //// Windows路径到JAVA路径的转换
		}
		directory.setText(loc);
	}

	public void actionPerformed(ActionEvent event) {
		String command = event.getActionCommand();
		if (command.equals("Start")) {
			startServer();
		}
		if (command.equals("Stop")) {
			stopServer();
		}
		if (command.equals("Exit")) {
			exitServer();
		}
		if (command.equals("...")) {
			selectPath();
		}
	}

	public static void main(String[] args) {
		Surface sf = new Surface();
		sf.init();
		// sf.outStatus.setText("服务器已开启:220.111.332.444:8000 sdafsagassasa");

		 String a = new String("源码下载站");
		 System.out.println("16制"+strTo16(a));

		char c = '8';
		System.out.printf("The value of char %c  %X,is %d.%n", c, (int)c,(int)c);
        //16进制 转成10进制。  java 内部编码是 utf-16 
		
		String str = String.valueOf(c);
		byte[] bys;
		try {
			bys = a.getBytes("UTF-8");
			for (int i = 0; i < bys.length; i++) {
				System.out.printf("%X ", bys[i]);
			}
            
			//因为是Unicode编码,所以编码前有字节序。
			//(byte & 0xFF) 这是一个字节相与,然后 << 8  代表数据 左移 8位 ,相当于 两字节大小 与后面的 或运算
			// 相当于 地位的字节 复制了 新添加的 字节
			//int unicode = (bys[2] & 0xFF) << 8 | (bys[3]& 0xFF);
			//System.out.printf("The unicode value of %c is %d.%n", c, unicode);

		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public static String strTo16(String s) {
		String str = "";

		System.out.println("字符串长度"+s.length());
		for (int i = 0; i < s.length(); i++) {
			int ch = (int) s.charAt(i);
			String s4 = Integer.toHexString(ch);
			str = str + s4;
		}
		return str;
	}

}

参考自:GitHub

另,Tomcat源码剖析电子书:

代码和UML图:https://github.com/Aresyi/HowTomcatWorks 
排版更好的百度电子书:https://yuedu.baidu.com/ebook/ac92f0d35122aaea998fcc22bcd126fff7055d60

网络编程资料:

自定义 服务器 css 设置成了 二进制类型 导致加载 不出来 样式,报文头和体 有且只有空一行 才能 被 浏览器 正确 解析。空两行 浏览器 加载不了 图片

http://localhost/~cool/beike/index.html

http://www.ietf.org/rfc/rfc1945.txt

 

http编码

https://blog.csdn.net/hongxingxiaonan/article/details/49963643

tcp连接详解:

https://blog.csdn.net/liuxinmingcode/article/details/50376035

tcp包长度

https://blog.csdn.net/lishanmin11/article/details/77045745

http 长度

https://blog.csdn.net/zerooffdate/article/details/78962818

http详解

https://segmentfault.com/a/1190000006689767#articleHeader8

https://blog.csdn.net/u012813201/article/details/70211255

websocket

https://www.jianshu.com/p/f666da1b1835

http://www.cnblogs.com/hustskyking/p/websocket-with-node.html

https://www.cnblogs.com/oshyn/p/3574497.html