1500行代码!拥有自己的聊天室------ socket聊天室实现(GUI,线程,TCP)

内容管理: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就解决

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

1.2 功能要求 (1)登录功能。 (2)客户可以通过服务器转发,实现一对一和多对多聊天。 (3)实现呼叫功能。 (4)客户端程序应该可以实时显示目前其它用户的状态。 (5)应该具有易用、美观的图形界面。 一、 实验目的: 1. 了解socket类的网络编程技术; 2. 熟悉socket聊天系统的结构和工作原理; 3. 掌握TCP传递消息的机制; 4. 应用delphi对该聊天系统进程序的编写。 二、 实验描述: 通过delphi编程实现局域网内的一个聊天系统,支持客户与服务器、客户与客户之间的消息传递,服务器允许多个客户端的聊天的功能,聊天记录的保存和查看的功能等。 三、 实验硬件、软件平台: 1. 硬件平台:多台PC机的一个局域网、Windows XP/2000、AMD Athlon64 X2 4000+ 、内存256MB以上、硬盘80G以上。 2. 软件平台:delphi7 1.3 运环境 本系统基于WIN NT 和ACCESS XP设计,适用于WIN2000/WIN XP等系统,并需要安装office2000/office xp. 1.4 功能实现 1.登录验证功能。 2. 客户通过服务器转发,实现聊天功能。 3. 实时显示目前其它用户的状态。 4. 保存并能察看聊天纪录。 5.申请新的用户号码。 6. 易用、美观的图形界面,实现系统托盘。 二.技术路线 2.1 总体方案 为实现网络聊天的功能,采用Windows Socket编程,服务器与客户端采用了TCP/IP连接方式,在设计聊天方案时,实将所有信息发往服务器端,再由服务器进分别处理的思路,服务器端是所有信息的中心。 由于服务器端要保存用户信息,我们利用数据库来实现这一功能,因此首先需要建立用户信息数据库。 在客户端保存聊天纪录和用户号码这一功能的实现中,采用了文件系统设计。 在信息到来及好友上线时,通过闪动托盘图标和播放不同的音乐进提示。 建立消息链表来保存用户接收的各种消息。 服务器及客户端的功能可划分为以下模块: 客户端: 1) 登陆功能:建立与服务器的连接并登陆,能显示登陆错误信息。 2) 界面显示:将在线好友显示在好友列表中,并实现系统托盘,加入工具栏便于操作。 3) 聊天功能:与好友聊天。 4) 聊天纪录:能保存聊天纪录,并能察看聊天纪录。 5) 信息提示:闪动托盘图标提示到来信息,并播放不同音乐来提示。 6) 其他: 用户登陆成功,将保存其号码,以便下次登陆时,不必再输入而可以直接选择,显示登陆时间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值