在讲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
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),它命名资源但不指定如何定位资源。上面的 mailto、news 和isbnURI 都是 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)
![](https://i-blog.csdnimg.cn/blog_migrate/70c4e5129a42b15d4dfb190133644c90.png)
状态代码为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
|
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 |
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/login或http://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文件中的信息,这种方法比较好,建议用。
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、长链接和短链接
长链接:一旦建立链接,除非人为干预,否则链接就不会中断,像数据库的链接就是属于长连接。
短连接:两个网络之间,当一个网络向另一个网络按一定格式发起链接,另一个网络接收链接信息,并给出响应,链接就自动中断,这样的链接我们成为短连接。像两个系统之间发送的报文信息,就属于短链接。