基于Socket的多人聊天雏形(java实现)

思路

网络上的聊天本质上属于数据的交互,一个服务方负责接收客户端发来的信息,并进行进一步处理。因为此次实验是在本地实验,ip地址相同,所以封给stocket加了一个名字用于区别。

服务器端

服务器端的设计,采用多线程的方式。
主线程:负责监听客户端的连接,当监听到一个匹配的客户端时,调用注册线程将该客户端加入map集合中。

主线程代码

public class Server {
    //存储客户端
    static volatile  Map<String,nameSocket> socketMap = new HashMap<String,nameSocket>();
    //存储客户端姓名,将用该集合进行轮询操作
    static volatile List<String> names = new ArrayList<>();
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(10086);
            //启动轮询线程
            new Thread(new lunxun()).start();
            //等待客户端连接
            while(true){
                //包装客户端
                nameSocket ns = new nameSocket();
                ns.setSocket(serverSocket.accept());
                //启动注册线程
                new Thread(new login(ns)).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注册线程:线程会给客户端进行一次交互,询问姓名,得到客户端输入的姓名后将该客户端进行保存,线程执行完毕后会自然销毁。

注册线程代码

class login implements Runnable{
    //包装的客户端
    private nameSocket ns;
    //用于接收客户端姓名
    private String str = null;
    public login(nameSocket ns) {
        this.ns = ns;
    }
    @Override
    public void run() {
        synchronized (Server.class){
            try {
                //向客户端发送信息
                OutputStream outputStream = ns.getSocket().getOutputStream();
                outputStream.write("来自服务器的消息:请输入您的用户名!".getBytes());
                //客户端时读取一行数据,需要发送"\n"结束
                outputStream.write("\n".getBytes());
                outputStream.flush();
                //读取客户端发送的消息
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(ns.getSocket().getInputStream()));
                if((str = bufferedReader.readLine())!=null){
                    ns.setName(str);
                    Server.names.add(str);
                    Server.socketMap.put(str,ns);
                }
                outputStream.write(("来自服务器的消息:欢迎您"+str).getBytes());
                outputStream.write("\n".getBytes());
                outputStream.flush();
            } catch (Exception e) {
                if(e.getClass() == SocketException.class){
                    //如果异常异常类型匹配,代表客户端断开连接。
                    if(str!=null){
                        //有名字则删除客户端
                        System.out.println(str+"下线了!");
                        Server.names.remove(str);
                        Server.socketMap.remove(str);
                    }else {
                        System.out.println("不知名的用户下线了!");
                    }
                }else {
                    e.printStackTrace();
                }
            }
        }
    }
}

轮询线程:线程负责循环读取map集合中的客户端,如果客户端有消息,将启用一个聊天线程负责输出。

轮询线程代码

class lunxun implements Runnable{
    //正在轮询到的客户端姓名
    private String na;
    private int num = 0;
    @Override
    public  void  run() {
        while(true){
                try {
                    //加上锁
                    synchronized (Server.class){
                        //用变量num的增长实现轮询。
                        for (int i = 0;i<Server.names.size();i++){
                            na = Server.names.get(num%Server.names.size());
                            if(num<Server.names.size()){
                                num++;
                            }else {
                                num = 1;
                            }
                            //给客户端设置超时时间,出了异常会跳出循环
                            Server.socketMap.get(na).getSocket().setSoTimeout(10);
                            InputStreamReader inputStreamReader = new InputStreamReader(Server.socketMap.get(na).getSocket().getInputStream());
                            //约定在发送的数据前多加一个字符,保证数据的完整性。
                            if (inputStreamReader.read()!=-1){
                                new Thread(new liaotian(na,new BufferedReader(inputStreamReader))).start();
                            }
                        }
                    }
                } catch (Exception e) {
                    if(e.getClass() == SocketException.class){
                        //异常类型相等则表示客户端下线,删除客户端。
                        System.out.println(na+"下线了!");
                        Server.names.remove(na);
                        Server.socketMap.remove(na);
                    }else if (e.getClass() == SocketTimeoutException.class){
                        //超时异常不做处理

                    }else {
                        e.printStackTrace();
                    }
                }
        }

    }
}

聊天线程:负责打印需要输出的客户端数据流,该线程在执行结束后将自然销毁。

class liaotian implements Runnable{
    //客户端姓名
    private String name;
    //数据流
    private BufferedReader bufferedReader;
    public liaotian(String name, BufferedReader bufferedReader) {
        this.name = name;
        this.bufferedReader = bufferedReader;
    }
    @Override
    public void run() {
        try{
            System.out.println(name+":"+bufferedReader.readLine());
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

客户端

在链接上服务端时,先与服务端进行一次录入姓名的交互,接着进入循环读取控制台的信息并发送至服务端。

public class khd {
    public static void main(String[] args) {
        try {
            String str = null;
            Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),10086);
            Scanner scanner = new Scanner(System.in);
            //获取输出流
            PrintStream printStream = new PrintStream(socket.getOutputStream());
            //获取输入流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //与服务器第一次交互开始------(有些臃肿)
            if ((str = bufferedReader.readLine())!= null){
                System.out.println(str);
            }
            printStream.write(scanner.nextLine().getBytes());
            printStream.write("\n".getBytes());
            printStream.flush();
            if ((str = bufferedReader.readLine())!= null){
                System.out.println(str);
            }
            //与服务器第一次交互结束------
            while(true){
                //与服务端约定在数据前加上一个字符
                printStream.write(("s"+ scanner.nextLine()+"\n").getBytes());
                printStream.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

效果:在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值