Java實現局域網聊天室功能(私聊、群聊)

本文實例為大傢分享瞭Java實現局域網聊天室功能的具體代碼,供大傢參考,具體內容如下

Server 服務端

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
 
/**
 * 服務端
 */
public class Server {
 
    private static final int SERVER_PORT=8080;
    //使用CrazyitMap對象來保存每個客戶名字和對應輸出流之間的對應關系
    public static CrazyitMap<String,PrintStream> clients=new CrazyitMap<>();
 
    public void init(){
        try( //建立監聽的ServerSocket
             ServerSocket ss=new ServerSocket(SERVER_PORT))
        {
            //采用死循環來不斷地接收來自客戶端的請求
            while(true){
                Socket socket=ss.accept();
                new ServerThread(socket).start();
            }
        }
        //如果拋出異常
        catch (IOException ex){
            System.out.println("服務器啟動失敗,是否端口"+SERVER_PORT+"已被占用");
        }
    }
    public static void main(String[] args){
        Server server=new Server();
        server.init();
    }
}

ServerThread 服務端線程

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
 
/**
 * 服務端線程
 */
public class ServerThread extends Thread {
    private Socket socket;
    BufferedReader br = null;
    PrintStream ps = null;
 
    //定義一個構造器,用於接收一個Socket來創建ServerThread線程
    public ServerThread(Socket socket) {
        this.socket = socket;
    }
 
    public void run() {
        try {
            //獲取該Socket對應的輸入流
            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //獲取該Socket對應的輸出流
            ps = new PrintStream(socket.getOutputStream());
            String line = null;
            while ((line = br.readLine()) != null) {
                //如果讀到的行以CrazyitProtocol.USER_ROUND開始,並以其結束
                //則可以確定讀到的是用戶登錄的用戶名
                if (line.startsWith(CrazyitProtocol.USER_ROUND) && line.endsWith(CrazyitProtocol.USER_ROUND)) {
                    //得到真實消息
                    String userName = getRealMsg(line);
                    //如果用戶名重復
                    if (Server.clients.map.containsKey(userName)) {
                        System.out.println("重復");
                        ps.println(CrazyitProtocol.NAME_REP);
                    } else {
                        System.out.println(userName+"上線");
                        ps.println(CrazyitProtocol.LOGIN_SUCCESS);
                        Server.clients.put(userName, ps);
                    }
                }
                //如果讀到的行以CrazyitProtocol.PRIVATE_ROUND開始,
                //則可以確定是私聊信息,私聊信息隻向特定的輸入流發送
                else if (line.startsWith(CrazyitProtocol.PRIVATE_ROUND) && line.endsWith(CrazyitProtocol.PRIVATE_ROUND)) {
                    //得到真實的消息
                    String userAndMsg = getRealMsg(line);
                    //以SPLIT_SIGN分割字符串,前半是私聊用戶,後半是聊天信息
                    String user = userAndMsg.split(CrazyitProtocol.SPLIT_SIGN)[0];
                    String msg = userAndMsg.split(CrazyitProtocol.SPLIT_SIGN)[1];
                    //獲取私聊用戶對應的輸出流,並發送私聊信息
                    Server.clients.map.get(user).println(Server.clients.getKeyByValue(ps) + "悄悄對你說:" + msg);
                }
                //公聊要向每一個Socket發送
                else {
                    //得到真實消息
                    String msg = getRealMsg(line);
                    //遍歷clients中的每個輸出流
                    for (PrintStream clientPs : Server.clients.valueSet()) {
                        clientPs.println(Server.clients.getKeyByValue(ps) + "說:" + msg);
                    }
                }
            }
        }
        //捕獲到異常後,表明Socket對應的客戶端已經出現瞭問題
        //所以程序將其對應的輸出流從Map中刪除
        catch (IOException e) {
            Server.clients.removeByValue(ps);
            System.out.println(Server.clients.map.size());
            //關閉網絡,IO資源
            try {
                if (br != null) {
                    br.close();
                }
                if (ps != null) {
                    ps.close();
                }
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
 
    //將讀到的內容去掉前後協議字符,恢復成真實數據
    private String getRealMsg(String line) {
        return line.substring(CrazyitProtocol.PROTOCOL_LEN,line.length()-CrazyitProtocol.PROTOCOL_LEN);
    }
}

Client 客戶端

import javax.swing.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
 
/**
 * 客戶端
 */
public class Client {
 
    private static final int SERVER_PORT=8080;
    private Socket socket;
    private PrintStream ps;
    private BufferedReader brServer;
    private BufferedReader keyIn;
 
    public void init(){
        try
        {
            //初始化代表鍵盤的輸入流
            keyIn=new BufferedReader(new InputStreamReader(System.in));
            //連接到服務器端
            socket=new Socket("127.0.0.1",SERVER_PORT);
            //獲取該Socket對應的輸入流和輸出流
            ps=new PrintStream(socket.getOutputStream());
            brServer=new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String tip=" ";
            //采用不斷循環地彈出對話框要求輸入用戶名
            while(true){
                String userName= JOptionPane.showInputDialog(tip+"輸入用戶名");
                //用戶輸入的用戶名前後增加協議字符串後發送
                ps.println(CrazyitProtocol.USER_ROUND+userName+CrazyitProtocol.USER_ROUND);
                //讀取服務器端的響應
                String result=brServer.readLine();
                //如果用戶名重復,則開始下一次循環
                if (result.equals(CrazyitProtocol.NAME_REP)){
                    tip="用戶名重復,請重試";
                    continue;
                }
                //服務器端登錄成功
                if (result.equals(CrazyitProtocol.LOGIN_SUCCESS)){
                    break;
                }
            }
        }
        //捕獲到異常,關閉網絡資源,並退出該程序
        catch (UnknownHostException ex){
            System.out.println("找不到遠程服務器,請確定服務器已經啟動");
            closeRs();
            System.exit(1);
        }
        catch(IOException ex){
            System.out.println("網絡異常,請重新登錄");
            closeRs();
            System.exit(1);
        }
        //以該Socket對應的輸入流啟動ClientThread線程
        new ClientThread(brServer).start();
    }
    //定義一個讀取鍵盤輸出,並以網絡發送的方法
    private void readAndSend(){
        try
        {
            //不斷讀取鍵盤輸入
            String line=null;
            while ((line=keyIn.readLine())!=null){
                //如果發送的信號中有冒號,並以//開頭,則認為想發送私聊信息
                if (line.indexOf(":")>0&&line.startsWith("//")){
                    line=line.substring(2);
                    ps.println(CrazyitProtocol.PRIVATE_ROUND+line.split(":")[0]+CrazyitProtocol.SPLIT_SIGN+line.split(":")[1]+CrazyitProtocol.PRIVATE_ROUND);
                }
                else{
                    ps.println(CrazyitProtocol.MSG_ROUND+line+CrazyitProtocol.MSG_ROUND);
                }
            }
        }
        catch (IOException ex){
            System.out.println("網絡通信異常!請重新登錄");
            closeRs();
            System.exit(1);
        }
    }
    //關閉Socket,輸入流,輸出流的方法
    private void closeRs(){
        try
        {
            if (keyIn!=null){
                ps.close();
            }
            if (brServer!=null){
                ps.close();
            }
            if (ps!=null){
                ps.close();
            }
            if (socket!=null){
                keyIn.close();
            }
        }
        catch (IOException ex){
            ex.printStackTrace();
        }
    }
 
    public static void main(String[] args){
        Client client=new Client();
        client.init();
        client.readAndSend();
    }
}

ClientThread 客戶端線程

import java.io.BufferedReader;
import java.io.IOException;
 
/**
 * 客戶端線程
 */
public class ClientThread extends Thread {
 
    //該客戶端線程負責處理輸入流
    BufferedReader br=null;
    //使用一個網絡輸入流來創建客戶端線程
    public ClientThread(BufferedReader br){
        this.br=br;
    }
    public void run(){
        try
        {
            String line=null;
            //不斷地從輸入流中讀取數據,並將這些數據打印輸出
            while((line=br.readLine())!=null){
                System.out.println(line);
            }
        }
        catch (IOException ex){
            ex.printStackTrace();
        }
        finally {
            try {
                if (br!=null){
                    br.close();
                }
            }
            catch (IOException ex){
                ex.printStackTrace();
            }
        }
    }
}

CrazyitMap 容器

import java.util.*;
 
/**
 * map容器
 *
 * @param <k>
 * @param <v>
 */
public class CrazyitMap<k,v> {
 
    //創建一個線程安全的HashMap
    public Map<k,v> map= Collections.synchronizedMap(new HashMap<k,v>());
    //根據value來刪除指定項
    public synchronized void removeByValue(Object value){
        for (Object key:map.keySet()){
            if (map.get(key)==value){
                map.remove(key);
                break;
            }
        }
    }
    //獲取所有value組成的Set集合
    public synchronized Set<v> valueSet(){
        Set<v> result=new HashSet<v>();
        //將map中的所有value添加到result集合中
        map.forEach((key,value)->result.add(value));
        return result;
    }
    //根據value查找key
    public synchronized k getKeyByValue(v value){
        //遍歷所有key組成的集合
        for (k key:map.keySet()){
            //如果指定key對應的value與被搜索的value相同,則返回對應的key
            if(map.get(key)==value||map.get(key).equals(value)){
                return key;
            }
        }
        return null;
    }
    //實現put()方法,該方法不允許value重復
    public synchronized v put(k key,v value){
        //遍歷所有value組成的集合
        for (v val:valueSet()){
            //如果某個value與試圖放入集合的value相同
            //則拋出一個RuntimeException異常
            if (val.equals(value)&&val.hashCode()==value.hashCode()){
                throw new RuntimeException("MyMap實例不允許有重復的value");
            }
        }
        return map.put(key,value);
    }
}

CrazyitProtocol 接口

/**
 * 自定義接口
 */
public interface CrazyitProtocol {
 
    //定義協議字符串的長度
    int PROTOCOL_LEN=2;
    //下面是一些協議字符串,服務器端和客戶端交換的信息都應該在前後添加這種特殊字符串
    String MSG_ROUND="ηθ";
    String USER_ROUND="∏∑";
    String LOGIN_SUCCESS="1";
    String NAME_REP="-1";
    String PRIVATE_ROUND="★【";
    String SPLIT_SIGN="卐";
}

測試

1、啟動服務端

2、啟動客戶端 (上線)

3 、群發

4、私聊 (//名字:)

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: