Java Socket Java Socket实现聊天室附1500行源代码
码农C风 人气:0Java养成计划(打卡第31,2天)
内容管理:Sockect聊天室的实现
Java界面 使用了各种组件,对于这部分不了解的不用担心,目前掌握一个大概就OK
项目需求分析
需要完成一个简单聊天工具的界面及功能,实现服务器中转下的多客户端之间的通信,系统完成的功能有
- 程序启动后能看到当前有那些机器上线,可弹出对话聊天框,可以在其中编辑要发送的聊天信息,并进行发送
- 一旦某个网内的机器上线了,可即时通知,并能更新用户界面的用户列表
- 双击某个列表项时,可弹出对话聊天框,可以在其中编辑要发送的信息并发送
- 聊天界面人性化,下面时发送框,上面有已有聊天记录,并借助滚动条看到当次所有聊天记录
- 当有人向本机器发送消息时,可显示用户接收到的信息,并且显示是谁所发,同时进行信息的回复
基础分析
首先这是一个聊天工具,使用的是C/S结构,要模拟就要使用net的Scocket和ServerSocket模拟客户端和服务端
这里综合运用了多种知识,已经不再是简单的java SE知识,其中界面编程占据主要代码,这里可以贴几张图看看效果,这是我肝了2天才肝完的,这里已经可以实现多态设备的连接
分为3个包
Sever包主要是服务器的相关代码,主要是实现与用户的交互
Dao包是模拟的数据库包,存储所有的用户信息,实现增删改的操作
Client是客户代码包,只要在电脑上运行这里的代码,就可以出现客户端界面,约定好ip和端口号就可以通信了。这里就真正实现了客户端型软件,只是软件功能简单,可以使用web编程实现另外一种架构
可以来看一下界面
再来看一下客户端和服务端的交流
项目部分代码摘要
Dao的链表存储实现
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
简单看一下这个监听线程,可以监听用户是否上线
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
该线程实现服务器与用户之间的信息交互
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
该线程是实现客户端与系统之间的信息交互,注解丰富
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就解决
写这个程序写了两天,直接废了~~
加载全部内容