首页 > 文章列表 > Java怎么用局域网实现聊天室功能

Java怎么用局域网实现聊天室功能

java
205 2023-04-27

Java怎么用局域网实现聊天室功能

类和接口

Server类(服务器端)

package Test;



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=30000;

    //使用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类

package Test;



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("成功");

                        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类

package Test;



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=30000;

    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类

package Test;



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类

package Test;



import java.util.*;



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类

package Test;



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="卐";

}

运行结果:

打开服务器并且运行三个客户端用户名分别是xuwei,jiji和yaou

首先发送一句公开信息:

xuwei发送了一句话

jiji收到:

yaou收到:

再发送一句私聊信息给jiji

xuwei发送了一句悄悄话:

jiji收到:

yaou没有收到: