网络聊天室之TCP协议版

java高级学到网络聊天室啦!刚好轮到我们小组展示,将自己写的放到上面作为笔记和分享,主要是功能的逻辑,如果又不足欢迎指出。

我的网络聊天室有连接,群聊,私聊,关闭这四个功能,我会贴源码在后面,前面文字内容为我的思路讲解,不感兴趣的可链接直接跳转,代码里面含有注释!!!

先上效果图

连接

欧克,首先是连接

这个地方是服务器的代码

注意哦,这里我在连接的时候进行了names的拼接,这里主要是接收每次上线的人的名字之后再通过输出流传送给每一个连接上的人然后进行实时更新在线人数列表。

在线人数列表主要是为了私聊,如果不做这个功能可以不做

并且我这里还将传过来的值用hashtable<"名字",“sk”>的集合形式存了起来,用名字作为键也是为了之后的私聊方便。

之后就是启动线程了,因为你不知道对方什么时候发消息过来所以要用线程来一直等待。

这个地方有一点可以注意

new ReadMessageThread(sk, users[1]).start();我这里是先new一个类然后才启动线程的,我往这个类里面传递了两个参数,一个sk一个users[1](当前人的名字),这里主要是起到每一个来连接的人单独有一个线程而且有了sk参数之后也好进行发消息,users[1]可不要。
  public void Lianjie() {
        try {
            ss = new ServerSocket(7777);//绑定端口
            while (true) {
           Socket sk = ss.accept();//获取连接的端口的Socket信息

                br = new DataInputStream(sk.getInputStream());//接收传过来的信息

                String user = br.readUTF();//读取

                String[] users = new String[10];

                users = user.split(",");//这里将字符串分割并用String数组接收

         jta.append(users[1] + "上线了\n");//在服务器显示文本消息,方便判断是否成功,可不要

                names += ","+users[1];//这里接收的是用户名

                System.out.println(uselist.put(users[1], sk));//将用户名和sk加入集合

                Set set = serverdemo.uselist.keySet();//这里进行遍历
                for (Object os : set) {
                    Socket s = uselist.get(os);
              bo = new DataOutputStream(s.getOutputStream());//向集合里存储的Socket分别发送
                    bo.writeUTF(0 + "" + names);//将目前连接的人名发送出去
                }
                new ReadMessageThread(sk, users[1]).start();//调用线程实时监听发过来的消息

            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

这里是客户端的进行连接操作,这里没有什么好说的,特殊一点的就是将按钮变成了不可点(可有无),还有发送格式下面代码有注释。

 if(s.equals("连接")){
            opt="0";
            try {
                username=unamejtf.getText();//获取名字
                //建立连接
                sk = new Socket(ipjtf.getText(), Integer.valueOf(portjtf.getText()));
                bo=new DataOutputStream(sk.getOutputStream());

                //发送格式 opt(干什么)+名字+
                bo.writeUTF(opt+","+username);

                b1.setEnabled(false);//连接按钮改成不可点

                opt="1";//默认发送为群发

                msgjtf.setText("");//清空发送文本框

                new ReadMessageThread().start();//连接完成启动接收消息的线程
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }

下面是客户端处理服务器发过来的连接消息的处理,再if判断前我就进行了字符串分割了哦。

第一句是获取服务器传过来的最后一个名字,再面板追加xx上线了,可有无。

第二句是我将我的上线人数列表的清空然后再重新放值进去,随后用for循环进行实时更新。

   if(users[0].equals("0")){//连接
              
                        jta.append(users[length-1]+"上线了\n");
                        clientdemo.dlm.clear();;
                        for (int i = 1; i < length; i++){
                            clientdemo.dlm.addElement(users[i]);
                        }

群聊私聊

好了,关于连接也就上面的这些了,有疑问评论区发言吧。

接下来是群聊还有私聊,因为私聊和群聊本质差不多。

都是将消息再客户端进行发送而后服务器转发,可能出现的问题就是sk可能为空或者可能发送到指定的sk,但是用集合保存后应该没什么问题。

还是先看客户端,if判断前依旧将发来的消息进行了字符串分割了哦。

下面代码是群发,其实没有什么好说的,就是进行了转发。

if (users[0].equals("1")) {//群发
                        jta.append(users[1] + users[2]);//追加到服务器可有无

                        Set set = serverdemo.uselist.keySet();//遍历
                        for (Object os : set) {
                            Socket s = serverdemo.uselist.get(os);
                            String names = (String) os;
                            if (name.equals(names)) {//判断,毕竟不能自己发自己
                                System.out.println(names);
                                continue;
                            }
                            bo = new DataOutputStream(s.getOutputStream());//转发给其他Socket
                            bo.writeUTF(msg);
                        }
                    }

 私聊比起群聊就是多了一个判断而已,利用列表选择的名字单独发送给指定用户

else if (users[0].equals("2")) {//私聊
                        jta.append(users[1] + users[3]);

                        Set set = serverdemo.uselist.keySet();//遍历
                        for (Object s : set) {
                            System.out.println(users[2]);
                            if (!s.equals(users[2]))//判断是否为要发送的对象
                                continue;
                            Socket sk = uselist.get(s);
                            bo = new DataOutputStream(sk.getOutputStream());

                            bo.writeUTF(users[0]+","+users[1] +","+ users[3]);

                        }
                    }

客户端的发送代码可以看到群发和私聊就两句,获取输出流,还有发送的消息,要注意的只有发送消息的格式,我这里是用字符串拼接的哦。

if(s.equals("发送")){
            if(msgjtf.getText().trim().equals("")){
                JOptionPane.showMessageDialog(null,"不能发送空消息");
            }
            else {
                try {
                    System.out.println(opt);
                    if(opt.equals("1")){//群发
                        bo=new DataOutputStream(sk.getOutputStream());
                        //opt(消息类型)+发送人+发送内容
                        bo.writeUTF(opt+","+username+",:"+msgjtf.getText()+"\n");
                    }else if(opt.equals("2")){//私聊
                        bo=new DataOutputStream(sk.getOutputStream());
                        //opt(消息类型)+发送人+要发送的人+发送内容
                        bo.writeUTF(opt+","+username+","+listName+",:"+msgjtf.getText()+"\n");
                    }
                    jta.append("我:" + msgjtf.getText() + "\n");
                    msgjtf.setText("");

                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }

 客户端的接收代码,就是进行追加

 {//群聊
                        if(users[0].equals("1"))
                        jta.append("【群发】"+users[1]+users[2]);
                        else
                            jta.append("【私发】"+users[1]+users[2]);
                    }

关闭

关闭就是再客户端,服务器终止对应的线程

下面是服务器的,再线程里我是用while进行循环用run来判断

这里的主要功能就是更新在线人数名单了,将之前的names字符串进行拆分,清空names字符串,for循环拼接的时候跳过当前关闭连接的人。

if(users[0].equals("3")){//关闭
                        run=false;//接收当前这个的线程
                        serverdemo.uselist.remove(users[1]);//删除当前的连接对象
                        String name[]=new String[10];
                        //更新在线名单
                        name=names.split(",");
                        names="";
                        for (String n:name){
                            if(!n.equals(users[1])){
                                names=","+n;
                            }
                        }

                 
                    }

下面是客户端,关闭发送消息给服务器,带上名字用来更新列表。

if(s.equals("关闭")){
            opt="3";
            try {
                bo=new DataOutputStream(sk.getOutputStream());
                bo.writeUTF(opt+","+username);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
            run=false;
        }

客户端接收别人关闭消息处理,就是再类表删除当前关闭的人 

if(users[0].equals("3")){
                        clientdemo.dlm.removeElement(users[1]);//更新在线人数
                    }

全部代码

感觉上面分析了和没有分析一样😂

客户端源码

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.*;
import java.net.Socket;
import java.util.Set;


public class clientdemo extends JFrame implements ActionListener {
    JTextField ipjtf,unamejtf,portjtf,msgjtf;
    //ip文本框,用户名文本框,端口文本框,发送消息文本框
    JTextArea jta;//查看消息文本域
    String opt="1";//判断发出的消息种类,例如0是连接,1是群聊,2是私聊
    DataInputStream br;//输入流
    DataOutputStream bo;//输出流
    Socket sk;
    String username;//该用户名字
    String mgs;//接收传过来的消息
    String listName;//存储列表选择的用户
    boolean run=true;//判断线程是否中断
    static DefaultListModel dlm = new DefaultListModel();//列表模型
    static  JList list;//在线列表
    JButton b1;//上线按钮
    public static void main(String[] args) {
        new clientdemo().InitFrame();
    }//主方法调用方法
    public void InitFrame(){
        //面板基本布局
        setSize(700,600);
        setLocationRelativeTo(null);
        setTitle("客户端");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //1.面板1,连接服务器
        JPanel jp1=new JPanel();
        add(jp1, BorderLayout.NORTH);
        JLabel jbl1=new JLabel("IP地址:");
        JLabel jbl2=new JLabel("端口号:");
        JLabel jbl3=new JLabel("用户名:");

        //文本域
        ipjtf=new JTextField(10);
        unamejtf=new JTextField(8);
        portjtf=new JTextField(5);

        jp1.add(jbl1);
        jp1.add(ipjtf);
        jp1.add(jbl2);
        jp1.add(portjtf);
        jp1.add(jbl3);
        jp1.add(unamejtf);

        b1=new JButton("连接");
        jp1.add(b1);
        JButton b5=new JButton("关闭");
        jp1.add(b5);

        //第二个面板,聊天记录
        JPanel jp2=new JPanel();//多行文本域
        add(jp2,BorderLayout.CENTER);
        jp2.setLayout(new BorderLayout());
        jta=new JTextArea(50,60);
        jp2.add(jta,BorderLayout.CENTER);
        jta.setEditable(false);//禁止编辑
        //面板3,发送消息
        JPanel jp3=new JPanel();
        JButton b3=new JButton("群聊");
        JButton b4=new JButton("私聊");
        jp3.add(b3);
        jp3.add(b4);

        add(jp3,BorderLayout.SOUTH);
        msgjtf=new JTextField(35);
        JButton b2 = new JButton("发送");
        jp3.add(msgjtf);
        jp3.add(b2);

        //添加按钮监听
        b1.addActionListener(this);
        b2.addActionListener(this);
        b3.addActionListener(this);
        b4.addActionListener(this);
        b5.addActionListener(this);

        //面板4,私聊列表
        JPanel jp4=new JPanel();
        add(jp4,BorderLayout.EAST);
        JLabel jl1=new JLabel("在线人数");
        jp4.add(jl1);
        list=new JList(dlm);
        jp4.add(list);
        list.addListSelectionListener(new myListener());//添加列表监听

        setVisible(true);
    }


    //列表监听,用来实时获取选中用户
    class myListener implements ListSelectionListener{
        @Override
        public void valueChanged(ListSelectionEvent e) {
            listName = (String) list.getSelectedValue();
//            System.out.println(listName);可以控制台输出看看是否生效
        }
    }
    //按钮监听
    public void actionPerformed(ActionEvent e) {
        String s=((JButton)(e.getSource())).getText();
        System.out.println(s);
        if(s.equals("连接")){
            opt="0";
            try {
                username=unamejtf.getText();//获取名字
                //建立连接
                sk = new Socket(ipjtf.getText(), Integer.valueOf(portjtf.getText()));
                bo=new DataOutputStream(sk.getOutputStream());

                //发送格式 opt(干什么)+名字+
                bo.writeUTF(opt+","+username);

                b1.setEnabled(false);//连接按钮改成不可点

                opt="1";//默认发送为群发

                msgjtf.setText("");//清空发送文本框

                new ReadMessageThread().start();//连接完成启动接收消息的线程
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }

        }else if(s.equals("发送")){
            if(msgjtf.getText().trim().equals("")){
                JOptionPane.showMessageDialog(null,"不能发送空消息");
            }
            else {
                try {
                    System.out.println(opt);
                    if(opt.equals("1")){//群发
                        bo=new DataOutputStream(sk.getOutputStream());
                        //opt(消息类型)+发送人+发送内容
                        bo.writeUTF(opt+","+username+",:"+msgjtf.getText()+"\n");
                    }else if(opt.equals("2")){//私聊
                        bo=new DataOutputStream(sk.getOutputStream());
                        //opt(消息类型)+发送人+要发送的人+发送内容
                        bo.writeUTF(opt+","+username+","+listName+",:"+msgjtf.getText()+"\n");
                    }
                    jta.append("我:" + msgjtf.getText() + "\n");
                    msgjtf.setText("");

                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }else if(s.equals("群聊")){
            opt="1";
        }else if(s.equals("私聊")){
            if( listName == null||listName.equals(username))
                JOptionPane.showMessageDialog(null,"不能发送空消息且不可以选自己");
            else {
                System.out.println(s);
                opt="2";
            }

        }else if(s.equals("关闭")){
            opt="3";
            try {
                bo=new DataOutputStream(sk.getOutputStream());
                bo.writeUTF(opt+","+username);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
            run=false;
        }
    }
    class ReadMessageThread extends Thread {//接收
        public void run(){
            super.run();
            while (run){
                try {
                    br=new DataInputStream(sk.getInputStream());
                    String msg=br.readUTF();
                    String[] users=new String[10];
                    //分割
                    users=msg.split(",");
                    int length=users.length;

                    if(users[0].equals("0")){//连接
                        jta.append(users[length-1]+"上线了\n");
                        clientdemo.dlm.clear();;
                        for (int i = 1; i < length; i++){
                            clientdemo.dlm.addElement(users[i]);
                        }
                    }else if(users[0].equals("3")){
                        clientdemo.dlm.removeElement(users[1]);//更新在线人数
                    }
                    else {//群聊
                        if(users[0].equals("1"))
                        jta.append("【群发】"+users[1]+users[2]);
                        else
                            jta.append("【私发】"+users[1]+users[2]);
                    }
//
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

服务器源码

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Hashtable;
import java.util.Set;


//服务器
public class serverdemo extends JFrame implements ActionListener {
    JTextField msgjtf;//发送文本框
    ServerSocket ss;//绑定端口
    DataInputStream br;//输入流
    DataOutputStream bo;//输出流
    String opt;//判断发出的消息种类,例如0是连接,1是群聊,2是私聊
    JTextArea jta;//聊天显示面板
    String names = "";//存储名字,用于实时更新列表名字

    boolean run =true;
    //用于存储各个用户的名字以及Score,用于群发私发判断
    static Hashtable<String, Socket> uselist = new Hashtable<String, Socket>();

    public void Lianjie() {
        try {
            ss = new ServerSocket(7777);//绑定端口
            while (true) {
                Socket sk = ss.accept();//获取连接的端口的Socket信息

                br = new DataInputStream(sk.getInputStream());//接收传过来的信息

                String user = br.readUTF();//读取

                String[] users = new String[10];

                users = user.split(",");//这里将字符串分割并用String数组接收

                jta.append(users[1] + "上线了\n");//在服务器显示文本消息,方便判断是否成功,可不要

                names += ","+users[1];//这里接收的是用户名

                System.out.println(uselist.put(users[1], sk));//将用户名和sk加入集合

                Set set = serverdemo.uselist.keySet();//这里进行遍历
                for (Object os : set) {
                    Socket s = uselist.get(os);
                    bo = new DataOutputStream(s.getOutputStream());//向集合里存储的Socket分别发送
                    bo.writeUTF(0 + "" + names);//将目前连接的人名发送出去
                }
                new ReadMessageThread(sk, users[1]).start();//调用线程实时监听发过来的消息

            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        new serverdemo().InitFrame();
    }

    public void InitFrame() {
        //基本面板布局
        setSize(700, 600);
        setLocationRelativeTo(null);
        setTitle("服务器端");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //第二个面板,聊天记录
        JPanel jp2 = new JPanel();//多行文本域
        add(jp2, BorderLayout.CENTER);
        jp2.setLayout(new BorderLayout());
        jta = new JTextArea(50, 60);
        jp2.add(jta, BorderLayout.CENTER);
        jta.setEditable(false);//禁止编辑
        //面板3,发送消息
        JPanel jp3 = new JPanel();
        add(jp3, BorderLayout.SOUTH);
        msgjtf = new JTextField(40);
        JButton b2 = new JButton("发送");
        jp3.add(msgjtf);
        jp3.add(b2);


        b2.addActionListener(this);//按钮监听

        setVisible(true);
        Lianjie();
    }

    @Override
    public void actionPerformed(ActionEvent e) {//按钮监听器
        String s = ((JButton) (e.getSource())).getText();
        if (s.equals("发送")) {
            opt = "1";//服务器发送统统按群发处理
            if (msgjtf.getText().trim().equals("")) {
                JOptionPane.showMessageDialog(null, "不能发送空消息");
                return;
            }

            try {
                Set set = uselist.keySet();//便利集合,向每个Socket发送消息

                for (Object o : set) {
                    Socket sk = uselist.get(o);
                    System.out.println(sk);
                    bo = new DataOutputStream(sk.getOutputStream());
                    //发送格式为 “判断消息为那种类型”,“发送者”,“发送内容”  “,”为分割符
                    bo.writeUTF(opt + ",服务器" + ",:" + msgjtf.getText() + "\n");
                }

                jta.append("服务器:" + msgjtf.getText() + "\n");//追加到面板
                msgjtf.setText("");//将发送文本框清空
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }

        }
    }

    class ReadMessageThread extends Thread {//重点!!!接收消息
        private Socket sk;
        private String name;

        public ReadMessageThread(Socket sk, String name) {
            this.sk = sk;
            this.name = name;
        }

        public void run() {
            super.run();
            while (run) {
                try {
                    //接收当前类的sk发来的消息
                    br = new DataInputStream(sk.getInputStream());
                    String msg = br.readUTF();
                    String[] users = new String[10];
                    //分割
                    users = msg.split(",");

                    if (users[0].equals("1")) {//群发
                        jta.append(users[1] + users[2]);//追加到服务器可有无

                        Set set = serverdemo.uselist.keySet();//遍历
                        for (Object os : set) {
                            Socket s = serverdemo.uselist.get(os);
                            String names = (String) os;
                            if (name.equals(names)) {//判断,毕竟不能自己发自己
                                System.out.println(names);
                                continue;
                            }
                            bo = new DataOutputStream(s.getOutputStream());//转发给其他Socket
                            bo.writeUTF(msg);
                        }
                    } else if (users[0].equals("2")) {//私聊
                        jta.append(users[1] + users[3]);

                        Set set = serverdemo.uselist.keySet();//遍历
                        for (Object s : set) {
                            System.out.println(users[2]);
                            if (!s.equals(users[2]))//判断是否为要发送的对象
                                continue;
                            Socket sk = uselist.get(s);
                            bo = new DataOutputStream(sk.getOutputStream());

                            bo.writeUTF(users[0]+","+users[1] +","+ users[3]);

                        }
                    }else if(users[0].equals("3")){//关闭
                        run=false;//接收当前这个的线程
                        serverdemo.uselist.remove(users[1]);//删除当前的连接对象
                        String name[]=new String[10];
                        //更新在线名单
                        name=names.split(",");
                        names="";
                        for (String n:name){
                            if(!n.equals(users[1])){
                                names=","+n;
                            }
                        }

                        Set set = serverdemo.uselist.keySet();
                        for (Object os : set) {
                            Socket s = uselist.get(os);
                            bo = new DataOutputStream(s.getOutputStream());
                            bo.writeUTF(msg);//将目前连接的人名发送出去
                        }
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值