想做一个简单的服务器,用来更好的了解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