基于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的简单聊天室_第1张图片

基于JavaSE的简单聊天室_第2张图片

二、多线程版本

服务器端:

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 clientMap = new ConcurrentHashMap(  );
    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>  clientSet = clientMap.entrySet();
        for (Map.Entryentry: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的简单聊天室_第3张图片

基于JavaSE的简单聊天室_第4张图片

二、群聊功能(G:message)

基于JavaSE的简单聊天室_第5张图片

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

基于JavaSE的简单聊天室_第6张图片

基于JavaSE的简单聊天室_第7张图片

四、退出(byebye)

基于JavaSE的简单聊天室_第8张图片

基于JavaSE的简单聊天室_第9张图片

以上为聊天室的基础功能实现,有许多功能仍可添加。

 

 

 

 

你可能感兴趣的:(Java)