Java学习打卡:第三十一、二天【完成sococket聊天室搭建项目】
Java养成计划(打卡第31,2天)
需要完成一个简单聊天工具的界面及功能,实现服务器中转下的多客户端之间的通信,系统完成的功能有
首先这是一个聊天工具,使用的是C/S结构,要模拟就要使用net的Scocket和ServerSocket模拟客户端和服务端
这里综合运用了多种知识,已经不再是简单的java SE知识,其中界面编程占据主要代码,这里可以贴几张图看看效果,这是我肝了2天才肝完的,这里已经可以实现多态设备的连接
分为3个包
Sever包主要是服务器的相关代码,主要是实现与用户的交互
Dao包是模拟的数据库包,存储所有的用户信息,实现增删改的操作
Client是客户代码包,只要在电脑上运行这里的代码,就可以出现客户端界面,约定好ip和端口号就可以通信了。这里就真正实现了客户端型软件,只是软件功能简单,可以使用web编程实现另外一种架构
可以来看一下界面
再来看一下客户端和服务端的交流
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());
}
*/
现在编写这段代码应当是非常简单的,注意一定要测试
简单看一下这个监听线程,可以监听用户是否上线
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();
}
}
}
}
该线程实现服务器与用户之间的信息交互
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(),将发送到发送到所有人的信息发送到各个客户端
*/
该线程是实现客户端与系统之间的信息交互,注解丰富
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就解决
写这个程序写了两天,直接废了~~