下面哪项技术可以用在WEB开发中实现会话跟踪实现? session 、Cookie 、地址重写 、隐藏域
HTTP是“无状态”协议:客户程序每次读取 Web 页面,都打开到 Web 服务器的单独的连接,并且,服务器也不自动维护客户的上下文信息。即使那些支持持续性 HTTP 连接的服务器,尽管多个客户请求连续发生且间隔很短时它们会保持 socket 打开,但是,它们也没有提供维护上下文信息的内建支持。上下文的缺失引起许多困难。例如,在线商店的客户向他们的购物车中加入商品时,服务器如何知道购物车中己有何种物品呢?类似地,在客户决定结账时,服务器如何能确定之前创建的购物车中哪个属于此客户呢?这些问题虽然看起来十分简单,但是由于 HTTP 的不足,解答它们却异常复杂困难。对于这个问题,存在 3 种典型的解决方案:
Cookie(结合session使用)
可以使用 cookie 存储购物会话的 ID;在后续连接中,取出当前的会话 ID,并使用这个 ID 从服务器上的查找表(lookup table)中提取出会话的相关信息。 以这种方式使用 cookie 是一种绝佳的解决方案,也是在处理会话时最常使用的方式。但是,sevlet 中最好有一种高级的 API 来处理所有这些任务,以及下面这些冗长乏味的任务:从众多的其他cookie中(毕竟可能会存在许多cookie)提取出存储会话标识符的 cookie;确定空闲会话什么时候过期,并回收它们;将散列表与每个请求关联起来;生成惟一的会话标识符。
URL 重写
采用这种方式时,客户程序在每个URL的尾部添加一些额外数据。这些数据标识当前的会话,服务器将这个标识符与它存储的用户相关数据关联起来。 URL重写是比较不错的会话跟踪解决方案,即使浏览器不支持 cookie 或在用户禁用 cookie 的情况下,这种方案也能够工作。URL 重写具有 cookie 所具有的同样缺点,也就是说,服务器端程序要做许多简单但是冗长乏味的处理任务。即使有高层的 API 可以处理大部分的细节,仍须十分小心每个引用你的站点的 URL ,以及那些返回给用户的 URL。即使通过间接手段,比如服务器重定向中的 Location 字段,都要添加额外的信息。这种限制意味着,在你的站点上不能有任何静态 HTML 页面(至少静态页面中不能有任何链接到站点动态页面的链接)。因此,每个页面都必须使用 servlet 或 JSP 动态生成。即使所有的页面都动态生成,如果用户离开了会话并通过书签或链接再次回来,会话的信息也会丢失,因为存储下来的链接含有错误的标识信息。
隐藏的表单域
HTML 表单中可以含有如下的条目:
这个条目的意思是:在提交表单时,要将指定的名称和值自动包括在 GET 或 POST 数据中。这个隐藏域可以用来存储有关会话的信息,但它的主要缺点是:仅当每个页面都是由表单提交而动态生成时,才能使用这种方法。单击常规的超文本链接并不产生表单提交,因此隐藏的表单域不能支持通常的会话跟踪,只能用于一系列特定的操作中,比如在线商店的结账过程。
当客户端访问Web站点时,首先会通过DNS服务查询到域名的IP地址。然后浏览器生成HTTP请求,并通过TCP/IP协议发送给Web服务器。Web服务器接收到请求后会根据请求生成响应内容,并通过TCP/IP协议返回给客户端。
URI:
URL、URN、URI
报文头:
在HTTP1.1里一共规范了47种报文头
概念:
分类:
Session的有效期
OSI七层:
挥手是为了终止连接
第一次挥手: ient发送一个FIN,用来关闭Client到 Server的数据传送,Client进入FIN_WAIT_1状态;
第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态;
第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态;
第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
为什么会有TIME_WAIT状态
为什么需要四次挥手才能断开连接
因为全双工,发送方和接收方都需要FIN报文和ACK报文
UDP特点
RTT和RTO
RTT:发送一个数据包到收到对应的ACK,所花费的时间
RTO:重传时间间隔
TCP使用滑动窗口做流量控制与乱序重排
保证TCP的可靠性
保证TCP的流控特性
Http报文层面:
数据库层面:
其他层面:
Socket是对TCP/IP协议的抽象,是操作系统堆外开放的接口
通信流程
编写一个网络应用程序,有客户端与服务器端,客户端向服务器发送一个字符串,服务器收到该字符串后将其打印到命令行上,然后向客户端返回该字符串的长度,最后,客户端输出服务器端返回的该字符串的长度,分别用TCP和UDP两种方式去实现。
方式一:TCP
Server
/**
* @Description:
* @ClassName algorithm
* @Author: 王瑞文
* @Date: 2021/3/11 13:17
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(4539);
//自旋去一直等待并处理客户端发来的请求
while (true) {
//监听端口,知道客户端返回连接信息
Socket socket=serverSocket.accept();
//获取请求信息,执行业务逻辑
new LengthCalculator(socket).start();
}
}
}
处理请求并返回:
/**
* @Description:
* @ClassName algorithm
* @Author: 王瑞文
* @Date: 2021/3/11 13:18
*/
public class LengthCalculator extends Thread {
private Socket socket;
public LengthCalculator(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
OutputStream os = socket.getOutputStream();//获取socket输入流
InputStream is = socket.getInputStream();//获取socket输出流
int ch = 0;//记录长度
byte[] buffer = new byte[1024];
ch = is.read(buffer);
//接受字节转换成字符串
String content = new String(buffer, 0, ch);
System.out.println(content);
//往输入流写入获得的字符串长度,回发给客户端
os.write(String.valueOf(content.length()).getBytes());
//关闭流和socket
is.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client
/**
* @Description:
* @ClassName algorithm
* @Author: 王瑞文
* @Date: 2021/3/11 13:20
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 4539);//创建Socket
OutputStream os = socket.getOutputStream();//获取socket输入流
InputStream is = socket.getInputStream();//获取socket输出流
os.write(new String("Hello My 小公主").getBytes());//
int ch = 0;
byte[] buffer = new byte[1024];
ch = is.read(buffer);
String content = new String(buffer, 0, ch);
System.out.println(content);
is.close();
os.close();
socket.close();
}
}
方式二:UDP
服务端
/**
* @Description:
* @ClassName algorithm
* @Author: 王瑞文
* @Date: 2021/3/11 13:20
*/
public class UDPServer {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(4369);
byte[] buff = new byte[100];//存储从客户端接受到的内容
DatagramPacket packet = new DatagramPacket(buff, buff.length);
socket.receive(packet);
byte[] data = packet.getData();//从DatagramPacket对象中获取真正存储的数据
//转换格式
String content = new String(data, 0, packet.getLength());
System.out.println(content);
byte[] sendContent = String.valueOf(content.length()).getBytes();
//从DatagramPacket对象中获取到数据的来源地址与端口号
DatagramPacket packetToClient = new DatagramPacket(sendContent, sendContent.length, packet.getAddress(), packet.getPort());
socket.send(packetToClient);//发送数据给客户端
}
}
客户端
public class UDPClient {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket();
byte[] buff = "Hello My pig".getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
//封装数据
DatagramPacket packet = new DatagramPacket(buff, buff.length, address, 4369);
socket.send(packet);//发送数据给客户端
byte[] data = new byte[100];
//DatagramPacket封装服务端发送来的数据
DatagramPacket receivedPacket = new DatagramPacket(data, data.length);
socket.receive(receivedPacket);
String content = new String(receivedPacket.getData(), 0, receivedPacket.getLength());
System.out.println(content);
}
}