1. 程式人生 > >IM即時通訊(三) 多客戶端文字傳輸之聊天功能

IM即時通訊(三) 多客戶端文字傳輸之聊天功能

主要內容:

多執行緒的應用
讀寫分離思想
伺服器轉發

伺服器轉發是什麼呢?

可以假想伺服器是一個大型的資料中心,按照一定的規則,將資訊送到你想送去的地方。
而且,這裡的規則是由開發者自己定義的。

原始碼例項:

客戶端

package me.mxzf;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * 
 * @Title: Client
 * @Dscription
: 客戶端 * @author Deleter * @date 2017年3月12日 下午2:29:00 * @version 1.0 */
public class Client { public static void main(String[] args) throws UnknownHostException, IOException { // 建立Socket例項 Socket socket = new Socket(InetAddress.getLocalHost(), 1234); // 包裝寫 new
ThreadWriter(socket).start(); // 包裝讀 new ThreadReader(socket).start(); } }

客戶端讀執行緒

package me.mxzf;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * 
 * @Title: ThreadReader
 * @Dscription: 讀執行緒
 * @author Deleter
 * @date 2017年3月12日 下午2:32:46
 * @version
1.0 */
public class ThreadReader extends Thread { private Socket socket; private DataInputStream dis; private String content; public ThreadReader(Socket socket) { this.socket = socket; } @Override public void run() { try { // 用DataInputStream包裝socket的輸入流 dis = new DataInputStream(this.socket.getInputStream()); while (true) { // 一直阻塞,直到有訊息到來,進入迴圈體 while ((content = dis.readUTF()) != null) { // 列印接收到的訊息 System.out.println(content); } } } catch (IOException e) { e.printStackTrace(); } } }

客戶端寫執行緒

package me.mxzf;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * 
 * @Title: ThreadWriter
 * @Dscription: 寫執行緒
 * @author Deleter
 * @date 2017年3月12日 下午2:33:25
 * @version 1.0
 */
public class ThreadWriter extends Thread {

    private Socket socket;
    private DataOutputStream dos;

    public ThreadWriter(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 用DataOutputStream包裝socket的輸出流
            dos = new DataOutputStream(socket.getOutputStream());
            while (true) {
                // 等待3後
                Thread.sleep(3000);
                // 向伺服器傳送"Hello World!"
                dos.writeUTF("Client:Hello World!");
                // 重新整理緩衝區,防止粘包
                dos.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

伺服器

package me.mxzf;

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

/**
 * 
 * @Title: Server
 * @Dscription: 伺服器
 * @author Deleter
 * @date 2017年3月12日 下午2:28:09
 * @version 1.0
 */
public class Server {

    // 建立靜態的集合來存放"客戶通道"
    public static HashSet<ServerThread> lists = new HashSet<>();

    public static void main(String[] args) throws IOException {
        // 建立ServerSocket例項
        ServerSocket server = new ServerSocket(1234);
        // 在迴圈的外部定義變數,節約記憶體空間
        Socket socket;
        ServerThread serverThread;
        while (true) {
            // 阻塞直到有客戶端接入
            socket = server.accept();
            // 包裝Socket
            serverThread = new ServerThread(socket, lists);
            serverThread.start();
            // 將當前接入的"客戶通道"儲存在集合中
            lists.add(serverThread);
        }
    }

    /**
     * 伺服器轉發
     * 
     * 這種遍歷的方式,效率極低,不介意使用
     * 
     * 但是為了初學者更好的理解,我還是用了這種方式
     */
    public void forword(String serverSocketName, String content) {
        try {
            // 遍歷集合
            for (ServerThread serverThread : lists) {
                // 如果該ServerThread的toString與即將傳送的目的地名稱一致時
                if (serverThread.toString().equals(serverSocketName)) {
                    // 獲取該ServerThread的輸出流,向其中寫出資訊
                    serverThread.getDataOutputStream().writeUTF(content);
                    // 同時,重新整理該緩衝區
                    serverThread.getDataOutputStream().flush();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

伺服器讀寫執行緒

package me.mxzf;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.HashSet;

/**
 * 
 * @Title: ServerThread
 * @Dscription: 伺服器讀寫執行緒
 * @author Deleter
 * @date 2017年3月12日 下午2:31:57
 * @version 1.0
 */
public class ServerThread extends Thread {

    private Socket socket;
    private HashSet<ServerThread> list;
    private DataInputStream dis;
    private DataOutputStream dos;

    public ServerThread(Socket socket, HashSet<ServerThread> list) {
        this.socket = socket;
        this.list = list;
    }

    @Override
    public void run() {
        try {
            // 用DataInputStream包裝socket的輸入流
            dis = new DataInputStream(socket.getInputStream());
            // 用DataOutputStream包裝socket的輸出流
            dos = new DataOutputStream(socket.getOutputStream());
            // 定義區域性變數
            String content;
            while (true) {
                // 一直阻塞,直到有訊息到來,進入迴圈體
                while ((content = dis.readUTF()) != null) {
                    // 列印接收到的訊息
                    System.out.println(content);
                    // 伺服器反饋訊息
                    dos.writeUTF("Server:ok");
                    // 重新整理緩衝區,防止粘包
                    dos.flush();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /*
     * 重寫toString方法
     * 
     * @see java.lang.Thread#toString()
     */
    @Override
    public String toString() {
        return "ServerThread [socket=" + socket + "]";
    }

    /*
     * 獲取輸出流
     */
    public DataOutputStream getDataOutputStream() {
        return dos;
    }
}

注意

1、客戶端先進行寫操作(重要)
2、必須先啟動服務端

擴充套件學習

可以自行擴充套件,新增自定義的資訊格式,或者加入Scanner輸入流等等