說在前面...
近日研究了一下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結構,目的是爲了讓核心代碼看上去顯得更加清晰一些。大家可以討論一下更好的結構設計。