此次在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
實現咱們的功能了。多線程
經過上面的圖示,咱們能夠知道,首先須要先開啓服務器
,而後等待客戶端的鏈接。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
出問題了很正常,斷點調試一下吧。發現問題所在:
客戶端執行到這一行時,中止,而後服務器端接着執行;
一樣的服務器端也到這行就中止了。
緣由是服務器一方等着輸入密碼,而客戶端一方等着響應結果,而後又沒有開始輸入密碼。因此雙方就這麼一直等着,誰都不動了。
經過上面的分析,咱們只要將僵局打破就可以解決問題。因此就先輸入密碼
,而後再獲取響應結果
。這樣就不會出問題了。
因此服務器端的代碼並不須要作什麼改動,只用修改客戶端程序就好了。
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(); } } }
成功實現!
功能成功實現以後,還不能知足於當前的情況。因爲在實際使用socket
的時候,不少時候都不是簡單的一對一的狀況,這種狀況下,就須要使用多線程來幫助咱們了。
使用多線程,咱們能夠實現一個服務器多個客戶端的狀況,每當有一個客戶端請求鏈接,咱們就會創建一個線程。
首先將服務器獨立成一個線程:
/** * 服務器線程 */ 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
端口。
相似服務器端,首先先創建客戶端線程:
/** * 客戶端線程 */ 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(); } }
此次的實驗給我提了個醒,看似簡單的東西,也永遠不要輕視它。只有拿出應有的實例去解決問題,問題纔是你眼中那個簡單的問題。