java中的socket

此次在java實驗的時候,要求使用server socket編寫服務器和客戶端的網絡通訊。最開始認爲應該是挺簡單的,可是後來發現低估了它。出現了很多的問題,因此也在這裏與你們分享。java

問題描述

服務器程序的處理規則以下:
1) 向客戶端程序發送Verifying Server!。
2) 若讀口令次數超過3次,則發送Illegal User!給客戶端,程序退出。不然向下執行步驟3)。
3) 讀取客戶端程序提供的口令。
4) 若口令不正確,則發送PassWord Wrong!給客戶端,並轉步驟2),不然向下執行步驟5)。
5) 發送Registration Successful!給客戶端程序。

客戶端程序的處理規則以下:
1) 讀取服務器反饋信息。
2) 若反饋信息不是Verifying Server!,則提示Server Wrong!,程序退出。不然向下執行步驟3)
3) 提示輸入PassWord並將輸入的口令發送給服務器。
4) 讀取服務器反饋信息。
5) 若反饋信息是Illegal User!,則提示Illegal User!,程序退出。不然向下執行步驟6)
6) 若反饋信息是PassWord Wrong!,則提示PassWord Wrong!,並轉步驟3),不然向下執行步驟。
7) 輸出Registration Successful!。服務器

初步實現

首先,咱們必定要清楚,此次和以前的程序不一樣,雖然都是在本地上,可是服務器客戶端須要兩個啓動程序來實現,以達到咱們模擬遠程鏈接的效果。網絡

而後就是如何利用socket實現咱們的功能了。多線程

clipboard.png

經過上面的圖示,咱們能夠知道,首先須要先開啓服務器,而後等待客戶端的鏈接。socket

當客戶端經過socket進行鏈接後,服務器端也會創建一個socket對象來幫助實現服務器和客戶端的通訊。ide

這時候就創建了一個TCP鏈接,咱們就能夠在服務器寫數據,而後在客戶端讀取了,實現雙方通訊。函數

最後,當有一方決定通訊結束,就會關閉鏈接。通訊結束。this

下面是初步實現的代碼:spa

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 服務器
 */
public class ServerTest {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            Socket clientSocket = serverSocket.accept();

            String welcome  = "verifying server!";
            OutputStream outputStream = clientSocket.getOutputStream();
            outputStream.write(welcome.getBytes());

            InputStream inputStream = clientSocket.getInputStream();
            int time = 0;

            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String password = bufferedReader.readLine();

            // 獲取登陸信息,容許3次登陸
            while (time < 3) {
                if (password.equals("123")) {
                    outputStream.flush();
                    outputStream.write("Registration Successful!".getBytes());
                    break;
                } else {
                    outputStream.write("PassWord Wrong!".getBytes());
                    outputStream.flush();
                    password = bufferedReader.readLine();
                    time++;
                }
            }

            if (time >= 3) {
                outputStream.flush();
                outputStream.write("Illegal User!".getBytes());
            }

            outputStream.close();
            clientSocket.close();
            serverSocket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.*;
import java.net.Socket;

/**
 * 客戶端
 */
public class ClientTest {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1", 8080);

            String aline = new String();
            
            // 獲取輸入流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            aline = bufferedReader.readLine();

            // 訪問失敗
            if (!aline.equals("verifying server!")) {
                System.out.println("Server Wrong!");
                return;
            }

            // 獲取響應結果
            String feedback = bufferedReader.readLine();

            while (feedback == null || feedback.equals("PassWord Wrong!")) {
                if (feedback != null) {
                    if (feedback.equals("PassWord Wrong!")) {
                        System.out.println("PassWord Wrong!");
                    } else if (feedback.equals("Registration Successful!")) {
                        System.out.println("Registration Successful!");
                        break;
                    }
                }

                System.out.println("輸入密碼:");
                
                // 輸入密碼
                Scanner scanner = new Scanner(System.in);
                OutputStream outputStream = socket.getOutputStream();
                String password = scanner.nextLine();
                outputStream.write(password.getBytes());
                
                feedback = bufferedReader.readLine();
            }

            // 關閉鏈接
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

初步實現後,運行:一片空白。什麼都沒有發生。.net

出問題了很正常,斷點調試一下吧。發現問題所在:

clipboard.png

客戶端執行到這一行時,中止,而後服務器端接着執行;

clipboard.png

一樣的服務器端也到這行就中止了。

緣由是服務器一方等着輸入密碼,而客戶端一方等着響應結果,而後又沒有開始輸入密碼。因此雙方就這麼一直等着,誰都不動了。

解決

經過上面的分析,咱們只要將僵局打破就可以解決問題。因此就先輸入密碼,而後再獲取響應結果。這樣就不會出問題了。

因此服務器端的代碼並不須要作什麼改動,只用修改客戶端程序就好了。

import java.io.*;
import java.net.Socket;

/**
 * 客戶端
 */
public class ClientTest {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1", 8080);

            String aline = new String();
            
            // 獲取輸入流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            aline = bufferedReader.readLine();

            // 訪問失敗
            if (!aline.equals("verifying server!")) {
                System.out.println("Server Wrong!");
                return;
            }

            // 主要修改這裏,不先獲取響應結果
            // 初始化響應結果
            String feedback = null;

            while (true) {
                if (feedback != null) {
                    if (feedback.equals("PassWord Wrong!")) {
                        System.out.println("PassWord Wrong!");
                    } else if (feedback.equals("Registration Successful!")) {
                        System.out.println("Registration Successful!");
                        break;
                    }
                }

                System.out.println("輸入密碼:");
                
                // 輸入密碼
                Scanner scanner = new Scanner(System.in);
                OutputStream outputStream = socket.getOutputStream();
                String password = scanner.nextLine();
                outputStream.write(password.getBytes());
                
                feedback = bufferedReader.readLine();
            }

            // 關閉鏈接
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

clipboard.png

成功實現!


升級:利用多線程實現

功能成功實現以後,還不能知足於當前的情況。因爲在實際使用socket的時候,不少時候都不是簡單的一對一的狀況,這種狀況下,就須要使用多線程來幫助咱們了。

使用多線程,咱們能夠實現一個服務器多個客戶端的狀況,每當有一個客戶端請求鏈接,咱們就會創建一個線程。

1.服務器端線程實現

首先將服務器獨立成一個線程:

/**
 * 服務器線程
 */
public class ServerThread extends Thread {
    private ServerSocket serverSocket;
    private Socket clientSocket;

    public ServerThread(int port) {
        try {
            serverSocket = new ServerSocket(port);
            // 接受客戶端鏈接請求
            clientSocket = serverSocket.accept();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在構造函數中咱們初始化服務器的socket,而後等待客戶端的鏈接。接着就是最主要的部分了,線程主要執行的邏輯功能:run

@Override
public void run() {
    try {
        // 提示鏈接成功
        DataOutputStream dataOutputStream = new DataOutputStream(clientSocket.getOutputStream());
        dataOutputStream.writeUTF("verifying server!");

        // 獲取輸入流
        InputStream inputStream = clientSocket.getInputStream();
        DataInputStream dataInputStream = new DataInputStream(inputStream);
        String password = dataInputStream.readUTF();

        // 獲取登陸信息,容許3次登陸
        int time = 0;

        // 密碼校驗,容許輸入3次
        while (time < 3) {
            if (password.equals("123")) {
                dataOutputStream.writeUTF("Registration Successful!");
                break;
            } else {
                dataOutputStream.writeUTF("PassWord Wrong!");
                password = dataInputStream.readUTF();
                time++;
            }
        }

        if (time >= 3) {
            dataOutputStream.writeUTF("Illegal User!");
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

run沒有什麼能夠說的,主要就是實現服務器與客戶端交互的時候執行的功能。而後在執行線程的時候,會自動調用run方法。

最後就是啓動服務器端了:

/**
 * 服務器端
 */
public class ServiceTest {
    public static void main(String[] args) {
        ServerThread serverThread = new ServerThread(8080);
        serverThread.start();
    }
}

一樣,啓用8080端口。

2.客戶端線程實現

相似服務器端,首先先創建客戶端線程:

/**
 * 客戶端線程
 */
public class ClientThread extends Thread {
    private Socket socket;

    public ClientThread(String host, int port) {
        try {
            this.socket = new Socket(host, port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在構造函數中,創建客戶端的socket對象。而後實現run方法。

@Override
public void run() {
    try {
        // 獲取輸入流
        DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
        String aline = dataInputStream.readUTF();

        // 鏈接服務器失敗
        if (!aline.equals("verifying server!")) {
            System.out.println("Server Wrong!");
            return;
        }

        // 獲取響應結果
        String feedback = null;

        // 進行密碼輸入
        while (true) {
            if (feedback != null) {
                if (feedback.equals("PassWord Wrong!")) {
                    System.out.println("PassWord Wrong!");
                } else if (feedback.equals("Registration Successful!")) {
                    System.out.println("Registration Successful!");
                    System.exit(1);
                }
            }

            System.out.println("輸入密碼:");
            Scanner scanner = new Scanner(System.in);
            DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
            String password = scanner.nextLine();
            dataOutputStream.writeUTF(password);
            
            // 獲取響應結果
            feedback = dataInputStream.readUTF();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

run方法中,主要實現了輸入密碼的功能。

最後就是啓動客戶端線程:

/**
 * 客戶端
 */
public class ClientTest {
    public static void main(String[] args) {
        ClientThread clientThread = new ClientThread("127.0.0.1", 8080);
        clientThread.start();
    }
}

總結

此次的實驗給我提了個醒,看似簡單的東西,也永遠不要輕視它。只有拿出應有的實例去解決問題,問題纔是你眼中那個簡單的問題。

相關文章
相關標籤/搜索