TCP UDP 聊天室 java 多线程

已经把UDP和TCP分成了两个线程,但是不能运行了,报错也看不出什么毛病,能不能帮忙看看什么毛病呢TOT

server
```java
package chat;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.*;
import java.util.ArrayList;

class server extends JFrame implements Runnable, ListSelectionListener, ActionListener {
     Socket s = null;
    private ServerSocket ss = null;
     ArrayList<ChatThread> users = new ArrayList<ChatThread>(); //容量能够动态增长的数组
    DefaultListModel<String> dl = new DefaultListModel<String>();
     JList<String> userList = new JList<String>(dl);//显示对象列表并且允许用户选择一个或多个项的组件。单独的模型 ListModel 维护列表的内容。

     JPanel jpl = new JPanel();
     JButton jbt = new JButton("踢出");
     JTextArea jta = new JTextArea(10,20);
     JScrollPane js = new JScrollPane(jta);
     JPanel center = new JPanel();

     JPanel jp=new JPanel();
     JButton jbt1 = new JButton("群发");
     JButton jbt2 = new JButton("组播1");
     JButton jbt3 = new JButton("组播2");
     JPanel west = new JPanel();
    //群发消息输入栏
     JTextField jtf = new JTextField();

    public server() throws Exception{
        this.setTitle("服务端");
//        this.add(userList, "North");//放在北面
        this.add(jpl, "South");

        Dimension dim = new Dimension(100,150);
        west.setPreferredSize(dim);//在使用了布局管理器后用setPreferredSize来设置窗口大小
        BorderLayout bl2 = new BorderLayout();
        west.setLayout(bl2);
        west.add(userList,BorderLayout.CENTER);//显示好友列表
        add(west,BorderLayout.EAST);
        userList.setFont(new Font("宋体",Font.BOLD,18));

        //仅将群发消息输入栏设为一栏
        jtf.setColumns(1);
        jpl.setLayout(new BorderLayout());
        jp.add(jbt1);//群发消息
        jp.add(jbt2);//组播1
        jp.add(jbt3);//组播2
        jpl.add(jtf, BorderLayout.NORTH);
        jpl.add(jbt,BorderLayout.EAST);//踢出聊天室
        jpl.add(jp,BorderLayout.WEST);




        //实现群发
        jbt1.addActionListener(this);
        //实现踢人
        jbt.addActionListener(this);
        //组播1
        jbt2.addActionListener(this);
        //组播2
        jbt3.addActionListener(this);



        //center 聊天消息框  发送消息操作面板
        jta.setEditable(false);//消息显示框是不能编辑的


        BorderLayout bl3 = new BorderLayout();
        center.setLayout(bl3);


        center.add(js,BorderLayout.CENTER);//js是消息展示框JScrollPane

        add(center,BorderLayout.CENTER);

        js.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);//需要时才显示滚动条


        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.setLocation(400,100);
        this.setSize(500, 400);
        this.setVisible(true);
        this.setAlwaysOnTop(true);



        ss = new ServerSocket(3000); //侦听端口号
        new Thread(this).start();//监听用户端的加入
    }
    @Override
    public void run() {
        while(true){
            try{
                s = ss.accept();
                ChatThread ct = new ChatThread(s); //为该客户开一个线程
                users.add(ct); //将每个线程加入到users
                //发送Jlist里的用户登陆信息,为了防止后面登陆的用户无法更新有前面用户的好友列表
                ListModel<String> model = userList.getModel();//获取Jlist的数据内容
                for(int i = 0; i < model.getSize(); i++){
                    ct.ps.println("USERS#" + model.getElementAt(i));
                }
                ct.start();
            }catch (Exception ex){
                ex.printStackTrace();
                javax.swing.JOptionPane.showMessageDialog(this,"服务器异常!");
                System.exit(0);
            }
        }
    }

    //群发消息按钮点击事件监听
    @Override
    public void actionPerformed(ActionEvent e) {
        String label = e.getActionCommand();
        if(label.equals("群发")){
            handleAll();
        }else if(label.equals("组播1")){
            handleBroadcast1();
        }else if(label.equals("组播2")){
            handleBroadcast2();
        }else if(label.equals("踢出")){
            try {
                handleExpel();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    public void handleAll(){
        if(!jtf.getText().equals("")){
            sendMessage("ALL#" + jtf.getText());
            //发送完后,是输入框中内容为空
            System.out.print("ALL#" + jtf.getText());
            jtf.setText("");
        }
    }//群发消息
    public void handleBroadcast1(){
        sendMessage("Broadcast");
        if(!jtf.getText().equals("")){


            try {
                InetAddress group = InetAddress.getByName("224.0.0.1");
                int port = 3000;

                MulticastSocket socket = new MulticastSocket();
                socket.setTimeToLive(99);
                String message = "GR1#" +jtf.getText();
                byte[] buffer = message.getBytes();
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length, group, port);
                socket.send(packet);
                System.out.print(message);
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            //发送完后,输入框中内容为空
            jtf.setText("");
        }
    }//群发消息
    public void handleBroadcast2(){
        sendMessage("Broadcast#");
        if(!jtf.getText().equals("")){

            try {
                InetAddress group = InetAddress.getByName("224.0.0.1");
                int port = 3000;

                MulticastSocket socket = new MulticastSocket();
                socket.setTimeToLive(99);
                String message ="GR2#" + jtf.getText();
                byte[] buffer = message.getBytes();
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length, group, port);
                socket.send(packet);
                System.out.print(message);
                //socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            //发送完后,输入框中内容为空
            jtf.setText("");
        }
    }//群发消息

    public void handleExpel() throws IOException {
        sendMessage("OFFLINE#" + userList.getSelectedValuesList().get(0));
        dl.removeElement(userList.getSelectedValuesList().get(0));//更新defaultModel
        userList.repaint();//更新Jlist
    }//踢人

    public class ChatThread extends Thread{
        Socket s = null;
        private BufferedReader br = null;
        private PrintStream ps = null;
        public boolean canRun = true;
        String nickName = null;
        public ChatThread(Socket s) throws Exception{
            this.s = s;
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            ps = new PrintStream(s.getOutputStream());
        }
        public void run(){
            while(canRun){
                try{
                    String msg = br.readLine();//接收客户端发来的消息
                    String[] strs = msg.split("#");
                    if(strs[0].equals("LOGIN")){//收到来自客户端的上线消息
                        nickName = strs[1];
                        dl.addElement(nickName);
                        userList.repaint();
                        //System.out.print(msg);
                        jta.append(strs[1] + "已上线\n");
                        sendMessage(msg);
                    }else if(strs[0].equals("MSG") ){

                        jta.append(strs[1] + "说:" + strs[2] + "\n");
                        //System.out.print(msg);
                        sendMessage(msg);
                    }else if(strs[0].equals("SMSG") || strs[0].equals("FSMSG")) {
                        //System.out.print(msg);
                        sendMessage(msg);
                    }else if(strs[0].equals("OFFLINE")){//收到来自客户端的下线消息
                        //System.out.print(msg);
                        jta.append(strs[1] + "已下线\n");
                        sendMessage(msg);
                        //System.out.println(msg);
                        dl.removeElement(strs[1]);
                        // 更新List列表
                        userList.repaint();
                    }
                }catch (Exception ex){}
            }
        }
    }

    public void sendMessage(String msg){  //服务器端发送给所有用户
        for(ChatThread ct : users){
            ct.ps.println(msg);
        }
    }

    @Override
    public void valueChanged(ListSelectionEvent e) {
        // TODO 自动生成的方法存根

    }

    public static void main(String[] args) throws Exception{
        new server();
    }
}


client
package chat;


import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.DatagramPacket;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.Socket;



public class client extends JFrame implements ActionListener {

    JPanel north = new JPanel();
    //west
    JPanel west = new JPanel();
    DefaultListModel<String> dl = new DefaultListModel<String>();//用来修改JList
    JList<String> userList = new JList<String>(dl);//用来展示和选择
    JScrollPane listPane = new JScrollPane(userList);
    //center
    JPanel center = new JPanel();
    JTextArea jta = new JTextArea(10,20);
    JScrollPane js = new JScrollPane(jta);
    JPanel operPane = new JPanel();//发送消息的操作面板
    JLabel input = new JLabel("请输入:");
    JTextField jtf = new JTextField(24);

    JButton jButton = new JButton("发消息");

     JButton jbt = new JButton("发送");
     JButton jbt1 = new JButton("私聊");
    /*private*/ BufferedReader br = null;
    /*private*/ PrintStream ps = null;
    /*private*/ String nickName = null;




    //私聊面板
    JTextArea jTextArea = new JTextArea(11,45);
    JScrollPane js1 = new JScrollPane(jTextArea);
    JTextField jTextField = new JTextField(25);
    String suser = new String();

    double MAIN_FRAME_LOC_X;//父窗口x坐标
    double MAIN_FRAME_LOC_Y;//父窗口y坐标

    boolean FirstSecret = true;//是否第一次私聊
    String sender=null;//私聊发送者的名字
    String receiver=null;//私聊接收者的名字

    public client() throws Exception{

        BorderLayout bl = new BorderLayout();
        north.setLayout(bl);

        add(north,BorderLayout.NORTH);

        //east 好友列表
        Dimension dim = new Dimension(100,150);
        west.setPreferredSize(dim);//在使用了布局管理器后用setPreferredSize来设置窗口大小

        BorderLayout bl2 = new BorderLayout();
        west.setLayout(bl2);
        west.add(listPane,BorderLayout.CENTER);//显示好友列表
        add(west,BorderLayout.EAST);
        userList.setFont(new Font("宋体",Font.BOLD,18));

        //center 聊天消息框  发送消息操作面板
        jta.setEditable(false);//消息显示框是不能编辑的
        jTextArea.setEditable(false);

        BorderLayout bl3 = new BorderLayout();
        center.setLayout(bl3);
        FlowLayout fl = new FlowLayout(FlowLayout.LEFT);
        operPane.setLayout(fl);
        operPane.add(input);
        operPane.add(jtf);
        operPane.add(jbt);
        operPane.add(jbt1);
        center.add(js,BorderLayout.CENTER);//js是消息展示框JScrollPane
        center.add(operPane,BorderLayout.SOUTH);
        add(center,BorderLayout.CENTER);

        js.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);//需要时才显示滚动条

        //鼠标事件,点击
        jbt.addActionListener(this);
        jbt1.addActionListener(this);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        //this.setAlwaysOnTop(true);

        nickName = JOptionPane.showInputDialog("请输入用户名:");
        this.setTitle("用户: "+nickName);
        this.setSize(700,400);
        this.setVisible(true);



        Thread t = new Thread(new chatroom());
// 启动线程
        t.start();
        Thread g = new Thread(new Broadcast());
// 启动线程
        g.start();

        ps.println("LOGIN#" + nickName);//发送登录信息,消息格式:LOGIN#nickName

        /*jtf.setFocusable(true);//设置焦点

        //键盘事件,实现当输完要发送的内容后,直接按回车键,实现发送
        //监听键盘相应的控件必须是获得焦点(focus)的情况下才能起作用
        jtf.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if(e.getKeyCode() == KeyEvent.VK_ENTER) {
                    ps.println("MSG#" + nickName + "#" +  jtf.getText());//发送消息的格式:MSG#nickName#message
                    //发送完后,是输入框中内容为空
                    jtf.setText("");
                }
            }
        });

        //私聊消息框按回车发送消息
        jTextField.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if(e.getKeyCode() == KeyEvent.VK_ENTER) {
                    handleSS();
                }
            }
        });
*/
        //监听系统关闭事件,退出时给服务器端发出指定消息
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                ps.println("OFFLINE#" + nickName);//发送下线信息,消息格式:OFFLINE#nickName
            }
        });

        this.addComponentListener(new ComponentAdapter() {//监听父窗口大小的改变
            public void componentMoved(ComponentEvent e) {
                Component comp = e.getComponent();
                MAIN_FRAME_LOC_X = comp.getX();
                MAIN_FRAME_LOC_Y = comp.getY();
            }
        });
    }






    @Override
    public void actionPerformed(ActionEvent e) {//鼠标点击事件
        String label = e.getActionCommand();
        if(label.equals("发送")){//群发
            handleSend();
        }else if(label.equals("私聊") && !userList.isSelectionEmpty()){//未点击用户不执行
            suser = userList.getSelectedValuesList().get(0);//获得被选择的用户
            handleSec(suser);//创建私聊窗口
            sender = nickName;
            receiver = suser;
        }else if(label.equals("发消息")){
            handleSS();//私发消息
        }else{
            System.out.println("不识别的事件");
        }
    }

    public void handleSS(){//在私聊窗口中发消息
        String name=sender;
        if(sender.equals(nickName)) {
            name = receiver;
        }
        if(FirstSecret) {
            ps.println("FSMSG#" + nickName + "#" + name + "#" + jTextField.getText());
            jTextField.setText("");
            FirstSecret = false;
        }
        else {
            ps.println("SMSG#" + nickName + "#" + name + "#" + jTextField.getText());
            jTextField.setText("");
        }
    }

    public void handleSend(){//群发消息
        //发送信息时标识一下来源
        ps.println("MSG#" + nickName + "#" +  jtf.getText());
        //发送完后,使输入框中内容为空
        jtf.setText("");
    }

    public void handleSec(String name){ //建立私聊窗口
        JFrame jFrame = new JFrame();//新建了一个窗口
        JPanel JPL = new JPanel();
        JPanel JPL2 = new JPanel();
        FlowLayout f2 = new FlowLayout(FlowLayout.LEFT);
        JPL.setLayout(f2);
        JPL.add(jTextField);
        JPL.add(jButton);
        JPL2.add(js1,BorderLayout.CENTER);
        JPL2.add(JPL,BorderLayout.SOUTH);
        jFrame.add(JPL2);

        jButton.addActionListener(this);
        jTextArea.setFont(new Font("宋体", Font.PLAIN,15));
        jFrame.setSize(400,310);
        jFrame.setLocation((int)MAIN_FRAME_LOC_X+20,(int)MAIN_FRAME_LOC_Y+20);//将私聊窗口设置总是在父窗口的中间弹出
        jFrame.setTitle("与" + name + "私聊中");
        jFrame.setVisible(true);

        jTextField.setFocusable(true);//设置焦点

        jFrame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                jTextArea.setText("");
                FirstSecret = true;
            }
        });
    }//私聊窗口

    public static void main(String[] args)throws Exception{
        new client();

    }
    class Broadcast implements Runnable{


        @Override
        public void run() {
            while (true) {
                try {

                    MulticastSocket socket = new MulticastSocket(9999);
                    InetSocketAddress group = new InetSocketAddress("224.0.1.1", 9999);
                    socket.joinGroup(group, null);
                    byte[] buffer = new byte[1024];
                    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                    socket.receive(packet);
                    String message = new String(packet.getData(), 0, packet.getLength());
                    String[] strs = message.split("#");

                    if (strs[0].equals("GR1")) {

                        jta.append("群组1管理员说" + strs[1] + "\n");
                    }
                    socket.leaveGroup(group, null);
                    socket.close();

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class chatroom implements Runnable{


        @Override
        public void run () {//客户端与服务器端发消息的线程

            while (true) {
                try {
                    Socket s = new Socket("127.0.0.1", 3000);
                    br = new BufferedReader(new InputStreamReader(s.getInputStream()));
                    ps = new PrintStream(s.getOutputStream());
                    new Thread(this).start();//run()

                    String msg = br.readLine();//读取服务器是否发送了消息给该客户端
                    String[] strs = msg.split("#");
                    //System.out.print(msg+" msg");



                    //判断是否为服务器发来的登陆信息
                    if (strs[0].equals("LOGIN")) {
                        if (!strs[1].equals(nickName)) {//不是本人的上线消息就显示,本人的不显示
                            jta.append(strs[1] + "已上线\n");
                            dl.addElement(strs[1]);//DefaultListModel来更改JList的内容
                            userList.repaint();
                        }
                    } else if (strs[0].equals("MSG")) {//接到服务器发送消息的信息
                        if (!strs[1].equals(nickName)) {//别人说的
                            jta.append(strs[1] + "说:" + strs[2] + "\n");
                        } else {
                            jta.append("我说:" + strs[2] + "\n");
                        }
                    } else if (strs[0].equals("USERS")) {//USER消息,为新建立的客户端更新好友列表
                        dl.addElement(strs[1]);
                        userList.repaint();
                    } else if (strs[0].equals("ALL")) {
                        jta.append("系统消息:" + strs[1] + "\n");
                    } else if (strs[0].equals("OFFLINE")) {
                        if (strs[1].equals(nickName)) {//如果是自己下线的消息,说明被服务器端踢出聊天室,强制下线
                            javax.swing.JOptionPane.showMessageDialog(null, "您已被系统请出聊天室!");
                            System.exit(0);
                        }
                        jta.append(strs[1] + "已下线\n");
                        dl.removeElement(strs[1]);
                        userList.repaint();
                    } else if ((strs[2].equals(nickName) || strs[1].equals(nickName)) && strs[0].equals("SMSG")) {
                        if (!strs[1].equals(nickName)) {
                            jTextArea.append(strs[1] + "说:" + strs[3] + "\n");
                            jta.append("系统提示:" + strs[1] + "私信了你" + "\n");
                        } else {
                            jTextArea.append("我说:" + strs[3] + "\n");
                        }
                    } else if ((strs[2].equals(nickName) || strs[1].equals(nickName)) && strs[0].equals("FSMSG")) {
                        sender = strs[1];
                        receiver = strs[2];
                        //接收方收到私聊消息,自动弹出私聊窗口
                        if (!strs[1].equals(nickName)) {
                            FirstSecret = false;
                            jTextArea.append(strs[1] + "说:" + strs[3] + "\n");
                            jta.append("系统提示:" + strs[1] + "私信了你" + "\n");
                            handleSec(strs[1]);
                        } else {
                            jTextArea.append("我说:" + strs[3] + "\n");
                        }
                    }

                } catch (Exception ex) {//如果服务器端出现问题,则客户端强制下线
                    javax.swing.JOptionPane.showMessageDialog(null, "您已被系统请出聊天室!");
                    System.exit(0);
                }


            }


        }

    }
}







```

  • 这篇博客: 【Java】第四十三节 TCP与UDP的区别中的 使用TCP进行通信: 部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:
  • 服务器程序将一个套接字绑定到一个特定的端口,并通过此套接字等待和监听客户端到端口的连接请求。

    package server;
    
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class Server {
    
    	public static void main(String[] args) {
    		try {
    			//声明服务器的端口号
    			ServerSocket serverSocket = new ServerSocket(1111);
    			//等待接收一个socket
    			Socket socket = serverSocket.accept();
    			//根据socket获得输入流
    			InputStream inputStream = socket.getInputStream();
    			//创建输出流
    			FileOutputStream fileOutputStream = new FileOutputStream("D:\\1.png");
    			byte[] car = new byte[1024];
    			int length = 0;
    			//将接收的输入流写入输出流,即写入本地文件中
    			while((length = inputStream.read(car)) != -1) {
    				fileOutputStream.write(car);
    			}
    			//刷新缓冲区
    			fileOutputStream.flush();
    			//关闭资源
    			fileOutputStream.close();
    			inputStream.close();
    			socket.close();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    客户端程序根据服务器程序所在的主机名和端口发出连接请求。

    package client;
    
    import java.io.FileInputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    
    public class Client {
    	public static void main(String[] args) {
    		try {
    			//创建套接字对象socket并封装ip与port
    			Socket socket = new Socket("127.0.0.1", 1111);
    			//根据创建的socket对象获得一个输出流
    			OutputStream outputStream = socket.getOutputStream();
    			//创建输入流,即要从本地发送给服务器的文件输入流
    			FileInputStream fileInputStream = new FileInputStream("D:\\icon.png");
    			byte[] car = new byte[1024];
    			int length = 0;
    			//将本地文件以IO流的方法发送给服务器
    			while((length = fileInputStream.read(car)) != -1) {
    				outputStream.write(car, 0, length);
    			}
    			//刷新缓冲区
    			outputStream.flush();
    			//关闭资源
    			fileInputStream.close();
    			fileInputStream.close();
    			socket.close();
    		} catch (Exception e) {
    			e.printStackTrace();
    		} 
    	}
    }
    

    测试:

    • 先运行服务器端程序,服务器端程序会处于等待连接状态;
    • 再运行客户端程序,确认连接后开始发送文件;
    • 发送完毕后,服务器与客户端确认断开连接。
    • 两个程序结束。
    • 本地磁盘中已经收到客户端发送的文件:
      在这里插入图片描述

报错信息呢