用Java實現Server-Client結構的聊天系統

說在前面...


  近日研究了一下Java,以及用Java實現socket網絡編程的方法,對於創建一個聊天系統總算有了一點心得,不過我究竟還是個 Java初學者,在衆多高手面前舞刀弄槍,免不了有些膽戰心驚,畢竟才疏學淺,如果在方法方面有什麼不足或者欠成熟的地方,各位大蝦看過之後還要多多指 點,畢竟我看Java只看了一個星期,很多高級方法還沒有接觸,不可能完全設計出一個完美的系統,大家還是分享一下自己的經驗好了,畢竟,重在交流嘛。

  言歸正傳,標準Java包中提供了相當完善的針對網絡通信和I/O相關操作的類庫,利用這些類,可以在很短時間內設計出一個網絡交互系統。 socket編程讓程序員幾乎不用瞭解任何網絡通信協議,就可以編寫出強大的網絡系統。要建立一個Server-Client結構的聊天系統,也非難事。 善加利用socket,I/O操作以及多線程編程,就能實現這個系統。


分析任務...


  聊天系統不外乎兩個方面,服務器端和客戶端。簡單分析一下兩個方面所要完成的任務,對設計這個程序來說,等於完成了一半。首先來看一下服務器端的任務:

  1.服務器端應當建立一個ServerSocket,並且不斷進行偵聽是否有客戶端連接或者斷開連接(包括判斷沒有響應的連接超時)。
  2.服務器端應當是一個信息發送中心,所有客戶端的信息都傳到服務器端,由服務器端根據要求分發信息。

  以上就是服務器端最主要的兩個任務。不難看出,服務器端的任務並不複雜。

  客戶端應該完成的工作包括:

  1.與服務器端建立通信通道,向服務器端發送信息。
  2.接收來自服務器的信息。

  相對服務器而言,客戶端的任務更加簡單,有了以上的簡單分析,可以知道,解決上述四個問題,即完成了該聊天系統的核心。


進一步分析系統結構...


  完成上述分析之後,就開始對每個問題展開研究。

  首先是服務器端的偵聽。由於服務器端要和多個客戶端同時進行通信,那麼系統的部分肯定必須用多個線程來完成,一旦服務器發現一個新的客戶端與之 建立了連接,就馬上建立一個線程與該客戶端進行通信。用多線程的好處在於,多個通信連接可以同時處理,不會出現由於數據排隊等待而發生的延遲或者丟失,可 以很好地利用系統的性能。

  完成了服務器端的第一個的任務,那麼第二個任務也就不難解決了。上面已經爲每一個連接着的客戶端建立了一個線程,這個線程好比一根電話線一直等 待客戶端說話,而信息發送中心就相當於一個總檯,一旦有人打電話進來,就通過電話線把信息傳送到總檯,再由總檯根據需要將這個信息發送給接收羣。這樣也就 意味着,服務器每接受一條信息,就要調用一次信息發送中心的方法,並將這條信息發送到所有客戶端(或者特定的某個/某幾個客戶端)。

  再來看看客戶端。由於客戶端需要同時能夠接收信息以及發送信息,所以也必須利用多個線程來實現。主線程用於接收用戶輸入的內容並將之發送到服務器端,而一個後臺線程將一直接收來自服務器端的信息,並將之返回給客戶端的用戶。

  這樣,一個Server-Client結構的聊天系統基本就完成了。


具體實現...

  
  首先建立一個Server類,Server類完成一個偵聽的工作,併爲建立連接的客戶端增加線程:

public class Server{

static Socket socketlist[] = new Socket[1000];

public Server( int socketport ) throws IOException {

listenForConnecting( socketport );

}

//listenForConnecting方法用於偵聽連接

private void listenForConnecting( int socketport ) throws IOException {

ServerSocket serversocket = new ServerSocket( socketport );

//關鍵是偵聽的部分,用一個while循環來實現

while (true) {

Socket socket = serversocket.accept();

//一旦客戶端連接進來,就增加一個線程用於與該客戶端的通信,關鍵在於傳遞參數,
//將Server類和客戶端的sokcet都傳送到這個通信線程

new ConnectionForClient( this, socket );

}

//SendMessageCenter方法用於發送信息給客戶端

public void SendMessageCenter (String message,Socket socket) throws IOException{


//建立輸出流,將信息發送給客戶端
//你會發現一個用於儲存客戶端socket信息的數組socketlist[],該數組在客戶
//端連接或斷開連接時更新數據,這裏不詳細討論這個問題

for(int k=0;k<socketlist.length();k++){

DataOutputStream dataOut = new DataOutputStream( socketlist[k].getOutputStream() );

dataOut.writeUTF( message );
}


//其他用於判斷信息的過程....

....

}
}


然後建立ConnectionForClient類,這個類完成與客戶端的連接,並與之進行通信:

public class ConnectionForClient extends Thread{

Server server;
Socket socket;

//所有準備工作在構造器中完成

public ConnectionForClient( Server server, Socket socket ) {

this.server = server;
this.socket = socket;

start();

}

public void run() {

//首先建立DataInputStream類用於接收客戶端發送的信息

DataInputStream dataIn = new DataInputStream( socket.getInputStream() );

//類似於服務器的偵聽連接工作,同樣偵聽客戶端發送的信息

while (true) {

String message = dataIn.readUTF();


//一旦接收到客戶端發送的信息,就將詳細發送者和發送的信息傳給Server類的信息發送中心
server.SendMessageCenter( message , this.socket );

}

}

static public void main( String args[] ) throws Exception {

//程序開始執行

int socketport = 8765;

new Server( socketport );

}

}


  完成了服務器端的程序,然後是客戶端:

public class Client extends Thread {

DataInputStream dataIn;
DataOutputStream dataOut;

//Client類的構造器

public Client( String host, int port ){

建立socket對象
Socket socket = new Socket( hostAdress, socketport );

//建立輸入輸出流對象
dataIn = new DataInputStream( socket.getInputStream() );
dataOut = new DataOutputStream( socket.getOutputStream() );


//開始後臺線程用於監聽服務器端傳送的信息
start();

//然後開始接收用戶輸入信息並傳送到客戶端

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

String words="";

while(true){

words = in.readLine();

sendMessage(words);

}

}

public void run(){

while (true) {

//等待服務器發送信息並顯示出來

String message = dataIn.readUTF();

System.out.print ( message + "/n>" );
}

}

//sendMessage方法將用戶輸入的信息發送給服務器

private void sendMessage( String message ){

dataOut.writeUTF( message );

}

//程序開始執行

public static void main(String args[]){

int socketport = 8765;

String hostAddress = "211.211.211.211" //其實可以通過參數指定IP和端口,這裏簡化這個過程

new Client( hostAdress , socketport );

}
}


這樣,一個Server-Client結構的聊天系統的核心就完成了,當然這只是一個最簡單的框架,要進一步完善的話還要進一步考慮很多問題,例如:

其他問題...

客戶端發送的信息應該分爲公用以及私有的信息,然後SendMessageCenter根據信息的類別來判斷應該將信息發送給哪些用戶。

擁有權限的客戶端可以動態地建立聊天頻道,然後SendMessageCenter還可以判斷信息所屬的聊天頻道來決定向誰發送信息

客戶端可以執行聊天命令,例如返回當前服務器的在線信息,返回某個用戶的相關信息,退出聊天系統等等。

所有這些問題都可以通過Client與Server類的SendMessageCenter方法建立一個不很複雜的通信協議來完成。一個通信協議就是一組約定,將相關的信息制定一個格式標準,讓接收方和發送方都知道這條信息將作爲什麼身份進行通信。

例如,發送方發送的信息爲普通的聊天信息時,可以增加一個前綴:words = "[chatmessage]" + words,而發送一條要求返回在線名單的信息時,可以發送另一個前綴 words="[returnonlinelist]"。

當SendMessageCenter接收到客戶端信息時,首先判斷message的前綴,如果是"[chatmessage]",則傳送給其他客戶端,如果是"[returnonlinelist]",則返回給該客戶端一個OnlineList。

一般地,這樣就制定了一個簡單的通信協議。


  關於聊天系統,大概就這樣一些心得,上面的具體實現中的代碼忽略了一些細節以及必要的try-catch-finally結構,目的是爲了讓核心代碼看上去顯得更加清晰一些。大家可以討論一下更好的結構設計。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章