Java 網絡

網絡

  • Socket 套接字

  • Socket 編程寫數(shù)據(jù)到輸出流:PrintWriter

  • Socket 模仿向服務端上傳文件

  • Socket 實現(xiàn)單對單的聊天

  • Socket 實現(xiàn)多對多的聊天

  • Http協(xié)議

  • Get請求 VS Post請求

  • URL 簡介

  • URL 發(fā)送Get請求

  • URL 發(fā)送POST請求

  • 參考文章

一.Socket 套接字

Socket底層學習---C語言


1. java.net 包中提供了兩種常見的網絡協(xié)議的支持:

  • TCPTCP 是傳輸控制協(xié)議的縮寫,它保障了兩個應用程序之間的可靠通信。通常用于互聯(lián)網協(xié)議,被稱 TCP / IP。

  • UDPUDP 是用戶數(shù)據(jù)報協(xié)議的縮寫,一個無連接的協(xié)議。提供了應用程序之間要發(fā)送的數(shù)據(jù)的數(shù)據(jù)包。


2. 套接字實現(xiàn)網絡通信:

  • 套接字使用TCP提供了兩臺計算機之間的通信機制。 客戶端程序創(chuàng)建一個套接字,并嘗試連接服務器的套接字。

  • 當連接建立時,服務器會創(chuàng)建一個Socket對象。客戶端和服務器現(xiàn)在可以通過對Socket對象的寫入和讀取來進行通信。

  • java.net.Socket類代表一個套接字,并且 java.net.ServerSocket類為服務器程序提供了一種來監(jiān)聽客戶端,并與他們建立連接的機制。


3. 兩臺計算機之間使用套接字建立TCP連接步驟:

  • 服務器實例化一個ServerSocket對象,表示通過服務器上的端口通信。

  • 服務器調用ServerSocket類的accept()方法,該方法將一直等待,直到客戶端連接到服務器上給定的端口。

  • 服務器正在等待時,一個客戶端實例化一個Socket對象,指定服務器名稱和端口號來請求連接。

  • Socket類的構造函數(shù)試圖將客戶端連接到指定的服務器和端口號。如果想建立通信,則在客戶端創(chuàng)建一個能夠與服務器進行通信的Socket對象。

  • 在服務器端,accept()方法返回服務器上一個新的socket引用,這個新的socket表示連接到服務端的一個socket。

  • 連接建立后,通過使用 I/O 流在進行通信,每一個socket都有一個輸出流和一個輸入流,客戶端的輸出流連接到服務器端的輸入流,而客戶端的輸入流連接到服務器端的輸出流。


4. ServerSocket 類的方法

方法 描述
public ServerSocket(int port) throws IOException 構造方法:創(chuàng)建綁定到特定端口的服務器套接字。
public int getLocalPort() 返回此套接字在其上偵聽的端口
public Socket accept() throws IOException 偵聽并接受到此服務器套接字的連接

5. Socket 類的方法

方法 描述
public Socket(String host, int port) throws UnknownHostException, IOException 構造方法:創(chuàng)建一個流套接字并將其連接到指定主機上的指定端口號
public int getPort() 返回此套接字連接到的遠程端口
public int getLocalPort() 返回此套接字綁定到的本地端口
public InputStream getInputStream() throws IOException 返回此套接字的輸入流
public OutputStream getOutputStream() throws IOException 返回此套接字的輸出流
public void shutdownOutput() throws IOException 只關閉OutputStream而在關閉的同時,并不關閉網絡連接
public void shutdownInput() throws IOException 只關閉InputStream,而在關閉的同時,并不關閉網絡連接
public void close() throws IOException 關閉此套接字

6. 客戶端和服務器端數(shù)據(jù)傳輸

客戶端和服務器端讀取數(shù)據(jù):

  • BuffereReader->InputStreamReader->socket.getInputStream()
  • 客戶端讀取服務器端的數(shù)據(jù),socket就是創(chuàng)建用于通信的socket
  • 服務器端讀取客戶端的數(shù)據(jù),socket就是連接客戶端后獲得的通信的socket
  • 客戶端、服務器端讀取終端數(shù)據(jù),BuffereReader->InputStreamReader->System.in

客戶端和服務器端輸出數(shù)據(jù):

  • PrintStream->socket.getOutputStream
  • 客戶端輸出數(shù)據(jù)到服務器端,socket就是創(chuàng)建用于通信的socket
  • 服務器端輸出數(shù)據(jù)到客戶端,socket就是連接客戶端后獲得的通信的socket

二.Socket編程寫數(shù)據(jù)到輸出流:PrintStream

1. BufferedReader 和 BufferedWriter 方式

//模擬客戶端
class Client{
   public static void main(String[] args) throws IOException {
       //創(chuàng)建用于通信的socket:

       // 1.指明和誰通信 ip地址 端口號
       Socket socket = new Socket("127.0.0.1",8989);

       //接受服務端的數(shù)據(jù)
       BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
       String line;
       while((line = bufferedReader.readLine()) != null){
           System.out.println(line);
       }

       //向服務器端發(fā)送數(shù)據(jù)
       BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
       bufferedWriter.write("請求數(shù)據(jù)");
       bufferedWriter.newLine();
       bufferedWriter.flush();
       socket.shutdownOutput();//輸出完畢,關掉輸出流,防止readLine一直等待讀
   }
}

//模擬服務器端
class Server{
   public static void main(String[] args) throws IOException {
       //1.創(chuàng)建服務器端的ServerSocket
       ServerSocket socket = new ServerSocket(8989);

       //2.獲取連接的客戶端的socket
       Socket cliensocket = socket.accept();

       //3.向客戶端發(fā)送數(shù)據(jù)
       BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(cliensocket.getOutputStream()));
       bufferedWriter.write("接連服務器成功");
       bufferedWriter.newLine();
       bufferedWriter.flush();
       cliensocket.shutdownOutput();//輸出完畢,關掉輸出流,防止readLine一直等待讀

       //4.接受客戶端發(fā)送的數(shù)據(jù)
       BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cliensocket.getInputStream()));
       String line;
       while((line = bufferedReader.readLine()) != null){
           System.out.println(line);
       }
   }
}

2. BufferedReader 和 PrintStream 方式

//模擬客戶端
class Client{
   public static void main(String[] args) throws IOException {
       //創(chuàng)建用于通信的socket:

       // 1.指明和誰通信 ip地址 端口號
       Socket socket = new Socket("127.0.0.1",8989);

       //接受服務端的數(shù)據(jù)
       BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
       String line;
       while((line = bufferedReader.readLine()) != null){
           System.out.println(line);
       }

       //向服務器端發(fā)送數(shù)據(jù)
       PrintStream printStream = new PrintStream(cliensocket.getOutputStream());
       printStream.println("連接服務器成功");
       socket.shutdownOutput();//輸出完畢,關掉輸出流,防止readLine一直等待讀
   }
}

//模擬服務器端
class Server{
   public static void main(String[] args) throws IOException {
       //1.創(chuàng)建服務器端的ServerSocket
       ServerSocket socket = new ServerSocket(8989);

       //2.獲取連接的客戶端的socket
       Socket cliensocket = socket.accept();

       //3.向客戶端發(fā)送數(shù)據(jù)
       PrintStream printStream = new PrintStream(cliensocket.getOutputStream());
       printStream.println("連接服務器成功");
       cliensocket.shutdownOutput();//輸出完畢,關掉輸出流,防止readLine一直等待讀

       //4.接受客戶端發(fā)送的數(shù)據(jù)
       BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cliensocket.getInputStream()));
       String line;
       while((line = bufferedReader.readLine()) != null){
           System.out.println(line);
       }
   }
}

3. BufferedWriter 和 PrintStream 的詳細使用:

  • BufferedWriter:將文本寫入字符輸出流,緩沖各個字符從而提供單個字符。通過write()方法可以將獲取到的字符輸出,然后通過newLine()進行換行操作或者加上\n。BufferedWriter中的字符流必須通過調用flush方法才能將其刷出去。BufferedWriter只能對字符流進行操作,如果要對字節(jié)流操作,則使用BufferedInputStream。

  • PrintStream:向文本輸出流打印對象的格式化表示形式。PrintStream相對于BufferedWriter的好處在于,PrintStream中的字符流在遇到\n自動刷新,或者調用println也可以自動刷新,PrintStream只能對字節(jié)流進行操作,如果要對字節(jié)流操作,則使用PrintWriter。但是PrintWriter想要自動刷新需要在構造方法中設置相關參數(shù),或者手動調用flush刷新。

  • 服務器接收不到客戶端的信息:客戶端中,write()的時候如果沒有發(fā)送換行標識符,那么服務器在接收的時候用readLine()就讀取不出來。因為readLine()是讀取一行,沒遇到換行就讀取不出來。

  • Socket編程中,盡量用PrintStream取代BufferedWrite。

三.Socket 模仿向服務端上傳文件

  • 客戶端和服務端連接成功后

  • 客戶端將文件數(shù)據(jù)寫入到內存中后,將文件的內容一點點傳給服務器

  • 服務端在內存中接收到文件數(shù)據(jù)后,將文件數(shù)據(jù)一點點寫出到服務器端

class Client{
   public static void main(String[] args) throws IOException {
       //連接服務器 獲取socket
       Socket socket = new Socket("127.0.0.1", 8686);

       //創(chuàng)建服務端對應的輸入流用于接受服務器端發(fā)來的數(shù)據(jù)
       BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
       System.out.println(bufferedReader.readLine());

       //向服務端發(fā)送文件(圖片)
       //1.將文件寫入到內存
       String path = "C:\\Users\\a2867\\Desktop\\壁紙\\1.jpg";
       FileInputStream fis = new FileInputStream(path);
       //2.將內容輸出到服務器端
       //將文件的內容一點點傳輸給服務器
       BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
       byte[] buf = new byte[1024];
       int len;
       while((len = fis.read(buf)) != -1){
           bos.write(buf,0,len);
       }
       socket.shutdownOutput();
   }
}

class Server{
   public static void main(String[] args) throws IOException {
       //創(chuàng)建服務器端的ServerSocket
       ServerSocket serverSocket = new ServerSocket(8686);

       //監(jiān)聽客戶端連接
       //當有客戶端來連接這個服務器,就可以得到對應的socket
       //當沒有客戶端來連接,服務器一直在這里等待
       Socket accept = serverSocket.accept();

       //創(chuàng)建客戶端對應的輸出流 用于向這個客戶端發(fā)送數(shù)據(jù)
       PrintStream printStream = new PrintStream(accept.getOutputStream());
       printStream.println("連接服務器成功");
       accept.shutdownOutput();

       //接受客戶端傳遞過來的圖片
       BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
       //文件對應的輸出流
       String path = "C:\\Users\\a2867\\Desktop\\love.jpg";
       FileOutputStream fos = new FileOutputStream(path);

       byte[] buf = new byte[1024];
       int len;
       while((len = bis.read(buf)) != -1){
           fos.write(buf,0,len);
       }
   }
}

四. Socket實現(xiàn)單對單的聊天


客戶端:

  • 主線程:接收終端輸入 將終端輸入發(fā)送給服務器端
  • 子線程:接受服務端發(fā)過來的數(shù)據(jù)

服務端:

  • 主線程:接收終端輸入 將終端輸入發(fā)送給客戶端
  • 子線程:接受服務端發(fā)過來的數(shù)據(jù)
//客戶端
class Client{
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",8989);

        //用一個子線程處理服務器端數(shù)據(jù)
        new AcceptData(socket).start();

        //終端輸入流
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        //socket輸出流
        PrintStream ps = new PrintStream(socket.getOutputStream());
        //讀取終端的輸入 將輸入輸出給服務器端
        String line;
        while ((line = br.readLine()) != null){
            ps.println(line);
        }
    }
}

//Server
class Server{
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8989);

        //獲取連接的客戶端的socket
        Socket accept = serverSocket.accept();

        //創(chuàng)建子線程 處理客戶端輸入信息
        new AcceptData(accept).start();

        //終端輸入流
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        //鏈接的socket輸出流
        PrintStream ps = new PrintStream(accept.getOutputStream());
        //讀取終端的輸入 將輸入輸出給客戶端
        String line;
        while ((line = br.readLine()) != null){
            ps.println(line);
        }
    }
}

//線程:處理對方傳輸?shù)臄?shù)據(jù)
class AcceptData extends Thread{
    private Socket mSocket;

    //保存Socket對象
    public AcceptData(Socket socket){
        mSocket = socket;
    }

    @Override
    public void run() {
        BufferedReader br = null;
        try {
            //獲取服務器端的輸入流對象
            br = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));

            //讀取數(shù)據(jù)
            String line ;
            while ((line = br.readLine()) != null){
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("網絡出錯");
            System.exit(-1);
        }finally {
            try {
                if (br != null){
                    br.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (mSocket != null) {
                try {
                    mSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

五. Socket實現(xiàn)多對多聊天

實現(xiàn)功能:登錄,群聊,私聊

  • 需要多個客戶端連接服務器端,客戶端發(fā)送消息會被服務器端接受。服務器端分析客戶端發(fā)送過來的消息,做出相應的抉擇私聊還是群發(fā)。

  • 多個客戶端連接服務器端就需要服務器端可以接受發(fā)起連接的客戶端并保存。

  • 客戶端發(fā)送的消息要經過統(tǒng)一規(guī)范發(fā)送到服務器端,服務器端才能分析出來。

客戶端的需求在發(fā)送的字符里面體現(xiàn),統(tǒng)一的規(guī)則如下:

  • 登錄:u+登錄名u+

  • 私聊:p+私聊的登錄名&發(fā)送的內容p+

  • 群聊:a+群聊內容a+

/**
 * 管理所有的常量變量
 */
public interface ChatProtocol {
    //登錄
    String LOGIN_FLAG = "u+";
    //私聊
    String PRIVATE_FLAG = "p+";
    //群聊
    String PUBLIC_FLAG = "a+";

    //分隔符
    String SPLITE_FLAG = "&";

    //成功的狀態(tài)
    String SUCCESS = "登陸成功";
    //失敗的狀態(tài)
    String FAILURE = "已經登錄了";
}
/**
 * 管理所有的登錄用戶(Map)
 * 判斷某個用戶是否已經登錄
 */
public class UserManager {

    //保存所有用戶信息
    private  Map<String, Socket> users = new HashMap<>();

    /**
     * 判斷用戶是否已經登錄
     * @param name 用戶名
     * @return 是否登錄
     */
    public synchronized  boolean isLogin(String name){
        //遍歷key數(shù)組
        for (String userName : users.keySet()) {
            if (userName.equals(name)){
                return true;
            }
        }
        return false;
    }

    /**
     * 保存用戶信息
     * @param name 用戶名
     * @param socket 用戶socket
     */
    public synchronized void saveUserMessage(String name,Socket socket){
        users.put(name,socket);
    }

    /**
     * 通過用戶找到對應的Socket
     * @param name 用戶名
     * @return Socket對象
     */
    public synchronized  Socket getSocketByName(String name){
        //默認連接
        return users.get(name);
    }

    /**
     * 通過Socket找到對應的用戶
     * @param socket socket
     * @return 用戶名
     */
    public synchronized  String getNameBySocket(Socket socket){
        //默認連接
        for (String key : users.keySet()) {
            //取出這個key對應的socket
            if(socket == users.get(key)){
                return key;
            }
        }
        return null;
    }

    /**
     * 獲取所有用戶的socket
     * @return 所有socket的集合
     */
    public synchronized  Collection<Socket> getAllUsers(){
        return users.values();
    }

}
/**
 * 服務端
 */
public class Server {
    //用戶管理者
    public static UserManager manager = new UserManager();

    public static void main(String[] args) {
        //創(chuàng)建一個ServerSocket
        try(ServerSocket serverSocket = new ServerSocket(8888)) {
            //監(jiān)聽所有來鏈接的客戶端
            while(true){
                Socket accept = serverSocket.accept();

                //讓子線程處理這個Socket
                new ServerThread(accept).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ServerThread extends Thread{
    private Socket mSocket;

    public ServerThread(Socket socket){
        mSocket = socket;
    }

    @Override
    public void run() {
        //登錄 私聊 群聊 發(fā)文件
        BufferedReader br = null;
        PrintStream ps = null;
        try {
            //得到對應的輸入流對象
            br = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
            //得到對應的輸出流對象
            ps = new PrintStream(mSocket.getOutputStream());

            String line = null;
            while((line = br.readLine()) != null){
                //登錄 u+...u+
                if (line.startsWith(ChatProtocol.LOGIN_FLAG) && line.endsWith(ChatProtocol.LOGIN_FLAG)){
                    //分割
                    //String[] items = line.substring(2).split(ChatProtocol.LOGIN_FLAG);
                    //String name = items[0];
                    //u+jacku+ 2 6
                    //System.out.println(line);
                    String name = line.substring(2,line.length()-2);
                    //System.out.println(name);
                    if (Server.manager.isLogin(name)){
                        //登陸過了
                        //返回結果給客戶端
                        ps.println(ChatProtocol.FAILURE);
                    }else {
                        //沒有登錄
                        ps.println(ChatProtocol.SUCCESS);
                        //保存當前登錄的用戶信息
                        Server.manager.saveUserMessage(name,mSocket);
                        System.out.println(name+"連接服務器了");
                    }
                }else if (line.startsWith(ChatProtocol.PRIVATE_FLAG) && line.endsWith(ChatProtocol.PRIVATE_FLAG)){
                    //p+jack&hellop+
                    //獲取信息
                    String msg = line.substring(2,line.length()-2);
                    //用戶名
                    String[] items = msg.split(ChatProtocol.SPLITE_FLAG);
                    String name = items[0];
                    String content = items[1];
                    //通過用戶名找到對應的socket
                    Socket socketName = Server.manager.getSocketByName(name);
                    PrintStream psPri = new PrintStream(socketName.getOutputStream());

                    //獲取當前用戶的名稱
                    String currentName = Server.manager.getNameBySocket(mSocket);

                    //發(fā)送私聊消息
                    psPri.println(currentName+"向你發(fā)來私聊"+content);
                }else if (line.startsWith(ChatProtocol.PUBLIC_FLAG) && line.endsWith(ChatProtocol.PUBLIC_FLAG)){
                    //群聊
                    //獲取信息
                    String msg = line.substring(2,line.length()-2);

                    //獲取用戶名稱
                    String currentName = Server.manager.getNameBySocket(mSocket);

                    //遍歷所有用戶信息
                    Collection<Socket> sockets = Server.manager.getAllUsers();
                    for (Socket socket : sockets) {
                        PrintStream temp = new PrintStream(socket.getOutputStream());
                        temp.println(currentName+":"+msg);
                        //temp.close();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/**
 * 用戶 可以復制多個該文件用作測試 模擬多個客戶端
 */
public class Client {
    public static void main(String[] args) {
        //接受終端輸入信息
        BufferedReader br = null;
        PrintStream ps = null;
        BufferedReader brr = null;

        //創(chuàng)建Socket 連接服務器
        try(Socket socket = new Socket("127.0.0.1",8888)) {
            //登錄
            while (true){
                //接受終端輸入流
                br = new BufferedReader(new InputStreamReader(System.in));
                //發(fā)給服務器輸出流
                ps = new PrintStream(socket.getOutputStream());
                //接受服務器輸入流
                brr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                //接受終端輸入信息
                String line = JOptionPane.showInputDialog("請輸入用戶名:");
                //拼接登錄格式
                String loginString = ChatProtocol.LOGIN_FLAG+line+ChatProtocol.LOGIN_FLAG;
                //發(fā)送給服務器端

                ps.println(loginString);
                //接受服務器端返回的結果

                String result = brr.readLine();
                if (result.equals(ChatProtocol.SUCCESS)){
                    System.out.println("登陸成功");
                    break;
                }else {
                    System.out.println("用戶名已存在,請重新登錄");
                }
            }

            //登陸成功

            //開啟線程 處理服務器端的輸入
            new ClientThread(socket).start();

            //接受終端輸入發(fā)送給服務器端
            //從終端輸入信息
            String line = null;
            while((line = br.readLine()) != null){
                //發(fā)送給服務器
                ps.println(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ClientThread extends Thread{
    private Socket mSocket;

    public ClientThread(Socket socket){
        mSocket = socket;
    }

    @Override
    public void run() {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
            String line = null;
            while((line = br.readLine()) != null){
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("網絡出錯");
        }finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        } 
    }
}

六. Http協(xié)議

Http簡介

  • HTTP協(xié)議是Hyper Text Transfer Protocol(超文本傳輸協(xié)議)的縮寫,是用于從萬維網(WWW:World Wide Web )服務器傳輸超文本到本地瀏覽器的傳送協(xié)議。

  • HTTP是一個基于TCP/IP通信協(xié)議來傳遞數(shù)據(jù)(HTML 文件, 圖片文件, 查詢結果等)。

  • 一個完整的http請求需要經歷兩個過程:客戶端發(fā)送請求到服務器,然后服務器將結果返回給客戶端

1. 客戶端->服務端

客戶端向服務器發(fā)送請求主要包含以下信息:請求的URL地址、請求頭以及可選的請求體

請求URL(Request URL)

  • 上圖中的Request URL就是請求的URL地址,即https://www.baidu.com,該URL沒有附加其他的參數(shù)。

  • 其實可以通過 ? 和 & 符號向URL地址后面追加一系列的鍵值對參數(shù),比如地址https://www.baidu.com/s?ie=utf-8&wd=Android,該URL包含兩個鍵值對,ie=utf-8,以及wd=Android,ie和wd是key,utf-8和Android分別是其對應的value,服務端可以獲取ie和wd所對應的value的值。

  • 由此我們可以看出,URL可以攜帶額外的數(shù)據(jù)信息。一般情況下,URL的長度不能超過2048個字符,即2KB,超過此限制的話服務器可能就不識別。

請求頭(Request Headers)

  • 上圖中Request Headers部分就是請求頭,請求頭其實也是一些鍵值對,不過這些鍵值通常都是W3C定義了的一些標準的Http請求頭的名稱,請求頭包含了客戶端想告訴服務端的一些元數(shù)據(jù)信息。

  • 注意是元數(shù)據(jù),而不是數(shù)據(jù),比如請求頭User-Agent會告訴服務器這條請求來自于什么瀏覽器,再比如請求頭Accept-Encoding會告訴服務器客戶端支持的壓縮格式。除了這些標準的請求頭,我們還可以添加自定義的請求頭。

請求體(Request Body)

  • 之前我們提到,URL的最大長度就是2048個字符,如果我們發(fā)送的數(shù)據(jù)很大,超過了2KB怎么辦?我們可以將很大的數(shù)據(jù)放到請求體中,GET請求不支持請求體,只有POST請求才能設置請求體。

  • 請求體中可以放置任意的字節(jié)流,從而可以很方便地發(fā)送任意格式的數(shù)據(jù),服務端只需要讀取該輸入流即可。

分析圖

http請求分析

2. 服務器->客戶端

服務器接收到客戶端發(fā)來的請求后,會進行相應的處理,并向客戶端輸出信息,輸出的信息包括響應頭和響應體。


響應頭

  • 響應頭也是一些鍵值對,包含了服務器想要告訴客戶端的一些元數(shù)據(jù)信息,注意不是數(shù)據(jù),是元數(shù)據(jù)。

  • 比如通過響應頭Content-Encoding告訴客戶端服務器所采用的壓縮格式,響應頭Content-Type告訴客戶端響應體是什么格式的數(shù)據(jù),再比如服務端可以通過多個Set-Cookie響應頭向客戶端寫入多條Cookie信息,等等。

  • 剛剛提到的幾個請求頭都是W3C規(guī)定的標準的請求頭名稱,我們也可以在服務端向客戶端寫入自定義的響應頭。

響應體

  • 響應體是服務端向客戶端傳輸?shù)膶嶋H的數(shù)據(jù)信息,本質就是一堆字節(jié)流,可以表示文本,也可以表示圖片或者其他格式的信息。

分析圖

http響應分析

七.Get請求 VS Post請求

Http協(xié)議支持的操作有GET、POST、HEADPUT、TRACE、OPTIONS、DELETE,其中最最常用的還是GET和POST操作。

1. GET請求:

  • GET請求可以被緩存。

  • 當發(fā)送鍵值對信息時,可以在URL上面直接追加鍵值對參數(shù)。當用GET請求發(fā)送鍵值對時,鍵值對會隨著URL一起發(fā)送的。

  • 由于GET請求發(fā)送的鍵值對時隨著URL一起發(fā)送的,所以一旦該URL被黑客截獲,那么就能看到發(fā)送的鍵值對信息,所以GET請求的安全性很低,不能用GET請求發(fā)送敏感的信息(比如用戶名密碼)。

  • 由于URL不能超過2048個字符,所以GET請求發(fā)送數(shù)據(jù)是有長度限制的。

  • 由于GET請求較低的安全性,我們不應該用GET請求去執(zhí)行增加、刪除、修改等的操作,應該只用它獲取數(shù)據(jù)。

2. POST請求:

  • POST請求從不會被緩存。

  • POST請求的URL中追加鍵值對參數(shù),不過這些鍵值對參數(shù)不是隨著URL發(fā)送的,而是被放入到請求體中發(fā)送的,這樣安全性稍微好一些。

  • 應該用POST請求發(fā)送敏感信息,而不是用GET。

  • 由于可以在請求體中發(fā)送任意的數(shù)據(jù),所以理論上POST請求不存在發(fā)送數(shù)據(jù)大小的限制。

  • 當執(zhí)行增減、刪除、修改等操作時,應該使用POST請求,而不應該使用GET請求。

八.URL

1.URL簡介

URL(Uniform Resource Locator)中文名為統(tǒng)一資源定位符,有時也被俗稱為網頁地址,表示為互聯(lián)網上的資源。

格式:protocol://host:port/path?variable=value&variable=value...

分析的例子:http://127.0.0.1/login.php?user_name=jack&user_password=123

  • 協(xié)議為(protocol):如http
  • 主機為(host:port):127.0.0.1
  • 端口號為(port): 80 ,上面的URL并未指定端口,因為 HTTP 協(xié)議默認的端口號為 80
  • 文件路徑為(path):/login.php
  • 請求參數(shù)(variable=value):user_name=jack&user_password=123

2.URL類的方法

方法 描述
public URL(String url) throws MalformedURLException 構造方法:通過給定的URL字符串創(chuàng)建URL
public String getPath() 返回URL路徑部分
public String getQuery() 返回URL查詢部分
public int getPort() 返回URL端口部分
public String getHost() 返回URL的主機
public String getFile() 返回URL文件名部分
public URLConnection openConnection() throws IOException 打開一個URL連接,并運行客戶端訪問資源

3.URLConnections 類的方法

方法 描述
Object getContent() 檢索URL鏈接內容
String getContentEncoding() 返回頭部 content-encoding 字段值
int getContentLength() 返回頭部 content-length字段值
String getContentType() 返回頭部 content-type 字段值
public void setDoInput(boolean input) URL 連接可用于輸入和/或輸出。如果打算使用 URL 連接進行輸入,則將 DoInput 標志設置為 true;如果不打算使用,則設置為 false。默認值為 true。
public void setDoOutput(boolean output) URL 連接可用于輸入和/或輸出。如果打算使用 URL 連接進行輸出,則將 DoOutput 標志設置為 true;如果不打算使用,則設置為 false。默認值為 false。
public InputStream getInputStream() throws IOException 返回URL的輸入流,用于讀取資源
public OutputStream getOutputStream() throws IOException 返回URL的輸出流, 用于寫入資源
public URL getURL() 返回 URLConnection 對象連接的URL

4.HttpURLConnection類(URLConnection的子類)的方法

方法 描述
public void setRequestMethod(String method) throws ProtocolException 設置請求方式,默認方式是GET
public int getResponseCode() throws IOException 請求的狀態(tài)碼,一個int代表三位HTTP狀態(tài)代碼。1xx:信息 ;2xx:成功 ;3xx:重定向 ;4xx:客戶端錯誤 ;5xx:服務器錯誤
public String getResponseMessage() throws IOException HTTP響應信息

5. 簡單的后臺文件

后臺文件目錄

保存上傳的文件目錄

login.php 文件

<?PHP
   #獲取用戶輸入的姓名和密碼
   $name = $_GET["user_name"];
   $pwd = $_GET["user_password"];
   $user = array(
        "name"=>$name,
        "password"=>$pwd,
   );

   $result = array(
          "user"=>$user,
          "total"=>"2",
          "status"=>0,
    );

   #規(guī)定返回類型數(shù)據(jù)為JSON數(shù)據(jù)
   header('Content-Type:application/json');
   echo json_encode($result);
?>

loginPOST.php 文件

<?PHP
   #獲取用戶輸入的姓名和密碼
   $name = $_POST["user_name"];
   $pwd = $_POST["user_password"];
   $user = array(
        "name"=>$name,
        "password"=>$pwd,
   );

   $result = array(
          "user"=>$user,
          "total"=>"2",
          "status"=>0,
    );

   #規(guī)定返回類型數(shù)據(jù)為JSON數(shù)據(jù)
   header('Content-Type:application/json');
   echo json_encode($result);
?>

upLoadFile.php 文件

<?PHP
   //獲取文件
   $file = $_FILES["file"];
   
   //獲取文件信息
   if($file["error"] > 0){

       //讀取文件出錯
       echo "Error:".$file["error"]."<br/>";
   }else{

       //輸出詳細信息
       echo "上傳的文件名:".$file["name"]."<br/>";
       echo "上傳的文件類型:".$file["type"]."<br/>";
       echo "上傳的文件大小:".($file["size"]/1024)."Kb<br/>";
       echo "臨時路徑:".$file["tmp_name"]."<br/>";
       
       //判斷文件類型
       $type = $file["type"];
       $path;
       if($type == "image/jpeg" || $type == "image/png"){
           //圖片
           $path = "upLoad/img/";
       }else if($type == "video/mp4"){
           //視頻
           $path = "upLoad/video/";
       }else if($type == "text/plain"){
           //文本
           $path = "upLoad/file/";
       }
   }
  
   $filePath = $path.$file["name"];
   //判斷文件是否存在

   if(file_exists($filePath)){
       //存在
       echo $file["name"]."已存在";
   }else{
       //不存在
       //將臨時文件里面的文件移動到指定目錄
       move_uploaded_file($file["tmp_name"],$filePath);
       
       echo "文件已保存在:".$filePath;
   }
?>

界面提交返回JSON數(shù)據(jù)的運行結果

上傳和下載文件返回結果數(shù)據(jù)的運行結果

九.URL 發(fā)送Get請求

1.GET方式請求:帶參數(shù)的下載

public static void getParam() throws IOException {

   //1.創(chuàng)建URL
   String path = "http://127.0.0.1/login.php?user_name=jack&user_password=123";
   URL url = new URL(path);

   //2.創(chuàng)建請求方式 獲取鏈接的對象 URLConnection封裝了Socket 默認的請求方式是Get
   URLConnection urlConnection = url.openConnection();
   //HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
   //urlConnection.setRequestMethod("GET");

   //查看請求狀態(tài)
   //System.out.println(urlConnection.getResponseCode());

   //3.發(fā)送數(shù)據(jù)-就在URL里面

   //4.接受服務器端的數(shù)據(jù) 一行一行的讀
   BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
   System.out.println(br.readLine());
}

2.GET方式請求:不帶參數(shù)下載

public static void getImage() throws IOException {
   //創(chuàng)建URL
   URL url = new URL("http://127.0.0.1/upLoad/img/1.jpg");

   //獲取與服務器連接的對象
   URLConnection urlConnection = url.openConnection();

   //讀取下載的內容-獲取輸入流
   InputStream is = urlConnection.getInputStream();

   //創(chuàng)建文件保存的位置
   FileOutputStream fos = new FileOutputStream(""C:\\Users\\a2867\\Desktop\\1.jpg"");
   byte[] buf = new byte[1024];
   int len;
   while ((len = is.read(buf)) != -1){
       fos.write(buf,0,len);
   }
}

3.Get 請求總結

  • Get請求常常被用來下載數(shù)據(jù),既可以下載服務器端響應的數(shù)據(jù),也可以下載具體的文件。

  • Get請求也可以用來上傳數(shù)據(jù),但是Get請求是通過 "key=value" 的鍵值對格式添加到URL后面的形式上傳數(shù)據(jù),由于沒有請求體導致上傳的數(shù)據(jù)大小受到URL長度限制。

十.URL 發(fā)送Post請求

1. POST方式上傳 "key=value" 的鍵值對數(shù)據(jù)

//POST方式上傳數(shù)據(jù)
public static void postParam() throws IOException {
   //1.創(chuàng)建url
   URL url = new URL("http://127.0.0.1/loginPOST.php");

   //2.獲取連接對象
   //HttpURLConnection的父類URLConnection
   //需要設定請求的內容(請求方式,上傳內容)用HttpURLConnection
   HttpURLConnection collection = (HttpURLConnection)url.openConnection();

   //3.設置請求方式為POST
   collection.setRequestMethod("POST");
   //設置有輸出流 需要上傳
   collection.setDoOutput(true);
   //設置有輸入流 需要下載 默認true
   collection.setDoInput(true);

   //4.準備上傳的數(shù)據(jù)
   String data = "user_name=jack&user_password=123";

   //5.開始上傳 輸出流對象
   OutputStream os = collection.getOutputStream();
   os.write(data.getBytes());
   os.flush();
   os.close();

   //6.接受服務器端返回數(shù)據(jù)
   BufferedReader br = new BufferedReader(new InputStreamReader(collection.getInputStream()));
   System.out.println(br.readLine());
}

2. POST方式下載圖片

public static void postForImg() throws IOException {
   //創(chuàng)建URL
   URL url = new URL("http://127.0.0.1/upLoad/img/1.jpg");

   //獲取與服務器連接的對象
   HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
   urlConnection.setRequestMethod("POST");

   //讀取下載的內容-獲取輸入流
   InputStream is = urlConnection.getInputStream();

   //創(chuàng)建文件保存的位置
   FileOutputStream fos = new FileOutputStream("C:\\Users\\a2867\\Desktop\\1.jpg");
   byte[] buf = new byte[1024];
   int len;
   while ((len = is.read(buf)) != -1){
       fos.write(buf,0,len);
   }
}

3. POST方式上傳 txt 文件

//POST方式上傳文件-文本
public static void postFile() throws IOException {
   //1.創(chuàng)建url
   URL url = new URL("http://127.0.0.1/upLoadFile.php");

   //2.獲取連接對象
   //HttpURLConnection的父類URLConnection
   //需要設定請求的內容(請求方式,上傳內容)用HttpURLConnection
   HttpURLConnection collection = (HttpURLConnection)url.openConnection();

   //3.設置請求方式為POST
   collection.setRequestMethod("POST");
   //設置有輸出流 需要上傳
   collection.setDoOutput(true);
   //設置有輸入流 需要下載 默認true
   collection.setDoInput(true);

   //上傳格式
   final String newLine = "\r\n";
   final String boundaryPrefix = "--";
   String boundary = "ABC";
   //collection.setRequestProperty("connection","Keep-Alive");
   //collection.setRequestProperty("Charset","UTF-8");
   collection.setRequestProperty("Content-Type","multipart/form-data;boundary="+boundary);
   StringBuilder sb = new StringBuilder();
   sb.append(boundaryPrefix);
   sb.append(boundary);
   sb.append(newLine);
   sb.append("Content-Disposition: form-data;name=\"file\";filename=\"test.txt\"" + newLine);
   sb.append("Content-Type:text/plain" + newLine);
   sb.append(newLine);
   OutputStream out = new DataOutputStream(collection.getOutputStream());
   out.write(sb.toString().getBytes());// 將參數(shù)頭的數(shù)據(jù)寫入到輸出流中

   //4.準備上傳的數(shù)據(jù)
   InputStream bis = new FileInputStream("C:\\Users\\a2867\\Desktop\\開發(fā)者文檔.txt");
   //5.開始上傳 輸出流對象
   byte[] buf = new byte[1024];
   while ((bis.read(buf)) != -1){
       out.write(buf,0,buf.length);
   }
   out.write(newLine.getBytes());
   bis.close();
   byte[] end_data = (newLine + boundaryPrefix + boundary + boundaryPrefix + newLine).getBytes();
   out.write(end_data);
   out.flush();
   out.close();

   //6.接受服務器端返回數(shù)據(jù)-響應
   BufferedReader br = new BufferedReader(new InputStreamReader(collection.getInputStream()));
   System.out.println(br.readLine());
}

4. POST方式上傳 圖片 文件

//POST方式上傳文件-文本
public static void postFile() throws IOException {
   //1.創(chuàng)建url
   URL url = new URL("http://127.0.0.1/upLoadFile.php");

   //2.獲取連接對象
   //HttpURLConnection的父類URLConnection
   //需要設定請求的內容(請求方式,上傳內容)用HttpURLConnection
   HttpURLConnection collection = (HttpURLConnection)url.openConnection();

   //3.設置請求方式為POST
   collection.setRequestMethod("POST");
   //設置有輸出流 需要上傳
   collection.setDoOutput(true);
   //設置有輸入流 需要下載 默認true
   collection.setDoInput(true);

   //上傳格式
   final String newLine = "\r\n";
   final String boundaryPrefix = "--";
   String boundary = "ABC";
   //collection.setRequestProperty("connection","Keep-Alive");
   //collection.setRequestProperty("Charset","UTF-8");
   collection.setRequestProperty("Content-Type","multipart/form-data;boundary="+boundary);
   StringBuilder sb = new StringBuilder();
   sb.append(boundaryPrefix);
   sb.append(boundary);
   sb.append(newLine);
   sb.append("Content-Disposition: form-data;name=\"file\";filename=\"test.jpeg\"" + newLine);
   sb.append("Content-Type:image/jpeg" + newLine);
   sb.append(newLine);
   OutputStream out = new DataOutputStream(collection.getOutputStream());
   out.write(sb.toString().getBytes());// 將參數(shù)頭的數(shù)據(jù)寫入到輸出流中

   //4.準備上傳的數(shù)據(jù)
   InputStream bis = new FileInputStream("C:\\Users\\a2867\\Desktop\\壁紙\\3.jpeg");
   //5.開始上傳 輸出流對象
   byte[] buf = new byte[1024];
   while ((bis.read(buf)) != -1){
       out.write(buf,0,buf.length);
   }
   out.write(newLine.getBytes());
   bis.close();
   byte[] end_data = (newLine + boundaryPrefix + boundary + boundaryPrefix + newLine).getBytes();
   out.write(end_data);
   out.flush();
   out.close();

   //6.接受服務器端返回數(shù)據(jù)-響應
   BufferedReader br = new BufferedReader(new InputStreamReader(collection.getInputStream()));
   System.out.println(br.readLine());
}

5. POST方式上傳 視頻 文件

//POST方式上傳文件-文本
public static void postFile() throws IOException {
   //1.創(chuàng)建url
   URL url = new URL("http://127.0.0.1/upLoadFile.php");

   //2.獲取連接對象
   //HttpURLConnection的父類URLConnection
   //需要設定請求的內容(請求方式,上傳內容)用HttpURLConnection
   HttpURLConnection collection = (HttpURLConnection)url.openConnection();

   //3.設置請求方式為POST
   collection.setRequestMethod("POST");
   //設置有輸出流 需要上傳
   collection.setDoOutput(true);
   //設置有輸入流 需要下載 默認true
   collection.setDoInput(true);

   //上傳格式
   final String newLine = "\r\n";
   final String boundaryPrefix = "--";
   String boundary = "ABC";
   //collection.setRequestProperty("connection","Keep-Alive");
   //collection.setRequestProperty("Charset","UTF-8");
   collection.setRequestProperty("Content-Type","multipart/form-data;boundary="+boundary);
   StringBuilder sb = new StringBuilder();
   sb.append(boundaryPrefix);
   sb.append(boundary);
   sb.append(newLine);
   sb.append("Content-Disposition: form-data;name=\"file\";filename=\"test.mp4\"" + newLine);
   sb.append("Content-Type:video/mp4" + newLine);
   sb.append(newLine);
   OutputStream out = new DataOutputStream(collection.getOutputStream());
   out.write(sb.toString().getBytes());// 將參數(shù)頭的數(shù)據(jù)寫入到輸出流中

   //4.準備上傳的數(shù)據(jù)
   InputStream bis = new FileInputStream("C:\\Users\\a2867\\Desktop\\壁紙\\testVideo.mp4");
   //5.開始上傳 輸出流對象
   byte[] buf = new byte[1024];
   while ((bis.read(buf)) != -1){
       out.write(buf,0,buf.length);
   }
   out.write(newLine.getBytes());
   bis.close();
   byte[] end_data = (newLine + boundaryPrefix + boundary + boundaryPrefix + newLine).getBytes();
   out.write(end_data);
   out.flush();
   out.close();

   //6.接受服務器端返回數(shù)據(jù)-響應
   BufferedReader br = new BufferedReader(new InputStreamReader(collection.getInputStream()));
   System.out.println(br.readLine());
}

6. POST請求總結

  • POST請求常用來上傳文件數(shù)據(jù),下載數(shù)據(jù)一般用GET請求。

  • POST請求和GET請求一樣都是可以用來提交表單數(shù)據(jù),提交表單數(shù)據(jù)的時候默認類型是 "application/x-www-form-urlencoded" 也就是key=value的鍵值對格式。

  • "multipart/from-data" 和 "application/x-www-form-urlencoded" 一樣都是一種進行表單提交時的消息格式,"multipart/from-data" 用于提交文件類型數(shù)據(jù)。

7.POST請求報文格式

參考格式圖片1

參考格式2

8.文件對應的 MIMEType

類型 文件拓展名 MIMEType
文本 js application/javascript
文本 pdf application/pdf
文本 text/txt text/plain
文本 json application/json
文本 xml text/xml
圖片 png image/png
圖片 bmp\dip image/bmp
圖片 jpe\jpeg\jpg image/jpeg
圖片 gif image/gif
多媒體 mp3 audio/mpeg
多媒體 mp4\mpg4\m4v\mp4v video/mp4

9. 上傳方法總結

public static void postFileToServer(String serverURL,String postKey,String fileName,String fileType,String myPath) throws IOException {
   //1.創(chuàng)建url
   URL url = new URL(serverURL);

   //2.獲取連接對象
   //HttpURLConnection的父類URLConnection
   //需要設定請求的內容(請求方式,上傳內容)用HttpURLConnection
   HttpURLConnection collection = (HttpURLConnection)url.openConnection();

   //3.設置請求方式為POST
   collection.setRequestMethod("POST");
   //設置有輸出流 需要上傳
   collection.setDoOutput(true);
   //設置有輸入流 需要下載 默認true
   collection.setDoInput(true);

   //上傳格式
   final String newLine = "\r\n";
   final String boundaryPrefix = "--";
   String boundary = "ABC";
   //collection.setRequestProperty("connection","Keep-Alive");
   //collection.setRequestProperty("Charset","UTF-8");
   collection.setRequestProperty("Content-Type","multipart/form-data;boundary="+boundary);
   StringBuilder sb = new StringBuilder();
   sb.append(boundaryPrefix);
   sb.append(boundary);
   sb.append(newLine);
   sb.append("Content-Disposition: form-data;name=\""+postKey+"\";filename=\""+fileName+"\"" + newLine);
   sb.append("Content-Type:" + fileType + newLine);
   sb.append(newLine);
   OutputStream out = new DataOutputStream(collection.getOutputStream());
   out.write(sb.toString().getBytes());// 將參數(shù)頭的數(shù)據(jù)寫入到輸出流中

   //4.準備上傳的數(shù)據(jù)
   InputStream bis = new FileInputStream(myPath);
   //5.開始上傳 輸出流對象
   byte[] buf = new byte[1024];
   while ((bis.read(buf)) != -1){
       out.write(buf,0,buf.length);
   }
   out.write(newLine.getBytes());
   bis.close();
   byte[] end_data = (newLine + boundaryPrefix + boundary + boundaryPrefix + newLine).getBytes();
   out.write(end_data);
   out.flush();
   out.close();

   //6.接受服務器端返回數(shù)據(jù)-響應
   BufferedReader br = new BufferedReader(new InputStreamReader(collection.getInputStream()));
   System.out.println(br.readLine());
}

調用例子:

postFileToServer("http://127.0.0.1/upLoadFile.php","file","test.jpg","image/jpeg","C:\\Users\\a2867\\Desktop\\壁紙\\2.jpg");

請求 "本地服務器的upLoadFile.php文件" 上傳到 "本機桌面壁紙文件夾里面的2.jpg,類型是image/jpeg的圖片" 到服務器端中,上傳后的名字是test.jpg

各個參數(shù)意義:

  • serverURL:請求那個URL即那個后臺文件處理我們當前的請求

  • postKey:上傳文件的Key,就是upLoad.php中$file = $_FILES["file"]中的"file"

  • fileName:上傳后文件的名稱

  • fileType:上傳的文件類型

  • myPath:上傳的文件在電腦上的路徑

參考文章:

HTTP POST請求報文格式分析與Java實現(xiàn)文件上傳

HTTP請求報文與響應報文格式,含:get與post的區(qū)別

Socket與ServerSocket的應用

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容