HUST-計算機網路實驗-socket程式設計

枕星辰發表於2021-12-17

隨筆---HUST計網實驗:socket程式設計

博主大三在讀,第一次寫隨筆,水平有限,就當記錄一下學習的過程,順便面試前複習專案的時候看看。

實驗要求:

編寫一個 Web 伺服器軟體,要求如下:

基本要求:

1.可配置 Web 伺服器的監聽地址、監聽埠和主目錄(不得寫在程式碼裡面,不能每配置一次都要重編譯程式碼);

2.能夠單執行緒處理一個請求。當一個客戶(瀏覽器,如輸入

“URL:http:// 202.103.2.3/index.html”)連線時建立一個連線套接字;

3.從連線套接字接收 http 請求報文,並根據請求報文的確定使用者請求的網頁檔案;

4.從伺服器的檔案系統獲得請求的檔案。 建立一個由請求的檔案組成的 http 響應報文。;

5.經 TCP 連線向請求的瀏覽器傳送響應,瀏覽器可以正確顯示網頁的內容;

高階要求:

1.能夠傳輸包含多媒體(如圖片)的網頁給客戶端,並能在客戶端正確顯示;

2.在伺服器端的螢幕上輸出請求的來源(IP 地址、埠號和 HTTP 請求命令列);

3.在伺服器端的螢幕上能夠輸出對每一個請求處理的結果;

4.對於無法成功定位檔案的請求,根據錯誤原因,作相應錯誤提示,並具備一定的異常情況處理能力。

Socket套接字介紹:

Socket 是一個抽象概念,代表了通訊雙方的端點(Endpoint),通訊雙方通過 Socket 傳送或接收資料。為了將應用程式和底層的網路通訊協議遮蔽開來,採用套接字(Socket)這樣一個抽象概念來作為應用程式和底層網路之間的應用程式程式設計介面(API)。因為網路應用程式是程式之間的通訊,為了唯一的標識通訊對等方的通訊程式,套接字必須包含 2 種資訊:(1) 通訊對等方的網路地址。(2) 通訊對等方的程式號,通常叫埠號。

構造方法(常用):ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException。引數port指定伺服器要繫結的埠(伺服器要監聽的埠),引數backlog指定客戶連線請求佇列的長度,引數bindAddr指定伺服器要繫結的IP地址。

java中的Socket類主要包括兩個:伺服器端ServerSocket和客戶端Socket。

系統實現:

實驗要求可配置web伺服器的監聽地址、埠,且不能寫在檔案裡面,因此可以建立一個新的配置檔案config.data,內容分兩行,包含埠和ip的配置資訊,用/結束。

內容如下:

點選檢視程式碼
port:5050/
inetaddr:10.21.207.240/

主類MultiThreadsServer:

public class MultiThreadsServer {
    // Main method
    public static void main(String[] args) {
        int Port_File = 0;//伺服器要繫結的埠
        int state = 0;
        try {
            //讀取配置檔案內容
            BufferedReader ConfigReader = new BufferedReader(new FileReader("D:\\新建qq檔案儲存位置\\netlab1\\netlab1\\src\\config.data"));
            String temp_str = "",addr="";//addr:要繫結的ip地址
            while ((temp_str = ConfigReader.readLine()) != null) {
                if (temp_str.contains("port:")) {
                    temp_str = temp_str.substring(temp_str.indexOf("port:") + 5, temp_str.indexOf("/")); //每一行以"/"結尾
                    Port_File = Integer.parseInt(temp_str);
                    state++;
                }
                if (temp_str.contains("inetaddr:")) {
                    temp_str = temp_str.substring(temp_str.indexOf("inetaddr:") + 9, temp_str.indexOf("/"));
                    addr=temp_str;
                    state++;
                }
                if (state == 2)
                    break;  //讀取完port和inetaddr就停止,防止檔案內容不符合要求
            }
            // 建立一個server socket
            ServerSocket serverSocket = new ServerSocket(Port_File, 10, InetAddress.getByName(addr));
            System.out.println("server is listening port:" + serverSocket.getLocalPort());
            // 給執行緒編號
            int i = 0;
            //用accept()方法監聽客戶端連結
            while (true) {
                Socket connectToClient = serverSocket.accept();
                // 在控制檯上輸出連線號
                System.out.println("Starting thread " + i);
                // 為連線建立一個新的執行緒
                ThreadHandler thread = new ThreadHandler(connectToClient);
                // 執行緒執行
                thread.start();
                i++;
            }
        } catch (IOException ex) {
            System.err.println(ex);
        }
    }
}

執行緒類ThreadHandler:

class ThreadHandler extends Thread {
    private Socket connectToClient; // 客戶端
    public ThreadHandler(Socket socket) {
        connectToClient = socket;
    }
    //實現run()方法
    public void run() {
        try {
            System.out.println("build a link with client:" + connectToClient.getPort());
            while (true) {
                //從socket建立輸入流
                InputStream socketInputStream = connectToClient.getInputStream();
                // 等待HTTP請求
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //位元組陣列buffer作為資料緩衝來讀取inputstream裡面的資料
                byte[] buffer = new byte[socketInputStream.available()];//如果網路被阻塞buffer大小為0,未被阻塞則正常接收到
                socketInputStream.read(buffer);
                String request = new String(buffer);
                //如果網路被阻塞,request長度為0,跳過
                if (request.length() != 0) {
                    //輸出請求
                    System.out.println(request);
                    String firstLineOfRequest = request.substring(0, request.indexOf("\r\n"));
                    String[] firstLineParts = firstLineOfRequest.split(" ");
                    String uri = firstLineParts[1];//獲取uri
                    String filename1 = uri.replace("/","");
                    String filename2 = "D:\\新建qq檔案儲存位置\\netlab1\\netlab1\\src\\"+filename1;//獲取請求的檔名
                    File file1 = new File(filename2);
                    //檔案不存在的時候,把uri設定為/,實驗要求不輸入檔名時無法定位的檔案中,實際開發一般自動定位到index.html
                    if(!file1.exists()) uri = "/";
                    // 定義預設狀態
                    if (uri.equals("/")) {
                        uri = "/error.html";    //把預設狀態和無法定位狀態放到一起去,少寫一個預設檔案
                        firstLineParts[1] = "/error.html";
                        System.out.println("查詢檔案可能不存在");
                    }
                    String contentType; //檔案型別
                    if (uri.contains(".html") || uri.contains(".htm")) {
                        contentType = "text/html";
                    } else {
                        if (uri.contains(".jpg") || uri.contains(".jpeg")) {
                            contentType = "image/jpeg";
                        } else {
                            contentType = "text/plain";
                        }
                    }
                    // 報文長度
                    long file_size = RequestLength("D:\\新建qq檔案儲存位置\\netlab1\\netlab1\\src\\" + uri); //*
                    // response result
                    String responseFirstLine = "HTTP/1.1 200 OK\r\n";
                    String responseHeader = "Content-Type:" + contentType + "\r\n";
                    String responseLength = "Content-Length:" + file_size + "\r\n\r\n";
                    InputStream inputStream = new FileInputStream("D:\\新建qq檔案儲存位置\\netlab1\\netlab1\\src\\" + uri); //*
                    OutputStream outputStream = connectToClient.getOutputStream(); //通過客戶端獲取outputStream
                    //輸出請求處理結果
                    outputStream.write(responseFirstLine.getBytes());
                    outputStream.write(responseHeader.getBytes());
                    outputStream.write(responseLength.getBytes());
                    System.out.println("Content-length=" + file_size);
                    System.out.println("uri"+uri);
                    int len = 0;
                    byte[] bytes = new byte[1024];
                    while ((len = inputStream.read(bytes)) != -1) {
                        outputStream.write(bytes, 0, len);
                    }
                    // 等待客戶接受HTTP響應結果
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // connectToClient.close();
            }
        } catch (IOException ex) {
            System.err.println(ex);
        }
    }
    public static long RequestLength(String filename) {
        File file = new File(filename);
        if (!file.exists() || !file.isFile()) {
            System.out.println("404!Not Found File!");
            return -1;
        } else
            return file.length();
    }
}

實驗結果:

本地ip(10.21.207.240)+port(5050)+uri(/): 正確顯示未定位到

本地ip(10.21.207.240)+port(5050)+uri(/index.html):正確獲取

本地ip(10.21.207.240)+port(5050)+uri(/index111.html):對不存在的檔案的訪問

把電腦和手機連上了同一個ip地址(此時為192.168.43.230),這樣就能在手機端顯示結果了:

實驗要求圓滿完成。伺服器的檔案就用靜態檔案存放在當前src目錄下,如果要使用程式碼的話記得改檔名稱和加自己的檔案哦。

相關文章