網絡模型
OSI模型(開放式系統互聯),它是由國際標準化組織(ISO)提出的。
TCP/IP系列協議
TCP/IP(Transmission Control Protocol/Internet Protocol)不只是TCP/IP兩個協議,而是有很多個協議組成,並且是在不同的層,是互聯網的基礎通信架構。
一個http請求瀏覽:應用層HTTP -> 傳輸層TCP -> 網絡層IP(數據包)、 ICMP(確保源地址和目的地址之間是網絡通)、IGMP(本地路由器和英特網的路由器聯通) ->鏈路層
直接使用網絡層協議的應用:ping命令,ICMP協議。
TCP的3次握手協議
第一次握手: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之間可以開始傳輸數據了。
TCP的4次揮手協議
(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("<html><head><title>http請求</title></head><body><h1>這是一個HTTP請求!</h1></body></html>");
//刷新輸出流,使得數據立馬發送
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,導致內存溢出,是這臺服務器宕機了,但是它只會影響的整個系統的訂單業務部分,對於用戶註冊登錄等業務沒有影響,同樣對於系統的日誌記錄也沒有影響。
RPC:
註冊中心:服務端會把它的服務註冊到註冊中心中,包括服務名稱、服務調用的ip地址、端口、協議、還有調用路徑等等。
RMI:
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"));
}
}
自己實現一個RPC 框架
需要掌握Socket通訊、動態代理和反射、Java反序列化
反射
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通訊
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"));
}
}