Java网络编程
一、网络分层模型
这是OSI的七层模型
最下面是物理层
物理层主要功能是:利用传输介质为数据链路层提供物理连接,实现比特流的透明传输。物理层的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。使其上面的数据链路层不必考虑网络的具体传输介质是什么。
物理层:网卡,网线,集线器,中继器,调制解调器;
数据链路层(Data Link Layer)是OSI模型的第二层,负责建立和管理节点间的链路。该层的主要功能是:通过各种控制协议,将有差错的物理信道变为无差错的、能可靠传输数据帧的数据链路。
数据链路层:网桥,交换机;
网络层(Network Layer)是OSI模型的第三层,它是OSI参考模型中最复杂的一层,也是通信子网的最高一层。
其主要任务是:通过路由选择算法,为报文或分组通过通信子网选择最适当的路径。该层控制数据链路层与传输层之间的信息转发,建立、维持和终止网络的连接。具体地说,数据链路层的数据在这一层被转换为数据包,然后通过路径选择、分段组合、顺序、进/出路由等控制,将信息从一个网络设备传送到另一个网络设备。
寻址:数据链路层中使用的物理地址(如MAC地址)仅解决网络内部的寻址问题。在不同子网之间通信时,为了识别和找到网络中的设备,每一子网中的设备都会被分配一个唯一的地址。由于各子网使用的物理技术可能不同,因此这个地址应当是逻辑地址(如IP地址)。
交换:规定不同的信息交换方式。常见的交换技术有:线路交换技术和存储转发技术,后者又包括报文交换技术和分组交换技术。
路由算法:当源节点和目的节点之间存在多条路径时,本层可以根据路由算法,通过网络为数据分组选择最佳路径,并将信息从最合适的路径由发送端传送到接收端。
连接服务:与数据链路层流量控制不同的是,前者控制的是网络相邻节点间的流量,后者控制的是从源节点到目的节点间的流量。其目的在于防止阻塞,并进行差错检测。
网络层:路由器;
传输层主要任务是:向用户提供可靠的端到端的差错和流量控制,保证报文的正确传输。传输层的作用是向高层屏蔽下层数据通信的细节,即向用户透明地传送报文。该层常见的协议:TCP/IP中的TCP协议。
网关工作在第四层传输层及其以上;
会话层(Session Layer)是OSI模型的第5层,是用户应用程序和网络之间的接口,主要任务是:向两个实体的表示层提供建立和使用连接的方法。将不同实体之间的表示层的连接称为会话。因此会话层的任务就是组织和协调两个会话进程之间的通信,并对数据交换进行管理。
表示层(Presentation Layer)是OSI模型的第六层,它对来自应用层的命令和数据进行解释,对各种语法赋予相应的含义,并按照一定的格式传送给会话层。其主要功能是“处理用户信息的表示问题,如编码、数据格式转换和加密解密”等。
应用层(Application Layer)是OSI参考模型的最高层,它是计算机用户,以及各种应用程序和网络之间的接口,其功能是直接向用户提供服务,完成用户希望在网络上完成的各种工作。它在其他6层工作的基础上,负责完成网络中应用程序与网络操作系统之间的联系,建立与结束使用者之间的联系,并完成网络用户提出的各种网络服务及应用所需的监督、管理和服务等各种协议。 我们熟悉的HTTP FTP Email这类服务都是在应用层。
OSI与TCP/IP对应关系
这个图也是常考的。OSI与TCPIP的对应关系。 传输层与网络层的对应关系是最明确的。
TCP/IP协议族并不包含物理层和数据链路层,因此它不能独立完成整个计算机网络系统的功能,必须与许多其他的协议协同工作。
TCP/IP分层模型的四个协议层分别完成以下的功能:
第一层:
网络接口层:包括用于协作IP数据在已有网络介质上传输的协议。实际上TCP/IP标准并不定义与ISO数据链路层和物理层相对应的功能。相反,它定义像地址解析协议(Address Resolution Protocol,ARP)这样的协议,提供TCP/IP协议的数据结构和实际物理硬件之间的接口。
第二层:
网间层:对应于OSI七层参考模型的网络层。本层包含IP协议、RIP协议(Routing Information Protocol,路由信息协议),负责数据的包装、寻址和路由。同时还包含网间控制报文协议(Internet Control Message Protocol,ICMP)用来提供网络诊断信息。
第三层:
传输层:对应于OSI七层参考模型的传输层,它提供两种端到端的通信服务。其中TCP协议(Transmission Control Protocol)提供可靠的数据流运输服务,UDP协议(Use Datagram Protocol)提供不可靠的用户数据报服务。
第四层:
应用层:对应于OSI七层参考模型的应用层和表达层。因特网的应用层协议包括Finger、Whois、FTP(文件传输协议)、Gopher、HTTP(超文本传输协议)、Telent(远程终端协议)、SMTP(简单邮件传送协议)、IRC(因特网中继会话)、NNTP(网络新闻传输协议)等,这也是本书将要讨论的重点。
TCP三次握手
第一次握手:主机 A 发送位码为 syn=1,随机产生 seq number=1234567 的数据包到服务器,主机 B 由 SYN=1 知道,A 要求建立联机;
第二次握手:主机 B 收到请求后要确认联机信息,向 A 发送 ack number=(主机 A 的
seq+1),syn=1,ack=1,随机产生 seq=7654321 的包;
第三次握手:主机 A 收到后检查 ack number 是否正确,即第一次发送的 seq number+1,以及位码ack 是否为 1,若正确,主机 A 会再发送 ack number=(主机 B 的 seq+1),ack=1,主机 B 收到后确认。
TCP四次挥手
TCP 建立连接要进行三次握手,而断开连接要进行四次。这是由于 TCP 的半关闭造成的。因为 TCP 连 接是全双工的(即数据可在两个方向上同时传递)所以进行关闭时每个方向上都要单独进行关闭。这个单 方向的关闭就叫半关闭。当一方完成它的数据发送任务,就发送一个 FIN 来向另一方通告将要终止这个 方向的连接。
- 关闭客户端到服务器的连接:首先客户端 A 发送一个 FIN,用来关闭客户到服务器的数据传送, 然后等待服务器的确认。其中终止标志位 FIN=1,序列号 seq=u
- 服务器收到这个 FIN,它发回一个 ACK,确认号 ack 为收到的序号加 1。
- 关闭服务器到客户端的连接:也是发送一个 FIN 给客户端。
- 客户段收到 FIN 后,并发回一个 ACK 报文确认,并将确认序号 seq 设置为收到序号加 1。 首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
二、HTTP
1.HTTP协议概述
HTTP 是一个无状态的协议。无状态是指客户机(Web 浏览器)和服务器之间不需要建立持久的连接。
这意味着当一个客户端向服务器端发出请求,然后服务器返回响应(response),连接就被关闭了,在服务器端不保留连接的有关信息。
HTTP 遵循请求(Request)/应答(Response)模型。客户机(浏览器)向 服务器发送请求,服务器处理请求并返回适当的应答。所有 HTTP 连接都被构造成一套请求和应答。
2.HTTP传输流程
-
协议解析
如用客户端请求这个页面:http://localhost.com:8080/index.html 从中分解出协议名、主机名、端口、对象路径等部分。 -
封装 HTTP 请求数据包
把以上部分结合本机自己的信息,封装成一个 HTTP 请求数据包 -
封装成 TCP 包并建立连接
封装成 TCP 包,建立 TCP 连接(TCP 的三次握手) -
客户机发送请求命
建立连接后,客户机发送一个请求给服务器,请求方式的格式为:统一资 源标识符(URL)、协议版本号,后边是 MIME 信息包括请求修饰符、客户机信息和可选内容。 -
服务器响应
服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或 错误的代码,后边是 MIME 信息包括服务器信息、实体信息和可能的内容。
-
服务器关闭 TCP 连接
服务器关闭 TCP 连接:一般情况下,一旦 Web 服务器向浏览器发送了 请求数据,它就要关闭 TCP 连 接,然后如果浏览器或者服务器在其头 信息加入了这行代码 Connection:keep-alive,TCP 连接在发送 后将仍然 保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保 持连接节省了为每个请求 建立新连接所需的时间,还节约了网络带宽。
3.HTTP是无状态的,如何识别用户的登录状态?
身份验证机制 Cookie,Session与Token
答案是用session机制。
浏览器第一次访问服务端时,服务器此时肯定不知道他的身份,所以创建一个独特的身份标识数据,格式为key=value,放入到Set-Cookie字段里,随着响应报文发给浏览器。浏览器看到有Set-Cookie字段以后就知道这是服务器给的身份标识,于是就保存起来,下次请求时会自动将此key=value值放入到Cookie字段中发给服务端。服务端收到请求报文后,发现Cookie字段中有值,就能根据此值识别用户的身份然后提供个性化的服务。
Session机制因为是通过随机生成的sessionid来确保用户是已登录的, 因此需要服务端保留每个最近访问客户的sessionid,这对于用户数量大的网站这是一个负担,服务器有多台的时候需要保证同一个sessionid的请求落在同一台机器上, 这台机器挂了要做session迁移,后来又引入memcached 来解决session存储的问题;
因为session机制的种种问题,token机制被发明出来。
Token在客户端也是存放在cookie里面, 但是Token是将用户的登录信息用加密算法生成的一个密文串,只有服务段可以对这个加密字符串进行解读,以判断用户的登录状态是否有效, 由于token自己就携带着验证信息,不像sessionid只是一个随机字符串,因此token的验证不需要存储用户的sessionid,这就是用计算时间来换存储空间的常用做法。
三、Socket
1.什么是Socket
在网络编程中,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
Socket套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
Socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
2.Socket建立
服务器监听(Listen)
是服务器端套接字并不知道哪个客户端会连接,处于等待连接,实时监控网络状态。
客户端请求(Connect)
是指由客户端提出连接请求,要连接的目标是服务器端的套接字。为此,客户端必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
连接确认(Accept)
是指当服务器端套接字监听到并响应客户端套接字的连接请求,,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
3.Socket类
Socket 类表示通信双方中的客户端,用于呼叫远端机器上的一个端口,主动向服务器端发送数据(当连接建立后也能接收数据)。
建立与服务器端的连接并将数据发送到服务器端:
socket=new Socket(serverIP,port); //建立连接
out=socket.getOutputStream(); //发送数据
out.write("我是客户端数据 ".getBytes());
从输入流中读出服务器的反馈信息
byte[] b=new byte[1024];
in=socket.getInputStream();
int len=in.read(b);
关闭输入/输出流以及 Socket 对象
in.close();
out.close();
socket.close();
4.SocketServer类
ServerSocket 类是与 Socket 类相对应的用于表示通信双方中的服务器端,用于在服务器上开一个端口,被动地等待数据(使用 accept() 方法)并建立连接进行数据交互。
ServerSocket=new ServerSocket(port); //创建服务器套接字
System.out.println("服务器开启,等待连接。。。");
socket=ServerSocket.accept(); //获得连接
//接收客户端发送的内容
in=socket.getInputStream();
byte[] b=new byte[1024];
int len=in.read(b);
System.out.println("客户端发送的内容为:"+new String(b,0,len));
out=socket.getOutputStream();
out.write("我是服务器端".getBytes());
in.close();out.close();
ServerSocket.close();
socket.close();
5.一个简单的服务端客户端程序
package week3.demo;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServerDemo {
public static void main(String[] argv) throws IOException {
ServerSocket serverSocket =new ServerSocket(8999); //创建服务器套接字
System.out.println("Server start, waiting....");
while(true) {
Socket socket = serverSocket.accept(); //获得连接
InputStream in = socket.getInputStream();
byte[] b = new byte[1024];
int len = in.read(b);
String message = new String(b, 0, len);
System.out.println("Message from client:" + message);
OutputStream out = socket.getOutputStream();
String reply = message.replaceFirst("Ping","Pong");
out.write(reply.getBytes());
in.close();
out.close();
socket.close();
}
}
}
package week3.demo;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class SocketClientDemo {
public static void main(String[] argv) throws IOException, InterruptedException {
for(int i =0;i<100;i++) {
Socket socket = new Socket("127.0.0.1", 8999); //建立连接
OutputStream out = socket.getOutputStream(); //发送数据
out.write(("Ping "+i).getBytes());
byte[] b = new byte[1024];
InputStream in = socket.getInputStream();
int len = in.read(b);
System.out.println("Message from server:" + new String(b, 0, len));
in.close();
out.close();
socket.close();
Thread.sleep(10);
}
}
}