java 网络编程基础

    在讲java网络编程前,肯定要对java io和 nio有个很好的理解,本篇纯讲一些java网络编程的基础,

1、网络

将不同区域的计算机连接到一起:局域网、城域网、广域网(互联网);

2、地址 IP地址

确定网络上一个绝对地址、位置 eg:房子的地址

3、端口

区分计算机软件的,0-65535 共65535个端口  eg:房子的房门号

注意:在同一协议下,端口不能重复,不同协议下可以重复

     1024以下的端口不要使用

4、资源定位

URL 统一资源定位符 URI:统一资源

5、数据传输

5.1协议:TCP协议、 UDP协议

1)  TCP协议:面向链接,安全可靠但效率低下 eg:打电话

2)  UDP协议:非面向链接,不可高但效率高   eg:发短信

5.2 传输 :先封装,再拆分

6、java中与网络有关常用的类

InetAddress、 InetSocketAddress       :地址

URL                                                      :资源定位

Socket 、ServerSocket                         :TCP协议 对应的客户端和服务端

DatagramSocket 、DatagramPacket   :UDP协议 对应的客户端和服务端

6.1 InetAddress: 封装计算机的ip地址和DNS(域名解析),不能包含端口

package net;


import java.net.InetAddress;
import java.net.UnknownHostException;


/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月2日 下午10:09:26
* @description:
*/
public class MyInetAddress {


	public static void main(String[] args) {
		getAllByName();
	}


	public static  InetAddress getAllByName() {
    	InetAddress inet =  null;
    			
    	try {
			inet = InetAddress.getByName("www.baidu.com");//域名
			
			System.out.println(inet.getHostAddress());//域名解析地址
			
			System.out.println(inet.getHostName());//域名
			//域名:类似于一个人的名字 张三   百度的域名为www.baidu.com
			//域名解析地址:类似于一个人家的具体地址 如张三家的地址为:某路某号某室几零几房间  百度的域名解析地址为119.75.213.61
			System.out.println(inet.getAddress());//ip对应的字节数组
			
			//本机地址
			System.out.println(InetAddress.getLocalHost().getHostAddress());
			
		} catch (UnknownHostException e) {
			
			e.printStackTrace();
		}
    	
    	return inet;
    }
	
}

6.2 InetSocketAddress   在InetAddress基础上进一步封装,可包含端口

package net;

import java.net.InetSocketAddress;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月2日 下午11:53:40
* @description:
*/
public class MyInetSocketAddress {
	
	public static void main(String[] args) {
		//创建对象
		InetSocketAddress inetSocketAddress = new InetSocketAddress("www.baidu.com", 9001);
	
		//返回主机名
		System.out.println(inetSocketAddress.getHostName());
		//返回InetAddress对象
		System.out.println(inetSocketAddress.getAddress());
		//返回端口
		System.out.println(inetSocketAddress.getPort());
		// 检查是否已解析地址。
		System.out.println(inetSocketAddress.isUnresolved());
	}
}

6.3 URL 

统一资源定位符,用来唯一标识一个资源,有4部分组成:协议、域名(或ip地址)、端口、资源文件名

eg:http://127.0.0.1:8080/index.html

协议:http协议(超文本传输协议)

域名或ip地址:127.0.0.1

端口:8080

资源文件名:index.html

package net;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月3日 上午12:16:43
* @description:
*/
public class MyURL {

	public static void main(String[] args) {
		
		try {
			//绝对路径构建
			URL url = new URL("http://www.baidu.com:80/index.html#aa?username=zhangsan");
			
			/*//相对路径构建
			url = new URL("http://www.baidu.com:80");
			url= new URL(url,"/index.html");*/
			System.out.println(url.toString());
			
			System.out.println("查看协议:"+url.getProtocol());
			System.out.println("查看主机名:"+url.getHost());
			System.out.println("查看端口:"+url.getPort());
			System.out.println("查看资源:"+url.getPath());
			System.out.println("相对路径:"+url.getFile());
			System.out.println("查看锚点:"+url.getRef());
			System.out.println("查看参数:"+url.getQuery());//存在锚点,返回null,不存在锚点就返回?后面的部分
			
			System.out.println("查看此URL的授权部分:"+url.getAuthority());//存在锚点,返回null,不存在锚点就返回?后面的部分
			System.out.println(url.getDefaultPort());
			
			/*//获取资源流
			InputStream ips = url.openStream();
			byte[] b = new byte[1024];
			int count1 = 0 ;
			while(-1 != (count1 = ips.read(b))){
				String s = new String(b,0,count1,"utf-8");
				System.out.println(s);
				
			}
			ips.close();*/
			
			BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("baidu11.html"),"utf-8"));

			String msg = null;
			
			while((msg=br.readLine()) != null){
				
				bw.write(msg);
			}
			bw.flush();
			bw.close();
			br.close();
			
		} catch (Exception e) {
			
			e.printStackTrace();
		}
	}
	
}

URI、URL 和 URN

URI 是统一资源标识符,而 URL 是统一资源定位符。因此,笼统地说,每个 URL 都是 URI,但不一定每个 URI 都是 URL。这是因为 URI 还包括一个子类,即统一资源名称(URN),它命名资源但不指定如何定位资源。上面的 mailtonewsisbnURI 都是 URN 的示例。

URI 和 URL 概念上的不同反映在此类和 URL 类的不同中。

注意,URI类在某些特定情况下对其组成字段执行转义。建议使用 URI管理 URL 的编码和解码,并使用 toURI()URI.toURL() 实现这两个类之间的转换。

6.4 UDP DatagramSocket  DatagramPacket

1、  UDP协议:非面向链接 不安全数据可能丢失  但效率高   例如发短信

客户端:

1)  创建客户端  DatagramSocket 类+指定端口

2)  准备数据,字节数组 打包DatagramPacket + 服务器地址及端口

3)  发送

4)  释放资源

服务端:

1)  创建服务端  DatagramSocket 类+指定端口

2)  准备接受数据容器  字节数组  封装DatagramPacket

3)  包接收数据

4)  分析

5)  释放资源

客户端:

package net;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月3日 下午10:16:57
* @description:
*/
public class MyUDPClient {

	public static void main(String[] args) {
		test();
		
	}
	
	public static void test(){
		
		try {
			//1、创建客户端 ,端口是客户端自己的端口,不能和服务端口重合
			DatagramSocket client = new DatagramSocket(8002);
		    //2、准备数据,打包
			String s = "we are family! + 准备数据,打包";                              //这里的ip和端口都是服务器的ip和端口
		    DatagramPacket dp = new DatagramPacket(s.getBytes(),s.getBytes().length,InetAddress.getByName("localhost"),8001);
            //3、发送数据
		    client.send(dp);
			//4、关闭资源
		    client.close();
		    
		} catch (Exception e) {

			e.printStackTrace();
		} 

	}
	
}
package net;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * @author:zhangfd
 * @version:1.0
 * @date:2017年9月3日 下午9:34:22
 * @description: UDP协议 服务端
 */
public class MyUDPServer {

	public static void main(String[] args) {
		test();
	}

	public static void test() {
		try {
			//1、创建服务+端口,等待客户端访问
			DatagramSocket server = new DatagramSocket(8001, InetAddress.getByName("localhost"));
			//2、创建接收容器
		    byte[] container = new byte[1024];
		    //3、封装成包
		    DatagramPacket dp = new DatagramPacket(container,container.length);
		    //4、接收数据
		    server.receive(dp);
		    //5、分析数据
		    byte[] b = dp.getData();
		    System.out.println(new String(b,0,b.length));
		    //6、关闭资源
		    server.close();
		    
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

 6.5 TCP协议  Socket 、ServerSocket

  TCP协议 面向链接 效率低但高可靠 数据不会丢失 例如打电话


Socket 、ServerSocket

1)面向链接   请求 + 响应  request和respone

2)Socket编程

服务端:

1)  创建服务端,指定端口

2)  接收客户端链接,阻塞式

3)  发送数据 + 接收数据

客户端:

1)  创建客户端,指定端口(服务器端口,客户端系统随机分配),和服务端口建立链接

2)  接收数据 + 发送数据

例一、1个client 对应1个server

package net;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月3日 下午11:01:47
* @description:服务端
*/
public class MyTCPServer {
	public static void main(String[] args) {
		test();
	}
	public static void test(){
		try {
			//1、创建服务端,指定端口
			ServerSocket server = new ServerSocket(8801);
			//2、接收客户端链接,阻塞式
			Socket socket = server.accept();
			System.out.println("接口请求。。。。");
			//3、发送数据
			BufferedWriter bf = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			bf.write("欢迎使用tcp协议");
			bf.newLine();
			bf.flush();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}


package net;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月3日 下午11:01:34
* @description:客户端
*/
public class MyTCPClient {
	public static void main(String[] args) {
		test();
	}
	public static void test(){
		try {
			//1、创建客户端,指定端口(服务器端口,客户端系统随机分配),和服务端口建立链接
			Socket client = new Socket("localhost",8801);
			//2、接收数据
			BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
			System.out.println(br.readLine());		
		} catch (Exception e) {	
			e.printStackTrace();
		}
	}
}

这样的话,先启动服务端,再启动客户端,就可建立一次链接,使得客户端从服务端获取数据,但是client-server建立一次链接后,服务端就自动关闭,显然是不合理的,为了不让服务端关闭,可对服务端代码做一点改动,添加一个while循环即可。

package net;

import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月3日 下午11:01:47
* @description:服务端
*/
public class MyTCPServer {
	public static void main(String[] args) {
		test();
	}
	public static void test(){
		try {
			//1、创建服务端,指定端口
			ServerSocket server = new ServerSocket(8801);
			while(true){
			//2、接收客户端链接,阻塞式
			Socket socket = server.accept();
			System.out.println("接口请求。。。。");
			//3、发送数据
			BufferedWriter bf = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			bf.write("欢迎使用tcp协议");
			bf.newLine();
			bf.flush();
			}
		} catch (Exception e) {	
		e.printStackTrace();
		}
	}
}

想象下QQ聊天软件, 即使这样也是有问题的,因为现在只能是服务端发数据,客户端接收数据,而客户端不能发送数据给服务端。下面就解决这个问题

package net.socket;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.Socket;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月3日 下午11:01:34
* @description:一个客户端   发送数据+接收数据
*/
public class MyTCPClient02 {

	public static void main(String[] args) {
		test();
	}
	
	public static void test(){
		Socket client = null;
		try {
			//1、创建客户端,指定端口(服务器端口,客户端系统随机分配),和服务端口建立链接
			client = new Socket("localhost",8801);
			
			//2、发送数据
			DataOutputStream dos  = new DataOutputStream(client.getOutputStream());
//			String console = "client 发送数据";
			//从控制台输入字符发送给服务端
			BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
			String console = br.readLine();
			dos.writeUTF(console);
			dos.flush();
			//3、接收数据
			DataInputStream dis = new DataInputStream(client.getInputStream());
			System.out.println(dis.readUTF());
			
		} catch (Exception e) {
			IoUtil.closeAll(client);
			e.printStackTrace();
		}
	}
}
package net.socket;

import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月3日 下午11:01:47
* @description:服务端  接收数据+发送数据    可发送给多个客户端
*/
public class MyTCPServer02 {

	public static void main(String[] args) {
		test();
	}
	
	public static void test(){
		ServerSocket serverSocket = null;
		try {
			//1、创建服务端,指定端口
			serverSocket = new ServerSocket(8801);
			while(true){
			//2、接收客户端链接,阻塞式
			Socket server = serverSocket.accept();
			System.out.println("接收请求。。。。");
		
			//接收数据
			DataInputStream dis = new DataInputStream(server.getInputStream());
			System.out.println(dis.readUTF());
			
			//发送数据
			DataOutputStream dos = new DataOutputStream(server.getOutputStream());
			dos.writeUTF("欢迎使用tcp协议");
			dos.flush();
		
			}
		} catch (Exception e) {
			IoUtil.closeAll(serverSocket);
			e.printStackTrace();
		}
	}
	
}

然而上面的例子也是有问题的,它只解决了客户端发送数据--服务端接收数据和 服务端发送数据客户端接收数据。而一旦客户端不发送数据,服务端就一直等待直到接收到客户端的数据才发送数据给客户端,这样导致客户端只有发数据才能接收数据,对此,我们可以通过多线程知识解决这个问题。

package net.socket;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月4日 上午12:54:52
* @description:client 接收数据
*/
public class ClientReceive implements Runnable{

	private DataInputStream dis ;
	
	private boolean isRunning = true;
	public ClientReceive() {
		
	}
	
	public ClientReceive(Socket client) {
		this();
		try {
			dis = new DataInputStream(client.getInputStream());
		} catch (IOException e) {
			IoUtil.closeAll(dis);
			isRunning = false;
		}
	}
	
	public void run() {
		while(isRunning){
			try {
				System.out.println(dis.readUTF());
			} catch (IOException e) {
				//IoUtil.closeAll(dis);
				isRunning = false;
			}
		}
		
	}

}
package net.socket;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月4日 上午12:50:46
* @description: client发送数据
*/
public class ClientSend implements Runnable{

	private BufferedReader br;
	
	private DataOutputStream dos;
	
	private boolean isRunning = true;
	
	public ClientSend() {
		br = new BufferedReader(new InputStreamReader(System.in));
	}
	
	public ClientSend(Socket client) {
        this();//调用上面的空构造器
        try {
			dos = new DataOutputStream(client.getOutputStream());
		} catch (IOException e) {
			isRunning = false;
			IoUtil.closeAll(br,dos);
			}
		}
	
	
	public void run() {
		while(isRunning){
			try {
				//读取控制台输入的信息
				String console = br.readLine();
				if(null != console && !"".equals(console)){
					dos.writeUTF(console);
					dos.flush();
				}
				
			} catch (IOException e) {
				isRunning = false;
				//IoUtil.closeAll(br,dos);
			}
		}
		
	}

}
package net.socket;

import java.io.DataInputStream;
import java.net.Socket;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月3日 下午11:01:34
* @description:一个客户端   发送数据+接收数据
*/
public class MyTCPClient03 {
	
	public static void main(String[] args) {
		test();
	}
	
	public static void test(){
		Socket client = null;
		try {
			//1、创建客户端,指定端口(服务器端口,客户端系统随机分配),和服务端口建立链接
			 client = new Socket("localhost",8801);
			
			//2、多线程发送数据
			new Thread(new ClientSend(client)).start(); 
			//3、接收数据
			new Thread(new ClientReceive(client)).start(); 
			
		} catch (Exception e) {
			IoUtil.closeAll(client);
			e.printStackTrace();
		}
	}
}

package net.socket;

import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月3日 下午11:01:47
* @description:服务端  发送数据    可发送给多个客户端
*/
public class MyTCPServer03 {

	public static void main(String[] args) {
		test();
	}
	
	public static void test(){
		ServerSocket serverSocket =null;
		try {
			//1、创建服务端,指定端口
		    serverSocket = new ServerSocket(8801);
		
			//2、接收客户端链接,阻塞式
			Socket server = serverSocket.accept();
			//System.out.println("接收请求。。。。");
			DataInputStream dis = new DataInputStream(server.getInputStream());
			
			//3、发送数据
			DataOutputStream dos = new DataOutputStream(server.getOutputStream());
			while(true){
			String msg = dis.readUTF();
			System.out.println("接收请求。。。。"+msg);
			dos.writeUTF("服务器----》 "+msg);
			dos.flush();
		
			}
		} catch (Exception e) {
			IoUtil.closeAll(serverSocket);
			e.printStackTrace();
		}
	}
	
}

6.6、协议(http Server)

应用层HTTP、FTP、TELNET、SNMP、DNS

传输层TCP、UDP

网络层IP

主机-网络层:以太网IEEE802.3  令牌环网 IEEE802.4

HTTP(超文本传输协议)是网络应用层的协议,建立在tcp/ip协议基础上,使用可靠的TCP链接,默认端口是80,目前最新版本是http1.1。

HTTP请求格式

1)、请求方法  URI(统一资源定位符)HTTP协议/版本

2)、请求头(Request Header)

3)、请求正文(Request Content)(post 请求才有

 

如下所示:

//请求方法 URI(统一资源定位符) HTTP协议/版本
POST  /index.html  HTTP/1.1
//请求头(Request Header)
Host: localhost:8888
Connection: keep-alive
Content-Length: 23(正文的字节数,这个必须有,否则服务端会一直等待)
Accept-Charset: utf-8
Cache-Control: max-age=0
Origin: null
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.3103.400 QQBrowser/9.6.11372.400
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8

//请求正文(Request Content)(post 请求才有)
uname=123&upassword=321

HTTP响应格式

1)HTTP协议版本 状态代码  描述

2)响应头(Response Header)

3)响应正文(Response Content)


状态代码为3位数字,200~299的状态码表示成功,300~399的状态码指资源重定向,400~499的状态码指客户端请求出错,500~599的状态码指服务端出错(HTTP/1.1向协议中引入了信息性状态码,范围为100~199)

常见的状态码说明

状态码

说明

200

响应成功

302

跳转,跳转地址通过响应头中的Location属性指定(JSP中Forward和Redirect之间的区别)

400

客户端请求有语法错误,不能被服务器识别

403

服务器接收到请求,但是拒绝提供服务(认证失败)

404

请求资源不存在

500

服务器内部错误

 常见响应头部如下:

响应头

说明

Server

服务器应用程序软件的名称和版本

Content-Type

响应正文的类型(是图片还是二进制字符串)

Content-Length

响应正文字节长度

Content-Charset

响应正文使用的编码

Content-Encoding

响应正文使用的数据压缩格式

Content-Language

响应正文使用的语言

 例如:

// HTTP协议版本 状态代码  描述
HTTP/1.1  200  OK
//响应头(Response Header)
Content-Type: txt/html;charset=utf-8
Content-Charset: utf-8
Date: Sun Sep 24 00:00:39 CST 2017
Content-Language: zh-CN,zh
Content-Length: 97 (正文的字节数,这个必须有,否则客户端会一直等待)

//响应正文(Response Content)
<html><head><meta charset="UTF-8"><title>响应实例</title></head><body> htpp响应实例</body></html>
下面分别举例说明:

1、get请求 案例

服务端代码:
package net.httpServer.demo1;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

import net.httpServer.util.CloseUtil;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月23日 下午4:33:08
* @description: 创建服务器并启动  get请求
*/
public class Server1 {

	private ServerSocket server;
	
	public static void main(String[] args) throws UnknownHostException {
		Server1 server1 = new Server1();
		server1.start();

	}
	
	public void start(){
		try {
			 server = new ServerSocket(8888);
			 this.reverice();
		} catch (IOException e) {
			CloseUtil.close(server);
			e.printStackTrace();
		}
	}
	
	
	
	private  void reverice(){
		Socket socket = null;
		try {
			socket = server.accept();
			String msg = null;
			BufferedReader bf = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			StringBuilder sb = new StringBuilder();
			
			while((msg = bf.readLine()).length() >0){
				sb.append(msg+"\n");
				if(null == msg)
					break;
			}
			
			System.out.println(sb.toString());
		} catch (IOException e) {
			CloseUtil.close(socket);
			e.printStackTrace();
		}
	}
	
	
	public void stop(){
		
	}
}

客户端代码直接写个页面:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>html简介</title>
</head>
<body>

<pre>from表单的两个常用请求是post请求和get请求

			get请求:量小 安全性不高,默认使用
			post请求:量大,安全性高,
			action:from表单的请求路径
			id:编号,前端(客户端)区分唯一性,js中经常使用
			name:名称,后端(服务端)区分唯一性,从前段获取值

		</pre>

		<form action="http://localhost:8888/index.html"  method="get">
		  用户名:<input  type="text" name="uname" id="name"></input>
	            密码:<input  type="password" name="upassword" id="password"></input>
	    <input  type="submit" value="登陆"/>
    </form>
</body>
</html>

先启动服务端代码,然后在本地浏览器直接打开xml文件即可看到服务端上打印的请求报文信息。

控制台打印的请求信息如下:

GET /index.html?uname=11&upassword=0 HTTP/1.1

Host: localhost:8888

Connection: keep-alive

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.3507.400 QQBrowser/9.6.12325.400

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

Accept-Encoding: gzip, deflate, sdch

Accept-Language: zh-CN,zh;q=0.8

注意红色部分为get请求的参数部分,这个和post请求的参数位置是不一样的。

2、post请求案例

服务端代码:

package net.httpServer.demo1;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月23日 下午4:33:08
* @description: 创建服务器并启动  post请求
*/
public class Server2 {

	private ServerSocket server;
	
	public static void main(String[] args) {
		Server2 server2 = new Server2();
		server2.start();
		
	}
	
	public void start(){
		try {
			 server = new ServerSocket(8888);
			
			 this.reverice();
		} catch (IOException e) {
			
			e.printStackTrace();
		}
	}
	
	
	
	private  void reverice(){
		try {
			Socket socket = server.accept();
			byte[] b = new byte[2048]; //这里假定请求信息量不大
			int length = socket.getInputStream().read(b);
			
			System.out.println(new String(b,0,length).trim());
			
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	public void stop(){
		
	}
}

客户端代码:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>html简介</title>
</head>
<body>

<pre>from表单的两个常用请求是post请求和get请求

			get请求:量小 安全性不高,默认使用
			post请求:量大,安全性高,
			action:from表单的请求路径
			id:编号,前端(客户端)区分唯一性,js中经常使用
			name:名称,后端(服务端)区分唯一性,从前段获取值,要想后台获取数据,必须定义name

		</pre>

		<form action="http://localhost:8888/index.html"  method="post">
		  用户名:<input  type="text" name="uname" id="name"></input>
	            密码:<input  type="password" name="upassword" id="password"></input>
	    <input  type="submit" value="登陆"/>
    </form>
</body>
</html>

控制台打印post的请求信息如下

POST /index.html HTTP/1.1

Host: localhost:8888

Connection: keep-alive

Content-Length: 23

Cache-Control: max-age=0

Origin: null

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.3507.400 QQBrowser/9.6.12325.400

Content-Type: application/x-www-form-urlencoded

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

Accept-Encoding: gzip, deflate

Accept-Language: zh-CN,zh;q=0.8

uname=222&upassword=333

注意红色部分为post请求参数内容,并且上面还有一行空行。

以上分别讲解了get和post请求发送的http请求的报文格式,下面列举一个服务端给出响应的案例:
package net.httpServer.demo1;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月23日 下午4:33:08
* @description: 创建服务器并启动  post请求 外加响应格式
*/
public class Server3 {

	private static final String CRLF="\r\n";
	private static final String FLAG=" ";
	
	private ServerSocket server;
	
	public static void main(String[] args) {
		Server3 server1 = new Server3();
		server1.start();
		
	}
	
	public void start(){
		try {
			 server = new ServerSocket(8888);
			
			 this.reverice();
		} catch (IOException e) {
			
			e.printStackTrace();
		}
	}
	
	
	
	private  void reverice(){
		try {
			Socket socket = server.accept();
			//假定接收的报文在2048个字节内
			byte[] b = new byte[2048];
			int length = socket.getInputStream().read(b);
			
			System.out.println(new String(b,0,length).trim());
			
			//拼接返回响应报文
			StringBuilder responseContent = new StringBuilder();
			responseContent.append("<html><head><title>响应实例</title></head><body> htpp响应实例</body></html>");
			
			StringBuilder response = new StringBuilder();
			response.append("HTTP/1.1").append(FLAG).append("200").append(FLAG).append("OK").append(CRLF);
			response.append("Content-Type:").append(FLAG).append("txt/html;charset=utf-8").append(CRLF);
			response.append("Content-Charset:").append(FLAG).append("utf-8").append(CRLF);
			response.append("Date:").append(FLAG).append(new Date()).append(CRLF);
			response.append("Content-Language:").append(FLAG).append("zh-CN,zh").append(CRLF);

			//报文体的长度,这个必须有,否则客户端会一直等待
			response.append("Content-Length:").append(FLAG).append(responseContent.toString().getBytes().length).append(CRLF);
			response.append(CRLF);
			response.append(responseContent);
			System.out.println(response.toString());
			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			bw.write(response.toString());
			bw.flush();
			bw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	public void stop(){
		try {
			server.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

我们发现 服务端返回的响应报文头有固定的格式,只是报文内容不同而已,此时我们可把返回报文的报文头抽象出来。

Demo4 、抽象响应报文的报文头

package net.httpServer.demo1;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;

import net.httpServer.util.CloseUtil;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月24日 下午9:44:25
* @description: 响应报文通用类
*/
public class ResponeUtil {

	private static final String CRLF="\r\n";
	private static final String FLAG=" ";
	
	//报文头信息
	private StringBuilder headerInfo;
	
	//报文体信息
	private StringBuilder content;
	
	private BufferedWriter bw;
	
	public ResponeUtil() {
		headerInfo = new StringBuilder();
		content = new StringBuilder();
	}
	
	public ResponeUtil(OutputStream ow) {
		this();
		bw = new BufferedWriter(new OutputStreamWriter(ow));
	}
	
	/**
	 * 正文
	 * @param msg 正文
	 * @return
	 */
	public ResponeUtil print(String msg){
		content.append(msg);
		return this;
	}
	
	/**
	 * 正文 + 换行符
	 * @param msg 正文
	 * @return 
	 */
	public ResponeUtil println(String msg){
		
		content.append(msg);
		content.append(CRLF);
		return this;
	}
	
	private void createHeader(int code){
		
		headerInfo.append("HTTP/1.1").append(FLAG);
		switch (code) {
		case 200:headerInfo.append("200").append(FLAG).append("OK");
			break;
		case 404:headerInfo.append("404").append(FLAG).append("NO FOUND");
		    break;
		case 500:headerInfo.append("500").append(FLAG).append("SERVER ERROR");
	    	break;
		default:
		    break;
		}
		
		headerInfo.append(CRLF);
		headerInfo.append("Content-Type:").append(FLAG).append("txt/html").append(CRLF);
		headerInfo.append("Content-Charset:").append(FLAG).append(RequestUtil.ENCODE).append(CRLF);
		headerInfo.append("Date:").append(FLAG).append(new Date()).append(CRLF);
		headerInfo.append("Content-Language:").append(FLAG).append("zh-CN,zh").append(CRLF);

		//报文体的长度,这个必须有,否则客户端会一直等待
		headerInfo.append("Content-Length:").append(FLAG).append(content.toString().getBytes().length).append(CRLF);
		headerInfo.append(CRLF);
		
	}
	
	
	public void pushToClient(int code) throws IOException{
		
			createHeader(code);
			bw.write(headerInfo.toString()+content.toString());
			bw.flush();
		
	}
	
	
	public void closeIO(){
		
			CloseUtil.close(bw);
		
	}
}
package net.httpServer.demo1;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月23日 下午4:33:08
* @description: 创建服务器并启动  post请求 外加响应格式
*/
public class Server4 {

	private static final String CRLF="\r\n";
	private static final String FLAG=" ";
	
	private ServerSocket server;
	
	public static void main(String[] args) {
		Server4 server1 = new Server4();
		server1.start();
		
	}
	
	public void start(){
		try {
			 server = new ServerSocket(8888);
			
			 this.reverice();
		} catch (IOException e) {
			
			e.printStackTrace();
		}
	}
	
	
	private  void reverice(){
		try {
			Socket socket = server.accept();
			byte[] b = new byte[2048];
			int length = socket.getInputStream().read(b);
			
			System.out.println(new String(b,0,length).trim());
			
			ResponeUtil resp = new ResponeUtil(socket.getOutputStream());
			resp.print("<html><head><title>响应实例</title></head><body> htpp响应实例</body></html>");
			resp.pushToClient(200);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	public void stop(){
		
	}
}

我们知道,post/get请求信息都有固定的格式,对此,我们可以去解析这个请求报文,从而获取请求方法、路径、报文体等信息

Demo5、解析请求报文信息

package net.httpServer.demo1;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月25日 下午11:27:56
* @description: 解析post/get请求报文信息
*/
public class RequestUtil {

	private static final String CRLF="\r\n";
	private static final String FLAG=" ";
	
	public static final String ENCODE = "utf-8";
	//请求方法
	private String method;
	//请求路径
	private String url;
	//请求参数
	private Map<String,List<String>> paramMapValue;
	
	private InputStream ins;
	//请求信息
	private String requestInfo;
	
	public RequestUtil() {
		method = "";
		url = "";
		paramMapValue = new HashMap<String,List<String>>();
		requestInfo = "";
	}
	
	public RequestUtil(InputStream ins)  {
		this();
		this.ins=ins;
		byte[] b = new byte[2048];
		try {
			int length = ins.read(b);
			requestInfo = new String(b,0,length).trim();
		} catch (IOException e) {
			return;
		}
		//分析头信息
		parseRequestInfo();
	}
	
	/**
	 * 分析头信息
	 */
	private void parseRequestInfo(){
		if("".equals(requestInfo)){
			return;
		}
		String paramString = "";
		
		String firstLine = requestInfo.substring(0, requestInfo.indexOf(CRLF));
		
		int count  = firstLine.indexOf("/");
		this.method = firstLine.substring(0,count).trim();
		String urlString = firstLine.substring(count,firstLine.indexOf("HTTP")).trim();
		if("get".equalsIgnoreCase(method)){
			 if(urlString.contains("?")){
				 String[] urlArray = urlString.split("\\?"); 
				 this.url = urlArray[0];
				 paramString = urlArray[1];
			 }else{
				 this.url = urlString; 
			 }
			 
		}else if("post".equalsIgnoreCase(method)){
			this.url = urlString;
			paramString = requestInfo.substring(requestInfo.lastIndexOf(CRLF));
		}
		
		//把请求参数放入paramMapValue 集合中
		if(!"".equals(paramString)){
			parseParams(paramString);
		}
		
		
	}
	/**
	 * 解析请求参数
	 * @param params 请求参数
	 */
	private void parseParams(String params){
		//eg:uname=22&upassword=33 
		params = params.trim();
		//String[] paramArray = params.split("&");
		StringTokenizer  tokenizer = new StringTokenizer(params, "&");
		while(tokenizer.hasMoreTokens()){
			String keyValue = tokenizer.nextToken();
			String[] keyValues = keyValue.split("=");
			
			if(1 == keyValues.length){
				keyValues = Arrays.copyOf(keyValues, 2);
				keyValues[1] = null;
			}
			
			List<String> s = new ArrayList<String>();
			if(null == paramMapValue.get(keyValues[0])){
				s.add(null == keyValues[1]?null:decode(keyValues[1].trim()));
			}else{
				s = paramMapValue.get(keyValues[0]);
				s.add(null == keyValues[1]?null:decode(keyValues[1].trim()));
			}
			paramMapValue.put(keyValues[0],s);

		}
	}
	/**
	 * 解决中文乱码问题
	 * @param value
	 * @param encode
	 * @return
	 */
	public String decode(String value){
		try {
			 return  URLDecoder.decode(value,ENCODE);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public String getMethod() {
		return method;
	}

	public void setMethod(String method) {
		this.method = method;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public Map<String, List<String>> getParamMapValue() {
		return paramMapValue;
	}

	public void setParamMapValue(Map<String, List<String>> paramMapValue) {
		this.paramMapValue = paramMapValue;
	}

	public InputStream getIns() {
		return ins;
	}

	public void setIns(InputStream ins) {
		this.ins = ins;
	}

	public String getRequestInfo() {
		return requestInfo;
	}

	public void setRequestInfo(String requestInfo) {
		this.requestInfo = requestInfo;
	}
}
package net.httpServer.demo1;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月23日 下午4:33:08
* @description: 创建服务器并启动  post请求 外加响应格式
*/
public class Server5 {

	private static final String CRLF="\r\n";
	private static final String FLAG=" ";
	
	private ServerSocket server;
	
	public static void main(String[] args) {
		Server5 server1 = new Server5();
		server1.start();
		
	}
	
	public void start(){
		try {
			 server = new ServerSocket(8888);
			
			 this.reverice();
		} catch (IOException e) {
			
			e.printStackTrace();
		}
	}
	
	
	
	private  void reverice(){
		try {
			Socket socket = server.accept();
			
			RequestUtil requestUtil = new RequestUtil(socket.getInputStream());
			System.out.println(requestUtil.getMethod()+"\n"+requestUtil.getUrl()+"\n"+requestUtil.getParamMapValue());
			ResponeUtil resp = new ResponeUtil(socket.getOutputStream());
			resp.print("<html><head><title>响应实例</title></head><body> htpp响应实例</body></html>");
			resp.pushToClient(200);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	public void stop(){
		
	}
}

然而这样的服务器也只能接收一次客户端发送的请求,我们期望客户端可发送多个请求,对此我们 对服务器加入多线程,并且把响应报文从服务器类代码中抽出来。

package net.httpServer.demo1;
/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月7日 下午10:25:39
* @description:
*/
public class Servlet {

	
	
	public Servlet() {
		
	}
	
	public void reverice(RequestUtil req,ResponeUtil resp){
		
		resp.print("<html><head><title>响应实例</title></head><body> htpp响应实例,欢迎"+req.getParamMapValue().get("uname")+"</body></html>");

	}
	
}
package net.httpServer.demo1;

import java.io.IOException;
import java.net.Socket;

import net.httpServer.util.CloseUtil;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月7日 下午10:33:07
* @description:  加入多线程,
*/
public class Dispatcher implements Runnable{

	private Socket client;
	
	private RequestUtil req;
	
	private ResponeUtil resp;
	
	private static  int code = 200;
	private Servlet ser ;
	
	public Dispatcher() {
		ser = new Servlet();
	}
	
    public Dispatcher(Socket client) {
		this();
		try {
			
			this.req = new RequestUtil(client.getInputStream());
			this.resp = new ResponeUtil(client.getOutputStream());

		} catch (IOException e) {
			this.code=500;
			e.printStackTrace();
			return;
		}
	}
	
	public void run() {
		
		
		ser.reverice(req, resp);
		try {
			resp.pushToClient(code);
		} catch (IOException e) {
			this.code = 500;
			e.printStackTrace();
		}
	}

}
package net.httpServer.demo1;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;

import net.httpServer.util.CloseUtil;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月23日 下午4:33:08
* @description: 对请求和响应加入多线程
*/
public class Server7 {

	private static final String CRLF="\r\n";
	private static final String FLAG=" ";
	
	private boolean isshutdown = false;
	
	private ServerSocket server;
	
	public static void main(String[] args) {
		Server7 server1 = new Server7();
		server1.start();
		
	}
	
	public void start(){
		start(8888);
	}
	
	public void start(int port){
		try {
			 server = new ServerSocket(port);
			 this.reverice();
		} catch (IOException e) {
			
			e.printStackTrace();
			stop();
			
		}
	}
	
	
	private  void reverice(){
		try {
			Socket socket = server.accept();
			//多线程发送请求
			while(!isshutdown){
				new Thread(new Dispatcher(socket)).start();
			}
			
		} catch (IOException e) {
			e.printStackTrace();
			stop();
		}
	}
	
	
	public void stop(){
		isshutdown = true;
		CloseUtil.close(server);
	}
}

尽管如此,客户端还是不能根据不同的url得到不同的响应,我们发现发送具体响应信息是由Dispatcher类中的Servlet 类决定,我们可由此处打开突破口:

 Demo8、客户端根据不同的url进行不同的访问

1)、利用多态,把Servlet抽象为抽象类,具体实现有子类负责

package net.httpServer.demo2;
/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月7日 下午10:25:39
* @description: 抽象servlet,具体servlet有子类实现
*/
public abstract class Servlet {

	
	public Servlet() {
		
	}
	
	public void reverice(RequestUtil req,ResponeUtil resp){
		
		//resp.print("<html><head><title>响应实例</title></head><body> htpp响应实例,欢迎"+req.getParamMapValue().get("uname")+"</body></html>");

		this.post(req,resp);
		this.get(req,resp);
	}
	
	
	public abstract void post(RequestUtil req,ResponeUtil resp);
	
	public abstract void get(RequestUtil req,ResponeUtil resp);
	
	
}

2)写一个存放url和Servlet对应的类

package net.httpServer.demo2;

import java.util.HashMap;
import java.util.Map;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月9日 下午11:36:36
* @description: 存放不同的url 对应的servlet
*/
public class ServletContext {

	//为每个具体的servlet起个别名,
	//login-->loginservlet
	private Map<String,Servlet> servletMap;
	//地址和别名之间的映射关系(因为一个资源可能有多个url访问)
	//url -->login 
	private Map<String,String> mapping;


	public ServletContext() {
		servletMap = new HashMap<String, Servlet>();
		
		mapping = new HashMap<String, String>();
	}
	
	public Map<String, Servlet> getServletMap() {
		return servletMap;
	}


	public void setServletMap(Map<String, Servlet> servletMap) {
		this.servletMap = servletMap;
	}


	public Map<String, String> getMapping() {
		return mapping;
	}


	public void setMapping(Map<String, String> mapping) {
		this.mapping = mapping;
	}
	
	
}

3)先手动添加几个url和Servlet对应类

package net.httpServer.demo2;

import java.util.Map;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月9日 下午11:42:48
* @description:
*/
public class Webapp {

	public static ServletContext context;
	
	
	static{
		context  = new ServletContext();
		
		Map<String,String> mapping = context.getMapping();
		mapping.put("/login", "login");
		mapping.put("/logi", "login");
		mapping.put("/reg", "register");
		
		Map<String,Servlet> servletMap = context.getServletMap();
		
		servletMap.put("login", new LoginServlet());
		servletMap.put("register", new RegisterServlet());
		
	}
	
	
	public static Servlet getServlet(String url){
		
		if(null == url || "".equals(url.trim())){
			return null;
		}else{
			return context.getServletMap().get(context.getMapping().get(url));
		}
		
	}
	
}

4)写个登陆和注册Serlvet类

package net.httpServer.demo2;
/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月9日 下午10:14:43
* @description:登陆类
*/
public class LoginServlet extends Servlet{

	@Override
	public void post(RequestUtil req, ResponeUtil resp) {
		resp.print("<html><head><title>登陆实例</title></head><body> 登陆实例,欢迎"+req.getParamMapValue().get("uname")+"登陆! </body></html>");

		
	}

	@Override
	public void get(RequestUtil req, ResponeUtil resp) {
	
	}

}
package net.httpServer.demo2;
/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月9日 下午10:16:57
* @description:
*/
public class RegisterServlet extends Servlet{

	@Override
	public void post(RequestUtil req, ResponeUtil resp) {
	
		
	}

	@Override
	public void get(RequestUtil req, ResponeUtil resp) {
		resp.print("<html><head><title>注册实例</title></head><body> 注册实例,欢迎"+req.getParamMapValue().get("uname")+"注册! </body></html>");

		
	}

}

5)更改Dispatcher获取Sevrlet实例的方法

package net.httpServer.demo2;
/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月9日 下午10:16:57
* @description:
*/
public class RegisterServlet extends Servlet{

	@Override
	public void post(RequestUtil req, ResponeUtil resp) {
	
		
	}

	@Override
	public void get(RequestUtil req, ResponeUtil resp) {
		resp.print("<html><head><title>注册实例</title></head><body> 注册实例,欢迎"+req.getParamMapValue().get("uname")+"注册! </body></html>");

		
	}

}

6)服务端代码和上例一样

package net.httpServer.demo2;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月25日 下午11:27:56
* @description:
*/
public class RequestUtil {

	private static final String CRLF="\r\n";
	private static final String FLAG=" ";
	
	public static final String ENCODE = "utf-8";
	//请求方法
	private String method;
	//请求路径
	private String url;
	//请求参数
	private Map<String,List<String>> paramMapValue;
	
	
	private InputStream ins;
	//请求信息
	private String requestInfo;
	
	public RequestUtil() {
		method = "";
		url = "";
		paramMapValue = new HashMap<String,List<String>>();
		requestInfo = "";
	}
	
	public RequestUtil(InputStream ins)  {
		this();
		this.ins=ins;
		byte[] b = new byte[2048];
		try {
			int length = ins.read(b);
			requestInfo = new String(b,0,length).trim();
		} catch (IOException e) {
			return;
		}
		//分析头信息
		parseRequestInfo();
	}
	
	/**
	 * 分析头信息
	 */
	private void parseRequestInfo(){
		if("".equals(requestInfo)){
			return;
		}
		String paramString = "";
		
		String firstLine = requestInfo.substring(0, requestInfo.indexOf(CRLF));
		
		int count  = firstLine.indexOf("/");
		this.method = firstLine.substring(0,count).trim();
		String urlString = firstLine.substring(count,firstLine.indexOf("HTTP")).trim();
		if("get".equalsIgnoreCase(method)){
			 if(urlString.contains("?")){
				 String[] urlArray = urlString.split("\\?"); 
				 this.url = urlArray[0];
				 paramString = urlArray[1];
			 }else{
				 this.url = urlString; 
			 }
			 
		}else if("post".equalsIgnoreCase(method)){
			this.url = urlString;
			paramString = requestInfo.substring(requestInfo.lastIndexOf(CRLF));
		}
		
		//把请求参数放入paramMapValue 集合中
		if(!"".equals(paramString)){
			parseParams(paramString);
		}
		
		
	}
	/**
	 * 解析请求参数
	 * @param params 请求参数
	 */
	private void parseParams(String params){
		//eg:uname=22&upassword=33 
		params = params.trim();
		//String[] paramArray = params.split("&");
		StringTokenizer  tokenizer = new StringTokenizer(params, "&");
		while(tokenizer.hasMoreTokens()){
			String keyValue = tokenizer.nextToken();
			String[] keyValues = keyValue.split("=");
			
			if(1 == keyValues.length){
				keyValues = Arrays.copyOf(keyValues, 2);
				keyValues[1] = null;
			}
			
			List<String> s = new ArrayList<String>();
			if(null == paramMapValue.get(keyValues[0])){
				s.add(null == keyValues[1]?null:decode(keyValues[1].trim()));
			}else{
				s = paramMapValue.get(keyValues[0]);
				s.add(null == keyValues[1]?null:decode(keyValues[1].trim()));
			}
			paramMapValue.put(keyValues[0],s);

		}
	}
	/**
	 * 解决中文乱码问题
	 * @param value
	 * @param encode
	 * @return
	 */
	public String decode(String value){
		try {
			 return  URLDecoder.decode(value,ENCODE);
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}
	
	public String getMethod() {
		return method;
	}

	public void setMethod(String method) {
		this.method = method;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public Map<String, List<String>> getParamMapValue() {
		return paramMapValue;
	}

	public void setParamMapValue(Map<String, List<String>> paramMapValue) {
		this.paramMapValue = paramMapValue;
	}

	public InputStream getIns() {
		return ins;
	}

	public void setIns(InputStream ins) {
		this.ins = ins;
	}

	public String getRequestInfo() {
		return requestInfo;
	}

	public void setRequestInfo(String requestInfo) {
		this.requestInfo = requestInfo;
	}
}
package net.httpServer.demo2;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;

import net.httpServer.util.CloseUtil;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月24日 下午9:44:25
* @description: 响应报文通用类
*/
public class ResponeUtil {

	private static final String CRLF="\r\n";
	private static final String FLAG=" ";
	
	//报文头信息
	private StringBuilder headerInfo;
	
	//报文体信息
	private StringBuilder content;
	
	private BufferedWriter bw;
	
	public ResponeUtil() {
		headerInfo = new StringBuilder();
		content = new StringBuilder();
	}
	
	public ResponeUtil(OutputStream ow) {
		this();
		bw = new BufferedWriter(new OutputStreamWriter(ow));
	}
	
	/**
	 * 正文
	 * @param msg 正文
	 * @return
	 */
	public ResponeUtil print(String msg){
		content.append(msg);
		return this;
	}
	
	/**
	 * 正文 + 换行符
	 * @param msg 正文
	 * @return 
	 */
	public ResponeUtil println(String msg){
		
		content.append(msg);
		content.append(CRLF);
		return this;
	}
	
	private void createHeader(int code){
		
		headerInfo.append("HTTP/1.1").append(FLAG);
		switch (code) {
		case 200:headerInfo.append("200").append(FLAG).append("OK");
			break;
		case 404:headerInfo.append("404").append(FLAG).append("NO FOUND");
		    break;
		case 500:headerInfo.append("500").append(FLAG).append("SERVER ERROR");
	    	break;
		default:
		    break;
		}
		
		headerInfo.append(CRLF);
		headerInfo.append("Content-Type:").append(FLAG).append("txt/html").append(CRLF);
		headerInfo.append("Content-Charset:").append(FLAG).append(RequestUtil.ENCODE).append(CRLF);
		headerInfo.append("Date:").append(FLAG).append(new Date()).append(CRLF);
		headerInfo.append("Content-Language:").append(FLAG).append("zh-CN,zh").append(CRLF);

		//报文体的长度,这个必须有,否则客户端会一直等待
		headerInfo.append("Content-Length:").append(FLAG).append(content.toString().getBytes().length).append(CRLF);
		headerInfo.append(CRLF);
		
	}
	
	
	public void pushToClient(int code) throws IOException{
		
			createHeader(code);
			bw.write(headerInfo.toString()+content.toString());
			bw.flush();
		
	}
	
	
	public void closeIO(){
		
			CloseUtil.close(bw);
		
	}
}
package net.httpServer.demo2;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import net.httpServer.util.CloseUtil;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年9月23日 下午4:33:08
* @description: 对请求和响应加入多线程
*/
public class Server {

	private static final String CRLF="\r\n";
	private static final String FLAG=" ";
	
	private boolean isshutdown = false;
	
	private ServerSocket server;
	
	public static void main(String[] args) {
		Server server = new Server();
		server.start();
		
	}
	
	public void start(){
		start(8888);
	}
	
	public void start(int port){
		try {
			 server = new ServerSocket(port);
			 this.reverice();
		} catch (IOException e) {
			
			e.printStackTrace();
			stop();
			
		}
	}

	private  void reverice(){
		try {
			Socket socket = server.accept();
			//多线程发送请求
			while(!isshutdown){
				new Thread(new Dispatcher(socket)).start();
			}
			
		} catch (IOException e) {
			e.printStackTrace();
			stop();
		}
	}
	
	
	public void stop(){
		isshutdown = true;
		CloseUtil.close(server);
	}
}

这样,在启动服务端后,通过浏览器访问 http://localhost:8888/loginhttp://localhost:8888/logi 均可得到LoginServlet类里写的登陆响应信息,访问http://localhost:8888/reg 可得到注册类RegistServlet的响应信息。

我们发现上面的例子有两处问题:

(1)、ServletContext类的servletMap集合存放的有对象,这样太消耗系统内存,待改进;

(2)、url和对应的Servlet是写死的,待改进


 Demo9、解决Demo8中的问题(1),

改进Demo8中ServletContext和Webapp类,具体方法就是通过java的反射机制获取对象,但map中保存的是类的全路径名。

package net.httpServer.demo3;

import java.util.HashMap;
import java.util.Map;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月9日 下午11:36:36
* @description:
*/
public class ServletContext {

	//为每个具体的servlet起个别名,
	//login-->loginservlet
	private Map<String,String> servletMap;
	//地址和别名之间的映射关系(因为一个资源可能有多个url访问)
	//url -->login 
	private Map<String,String> mapping;


	public ServletContext() {
		servletMap = new HashMap<String, String>();
		
		mapping = new HashMap<String, String>();
	}
	
	public Map<String, String> getServletMap() {
		return servletMap;
	}


	public void setServletMap(Map<String, String> servletMap) {
		this.servletMap = servletMap;
	}


	public Map<String, String> getMapping() {
		return mapping;
	}


	public void setMapping(Map<String, String> mapping) {
		this.mapping = mapping;
	}
	
	
}
package net.httpServer.demo3;

import java.util.Map;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月9日 下午11:42:48
* @description:
*/
public class Webapp {

	public static ServletContext context;
	
	
	static{
		context  = new ServletContext();
		
		Map<String,String> mapping = context.getMapping();
		mapping.put("/login", "login");
		mapping.put("/logi", "login");
		mapping.put("/reg", "register");
		
		Map<String,String> servletMap = context.getServletMap();
		servletMap.put("login", "net.httpServer.demo3.LoginServlet");
		servletMap.put("register", "net.httpServer.demo3.RegisterServlet");
		
	}
	
	
	public static Servlet getServlet(String url){
		
		if(null == url || "".equals(url.trim())){
			return null;
		}else{
			String s = context.getServletMap().get(context.getMapping().get(url));
			
			try {
				Servlet servlet = (Servlet) Class.forName(s).newInstance();
				return servlet;
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			} 
		}
		
	}
	
}
Demo 10、解决Demo8中的问题(2)

 把具体的url和Servlet对应关系写入配置文件里,而不是在代码写死,然后去解析xml配置文件。

 1)新加xml的配置文件 web.xml

2)新加解析xml配置文件的bean :ServletClassBean 和ServLetMappingBean

3)新加解析配置文件的处理handler类: WebHandler

4) 修改Webapp类

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
	<servlet>
		<servlet-name>login</servlet-name>
		<servlet-class>net.httpServer.demo4.LoginServlet</servlet-class>
	</servlet>

	<servlet-mapping>
		<servlet-name>login</servlet-name>
		<url-pattern>/login</url-pattern>
	</servlet-mapping>
	
	<servlet-mapping>
		<servlet-name>login</servlet-name>
		<url-pattern>/logi</url-pattern>
	</servlet-mapping>
	
	<servlet>
		<servlet-name>register</servlet-name>
		<servlet-class>net.httpServer.demo4.RegisterServlet</servlet-class>
	</servlet>

	<servlet-mapping>
		<servlet-name>register</servlet-name>
		<url-pattern>/reg</url-pattern>
	</servlet-mapping>

</web-app>
package net.httpServer.demo4;

import java.io.Serializable;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月12日 下午11:30:32
* @description:
*/
public class ServletClassBean implements Serializable{

	private String servletClass;
	
	private String servletName;

	public String getServletClass() {
		return servletClass;
	}

	public void setServletClass(String servletClass) {
		this.servletClass = servletClass;
	}

	public String getServletName() {
		return servletName;
	}

	public void setServletName(String servletName) {
		this.servletName = servletName;
	}
	
}
package net.httpServer.demo4;
/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月12日 下午11:30:50
* @description:
*/
public class ServLetMappingBean {

	private String servletName;
	
	private String urlPattern;

	public String getServletName() {
		return servletName;
	}

	public void setServletName(String servletName) {
		this.servletName = servletName;
	}

	public String getUrlPattern() {
		return urlPattern;
	}

	public void setUrlPattern(String urlPattern) {
		this.urlPattern = urlPattern;
	}

下面我们使用SAX(simple API for xml)的方式解析xml(也可以通过DOM的方式解析xml)。
package net.httpServer.demo4;

import java.util.ArrayList;
import java.util.List;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月12日 上午12:53:09
* @description:
*/
public class WebHandler  extends DefaultHandler{

	private List<ServletClassBean> ServletClassList;
	
	private List<ServLetMappingBean> ServletMappingList;
	
	private ServletClassBean servletClass;
	
	private ServLetMappingBean servLetMapping;
	
	private String tag = null;
	
	private boolean flag = false;
	@Override
	public void startDocument() throws SAXException {
		System.out.println("开始解析==========begin");
		ServletClassList = new ArrayList<ServletClassBean>();
		ServletMappingList = new ArrayList<ServLetMappingBean>();
	}
	
	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		//System.out.println("开始解析元素----"+qName);
		tag = qName;
		if("servlet".equals(qName)){
			servletClass = new ServletClassBean();
			flag = true;
		}else if("servlet-mapping".equals(qName)){
			servLetMapping =  new ServLetMappingBean();
			flag = false;
		}
	}

	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		String value = new String(ch,start,length);
		//System.out.println(value);
		if(flag){
			if("servlet-name".equals(tag)){
				servletClass.setServletName(value);
				tag = null;
			}else if("servlet-class".equals(tag)){
				servletClass.setServletClass(value);
				tag = null;
			}
		}else{
			if("servlet-name".equals(tag)){
				servLetMapping.setServletName(value);
				tag = null;
			}else if("url-pattern".equals(tag)){
				servLetMapping.setUrlPattern(value);
				tag = null;
			}
		}
	}
	
	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		//System.out.println("结束解析元素----"+qName);
		if("servlet".equals(qName)){
			ServletClassList.add(servletClass);
		}else if("servlet-mapping".equals(qName)){
			ServletMappingList.add(servLetMapping) ;
		}
	}

	
	@Override
	public void endDocument() throws SAXException {
		System.out.println("结束解析==========end");
	}

	public List<ServletClassBean> getServletClassList() {
		return ServletClassList;
	}

	public void setServletClassList(List<ServletClassBean> servletClassList) {
		ServletClassList = servletClassList;
	}

	public List<ServLetMappingBean> getServletMappingList() {
		return ServletMappingList;
	}

	public void setServletMappingList(List<ServLetMappingBean> servletMappingList) {
		ServletMappingList = servletMappingList;
	}

	

	
	
	

	

}

注意:关于xml的解析有两种,一种是基于树(DOM)解析的,这种形式一次把所有的xml全部解析完,放入内存中,用什么拿什么,所以比较消耗内存。另一种是基于流(SAX)的形式解析的,就是一行一行的去解析xml文件中的信息,这种方法比较好,建议用。

对上面写的解析xml的类进行测试:
package net.httpServer.demo4;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXException;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月12日 下午11:38:51
* @description:
*/
public class XmlParseTest {

	public static void main(String[] args) throws Exception {
		SAXParserFactory sAXParserFactory = SAXParserFactory.newInstance();
		
		SAXParser parse = sAXParserFactory.newSAXParser();
		WebHandler webHandlernew = new  WebHandler();
		parse.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("net\\httpServer\\demo4\\web.xml"), webHandlernew);
		
		for (ServletClassBean servletClass : webHandlernew.getServletClassList()) {
			System.out.println(servletClass.getServletClass()+"===="+servletClass.getServletName());
		}
		
		for (ServLetMappingBean servLetMapping : webHandlernew.getServletMappingList()) {
			System.out.println(servLetMapping.getUrlPattern()+"===="+servLetMapping.getServletName());

		}
	}
	
}
package net.httpServer.demo4;

import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXException;

/**
* @author:zhangfd
* @version:1.0 
* @date:2017年10月9日 下午11:42:48
* @description:
*/
public class Webapp {

	public static ServletContext context;
	
	
	static{
		context  = new ServletContext();
	
		WebHandler webHandler = parseXml("net\\httpServer\\demo4\\web.xml") ;
		
		if(null != webHandler){
			
			List<ServletClassBean> ServletClassList = webHandler.getServletClassList();
			Map<String,String> servletMap = context.getServletMap();

			for (ServletClassBean servletClass : ServletClassList) {
				servletMap.put(servletClass.getServletName(), servletClass.getServletClass());
			}
		
			List<ServLetMappingBean> ServletMappingList = webHandler.getServletMappingList();
			Map<String,String> mapping = context.getMapping();

			for (ServLetMappingBean servLetMapping : ServletMappingList) {
				mapping.put(servLetMapping.getUrlPattern(),servLetMapping.getServletName());
			}
		}
		
	
	}
	
	public static WebHandler parseXml(String url) {
		
        try {
			SAXParserFactory sAXParserFactory = SAXParserFactory.newInstance();
			
			SAXParser parse = sAXParserFactory.newSAXParser();
			WebHandler webHandlernew = new  WebHandler();
			parse.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream(url), webHandlernew);
			
			return webHandlernew;
		} catch (Exception e) {
			
			e.printStackTrace();
		}
		return null;
	}
	
	public static Servlet getServlet(String url){
		
		if(null == url || "".equals(url.trim())){
			return null;
		}else{
			String s = context.getServletMap().get(context.getMapping().get(url));
			
			try {
				Servlet servlet = (Servlet) Class.forName(s).newInstance();
				return servlet;
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			} 
		}
		
	}
	
}

这样手写服务器就大功告成。

6.8、长链接和短链接

长链接:一旦建立链接,除非人为干预,否则链接就不会中断,像数据库的链接就是属于长连接。

短连接:两个网络之间,当一个网络向另一个网络按一定格式发起链接,另一个网络接收链接信息,并给出响应,链接就自动中断,这样的链接我们成为短连接。像两个系统之间发送的报文信息,就属于短链接。

















































 
  




猜你喜欢

转载自blog.csdn.net/zhang1314fudao/article/details/80788108