先來看一段單線程的原始代碼(代碼中有詳細的注釋):
服務器(TCPServer.java):
import java.net.*;
import java.io.*;
public class TCPServer{
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(5566); //創建一個Socket服務器,監聽5566端口
int i=0;
//利用死循環不停的監聽端口
while(true){
Socket s = ss.accept(); //利用Socket服務器的accept()方法獲取客戶端Socket對象。
i++;
System.out.println("第" + i +"個客戶端成功連接!");
DataInputStream dis = new DataInputStream(s.getInputStream()); //獲取客戶端Socket對象的輸入流,並在外邊加一層DataInputStream管道,目的是方便讀取數據
System.out.println(dis.readUTF()); //讀出流中的數據,DataInputStream對象的readUTF()方法可以讀出流中的數據,而且支持中文
dis.close(); //關閉管道連接
s.close(); //關閉Socket連接
}
}
}
客戶端(TCPClient.java):
import java.net.*;
import java.io.*;
public class TCPClient{
public static void main(String[] args) throws Exception{
Socket s = new Socket("192.168.24.177",5566); //創建一個Socket對象,連接IP地址為192.168.24.177的服務器的5566端口
DataOutputStream dos = new DataOutputStream(s.getOutputStream()); //獲取Socket對象的輸出流,並且在外邊包一層DataOutputStream管道,方便輸出數據
dos.writeUTF("客戶端消息"); //DataOutputStream對象的writeUTF()方法可以輸出數據,並且支持中文
dos.flush(); //確保所有數據都已經輸出
dos.close(); //關閉輸出流
s.close(); //關閉Socket連接
}
}
以上代碼利用Socket對象和ServerSocket對象進行簡單的網絡交互,即客戶端通過DataOutputStream對象的writeUTF()方法向服務器發送消息,服務器利用DataInputStream對象的readUTF()方法讀出數據。
看上去挺好,但ServerSocket對象的accept()方法是阻塞的方法,它會一直等待,直到有客戶端連接。
同理,DataInputStream對象的readUTF()方法也是阻塞的方法,它也會一直等待,直到客戶端調用writeUTF()方法。
因此,假如某個客戶端成功連接服務器,但是遲遲不調用writeUTF()方法發送數據,服務器就要一直等待,直到客戶端調用writeUTF()方法為止,此期間整個服務器是阻塞的,無法再接受其他客戶端連接,顯然這不符合實際情況。
要解決這個問題,當然要用多線程。
如果每個客戶端都獨有一個線程,讓readUTF()方法阻塞客戶端獨有的線程,而不去阻塞服務器主線程,這樣服務器就可以同時接受多個客戶端連接,而不用考慮客戶端何時調用writeUTF()方法發送數據。代碼如下:
服務器(TCPServer.java):
import java.net.*;
import java.io.*;
public class TCPServer{
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(5566); //創建一個Socket服務器,監聽5566端口
int i=0;
//利用死循環不停的監聽端口
while(true){
Socket s = ss.accept();//利用Socket服務器的accept()方法獲取客戶端Socket對象。
i++;
System.out.println("第" + i +"個客戶端成功連接!");
Client c = new Client(i,s); //創建客戶端處理線程對象
Thread t =new Thread(c); //創建客戶端處理線程
t.start(); //啟動線程
}
}
}
//客戶端處理線程類(實現Runnable接口)
class Client implements Runnable{
int clientIndex = 0; //保存客戶端id
Socket s = null; //保存客戶端Socket對象
Client(int i,Socket s){
clientIndex = i;
this.s = s;
}
public void run(){
//打印出客戶端數據
try{
DataInputStream dis = new DataInputStream(s.getInputStream());
System.out.println("第" + clientIndex + "個客戶端發出消息:" + dis.readUTF());
dis.close();
s.close();
}
catch(Exception e)
{}
}
}
客戶端(TCPClient.java):
import java.net.*;
import java.io.*;
public class TCPClient{
public static void main(String[] args) throws Exception{
Socket s = new Socket("192.168.24.177",5566); //創建一個Socket對象,連接IP地址為192.168.24.177的服務器的5566端口
DataOutputStream dos = new DataOutputStream(s.getOutputStream()); //獲取Socket對象的輸出流,並且在外邊包一層DataOutputStream管道,方便輸出數據
Thread.sleep((int)(Math.random()*3000)); //讓客戶端不定時向服務器發送消息
dos.writeUTF("客戶端消息"); //DataOutputStream對象的writeUTF()方法可以輸出數據,並且支持中文
dos.flush(); //確保所有數據都已經輸出
dos.close(); //關閉輸出流
s.close(); //關閉Socket連接
}
}
運行結果如下(參考結果,不一定相同!):

明顯看出第2、3、4客戶端都沒有向服務器端發出消息,但都成功連接,而且第2、3、4客戶端向服務器發出消息也沒有順序。
通過多線程,實現了多個客戶端同時連接服務器,並且服務器能實時處理多個客戶端發出的消息。
以上僅僅是作為初學者的一些想法,僅供參考!
