zoukankan      html  css  js  c++  java
  • TCP多线程聊天室

     TCP协议,一个服务器(ServerSocket)只服务于一个客户端(Socket),那么可以通过ServerSocket+Thread的方式,实现一个服务器服务于多个客户端。

    多线程服务器实现原理——多线程并发机制
    1、创建服务器ServerSocket,开启监听.accept(),当有客户端连接,返回一个Socket对象。
    2、把Socket对象需要处理的事务,交给Thread,此线程会到一边默默地执行,那么服务器监听就会空闲出来,等待另外一个客户端连接。
    3、另一个客户端连接成功,新的Socket对象又交给另一个Thread,以此类推。

    【多线程聊天室---群聊】先运行服务器,在运行客户端(2个客户端则运行两次)

                                    

    import java.io.*;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.HashSet;
    
    //获取客户端发来的消息,并把消息发送到所有客户端
    public class Server {
        private HashSet<Socket> allSocket;//保存所有服务器的套接字集合
        private ServerSocket server;//服务器套接字
    
        public Server() {//构造方法
            try {
                server = new ServerSocket(4567);//实例化服务器套接字,设定端口4567
            } catch (IOException e) {
                e.printStackTrace();
            }
            allSocket = new HashSet<>();//实例化服务器套接字集合
        }
    
        public void startServer() {//启动服务器,循环获取服务器监听
            while (true) {
                Socket socket = null;
                try {
                    socket = server.accept();//服务器监听是否有客户端接入
                } catch (IOException e) {
                    e.printStackTrace();
                }
                System.out.println("用户进入聊天室");//监听成功(客户端连接成功),输出此句代码
                allSocket.add(socket);//连接成功的客户端放入集合中
                ServerThread t = new ServerThread(socket);//将成功连接的客户端交给线程
                t.start();
            }
        }
    
        private class ServerThread extends Thread {//发送一个客户端信息给所有客户端
            Socket socket;//客户端套接字
    
            public ServerThread(Socket socket) {//带参数(客户端套接字)的构造方法
                this.socket = socket;//参数赋值给客户端套接字
            }
    
            public void run() {//发送一个客户端信息给所有客户端
                BufferedReader br = null;//读取客户端发来的消息
                try {
                    br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//获得套接字的字节流
                    while (true) {//把客户端发来的消息,发给所有客户端
                        String str = br.readLine();//读取字符串
                        //若字符串中有EXIT内容(有用户退出聊天室),提醒其他人
                        if (str.contains("EXIT")) {//用户退出聊天室,给服务器发送"%EXIT%:用户名"(自定义规则)
                            String tmp = str.split(":")[1] + "用户退出聊天室";//依据:分隔成两个元素,取第2个元素(用户名)
                            sendMessageToAllClient(tmp);//告诉所有人,他退出了
                            allSocket.remove(socket);//移除离开的客户端
                            socket.close();//关闭客户端
                            return;//停止线程
                        }
                        sendMessageToAllClient(str);//如果没有EXIT内容,直接发送给所有客户端。调用自定义方法
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private void sendMessageToAllClient(String str) {//发给所有人,参数str是发送的内容
            for (Socket s : allSocket) {//遍历所有接入的客户端
                try {
                    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//客户端输出流
                    bw.write(str);//输出str
                    bw.newLine();//换行
                    bw.flush();//强制将输出流缓冲区的数据送出,清空缓冲区(一般用在IO中)
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
            Server s = new Server();
            s.startServer();
        }
    }
    import javax.swing.*;
    import javax.swing.border.EmptyBorder;
    import java.awt.*;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    //登陆界面、获取用户名与IP
    public class LinkServerFrame extends JFrame {
        private JPanel contentPane;
        private JLabel lblIP;
        private JLabel lblUserName;
        private JTextField tfIP;
        private JTextField tfUserName;
        private JButton btnLink;
    
        public LinkServerFrame(){//构造方法
            setTitle("连接服务器");
            setResizable(false);
            setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            setBounds(100,100,390,150);
    
            contentPane=new JPanel();
            contentPane.setBorder(new EmptyBorder(5,5,5,5));
            contentPane.setLayout(null);
            setContentPane(contentPane);
    
            lblIP=new JLabel("服务器IP地址:");
            lblIP.setFont(new Font("微软雅黑",Font.PLAIN,14));
            lblIP.setBounds(20,15,100,15);
            contentPane.add(lblIP);
    
            tfIP=new JTextField("127.0.0.1");//指定IP内容,本地服务器
            tfIP.setBounds(121,13,242,21);
            contentPane.add(tfIP);
            tfIP.setColumns(10);
    
            lblUserName=new JLabel("用户名");
            lblUserName.setFont(new Font("微软雅黑",Font.PLAIN,14));
            lblUserName.setBounds(60,40,60,15);
            contentPane.add(lblUserName);
    
            tfUserName=new JTextField();
            tfUserName.setBounds(121,42,242,21);
            contentPane.add(tfUserName);
            tfUserName.setColumns(10);
    
            btnLink=new JButton("连接服务器");//登录按钮
            btnLink.addActionListener(new ActionListener() {//实现登录,显示客户端窗体,关闭登录窗体
                public void actionPerformed(ActionEvent e) {
                    do_btnLink_actionPerformed(e);
                }
            });
            btnLink.setFont(new Font("微软雅黑",Font.PLAIN,14));
            btnLink.setBounds(140,80,120,23);
            contentPane.add(btnLink);
        }
    
        public static void main(String[] args) {
            LinkServerFrame linkServerFrame=new LinkServerFrame();//创建窗体对象
            linkServerFrame.setVisible(true);//显示窗体
        }
    
        protected void do_btnLink_actionPerformed(ActionEvent e){
            if(!tfIP.getText().equals("")&&!tfUserName.getText().equals("")){//文本框内容不为空
                dispose();//关闭窗体,释放资源
                //获取文本并除去空格,.trim()的作用是:去掉字符串左右的空格
                ClientFrame clientFrame=new ClientFrame(tfIP.getText().trim(),tfUserName.getText().trim());//实例化登录窗口,并赋值IP、用户名
                clientFrame.setVisible(true);//显示客户端窗体
            }else {
                JOptionPane.showMessageDialog(null,"内容不能为空!","警告",JOptionPane.WARNING_MESSAGE);
            }
        }
    }
    import javax.swing.*;
    import javax.swing.border.EmptyBorder;
    import java.awt.event.*;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    //客户端窗体、共享消息在文本域中、用户退出提醒
    public class ClientFrame extends JFrame {
        private JPanel contentPane;
        private JLabel lblUserName;//显示用户名
        private JTextField tfMessage;//信息输入文本框
        private JButton btnSend;//发送按钮
        private JTextArea textArea;//信息接收文本域
        private String userName;//用户名称
        Client client;//调用自定义类(在Client.java中)
    
        public ClientFrame(String ip,String userName){//ip与userName由登录窗体获得(LinkServerFrame.java中)
            this.userName=userName;
            init();//窗体初始化,自定义方法
            addListener();//调用发送按钮监听,自定义方法
    
            client=new Client(ip,4567);//赋值形参。实例化自定义类(在Client.java中)
            ReadMessageThread t=new ReadMessageThread();//显示消息在文本域中
            t.start();
        }
    
        private void init(){//窗体属性(显示与否,由LinkServerFrame.java控制)
            setTitle("客户端");
            setResizable(false);
            setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
            setBounds(100,100,450,300);
    
            contentPane=new JPanel();
            contentPane.setBorder(new EmptyBorder(5,5,5,5));
            contentPane.setLayout(null);
            setContentPane(contentPane);
    
            JScrollPane scrollPane=new JScrollPane();
            scrollPane.setBounds(5,5,434,229);
            contentPane.add(scrollPane);
    
            textArea=new JTextArea();
            //添加滚动条,或JScrollPane scrollPane=new JScrollPane(textArea);
            scrollPane.setViewportView(textArea);
            textArea.setEditable(false);//文本域不可编辑
    
            JPanel panel=new JPanel();
            panel.setBounds(5,235,434,32);
            contentPane.add(panel);
            panel.setLayout(null);
    
            lblUserName=new JLabel(userName);
            lblUserName.setHorizontalAlignment(SwingConstants.TRAILING);
            lblUserName.setBounds(2,4,55,22);
            panel.add(lblUserName);
    
            tfMessage=new JTextField();
            tfMessage.setBounds(62,5,274,22);
            tfMessage.setColumns(10);
            panel.add(tfMessage);
    
            btnSend=new JButton("发送");
            btnSend.setBounds(336,4,93,23);
            panel.add(btnSend);
    
            tfMessage.validate();//验证容器中的组件,即刷新。
        }
        //显示消息在文本域中
        private class ReadMessageThread extends Thread{//内部类,收消息线程类
            public void run() {
                while (true) {
                    String str=client.receiveMessage();//调用自定义方法(在Client.java中)
                    textArea.append(str+"
    ");//将消息显示在文本域中
                }
            }
        }
    
        private void addListener(){
            btnSend.addActionListener(new ActionListener() {//发送按钮的动作监听
                public void actionPerformed(ActionEvent e) {
                    Date date=new Date();
                    SimpleDateFormat sf=new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
                    client.sendMessage(userName+" "+sf.format(date)+":
    "+tfMessage.getText());
                    tfMessage.setText("");//消息发送后,文本框清空
                }
            });
            //开启窗口监听,单击窗口“X”,弹出确认对话框。“是”则关闭客户端窗体
            this.addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent atg0){//窗口关闭时
                  int op=JOptionPane.showConfirmDialog(ClientFrame.this,"确定要退出聊天室吗?","确定",JOptionPane.YES_NO_OPTION);
                  if(op==JOptionPane.YES_OPTION){//如果选择“是”
                      client.sendMessage("%EXIT%:"+userName);//如果退出,发送"%EXIT%:用户名"给服务器
                      try {
                          Thread.sleep(200);//200ms后,关闭客户端
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      client.close();//关闭客户端套接字,调用Client类中的close方法(Client.java中)
                      System.exit(0);//关闭程序
                  }
                }
            });
        }
    }
    import java.io.*;
    import java.net.Socket;
    //供ClientFrame.java调用的各种方法,消息输入、输出,关闭套接字
    public class Client {
        Socket socket;//客户端套接字
        BufferedWriter bw;//发数据
        BufferedReader br;//收数据
    
        public Client(String ip,int port){//带参数构造方法,参数值由ClientFrame.java赋予
            try {
                socket=new Socket(ip,port);//实例化客户端套接字,连接服务器
                bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));//客户端输出流,输出到服务器
                br=new BufferedReader(new InputStreamReader(socket.getInputStream()));//客户端输入流,服务器反馈给客户端
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public void sendMessage(String message){//发消息
            try {
                bw.write(message);//发消息
                bw.newLine();//换行
                bw.flush();//强制将输出流缓冲区的数据送出,清空缓冲区(一般用在IO中)
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public String receiveMessage(){//收消息
            String message=null;
            try {
                message=br.readLine();//收消息
            } catch (IOException e) {
                e.printStackTrace();
            }
            return message;//返回消息结果
        }
    
        public void close(){//关闭客户端套接字
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
  • 相关阅读:
    Spring事务管理
    Spring Bean装配(下)——注解
    Spring Bean装配(上)
    Spring入门篇
    Spring入门篇——AOP基本概念
    计算机组成原理(1)——系统概述
    浏览器缓存 总结
    React-router 4 总结
    Redux 总结
    操作系统位数 的 概念(转)
  • 原文地址:https://www.cnblogs.com/xixixing/p/9665176.html
Copyright © 2011-2022 走看看