Android之聊天室設計與開發


我們要設計和實現一個有聊天室功能的APP,需要服務器不斷讀取來自客戶端的信息,並即時地將信息發送給每個連接到本服務器上的客戶端,每個客戶端可以向服務器發送消息,並不斷地接收來自服務器的消息,並將消息顯示在界面上。這樣就實現了客戶端與客戶端之間的即時聊天功能。

我用草圖畫了一下及基本流程為:

原理與思路:

使用TCP面向連接的套接字來建立服務端和客戶端兩個IP地址端點之間的會畫。

服務器端主要完成用戶信息的存儲、客戶端命令的響應與接收、信息的轉發等功能。

客戶端主要完成登陸、聊天信息的接收以及發送信息等功能。客戶端必須在服務器啟動后才能連接成功,用戶登陸以后聊天室的客戶端會顯示用戶已經登陸,可以與服務器進行即使聊天。

服務器端設計

服務器端是整個聊天室的核心部分,它涵蓋了客戶端的加入、客戶端請求如何處理等一系列功能,客戶發送的信息也是通過服務器端發送給其他用戶的。

系統的安全性

服務器的安全包括兩個部分,一是服務器本身軟件和硬件上的安全性,比如防止安全漏洞;而是客戶和服務器通訊協議的安全性設計,防止通過協議本身非法攻擊服務器。

系統並發服務能力

多線程並發處理可以使CPU效率達到最高,舉個例子來說,網絡的數據傳送速率遠遠低於CPU的處理能力,本地文件系統資源的讀寫速度也遠遠低於CPU的處理能力,在傳統的單線程環境中,在一個線程程序中如果出現阻塞則整個程序都可以能停止運行,而在一個多線程的程序中就不會出現這樣的問題。當一個線程阻塞時,別的線程依然可以大大地提高CPU的效率。

服務器端主要類的定義

SerFrame類是服務器端程序的入口,其中包括一個主窗體、若干個容器、按鈕和文本框。Init()方法是使整個圖形界面初始化。同時還有ServerStart() 方法和ServerStop()兩個方法,當運行ServerStart()方法時就會創建一個ServerSocket()對象並設定1001端口號,關閉聊天界面會自動斷開連接。

serConnect類集成了Thread線程類,並重寫了父類的main()方法。方法中的run()方法一直處於監聽狀態,直到返回值為false時,也就是服務器關閉時。控制輸入輸出流來顯示在線信息,並發送到用戶管理的設計。

服務器端用戶管理的設計

通過定義UesrInfoList和Node這兩個類來實現服務器管理用戶功能,當服務器接收到一個客戶端的請求,服務器就會初始化一個Node節點作為客戶端,Node類中包括輸入輸出、用戶名等必要的屬性,還定義了其它自身引用NodeNext,其作用是將客戶端設計成鏈接表,通過這個屬性讓他們互相連接,這樣設計的優點是不需要再定義Map或者List來存放客戶端結點,只要在Node中增加一個屬性就能夠實現,從而提高性能。UserInfoList中定義了對Node結點查找、增加、刪除等方法,無論是發送信息、上線、下線和私聊都是調用UserInfoList中的方法來確定對哪個客戶端進行操作的。Node對象是存放在內存中的,當ServerSocket關閉的時候會釋放資源,Node結點失效。

服務器端消息顯示設計

服務器端是所有消息中轉戰和系統消息發出站。在客戶端Node類定義兩個屬性,分別是ObjectOutputStream和ObjectIntputStream,這是java語言的輸出輸入流,應用所有所有信息的傳遞。首先ServerListenerThread會捕獲到客戶端的請求,然后引用ChatRoom類並調用ObjectOutStream方法發送消息。如果是群聊,則調用SendMessage中的SendMsgToAll發送到每一個客戶端,並調用UserInfoList來獲取所有客戶。客戶端獲得消息后調用服務器中定義的Node類中的OutputStream將接受的顯示到圖形界面中的文本輸出框。若果是私聊,UserInfoList會查找到是哪個用戶,再調用SendMessage方法將消息發送到指定客戶端。

客服端設計

客戶端中實現了客戶界面的顯示以及與服務器端的數據交換,包括接收和發送信息。它繼承了Thread線程類,因而多個客戶端可以並發執行。

客戶端設計准則

客戶端作為面對普通的群體,其作用是非常重要的,它的權限沒有服務器高。在客戶端的設計中只要加入客戶端啟動界面和發送、接收信息的線程以及一些對基本字符串的簡單功能,其他復雜的功能都由服務器來完成。

客戶端主要類的定義

ChatClient類中定義了客戶端的主要函數,login方法是用於用於登陸的 ,不需要事先注冊,用戶名不為空即可登陸。另外,還要logout下線以及發送信息的方法。客戶端的主界面的初始化寫在inti方法中,和服務器端界面類似,它由一個panel和若干個按鈕和textArea組成,這些Swing組件與服務器端的監聽器相連。

客戶端登陸設計

程序登陸需要驗證,若果用戶不是已經存在的用戶名,則基本的那個路成功。登陸功能中首先要實例化一個Socket,傳入本機的IP地址和連接服務器的端口號,然后congratulationsocket對象獲取輸入流,實例化用戶線程並啟用,若果服務器未啟動則拋出異常,並在主界面中顯示錯誤信息。

客戶端發端發送信息的設計

SendMessage方法實現發送信息,該方法首先獲得要發送的對象和內容,然后把提示信息、聊天方法、聊天對象、聊天內容和表情依次通過輸出流發送給服務器端,發送一條並及時清空一次緩存,服務器會根據接收到的信息作相應的處理。

客戶端顯示收到的消息的設計

ReiMessage方法實現接收消息,該方法中定義了一個while循環,只要用戶沒有斷開與服務器的連接或者下線,則消息一直處於接收狀態。當輸入流接收服務器發送的內容后,用了條件語句判斷是和何種類型的信息,並對其加上相對應的類型提示信息的處理。最后送交主界面進行顯示。

創建服務器主類:

package com.home.server; 
 
import java.io.IOException; 
import java.net.ServerSocket; 
import java.net.Socket; 
import java.util.ArrayList; 
 
public class MyServer { 
 
    // 定義保存所有Socket的集合  
    public static ArrayList<Socket> socketList = new ArrayList<Socket>(); 
 
    public static void main(String[] args) throws IOException { 
        ServerSocket ss = new ServerSocket(20000); 
        System.out.println("服務器創建成功!"); 
        System.out.println("等待客戶端的連接。。。"); 
        while (true) { 
            // 此行代碼會阻塞,等待用戶的連接  
            Socket socket = ss.accept(); 
            System.out.println("有客戶端連接進來!"); 
            socketList.add(socket); 
            // 每當客戶端連接后啟動一條ServerThread線程為該客戶端服務  
            new Thread(new ServerThread(socket)).start(); 
        } 
    } 
 
} 

  Server:

package com.home.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class MyServer {

 // 定義保存所有Socket的集合
 public static ArrayList<Socket> socketList = new ArrayList<Socket>();

 public static void main(String[] args) throws IOException {
  ServerSocket ss = new ServerSocket(20000);
  System.out.println("服務器創建成功!");
  System.out.println("等待客戶端的連接。。。");
  while (true) {
   // 此行代碼會阻塞,等待用戶的連接
   Socket socket = ss.accept();
   System.out.println("有客戶端連接進來!");
   socketList.add(socket);
   // 每當客戶端連接后啟動一條ServerThread線程為該客戶端服務
   new Thread(new ServerThread(socket)).start();
  }
 }

}

服務器的線程:

package com.home.server; 
 
import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStreamReader; 
import java.io.OutputStream; 
import java.net.Socket; 
 
public class ServerThread implements Runnable { 
    // 定義當前線程所處理的Socket  
    private Socket socket = null; 
    // 該線程所處理的Socket所對應的輸入流  
    BufferedReader br = null; 
 
    public ServerThread(Socket socket) throws IOException { 
        this.socket = socket; 
        // 初始化該Socket對應的輸入流  
        br = new BufferedReader(new InputStreamReader(socket.getInputStream(), 
                "utf-8")); 
    } 
 
    @Override 
    public void run() { 
        try { 
            String content = null; 
            // 采用循環不斷從Socket中讀取客戶端發送過來的數據  
            while ((content = readFromClient()) != null) { 
                // 遍歷socketList中的每個Socket,將讀到的內容向每個Socket發送一次  
                for (Socket s : MyServer.socketList) { 
                    OutputStream os = s.getOutputStream(); 
                    os.write((content + "\n").getBytes("utf-8")); 
                } 
            } 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
    } 

定義讀取客戶端數據的方法:

    private String readFromClient() { 
        try { 
            return br.readLine(); 
        } 
        // 如果捕捉到異常,表明該Socket對應的客戶端已經關閉  
        catch (Exception e) { 
            // 刪除該Socket  
            MyServer.socketList.remove(socket); 
            e.printStackTrace(); 
        } 
        return null; 
    } 
} 

客戶端主體類Activity:

package com.home.activity; 
 
import java.io.OutputStream; 
import java.net.Socket; 
 
import android.app.Activity; 
import android.os.Bundle; 
import android.os.Handler; 
import android.os.Message; 
import android.view.View; 
import android.view.View.OnClickListener; 
import android.widget.Button; 
import android.widget.EditText; 
 
import com.home.R; 
import com.home.util.ClientThread; 
 
public class MultiThreadClient extends Activity { 
    private EditText input, show; 
    private Button sendBtn; 
    private OutputStream os; 
    private Handler handler; 
 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        input = (EditText) findViewById(R.id.main_et_input); 
        show = (EditText) findViewById(R.id.main_et_show); 
        sendBtn = (Button) findViewById(R.id.main_btn_send); 
        handler = new Handler() { 
            @Override 
            public void handleMessage(Message msg) { 
                // 如果消息來自子線程  
                if (msg.what == 0x234) { 
                    // 將讀取的內容追加顯示在文本框中  
                    show.append("\n" + msg.obj.toString()); 
                } 
            } 
        }; 
        Socket socket; 
        try { 
            socket = new Socket("192.168.0.101", 20000); 
            // 客戶端啟動ClientThread線程不斷讀取來自服務器的數據  
            new Thread(new ClientThread(socket, handler)).start(); 
            os = socket.getOutputStream(); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
        sendBtn.setOnClickListener(new OnClickListener() { 
 
            @Override 
            public void onClick(View v) { 
                try { 
                    // 將用戶在文本框內輸入的內容寫入網絡  
                    os.write((input.getText().toString() + "\r\n").getBytes()); 
                    // 清空input文本框數據  
                    input.setText(""); 
                } catch (Exception e) { 
                    e.printStackTrace(); 
                } 
            } 
        }); 
    } 
 
} 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM