天天看點

基于JavaSE的簡單聊天室

《一》、需求分析

一、前提:同一區域網路内

二、功能:

1、注冊:userName:name

2、群聊:G:message

3、私聊:P:userName-message

4、退出:byebye

三、基本模型:C/S架構

四、開發環境及工具:JDK 1.8+IDEA開發工具

五、實作原理

伺服器端:

          1、伺服器端執行個體化一個SeverSocket對象,設定端口号。

          2、伺服器端的SeverSocket對象調用accept方法,等待用戶端連接配接伺服器的端口。

          3、擷取用戶端的輸入輸出流并向用戶端輸出。

          4、關閉輸入輸出流,關閉伺服器端。

用戶端:

         1、用戶端執行個體化一個Socket對象并擷取伺服器域名和端口号。

         2、在伺服器端中, accept将傳回一個Socket對象,該socket連接配接到用戶端的socket。

         3、擷取伺服器端的輸入輸出流并向伺服器端輸出。

         4、關閉輸入輸出流,關閉用戶端。

六、使用技術:

    通過Executors建立線程池實作多線程版的聊天室,使用HashMap存放客戶Socket實作多人聊天功能, 在項目中添加了鎖

代碼塊解決注冊使用者名重複問題。

《二》、代碼實作

一、單線程版本:

伺服器端:

package com.company.SingleThread;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Sever {
    public static void main(String[] args){
        ServerSocket serverSocket;
        {
            try {
                //建立伺服器端Socket,端口号為8888;
                serverSocket = new ServerSocket( 8888 );
                System.out.println("伺服器端已就緒,等待用戶端連接配接。。。" );
                //等待用戶端連接配接,有用戶端連接配接時傳回用戶端的Socket對象,否則線程一直處于阻塞狀态;
                Socket cliet = serverSocket.accept();
                System.out.println("有新用戶端連接配接,端口号為:" + cliet.getPort());
                //擷取用戶端的輸入流
                Scanner clientinput = new Scanner( cliet.getInputStream() );
                //useDelimiter(",");   以','為分隔符
                //useDelimiter("\n"); “\n”換行符(回車)作為輸入的分隔符。
                clientinput.useDelimiter( "\n" );
                //擷取用戶端的輸出流
                PrintStream clientout = new PrintStream( cliet.getOutputStream() );
                //讀取用戶端的輸入流
                if (clientinput.hasNext()){
                    System.out.println(cliet.getInetAddress()+"說"+clientinput.next());
                }
                //向用戶端輸出
                clientout.println( "hello  i am Sever " );
                //關閉輸入輸出流,關閉伺服器端。
                clientinput.close();
                clientout.close();
                serverSocket.close();
            } catch (IOException e) {
                System.out.println("伺服器端通信出現異常,錯誤為" + e);
            }
        }
    }
}
           

用戶端:

package com.company.SingleThread;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args){
        String severName = "127.0.0.1";
        Integer port = 8888;
        try {
            //建立并擷取伺服器域名和端口号
            Socket client = new Socket( severName,port );
            //列印伺服器位址
            System.out.println("連接配接上伺服器,伺服器位址為:"+ client.getInetAddress());
            //擷取輸入輸出流
            PrintStream out = new PrintStream( client.getOutputStream() );
            Scanner in = new Scanner( client.getInputStream() );
            //向伺服器輸出内容
            in.useDelimiter( "\n" );
            //讀取伺服器輸入
            out.println( "hi i am client" );
            if (in.hasNext()){
                System.out.println("伺服器端發來的消息是"+ in.next() );
            }
            //關閉輸入輸出流及用戶端
            in.close();
            out.close();
            client.close();
        } catch (IOException e) {
            System.out.println("用戶端通信出現異常,錯誤資訊是"+ e );
        }
    }
}
           

測試:

基于JavaSE的簡單聊天室
基于JavaSE的簡單聊天室

二、多線程版本

伺服器端:

package com.company.SingleThread;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SeveralThreadServer {
    //利用ConcurrentHashMap的高效和安全性來存儲所有連接配接到伺服器的用戶端資訊。
    private  static Map<String, Socket> clientMap = new ConcurrentHashMap<String, Socket>(  );
    private  static  class ExecuteClient implements Runnable{
        private  Socket client;
        public  ExecuteClient (Socket client){
            this.client = client;
        }
        @Override

    /*
    userName:注冊
    G:群聊
    P:私聊
    byebye:使用者退出
    */
    public void run( ) {
            try {
                //擷取用戶端輸入流,讀取用戶端發來的資訊
                Scanner in = new Scanner( client.getInputStream() );
                String strFromClient;
                while (true){

                    if (in.hasNextLine()){
                        strFromClient = in.nextLine();
                        //識别Windows下的換行符,将多餘的"/r"替換為空
                        //Windows下換行:/n/r  Linux下換行:/n;
                        Pattern pattern = Pattern.compile( "\r" );
                        Matcher matcher = pattern.matcher(strFromClient);
                        strFromClient = matcher.replaceAll( "" );
                        //注冊流程
                        if (strFromClient.startsWith( "userName" )){
                            String  userName = strFromClient.split( "\\:" )[1];
                            registerUser(userName,client);
                            continue;
                        }
                        //群聊流程
                        if (strFromClient.startsWith( "G" )){
                            String msg = strFromClient.split( "\\:" )[1];
                            groupChat(msg);
                            continue;
                        }
                        //私聊流程
                        //P:1-msg
                        if (strFromClient.startsWith( "P" )){
                            String userName = strFromClient.split( "\\:" )[1]
                                    .split( "-" )[0];
                            String msg = strFromClient.split( "\\:" )[1]
                                    .split( "-" )[1];
                            privateChat(userName,msg);
                        }
                        //退出流程
                        if (strFromClient.contains( "byebye" )){
                            //周遊Map,擷取userName;
                            String userName = null;
                            for (String keyName:clientMap.keySet()){
                                if (clientMap.get( keyName ).equals( client )){
                                    userName = keyName;
                                }
                            }
                            System.out.println("使用者"+userName+"下線了" );
                            clientMap.remove( userName );
                            continue;

                        }
                    }
                }
            } catch (IOException e) {
                System.out.println("伺服器通信異常,錯誤是" + e);
            }
    }
    //注冊
    private void  registerUser(String userName,Socket client){
        System.out.println("使用者姓名為:"+userName );
        System.out.println("使用者"+userName+"上線了" );
        System.out.println("目前群聊人數為:"+(clientMap.size()+1)+"人" );
        clientMap.put( userName,client );
        try {
            PrintStream out = new PrintStream( client.getOutputStream() );
            out.println("使用者注冊成功" );
        } catch (IOException e) {
            e.printStackTrace( );
        }
    }
    //群聊
    private  void groupChat(String msg){
        Set<Map.Entry<String,Socket>>  clientSet = clientMap.entrySet();
        for (Map.Entry<String,Socket>entry:clientSet){
            //周遊取出每個Socket
            Socket socket = entry.getValue();
            PrintStream out = null;
            try {
                out = new PrintStream( socket.getOutputStream() );
                out.println( "群聊資訊為"+msg );

            } catch (IOException e) {
                System.out.println("群聊異常,錯誤為"+e );
            }
        }
    }
    //私聊
    private  void privateChat(String userName,String msg){
            Socket privateSocket = clientMap.get( userName );
        try {
            PrintStream out = new PrintStream( privateSocket.getOutputStream() );
            out.println( "私聊資訊為"+msg );
        } catch (IOException e) {
            System.out.println("私聊異常,錯誤為"+e );
        }
        }
    }
    public static void  main(String[] args) throws Exception {
            //建立大小為20的線程池
            ExecutorService executorService = Executors.newFixedThreadPool( 20 );
            //建立基站
            ServerSocket serverSocket = new ServerSocket( 6666 );
            //等待連接配接
            for (int i = 0;i<20;i++){
                System.out.println("等待用戶端連接配接。。。。" );
                Socket client = serverSocket.accept();
                System.out.println("有新的用戶端連接配接,端口号為:"+client.getPort() );
                executorService.submit( new ExecuteClient( client ) );
            }
            //關閉線程池
            executorService.shutdown();
            //關閉基站
            serverSocket.close();
    }
}
           

用戶端:

package com.company.SingleThread;

import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

    //用戶端讀取伺服器發來的資訊線程
    class  ReadFromSeverThread implements Runnable{
    private Socket client;
    public ReadFromSeverThread(Socket client){
        this.client = client;
    }
    @Override
    public void run( ) {
        Scanner in = null;
        try {
            in = new Scanner( client.getInputStream( ) );
            in.useDelimiter( "\n" );
            while (true) {
                if (in.hasNext( )) {
                    System.out.println( "從伺服器發過來的資訊是" + in.next( ) );
                }
                if (client.isClosed( )) {
                    System.out.println( "用戶端關閉" );
                    break;
                }
            }
            in.close( );
        } catch (IOException e) {
            e.printStackTrace( );
        }
    }
}
    //用戶端寫資訊給伺服器
    class  WriteToSeverThread implements  Runnable{
    private  Socket client;

    public WriteToSeverThread(Socket client){
        this.client = client;
    }
    public void run( ) {
        //擷取鍵盤輸入流,讀取從鍵盤發來的資訊
        Scanner scanner = new Scanner( System.in );
        scanner.useDelimiter( "\n" );
        try {
            //擷取用戶端輸出流,将使用者輸入的資訊發送給伺服器
            PrintStream out = new PrintStream( client.getOutputStream() );
            while (true){
                System.out.println( "請輸入要發送的資訊。。");
                String strToSever;
                if (scanner.hasNextLine()){
                    strToSever = scanner.nextLine().trim();
                    out.println( strToSever );
                    //退出标志
                    if (strToSever.contains( "byebye" )){
                        System.out.println("用戶端退出,不聊了" );
                        scanner.close();
                        out.close();
                        client.close();
                        break;
                    }
                }
            }
        } catch (IOException e) {
            System.out.println("用戶端寫入資訊程式異常,錯誤為"+e );
        }
    }
}
public class SeveralThreadClient {
    public static void main(String[] args) {
        try {
            Socket client = new Socket("127.0.0.1",6666 );
            Thread readFromSever = new Thread( new ReadFromSeverThread(client) );
            Thread writeToSever = new Thread( new WriteToSeverThread(client) );
            readFromSever.start();
            writeToSever.start();
        } catch (IOException e) {
            e.printStackTrace( );
        }
    }
}
           

 測試:

一、注冊功能:(userName:name)

基于JavaSE的簡單聊天室
基于JavaSE的簡單聊天室

二、群聊功能(G:message)

基于JavaSE的簡單聊天室

三、私聊功能(P:userName-message)

基于JavaSE的簡單聊天室
基于JavaSE的簡單聊天室

四、退出(byebye)

基于JavaSE的簡單聊天室
基于JavaSE的簡單聊天室

以上為聊天室的基礎功能實作,有許多功能仍可添加。