Java Socket实现聊天室附1500行源代码

时间:2022-05-31 21:11:00

Java养成计划(打卡第31,2天)

内容管理:Sockect聊天室的实现

Java界面 使用了各种组件,对于这部分不了解的不用担心,目前掌握一个大概就OK

项目需求分析

需要完成一个简单聊天工具的界面及功能,实现服务器中转下的多客户端之间的通信,系统完成的功能有

  • 程序启动后能看到当前有那些机器上线,可弹出对话聊天框,可以在其中编辑要发送的聊天信息,并进行发送
  • 一旦某个网内的机器上线了,可即时通知,并能更新用户界面的用户列表
  • 双击某个列表项时,可弹出对话聊天框,可以在其中编辑要发送的信息并发送
  • 聊天界面人性化,下面时发送框,上面有已有聊天记录,并借助滚动条看到当次所有聊天记录
  • 当有人向本机器发送消息时,可显示用户接收到的信息,并且显示是谁所发,同时进行信息的回复

基础分析

首先这是一个聊天工具,使用的是C/S结构,要模拟就要使用net的Scocket和ServerSocket模拟客户端和服务端

这里综合运用了多种知识,已经不再是简单的java SE知识,其中界面编程占据主要代码,这里可以贴几张图看看效果,这是我肝了2天才肝完的,这里已经可以实现多态设备的连接

分为3个包

Sever包主要是服务器的相关代码,主要是实现与用户的交互

Dao包是模拟的数据库包,存储所有的用户信息,实现增删改的操作

Client是客户代码包,只要在电脑上运行这里的代码,就可以出现客户端界面,约定好ip和端口号就可以通信了。这里就真正实现了客户端型软件,只是软件功能简单,可以使用web编程实现另外一种架构
可以来看一下界面

Java Socket实现聊天室附1500行源代码

再来看一下客户端和服务端的交流

Java Socket实现聊天室附1500行源代码

项目部分代码摘要

Dao的链表存储实现

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package Dao;
 
/**
 * 演示程序为了简化就不用数据库存储,使用单链表完成数据库各项功能
 * 这里一定要写测试代码检查各项功能是否可用
 * 最开开始我测试了add,del,find功能,却没有测试getCount功能,结果存在问题,后面突然放开测试才发现错误
 */
public class UserLinkList {
    private  Node head;
    private int count;
 
    public boolean addUser(Node client)
    {
        if(head == null)
        {//头节点也存储数据
            head = client;
            count++;
            return true;
        }
        else {
            Node p = head;
            for(;p.next != null;p = p.next);
            {
                p.next = client;
                count++;
                return true;
            }
        }
    }
    
    public int getCount() {
        return count;
    }
    
    public Node findUser(String name)
    {
        Node p = head;
        while(p != null )//p.next != null没有包含最后一个结点
        {
            if(p.username.equals(name))
            {
                return p;
            }
            p = p.next;
        }
        return null;
    }
    
    public Node findUser(int index)
    {
        int pos = 0;
        Node p = head;
        while(p != null&& pos < index)
        {
            p = p.next;
            pos++;
        }
        if(p != null&& pos == index)
        {
            return p;
        }
        return null;
    }
    
    public boolean delUser(Node client)
    {//删除后长度也要减少
        Node p = head;
        if(p.username.equals(client.username))
        {//删除头结点
            head = head.next;
            count--;
            return true;
        }
        while(p != null)
        {//忘记循环了
            if(p.next.username.equals(client.username))
            {
                p.next = p.next.next;
                count--;
                return true;
            }
            p = p.next;
        }
        return false;
    }
    
    /**
     * 这里可以设置一个显示的方法,供检查使用
     */
    public void display() {
        Node p = head;
        int pos = 1;
        while(p != null)
        {
            System.out.println("第"+pos + "个用户"+p.username);
            p = p.next;
            pos++;
        }
    }
}
/* 
    public static void main(String[] args) {//经过测试发现没有问题,可以正常使用
        Node client1 = new Node();
        client1.username = "张三";
        Node client2 = new Node();
        client2.username = "李四";
        Node client3 = new Node();
        client3.username = "王五";
        //其他的就不测试了,反正该项就可以测试了
        UserLinkList userLinkList = new UserLinkList();//自动初始化
        userLinkList.addUser(client1);
        userLinkList.addUser(client2);
        userLinkList.addUser(client3);
//      userLinkList.display();
        Node node = userLinkList.findUser(0);
        userLinkList.delUser(node);
        userLinkList.display();
        System.out.println(userLinkList.getCount());
    }
*/

现在编写这段代码应当是非常简单的,注意一定要测试

ServerListen

简单看一下这个监听线程,可以监听用户是否上线

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package Server;
/**
 * @author OMEY-PC
 *本程序的作用是实现服务器侦听的线程化,其中run方法通过client = new Node();创建一个客户端对象,通过client.socket = server.accept来设定接口,通过client.input
 *output来建立输入输出流
 */
 
import java.io.*;
import java.net.*;
import Dao.*; //连接数据
import javax.swing.*;
 
public class ServerListen extends Thread{
    ServerSocket server;
    JComboBox combobox;
    JTextArea textarea;
    JTextField textfield;
    UserLinkList userLinkList;
    Node client;
    ServerReceive recvThread;
    public boolean isStop;
    /**
     * 聊天服务端的用户上下线侦听类
     */
    public ServerListen(ServerSocket server,JComboBox combobox,JTextArea textarea,JTextField textField,UserLinkList userLinkList) {
        this.server = server;
        this.combobox = combobox;
        this.textarea = textarea;
        this.textfield = textField;
        this.userLinkList = userLinkList;
        isStop = false;
    }
    @Override
    public void run() {
        while(!isStop && !server.isClosed())//没有停止服务
        {
            try {
                client = new Node();
                client.socket = server.accept();//用来指代所连接的客户端
                client.output = new ObjectOutputStream(client.socket.getOutputStream());
                client.output.flush();
                client.input = new ObjectInputStream(client.socket.getInputStream());
                client.username = (String)client.input.readObject();
                //显示提示信息
                combobox.addItem(client.username);//改成用户名
                userLinkList.addUser(client);
                textarea.append("用户" + client.username+"上线"+"\n");
                textfield.setText("在线用户"+ userLinkList.getCount()+"人\n");
                
                recvThread = new ServerReceive(textarea,textfield,combobox,client,userLinkList);
                recvThread.start();//启动线程
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

ServerReceive

该线程实现服务器与用户之间的信息交互

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package Server;
/**
 * @author OMEY-PC
 *服务器收发消息的类
 */
 
import java.net.ServerSocket;
 
import javax.swing.*;
import Dao.*;
 
public class ServerReceive extends Thread{
    JTextArea textarea;//消息展示域
    JTextField textfield;//文本输入域
    JComboBox combobox; //复选框
    Node client;//用户
    UserLinkList userLinkList;
    public boolean isStop;
    public ServerReceive(JTextArea textarea, JTextField textfield, JComboBox combobox, Node client,
            UserLinkList userLinkList) {
        this.textarea = textarea;
        this.textfield = textfield;
        this.combobox = combobox;
        this.client = client;
        this.userLinkList = userLinkList;
        isStop = false;
    }
    
    @Override
    public void run()
    {
        //向所有人发送用户的列表
        sendUserList();
        while(!isStop && !client.socket.isClosed())
        {
            try {//类型,对谁,状况,行为,信息
                String type = (String)client.input.readObject();
                if(type.equalsIgnoreCase("聊天信息"))
                {
                    String toSomebody =(String)client.input.readObject();//从客户端接收信息
                    String status = (String)client.input.readObject();
                    String action = (String)client.input.readObject();
                    String message = (String)client.input.readObject();
                    String msg = client.username+" "+ action + "对"+ toSomebody +" 说 " + message + "\n";//接收的消息
                    if(status.equalsIgnoreCase("悄悄话"))
                    {
                        msg = "[悄悄话]" + msg; //若为悄悄话,就在前面加上标识
                    }
                    textarea.append(msg);
                    if(toSomebody.equalsIgnoreCase("所有人"))
                    {
                        sendToAll(msg);//这里是接受的用户消息,和之前的向所有人发消息不一样
                    }
                    else {//向用户发消息
                        try {
                            client.output.writeObject("聊天信息");
                            client.output.flush();//刷新流
                            client.output.writeObject(msg);
                            client.output.flush();
                        }catch (Exception e) {
                            e.printStackTrace();
                        }
                        Node node = userLinkList.findUser(toSomebody);
                        if(node != null)
                        {
                            node.output.writeObject("聊天信息");
                            node.output.flush();
                            node.output.writeObject(msg);//向选定信息发送信息
                            node.output.flush();//刷新输出流缓冲区中的信息
                        }
                    }
                }
                else if(type.equalsIgnoreCase("用户下线"))
                {
                    Node node = userLinkList.findUser(client.username);
                    userLinkList.delUser(node);
                    String msg = "用户"+ client.username +"下线\n";
                    int count = userLinkList.getCount();
                    combobox.removeAllItems();
                    combobox.addItem("所有人");
                    int i = 0;
                    while(i < count)
                    {
                        node = userLinkList.findUser(i);
                        if(node == null)
                        {
                            i++;
                            continue;
                        }
                        combobox.addItem(node.username);
                        i++;
                    }
                    combobox.setSelectedIndex(0);//选择第一个,所有人
                    textarea.append(msg);
                    textfield.setText("在线用户"+ userLinkList.getCount() +"人\n");
                    
                    sendToAll(msg);
                    sendUserList();//重新发送用户列表
                    break;
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 向所有人发送消息
     */
    public void sendToAll(String msg)
    {
        int count = userLinkList.getCount();
        int i = 0;
        while(i < count)
        {//给用户列表中的每一个人都发送消息
            Node node = userLinkList.findUser(i);
            if(node == null)
            {
                i++;
                continue;
            }
            try {//输出流
                node.output.writeObject("聊天信息");
                node.output.flush();
                node.output.writeObject(msg);//聊天消息写入输出流(to client)
                node.output.flush();
            }catch (Exception e) {
                e.printStackTrace();
            }
            i++;
        }
    }
    /**
     * 向所有人发送用户列表
     */
    public void sendUserList() {
        String userList = "";
        int count = userLinkList.getCount();
        int i = 0;
        while(i < count)
        {
            Node node = userLinkList.findUser(i);
            if(node == null)
            {
                i++;
                continue;
            }
            userList += node.username;
            userList += "\n";
            i++;
        }
        i = 0; //给每个人发送消息
        while(i < count)
        {
            Node node = userLinkList.findUser(i);
            if(node == null)
            {
                i++;
                continue;
            }
            try {
                node.output.writeObject("用户列表");
                node.output.flush();
                node.output.writeObject(userList);
                node.output.flush();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        i++;
    }  
}
/**
 * 本程序可以实现通过线程向所有人发送消息,用户列表,以及向选定的人发送聊天消息等,主要是是实现服务端收发消息的线程化,其中sendUserList()发送列表,
 * client.input.redObject()获取客户端发送到服务端的消息,通sendToAll(),将发送到发送到所有人的信息发送到各个客户端
 */

再看一下客户端的ClientReceive

该线程是实现客户端与系统之间的信息交互,注解丰富

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package Client;
 
import java.io.*;
import java.net.*;
 
import javax.swing.*;
 
public class ClientReceive extends Thread{
    private JComboBox combobox;
    private JTextArea textarea;
    Socket socket;
    ObjectOutputStream output;
    ObjectInputStream input;
    JTextField showStatus;
    public ClientReceive(JComboBox combobox, JTextArea textarea, Socket socket, ObjectOutputStream output,
            ObjectInputStream input, JTextField showStatus) {
        this.combobox = combobox;
        this.textarea = textarea;
        this.socket = socket;
        this.output = output;
        this.input = input;
        this.showStatus = showStatus;
    }
    
    @Override
    public void run() {//从服务端获得消息
        while(!socket.isClosed())
        {
            try {
                String type = (String)input.readObject();//获得流,read读取信息
                if(type.equalsIgnoreCase("系统信息"))
                {
                    String sysmsg = (String)input.readObject();
                    textarea.append("系统信息" + sysmsg);
                }
                else if(type.equalsIgnoreCase("服务关闭"))
                {
                    output.close();
                    input.close();
                    socket.close();
                    textarea.append("服务器已经关闭!\n");
                    break;
                }
                else if(type.equalsIgnoreCase("聊天信息"))
                {
                    String message = (String)input.readObject();
                    textarea.append(message);
                }
                else if(type.equalsIgnoreCase("用户列表"))
                {
                    String userlist = (String)input.readObject();
                    String[] usernames = userlist.split("\n"); //用换行符分隔
                    combobox.removeAll();//先移出去
                    int i = 0;
                    combobox.addItem("所有人");
                    while(i < usernames.length)
                    {
                        combobox.addItem(usernames[i]);
                        i++;
                    }
                    combobox.setSelectedIndex(0);
                    showStatus.setText("在线用户"+ usernames.length +" 人");
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

其余的界面的部分就不放出来了,代码太长,每个都有400多行,如果有兴趣,就到我的gitee上去浏览,后面会放上地址

项目问题

选择框中出现的不是用户名

查找相应模块发现是因为addItem中添加的时结点,而不是结点中的username,修改后正常

服务端点击消息发送按钮没有反应

查找监听器部分,发现监听器监听该部分代码写错,将button又写成sysMessage

不能显示在线人数

查找侦听线程,启动客户端发现抛出异常

Cannot invoke “javax.swing.JTextField.setText(String)” because “this.textfield” is null

textfield为空,查找问题源头;发现在构造方法中:the assignmen to variable has no effect;这是因为单词拼写错误,编译器并没有报错

服务端退出时没有消息

系统报错

Cannot read field “input” because “node” is null

意识到问题出在链表上,系统要求从0开始,而链表中的序号是从1开始的,修该链表中的findUser中的pos为0就解决

写这个程序写了两天,直接废了~~

到此这篇关于Java Socket实现聊天室附1500行源代码的文章就介绍到这了,更多相关Java Socket内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/a23452/article/details/120791330