Socket编程:聊天室不同版本V4-V5

UDP通信

聊天室V4.
服务器端可以将用户的信息转发给所有的客户端(广播),并在每个客户端控制台上显示.
1.对某个客户端发送的信息进行广播(转发给所有的客户),并且使的客户在接收到服务器端
转发的信息后输出到控制台

解决步骤:
1.需要在服务器定义一个集合类型的属性,用于存储所有客户端的输出流.
2.在Server类中的run方法最开始处,将客户端的输出流存入该集合,之后每当客户端发送信
息后就遍历集合,将信息写入集合中所有的输出流中(相当于将信息转发给所有的客户端)
3.在客户端使用多线程机制读取服务端发送过来的信息
3.1在客户端定义内部类ServerHandler实现Runnable接口
在run方法实现该线程要执行的任务,循环接收服务器端的信息并打印在控制台上
3.2修改start方法,在该方法中创建线程

聊天室V5.
目前存在的隐患:
1.随着用户的增多,服务端会频繁的和销毁线程,会很多程度上对系统资源造成浪费
2.过多的线程导致的过度切换也会为服务端带来崩溃的风险
3.多个线程会共享服务端的集合属性allout,这里还存在着多线程的并发安全问题

问题:
需要对之前程序进行优化,使程序更加健壮

解决方案:
需要使用线程池来控制客户端连接后启动和管理线程.并解决多线程共享Server属性allout
所引起的并发安全性问题

步骤一:
在server类中创建线程池属性,使用线程池管理服务端线程的创建.

步骤二:
修改server中的start方法,将原来创建并启动线程的代码替换为使用线程池管理的方式

步骤三:
在server中添加三个方法,用于操作属性allout,并使用同步锁,使三个方法变为同步的.

步骤四:
将原来操作向集合中添加,删除,遍历集合改为调用三个方法,以确保同步安全

 

//客户端应用程序
//第一步:实现向服务器发送一条信息
public class Client {
//    创建Socket实例
    private Socket socket;
    public Client() {
        try {
            socket = new Socket("localhost",8060);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
//客户端工作方法
    public void start() {
        try {
//            接收服务器端的信息并且启动线程
            ServerHandler handler = new ServerHandler();
            Thread r = new Thread(handler);
            r.start();
            
//            获取OutputStream对象
            OutputStream os = socket.getOutputStream();
//            缓冲字符输出流
            OutputStreamWriter osw = new OutputStreamWriter(os,"UTF-8");
            PrintWriter pw = new PrintWriter(osw,true);
//            创建Scanner读取用户输入的内容
            Scanner scanner = new Scanner(System.in);
            while(true) {
                pw.println(scanner.nextLine());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
          try {
                if(socket!=null) {
                socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
                }
        }
    public static void main(String[] args) {
        Client c = new Client();
        c.start();
    }
    
    private class ServerHandler implements Runnable{

        @Override
        public void run() {
            // 接收服务端发送的信息
            InputStream in;
            try {
                in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in,"UTF-8");
                BufferedReader br = new BufferedReader(isr);
                while(true) {
                    System.out.println(br.readLine());
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }   
    }
}
 

 

 

 

public class Server {
    
    private ServerSocket serverSocket;
//    存储所有客户端的输出流
    private List<PrintWriter> allout;
//    线程池
    private ExecutorService threadPool;
    
    Server(){
        try {
            serverSocket = new ServerSocket(8060);
            allout = new ArrayList<PrintWriter>();
            threadPool = Executors.newFixedThreadPool(40);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
//    创建内部类,继承Runnable的形式创建线程
    private class ClientHandler implements Runnable{
        private Socket socket;

        public ClientHandler(Socket socket) {
            this.socket = socket;
        }
        @Override
        public void run() {
//            获取向该客户端发送信息的输出流,并且将该流包装PrintWriter之后
//            放入集合中.如果客户掉线,集合中对应的输出流也要从集合中删除
            PrintWriter pw = null;
            try {
                OutputStream os = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(os,"UTF-8");
                pw = new PrintWriter(osw,true);
//                将用户信息放入集合中
                allout(pw);
//                在获取到了客户端发送过来的信息后,遍历集合allout,将
//                获取到的信息写入该集合中每一个输出流中,从而将信息发送给所有的客户端
                InputStream in;
                in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in,"UTF-8");
                BufferedReader br = new BufferedReader(isr);
                String message = null;
//        readLine只有在数据流发生异常,或者另一端被close()的时候返回null值
                while((message = br.readLine())!=null) {
//        遍历所有输出流,将该客户端发送的信息转发给所有的客户端
                    sendMessage(message);
                }
                
                }catch(IOException e) {
                    e.printStackTrace();
                }finally {
//                    当客户掉线的时候,要将输出流从集合中删除
                    removeOut(pw);
                    if(socket!=null) {
                        try {
                            socket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    
            }
        }
        
    }
    
    public void start() {
        System.out.println("等待客户端的连接");
//        监听客户端的连接
        while(true) {
            try {
                Socket socket = serverSocket.accept();
                System.out.println("客户端已经连接");
//                启动线程来完成针对该客户端的交互
                ClientHandler handle = new ClientHandler(socket);
                threadPool.execute(handle);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Server s = new Server();
        s.start();
    }
//    将输出流存入共享集合
    private synchronized void allout(PrintWriter pw) {
        allout.add(pw);
    }
//    将消息转发给所有客户端
    private synchronized void sendMessage(String message) {
        for(PrintWriter o:allout) {
            o.println(message);
        }
    }
//    将给定输出流从共享集合删除
    private synchronized void removeOut(PrintWriter pw) {
        allout.remove(pw);
    }
}
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值