OSI模型(开放式系统互联),它是由国际标准化组织(ISO)提出的。
TCP/IP(Transmission Control Protocol/Internet Protocol)不只是TCP/IP两个协议,而是有很多个协议组成,并且是在不同的层,是互联网的基础通信架构。
一个http请求浏览:应用层HTTP -> 传输层TCP -> 网络层IP(数据包)、 ICMP(确保源地址和目的地址之间是网络通)、IGMP(本地路由器和英特网的路由器联通) ->链路层
直接使用网络层协议的应用:ping命令,ICMP协议。
第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
TCP的通讯原理
“阻塞模式”:如果接收端,当然接收端缓冲区为空的时候,调用Socket的read方法的线程会阻塞,阻塞到有数据进入接收缓冲区;另外对于写数据到Socket中的线程来说,如果待发送的数据长度大于发送缓冲区空余长度,则会阻塞在write方法上,等待发送缓冲区的报文被发送到网络上,所以呢这个就是TCP的阻塞。
滑动窗口协议
发送方和接收方都会维护一个数据帧的序列,这个序列被称作窗口。发送方的窗口大小由接收方确认,目的是控制发送速度,以免接收方的缓存不够大导致溢出,同时控制流量也可以避免网络拥塞
HTTP协议
URI是请求的资源,URL是你请求的资源的地址也就是地址路径。
UDP协议
那为什么UDP不可靠,我们还使用它了,在这种在线视频中,丢失数据只会作为干扰出现,并且这种干扰是可以容忍的,就比如看视频的时候出现了画面与声音不同步的现象,大家还是会忍受的,同时UDP传输比较高效
实战
TCP的实战,因为TCP是要建立连接的,所以需要Socket和ServerSocket之间建立连接。
TCP Server
package enjoy.protocol.tcp;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* TCP服务器
*/
public class TcpServer {
public static void main(String[] args) throws Exception{
//创建一个ServerSocket监听一个端:8888
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("TCP服务器已经启动,端口是8888");
//无限循环
while (true){
//等待客户端的请求,如果有请求分配一个Socket
Socket socket = serverSocket.accept();
//根据标准输入构造一个BufferedReader对象
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String buffer =null;
//循环读取输入的每一行数据
while ((buffer = reader.readLine()) !=null && !buffer.equals("")){
System.out.println(buffer);//输出每一行
}
//通过Socket对象得到输出流,构造BufferedWrite对象
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//模拟了http的请求头信息
writer.write("HTTP/1.1 200 OK \r\n Content-Type:text/html \r\n charset=UTF-8\r\n\r\n ");
//写一些html的体
writer.write("http请求 这是一个HTTP请求!
");
//刷新输出流,使得数据立马发送
writer.flush();
//关闭
reader.close();
writer.close();
socket.close();
}
}
}
TCP Client
package enjoy.protocol.tcp;
import java.io.PrintWriter;
import java.net.Socket;
/**
* TCP客户端
*/
public class TcpClient {
public static void main(String[] args) throws Exception{
String msg = "hello 13号技师!";
//创建一个Socket,跟本机的8888端口进行连接
Socket socket = new Socket("127.0.0.1",8888);
//使用Socket创建一个PrintWriter进行写数据
PrintWriter printWriter = new PrintWriter(socket.getOutputStream());
//发送数据
printWriter.println(msg);
//刷新一下,使得服务立马可以收到请求信息
printWriter.flush();
printWriter.println(msg);
printWriter.println(msg);
printWriter.println(msg);
printWriter.println(msg);
//关闭资源
printWriter.close();
socket.close();
}
}
UDP呢,首先UDP是没有任何两台主机之间连接的概念,它只管发给谁就可以了,TCP可以使用流数据,而UDP不行,UDP在处理的时候以一个包的形式进行发送,要么就发送到了,要么就丢失。
UDP Recive
package enjoy.protocol.udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* UDP接收端
*/
public class ReciveDemo {
public static void main(String[] args)throws Exception {
//创建一个DatagramSocket实例,并且把实例绑定到本机的地址,端口10005
DatagramSocket datagramSocket = new DatagramSocket(10005);
byte bytes[] = new byte[1024];
//以一个空数组来创建 DatagramPacket,这个对象作用是接收DatagramSocket中的数据
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);
while(true){//无限循环是必须要的,因为不知道数据何时来
//接收到的数据包
datagramSocket.receive(datagramPacket);
//获取接收的数据
byte[] data = datagramPacket.getData();
//把数组转成字符
String str = new String(data,0,datagramPacket.getLength());
//如果数据包中是88的信息,则跳出并且关闭
if("88".equals(str)){
break;
}
//打印数据
System.out.println("接收到的数据为:"+str);
}
//关闭
datagramSocket.close();
}
}
UDP Send
package enjoy.protocol.udp;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDP发送端
*/
public class SendDemo {
public static void main(String[] args)throws Exception {
//创建一个DatagramSocket实例
DatagramSocket datagramSocket = new DatagramSocket();
//使用键盘输入构建一个BufferedReader
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String line =null;
while((line = bufferedReader.readLine())!=null){
//转成byte
byte[] bytes = line.getBytes();
//创建一个用于发送的DatagramPacket对象
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("127.0.0.1"),10005);
//发送数据包
datagramSocket.send(datagramPacket);
if("88".equals(line)){//当输入88时结束发送
break;
}
}
//关闭
datagramSocket.close();
}
}
RPC可以提高系统稳定性,比如说,我们的订单服务程序更新出BUG,导致内存溢出,是这台服务器宕机了,但是它只会影响的整个系统的订单业务部分,对于用户注册登录等业务没有影响,同样对于系统的日志记录也没有影响。
注册中心:服务端会把它的服务注册到注册中心中,包括服务名称、服务调用的ip地址、端口、协议、还有调用路径等等。
RMI接口和实现类不灵活,RMI必须继承和实现Remote接口之类的。
rmi.1 继承 Remote 接口
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* RMI 订单接口,继承Remote类
*/
public interface IOrder extends Remote{
//付款的方法
public String pay(String id) throws RemoteException;
}
rmi.2 继承UnicastRemoteObject
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* 接口实现类,继承UnicastRemoteObject
*/
public class OrderImpl extends UnicastRemoteObject implements IOrder {
protected OrderImpl() throws RemoteException{
super();
}
@Override
public String pay(String id ) throws RemoteException{
//默认返回成功
return "支付成功!商品订单号:"+id;
}
}
rmi.3 服务提供方 1. LocateRegistry.createRegistry 2. Naming.bind
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
/**
* RMI--服务端
*/
public class Server {
public static void main(String[] args)throws Exception {
//接口实例化
IOrder iOrder = new OrderImpl();
//本地的服务注册到6666端口中
LocateRegistry.createRegistry(6666);
//把刚才的实例绑定到本地端口上的一个路径上
Naming.bind("rmi://localhost:6666/order",iOrder);
System.out.println("服务器已经启动了!");
}
}
rmi.4 服务消费方 Naming.lookup
import java.rmi.Naming;
/**
* RMI-客户端
*/
public class Client {
public static void main(String[] args)throws Exception {
//通过RMI发现服务并且转成一个对象
IOrder iOrder = (IOrder)Naming.lookup("rmi://localhost:6666/order");
//远程调用下
System.out.println(iOrder.pay("168888"));
}
}
demo 类
public class Tech {
//洗脚服务
public boolean XJ(String name) {
System.out.println("13号技师为("+name+")服务");
return true;
}
@Override
public String toString() {
return "这是一名技师";
}
}
反射使用 1. Class.forName 2. TechClazz.newInstance() 3. TechClazz.getMethods() 4. method.invoke
/**
*类说明:演示反射的使用
*/
public class RefleDemo {
public static void main(String[] args) throws ClassNotFoundException,
InstantiationException, IllegalAccessException {
//通过全限定名拿到类的class对象
Class TechClazz = Class.forName("cn.enjoyedu.refle.Tech");
//通过class对象拿到类的实例
Tech shapeInst = (Tech)TechClazz.newInstance();
//通过class对象拿到方法列表
Method[] methods = TechClazz.getMethods();
for(Method method:methods) {
System.out.println(method.getName());
if(method.getName().equals("XJ")){//洗脚服务
try {
//执行指定方法
method.invoke(TechClazz.newInstance(),"king");
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
动态代理,两个概念,一个是代理,一个是动态。
代理的本质就是代理模式,代理模式一定要有这三个要素:接口,提供服务的真实对象,代理对象。
三个要素之外还有重要的两个动作,
首先是真实对象以及代理的对象都必须同时继承一个指定的接口。
第二个,这个代理对象必须包含这个真实的对象。
第一个小伙伴,Proxy, 它就是一个调度器,这个是专门调度人.
第二个小伙伴就是Invocationhandler,这个是一个增强器,专门做增强的.
Invocationhandler的源码,这个代码就只有一个方法,我们把这个方法实现了就可以了。
为什么在RPC中用动态代理增强?-----增强了网络远程调用功能。
interface
public interface Girl {
void date();
void watchMovie();
}
implements
public class WangMeiLi implements Girl {
@Override
public void date() {
System.out.println("王美丽说:跟你约会好开心啊");
this.watchMovie();
}
@Override
public void watchMovie() {
System.out.println("王美丽说:这个电影我不喜欢看");
}
}
Proxy.newProxyInstance & InvocationHandler invoke
public class WangMeiLiProxy implements InvocationHandler {
private Girl gilr;
public WangMeiLiProxy(Girl gilr) {
super();
this.gilr = gilr;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//事前
doSomeThingBefore();
Object ret = method.invoke(gilr, args);
//事后
doSomeThingEnd();
return ret;
}
private void doSomeThingBefore(){
System.out.println("王美丽的父母说:我得先调查下这个男孩子的背景!");
}
private void doSomeThingEnd(){
System.out.println("王美丽的父母说:他有没有对你动手动脚啊?");
}
//代理:调度
public Object getProxyInstance(){
return Proxy.newProxyInstance(gilr.getClass().getClassLoader(), gilr.getClass().getInterfaces(), this);
}
}
调用
public class King {
public static void main(String[] args) {
//隔壁有个女孩,叫王美丽
Girl girl = new WangMeiLi();
//他有个庞大的家庭,想要跟她约会必须征得她家里人的同意
WangMeiLiProxy family = new WangMeiLiProxy(girl);
//有一次我去约王美丽,碰到了她的妈妈,我征得了她妈妈的同意
Girl mother = (Girl) family.getProxyInstance();
//通过她的妈妈这个代理才能与王美丽约会
mother.date();
}
}
Bio 服务端 new Thread(new ServerTask(serverSocket.accept())).start()
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
*类说明:Bio通信的服务端
*/
public class Server {
public static void main(String[] args) throws IOException {
//服务端启动必备
ServerSocket serverSocket = new ServerSocket();
//表示服务端在哪个端口上监听
serverSocket.bind(new InetSocketAddress(10001));
System.out.println("Start Server ....");
try{
while(true){
new Thread(new ServerTask(serverSocket.accept())).start();
}
}finally {
serverSocket.close();
}
}
//每个和客户端的通信都会打包成一个任务,交个一个线程来执行
private static class ServerTask implements Runnable{
private Socket socket = null;
public ServerTask(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//实例化与客户端通信的输入输出流
try(ObjectInputStream inputStream =
new ObjectInputStream(socket.getInputStream());
ObjectOutputStream outputStream =
new ObjectOutputStream(socket.getOutputStream())){
//接收客户端的输出,也就是服务器的输入
String userName = inputStream.readUTF();
System.out.println("Accept client message:"+userName);
//服务器的输出,也就是客户端的输入
outputStream.writeUTF("Hello,"+userName);
outputStream.flush();
}catch(Exception e){
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Bio 客户端 socket.connect(inetSocketAddress)
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
*类说明:Bio通信的客户端
*/
public class Client {
public static void main(String[] args) throws IOException {
//客户端启动必备
Socket socket = null;
//实例化与服务端通信的输入输出流
ObjectOutputStream output = null;
ObjectInputStream input = null;
//服务器的通信地址
InetSocketAddress addr = new InetSocketAddress("127.0.0.1",10001);
try{
socket = new Socket();
socket.connect(addr);//连接服务器
output = new ObjectOutputStream(socket.getOutputStream());
input = new ObjectInputStream(socket.getInputStream());
/*向服务器输出请求*/
output.writeUTF("Mark");
output.flush();
//接收服务器的输出
System.out.println(input.readUTF());
}finally{
if (socket!=null) socket.close();
if (output!=null) output.close();
if (input!=null) input.close();
}
}
}
ObjectOutputStream和ObjectInputStream结合Socket通讯就是我们实现的网络增强部分的组成.
Java实现:
1.服务端定义接口和服务实现类并且注册服务
2.客户端查询出服务
3.客户端使用动态代理调用服务(动态代理)
4.客户端代理把调用对象、方法、参数序列化成数据
5.客户端与服务端通过Socket通讯传输数据
6.服务端反序列化数据成对象、方法、参数。
7.服务端代理拿到这些对象和参数后通过反射的机制调用服务的实例。
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*类说明:服务注册中心
*/
public class RegisterCenter {
//线程池
private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//定义注册中心的静态对象
private static final HashMap<String, Class> serviceRegistry = new HashMap<String, Class>();
private static boolean isRunning = false;
private static int port;
public RegisterCenter(int port) {
this.port = port;
}
//服务注册中心启动
public void start() throws IOException {
//服务器监听
ServerSocket server = new ServerSocket();
//监听绑定端口
server.bind(new InetSocketAddress(port));
System.out.println("start server");
try {
while (true) {
// 1.监听客户端的TCP连接,接到TCP连接后将其封装成task,由线程池执行,并且同时将socket送入(server.accept()=socket)
executor.execute(new ServiceTask(server.accept()));
}
} finally {
server.close();
}
}
//服务的注册:socket通讯+反射
public void register(Class serviceInterface, Class impl) {
serviceRegistry.put(serviceInterface.getName(), impl);
}
//服务的获取运行
private static class ServiceTask implements Runnable {
//客户端socket
Socket clent = null;
public ServiceTask(Socket client) {
this.clent = client;
}
//远程请求达到服务端,我们需要执行请求结果,并且把请求结果反馈至客户端,使用Socket通讯
public void run() {
//反射
//同样适用object流
ObjectInputStream inputStream = null;
ObjectOutputStream outputStream = null;
try {
//1.客户端发送的object对象拿到,2.在采用反射的机制进行调用,3.最后给返回结果
inputStream = new ObjectInputStream(clent.getInputStream());
//顺序发送数据:类名、方法名、参数类型、参数值
//拿到接口名
String serviceName = inputStream.readUTF();
//拿到方法名
String methodName = inputStream.readUTF();
//拿到参数类型
Class<?>[] paramTypes = ( Class<?>[])inputStream.readObject();
//拿到参数值
Object[] arguments = (Object[])inputStream.readObject();
//要到注册中心根据 接口名,获取实现类
Class serviceClass =serviceRegistry.get(serviceName);
//使用反射的机制进行调用
Method method = serviceClass.getMethod(methodName,paramTypes);
//反射调用方法,把结果拿到
Object result = method.invoke(serviceClass.newInstance(),arguments);
//通过执行socket返回给客户端
outputStream = new ObjectOutputStream(clent.getOutputStream());
// /把结果返回给客户端
outputStream.writeObject(result);
//记得关闭
outputStream.close();
inputStream.close();
clent.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
rpc的服务端,提供服务
/**
*类说明:rpc的服务端,提供服务
*/
public class Server {
public static void main(String[] args) throws Exception{
new Thread(new Runnable() {
public void run() {
try {
//起一个服务中心
RegisterCenter serviceServer = new RegisterCenter(8888);
//注册技师对象至注册中心
serviceServer.register(TechInterface.class, TechImpl.class);
//运行我们的服务
serviceServer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
rpc框架的客户端代理部分
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
*类说明:rpc框架的客户端代理部分
*/
public class RpcClientFrame {
/*远程服务的代理对象,参数为客户端要调用的的服务*/
public static <T> T getRemoteProxyObj(final Class<?> serviceInterface)
throws Exception {
// 默认端口8888
InetSocketAddress serviceAddr = new InetSocketAddress("127.0.0.1",8888);
// 1.将本地的接口调用转换成JDK的动态代理,在动态代理中实现接口的远程调用
//进行实际的服务调用(动态代理)
return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface},new DynProxy(serviceInterface,serviceAddr));
}
/*动态代理类,实现了对远程服务的访问*/
private static class DynProxy implements InvocationHandler {
//接口
private final Class<?> serviceInterface;
//远程调用地址
private final InetSocketAddress addr;
//构造函数
public DynProxy(Class<?> serviceInterface, InetSocketAddress addr) {
this.serviceInterface = serviceInterface;
this.addr = addr;
}
/*动态代理类,增强:实现了对远程服务的访问*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/* 网络增强部分*/
Socket socket = null;
//因为传递的大部分是 方法、参数,所以我们使用Object流对象
ObjectInputStream objectInputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
//新建一个Socket
socket = new Socket();
//连接到远程的地址和端口
socket.connect(addr);
//往远端 发送数据,按照顺序发送数据:类名、方法名、参数类型、参数值
//拿到输出的流
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
//发送 调用方法的 类名,使用UTF-8避免乱码
objectOutputStream.writeUTF(serviceInterface.getName());
//发送 方法名
objectOutputStream.writeUTF(method.getName());
//发送 参数类型,使用Object
objectOutputStream.writeObject(method.getParameterTypes());
//发送 参数的值,使用UTF-8避免乱码
objectOutputStream.writeObject(args);
//刷新缓冲区,使得数据立马发送
objectOutputStream.flush();
//立马拿到远程执行的结果
objectInputStream = new ObjectInputStream(socket.getInputStream());
//我们要把调用的细节打印出来
System.out.println("远程调用成功!" + serviceInterface.getName());
//最后要网络的请求返回给返回
return objectInputStream.readObject();
} finally {
//最后记得关闭
socket.close();
objectOutputStream.close();
objectInputStream.close();
}
}
}
}
rpc的客户端,调用远端服务
/**
*类说明:rpc的客户端,调用远端服务
*/
public class Client {
public static void main(String[] args) throws Exception {
//动态代理获取我们的对象
TechInterface techInterface = RpcClientFrame.getRemoteProxyObj(TechInterface.class);
//进远程调用我们的对象
System.out.println(techInterface.XJ("king"));
}
}