Java简单项目——ServerSocket聊天室(基础)

前言:

使用java来搭建一个简单的聊天室,参考作者—— java GUI 网络编程:图形界面聊天室

 

 

 

 

 

 


服务端

新建一个项目,我们要写服务端。

在src下新建包——com.myserversocket.main,

然后在包下新建一个文件Server.java作为服务端的入口文件

Server.java的内容如下:

package com.myserversocket.main;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server{
    public static void main(String[] args) {
        try {
            int localPort = 12223;
            //建立服务器ServerSocket
            ServerSocket ss = new ServerSocket(localPort);
            //打印Server建立成功
            System.out.println("Server on"
                    +ss.getInetAddress().getHostAddress()
                    +":"+localPort);
            //监听端口,每当一个客户端连接就建立一个线程服务这个客户端
            while(true){
                //接收客户端的socket
                Socket socket=ss.accept();
                //提取客户端的ip和端口
                String ip= socket.getInetAddress().getHostAddress();
                int port=socket.getPort();
                //建立新的服务端线程
                //向该线程提供ServerSocket、Socket、客户端ip、客户端端口
                new Thread(new ServerThread(socket,ss,ip,port)).start();
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我们的思路很简单,来一个客户端,我们就给它分配一个线程为这个客户端服务。

接下来我们来写同包下的ServerThread.java

这是一个实现Runnable接口的类

package com.myserversocket.main;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

public class ServerThread implements Runnable {
    //客户端的socket
    Socket socket=null;
    //服务器的ServerSocket
    ServerSocket ss=null;
    //获取的客户端IP
    String ip=null;
    //获取的客户端端口
    int port=0;
    //组合客户端的ip和端口字符串得到的uid字符串
    String uid=null;

    //静态ArrayList存储所有的uid,uid由ip和端口组成
    static ArrayList<String> uid_arr=new ArrayList<String>();
    //静态HashMap存储所有uid,ServerThread对象组成的键值对
    static HashMap<String,ServerThread> hm=new HashMap<String,ServerThread>();

    public ServerThread(Socket socket,ServerSocket ss,String ip,int port){
        this.socket=socket;
        this.ss=ss;
        this.ip=ip;
        this.port=port;
        uid=ip+":"+port;
    }

    @Override
    public void run() {
        //将客户端uid存入ArrayList
        uid_arr.add(uid);
        //将当前uid和ServerThread对存入HashMap
        hm.put(uid,this);

        //时间显示模式
        SimpleDateFormat sdf=new SimpleDateFormat("MM-dd hh:mm:ss");

        //控制台打印客户端IP和端口
        System.out.println("Client connected:"+uid);

        try{
            //获取输入流
            InputStream in=socket.getInputStream();
            //获取输出流
            OutputStream out=socket.getOutputStream();

            //向当前客户端传输连接成功的信息
            String welcome=sdf.format(new Date())
                    +"\n成功连接服务器..."
                    +"\n服务器IP:"+ss.getInetAddress().getLocalHost()
                    +":"+ss.getLocalPort()
                    +"\n客户端IP:"+ip
                    +":"+port+"\n";
            out.write(welcome.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

接下来我们可以用telnet连接本机的12223端口,就会看到输出,证明成功连接服务端。

下面还是ServerThread.java文件,我们实现全部的服务端代码,下面有的内容需要结合客户端才能理解,不理解先不要着急

完整的 ServerThread.java 代码

package com.myserversocket.main;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

public class ServerThread implements Runnable {
    //客户端的socket
    Socket socket=null;
    //服务器的ServerSocket
    ServerSocket ss=null;
    //获取的客户端IP
    String ip=null;
    //获取的客户端端口
    int port=0;
    //组合客户端的ip和端口字符串得到的uid字符串
    String uid=null;

    //静态ArrayList存储所有的uid,uid由ip和端口组成
    static ArrayList<String> uid_arr=new ArrayList<String>();
    //静态HashMap存储所有uid,ServerThread对象组成的键值对
    static HashMap<String,ServerThread> hm=new HashMap<String,ServerThread>();

    public ServerThread(Socket socket,ServerSocket ss,String ip,int port){
        this.socket=socket;
        this.ss=ss;
        this.ip=ip;
        this.port=port;
        uid=ip+":"+port;
    }

    @Override
    public void run() {
        //将客户端uid存入ArrayList
        uid_arr.add(uid);
        //将当前uid和ServerThread对存入HashMap
        hm.put(uid,this);

        //时间显示模式
        SimpleDateFormat sdf=new SimpleDateFormat("MM-dd hh:mm:ss");

        //控制台打印客户端IP和端口
        System.out.println("Client connected:"+uid);

        try{
            //获取输入流
            InputStream in=socket.getInputStream();
            //获取输出流
            OutputStream out=socket.getOutputStream();

            //向当前客户端传输连接成功的信息
            String welcome=sdf.format(new Date())
                    +"\n成功连接服务器..."
                    +"\n服务器IP:"+ss.getInetAddress().getLocalHost()
                    +":"+ss.getLocalPort()
                    +"\n客户端IP:"+ip
                    +":"+port+"\n";
            out.write(welcome.getBytes());

            //广播更新在线名单
            updateOnLineList(out);

            //准备缓冲区
            byte[] buf=new byte[1024];
            int len=0;

            //持续监听并转发客户端消息
            while(true){
                //读取客户端输入
                len = in.read(buf);
                String msg=new String(buf,0,len);
                System.out.println(msg);
                //消息类型:退出或者聊天
                String type=msg.substring(0,msg.indexOf("/"));
                //消息本体
                String content=msg.substring(msg.indexOf("/")+1);
                //根据消息类型分别处理
                //客户端要退出
                if(type.equals("Exit")){
                    //更新ArrayList和HashMap,删除退出的uid和线程
                    uid_arr.remove(uid_arr.indexOf(uid));
                    hm.remove(uid);

                    //广播更新在线名单
                    updateOnLineList(out);

                    //控制台打印客户端IP和端口
                    System.out.println("Client Exited"+uid);
                    //结束循环(本线程即将结束)
                    break;
                }else if(type.equals("Chat")){
                    //提取收信者地址
                    String[] receiver_arr=content.substring(0,
                            content.indexOf("/")).split(",");
                    //提取聊天内容
                    String word=content.substring(content.indexOf("/")+1);
                    //向收信者广播发出聊天信息
                    chatOnlineList(out,uid,receiver_arr,word);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //向所有已连接的客户端更新在线名单
    public void updateOnLineList(OutputStream out) {
        try {
            for (String tmp_uid : uid_arr) {
                //获取广播收听者的输出流
                //所有连接中的客户端的线程的客户端socket的输出流
                out = hm.get(tmp_uid).socket.getOutputStream();
                //将当前在线名单以逗号为分割组合成长字符串进行一次传送
                StringBuilder sb=new StringBuilder("OnlineListUpdate/");
                //给sb追加在线用户的uid,以逗号分隔
                for(String member:uid_arr){
                    sb.append(member);
                    //以逗号分隔uid,除了最后一个
                    if(uid_arr.indexOf(member) != uid_arr.size()-1)
                        sb.append(",");
                }
                out.write(sb.toString().getBytes());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //向指定的客户端发送聊天信息
    public void chatOnlineList(OutputStream out,
                               String uid,
                               String[] receiver_arr,
                               String word){
        try {
            for (String tmp_uid : receiver_arr) {
                //获取广播收听者的输出流
                out = hm.get(tmp_uid).socket.getOutputStream();
                //发送聊天信息
                out.write(("Chat/" + uid + "/" + word).getBytes());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

知识点:

  • 之前做Unity的SocketIO通信,服务端与客户端传的都是json格式的数据,而我们这里因为只是聊天工具,传的成了字符串,比如在线客户端的字符串:“OnlineListUpdate/1.1.1.1:1,2.2.2.2:2”、传输聊天内容:“Chat/1.1.1.1:1/asdflksjafkljlkdjsfak”,这样。
  • String msg=new String(buf,0,len):创建String类对象的一种构造方法,取值是从第0个开始, 长度为len, 取的是 buf 数组
  • msg.substring(0,msg.indexOf(“/”)):从零索引开始读取到/字符(不包括/),关于substring,可以参考菜鸟教程

 

服务端其实很简单,我们的重点是下面的客户端


客户端

新建一个项目,这个就是我们客户端的项目。

在src下新建两个包,com.myjavachatclient.main、com.myjavachatclient.view,

接着,在main下面新建一个java类,叫做 Client1.java,这个就是我们客户端的主类。

再在view下面新建一个java类,ClientFrame.java,这个主要负责编写GUI界面。

ClientFrame.java的内容如下

package com.myjavachatclient.view;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.text.SimpleDateFormat;

public class ClientFrame extends JFrame {
    //时间显示格式
    public SimpleDateFormat sdf=new SimpleDateFormat("MM-dd hh:mm:ss");

    //窗口宽度
    public final int WIDTH=700;
    //窗口高度
    public final int HEIGHT=700;

    //创建发送按钮
    JButton btnSend=new JButton("发送");
    //创建清屏按钮
    JButton btnClear=new JButton("清屏");
    //创建退出按钮
    JButton btnExit=new JButton("退出");

    //创建消息接收者标签
    JLabel lb1Receiver=new JLabel("右边选择聊天对象");

    //创建文本输入框,参数分别为行数和列数(用来输入聊天信息)
    JTextArea jtaSay=new JTextArea();

    //创建聊天信息框(用来显示聊天记录)
    JTextArea jtaChat=new JTextArea();

    //当前在线列表的列标题
    String[] colTitles={"网名","IP","端口"};
    //当前在线列表的数据
    String[][] rowData=null;
    //创建当前在线列表
    public JTable jtbOnline=new JTable(
            new DefaultTableModel(rowData,colTitles){
                //表格不可编辑,只可以显示
                //构造这个对象时重写isCellEditable方法
                @Override
                public boolean isCellEditable(int row, int column) {
                    return false;
                }
            }
    );
    //创建聊天信息框的滚动窗(通过jtaChat)
    JScrollPane jspChat=new JScrollPane(jtaChat);

    //创建当前在线列表的滚动窗
    JScrollPane jspOnline=new JScrollPane(jtbOnline);

    //设置默认窗口属性,连接窗口组件
    public ClientFrame(){
        //标题
        this.setTitle("蝙蝠通讯");
        //大小
        this.setSize(WIDTH,HEIGHT);
        //不可缩放
        this.setResizable(false);
        //设置布局,不适用默认布局,完全自定义
        this.setLayout(null);

        //设置按钮大小和位置
        addButton(btnSend,20,600,100,60,
                new Font("黑体", Font.BOLD,18));
        addButton(btnClear,140,600,100,60,
                new Font("黑体", Font.BOLD,18));
        addButton(btnExit,260,600,100,60,
                new Font("黑体", Font.BOLD,18));

        //设置标签大小和位置
        lb1Receiver.setBounds(20,420,300,30);
        //添加标签
        this.add(lb1Receiver);

        //添加文本输入框
        //设置文本输入框大小和位置
        jtaSay.setBounds(20,460,360,120);
        //设置文本输入框字体
        jtaSay.setFont(new Font("楷体",Font.BOLD,16));
        //添加文本输入框
        this.add(jtaSay);

        //聊天消息框自动换行
        jtaChat.setLineWrap(true);
        //聊天消息框不可编辑,只可显示
        jtaChat.setEditable(false);
        //设置聊天消息框字体
        jtaChat.setFont(new Font("楷体",Font.BOLD,16));

        //设置聊天框滚动区(内容就是聊天消息文字域)
        addScrollPane(jspChat,20,20,360,400);

        //设置在线用户滚动区(内容就是在线用户文字域)
        addScrollPane(jspOnline,420,20,250,400);

    }
    private void addButton(JButton btn,int x,int y,
                         int width,int height,Font f){
        btn.setBounds(x,y,width,height);
        btn.setFont(f);
        //添加按钮
        this.add(btn);
    }
    private void addScrollPane(JScrollPane jsp,int x,int y,
                                int width,int height){
        //设置滚动窗的水平滚动条属性:不可见
        jsp.setHorizontalScrollBarPolicy(
                           JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        //设置滚动窗的垂直滚动条属性:需要时自动出现
        jsp.setVerticalScrollBarPolicy(
                          JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        //设置滚动窗的大小和位置
        jsp.setBounds(x,y,width,height);
        //添加滚动窗
        this.add(jsp);
    }
}

Client1.java中,我们在主函数中调用

        ClientFrame cframe=new ClientFrame();
         cframe.setVisible(true);

即可看到大致GUI布局

设计好GUI界面后,我们来到Client1,实现通信功能

还记得我们在服务端中有这么一句吗?

//向当前客户端传输连接成功的信息
String welcome=sdf.format(new Date()) 
                     +"\n成功连接服务器..." 
                     +"\n服务器IP:"
                     +ss.getInetAddress().getLocalHost() 
                     +":"+ss.getLocalPort() 
                     +"\n客户端IP:"+ip +":"+port+"\n";
out.write(welcome.getBytes());

我们现在在客户端的Client1.java 写上如下代码,就可以成功接收这段信息

package com.myjavachatclient.main;

import com.myjavachatclient.view.ClientFrame;

import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class Client1 {
    //连接服务端的IP和端口
    static String ip="127.0.0.1";
    static int port=12223;
    //建立客户端Socket
    public static Socket socket=null;
    //消息接收者uid(本客户端)
    public static StringBuilder uidReceiver=null;

    public static void main(String[] args){
        //创建客户端窗口对象
        ClientFrame cframe=new ClientFrame();
        //窗口关闭键无效,必须通过退出键退出客户端以便善后
        cframe.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        //获得本机屏幕横向分辨率
        int w= Toolkit.getDefaultToolkit().getScreenSize().width;
        //获得本机屏幕纵向分辨率
        int h= Toolkit.getDefaultToolkit().getScreenSize().height;
        //将窗口置中
        cframe.setLocation((w-cframe.WIDTH)/2,(h-cframe.HEIGHT)/2);
        //设置客户端窗口为可见
        cframe.setVisible(true);

        try{
            //连接服务器
            socket=new Socket(ip,port);
            //获取输入流
            InputStream in=socket.getInputStream();
            //获取输出流
            OutputStream out=socket.getOutputStream();

            //定义一个缓冲区 获取服务端欢迎信息
            byte[] buf=new byte[1024];
            int len=in.read(buf);

            //将欢迎信息打印在聊天消息框内
            cframe.jtaChat.append(new String(buf,0,len));
            cframe.jtaChat.append("\n");
        }catch (Exception e) {
             cframe.jtaChat.append("服务器挂了.....\n");
             e.printStackTrace();
        }
    }
}

开启服务端,再开启客户端测试一下吧!

可见已经成功连接,下面我们就来实现聊天功能

最终Client1.java的全部代码

package com.myjavachatclient.main;

import com.myjavachatclient.view.ClientFrame;

import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Date;

public class Client1 {
    //连接服务端的IP和端口
    static String ip="127.0.0.1";
    static int port=12223;
    //建立客户端Socket
    public static Socket socket=null;
    //消息接收者uid(本客户端)
    public static StringBuilder uidReceiver=null;

    public static void main(String[] args){
        //创建客户端窗口对象
        ClientFrame cframe=new ClientFrame();
        //窗口关闭键无效,必须通过退出键退出客户端以便善后
        cframe.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        //获得本机屏幕横向分辨率
        int w= Toolkit.getDefaultToolkit().getScreenSize().width;
        //获得本机屏幕纵向分辨率
        int h= Toolkit.getDefaultToolkit().getScreenSize().height;
        //将窗口置中
        cframe.setLocation((w-cframe.WIDTH)/2,(h-cframe.HEIGHT)/2);
        //设置客户端窗口为可见
        cframe.setVisible(true);

        try{
            //连接服务器
            socket=new Socket(ip,port);
            //获取输入流
            InputStream in=socket.getInputStream();
            //获取输出流
            OutputStream out=socket.getOutputStream();

            //定义一个缓冲区 获取服务端欢迎信息
            byte[] buf=new byte[1024];
            int len=in.read(buf);

            //将欢迎信息打印在聊天消息框内
            cframe.jtaChat.append(new String(buf,0,len));
            cframe.jtaChat.append("\n");

            //持续等待接收服务器信息直至退出
            while(true){
                in=socket.getInputStream();
                len=in.read(buf);
                System.out.println(len);
                //处理服务器传来的信息
                String msg=new String(buf,0,len);
                //消息类型
                String type = msg.substring(0, msg.indexOf("/"));
                //消息本体:更新后的名单或者聊天内容
                String content = msg.substring(msg.indexOf("/") + 1);
                //根据消息类型分别处理
                //更新在线名单
                if(type.equals("OnlineListUpdate")){
                    //提取在线列表的数据模型
                    DefaultTableModel tbm=(DefaultTableModel)cframe
                                          .jtbOnline.getModel();
                    //清楚在线名单列表
                    tbm.setRowCount(0);
                    //更新在线名单
                    String[] onlinelist=content.split(",");
                    //逐一添加到当前在线者
                    for(String member:onlinelist){
                        String[] tmp=new String[3];
                        //如果是自己则不在名单中显示
                        if(member.equals(socket.getInetAddress().getHostAddress()
                                +":"+socket.getLocalPort())){
                            continue;
                        }
                        tmp[0]="";
                        tmp[1]=member.substring(0,member.indexOf(":"));
                        tmp[2]=member.substring(member.indexOf(":")+1);
                        //添加当前在线者之一
                        tbm.addRow(tmp);
                    }
                    //提取在线列表的渲染模型
                    DefaultTableCellRenderer tbr = new DefaultTableCellRenderer();
                    //表格数据居中显示
                    tbr.setHorizontalAlignment(JLabel.CENTER);
                    cframe.jtbOnline.setDefaultRenderer(Object.class,tbr);
                }else if(type.equals("Chat")){
                    //得到发送者和发送内容
                    String sender=content.substring(0,content.indexOf("/"));
                    String word=content.substring(content.indexOf("/")+1);
                    //在聊天显示窗展示聊天信息
                    cframe.jtaChat.append(cframe.sdf.format(new Date())
                                           +"\n来自 "+sender+":\n"
                                           +word+"\n\n");
                    //展示最新消息
                    cframe.jtaChat.setCaretPosition(cframe.jtaChat
                                      .getDocument().getLength());
                }
            }
        }catch (Exception e) {
              cframe.jtaChat.append("服务器挂了.....\n");
              e.printStackTrace();
        }
    }
}

分析服务端代码与目前客户端代码可知,完成数据通讯最后就差一个客户端向服务端发送“Chat/……”信息的功能,这就要给我们的ClientFrame.java中的按钮注册响应事件了。

客户端完整代码

Client1.java

package com.myjavachatclient.main;

import com.myjavachatclient.view.ClientFrame;

import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Date;

public class Client1 {
    //连接服务端的IP和端口
    static String ip="127.0.0.1";
    static int port=12223;
    //建立客户端Socket
    public static Socket socket=null;
    //消息接收者uid(本客户端)
    public static StringBuilder uidReceiver=null;

    public static void main(String[] args){
        //创建客户端窗口对象
        ClientFrame cframe=new ClientFrame();
        //窗口关闭键无效,必须通过退出键退出客户端以便善后
        cframe.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        //获得本机屏幕横向分辨率
        int w= Toolkit.getDefaultToolkit().getScreenSize().width;
        //获得本机屏幕纵向分辨率
        int h= Toolkit.getDefaultToolkit().getScreenSize().height;
        //将窗口置中
        cframe.setLocation((w-cframe.WIDTH)/2,(h-cframe.HEIGHT)/2);
        //设置客户端窗口为可见
        cframe.setVisible(true);

        try{
            //连接服务器
            socket=new Socket(ip,port);
            //获取输入流
            InputStream in=socket.getInputStream();
            //获取输出流
            OutputStream out=socket.getOutputStream();

            //定义一个缓冲区 获取服务端欢迎信息
            byte[] buf=new byte[1024];
            int len=in.read(buf);

            //将欢迎信息打印在聊天消息框内
            cframe.jtaChat.append(new String(buf,0,len));
            cframe.jtaChat.append("\n");

            //持续等待接收服务器信息直至退出
            while(true){
                in=socket.getInputStream();
                len=in.read(buf);
                System.out.println(len);
                //处理服务器传来的信息
                String msg=new String(buf,0,len);
                //消息类型
                String type = msg.substring(0, msg.indexOf("/"));
                //消息本体:更新后的名单或者聊天内容
                String content = msg.substring(msg.indexOf("/") + 1);
                //根据消息类型分别处理
                //更新在线名单
                if(type.equals("OnlineListUpdate")){
                    //提取在线列表的数据模型
                    DefaultTableModel tbm=(DefaultTableModel)cframe
                                          .jtbOnline.getModel();
                    //清楚在线名单列表
                    tbm.setRowCount(0);
                    //更新在线名单
                    String[] onlinelist=content.split(",");
                    //逐一添加到当前在线者
                    for(String member:onlinelist){
                        String[] tmp=new String[3];
                        //如果是自己则不在名单中显示
                        if(member.equals(socket.getInetAddress().getHostAddress()
                                +":"+socket.getLocalPort())){
                            continue;
                        }
                        tmp[0]="";
                        tmp[1]=member.substring(0,member.indexOf(":"));
                        tmp[2]=member.substring(member.indexOf(":")+1);
                        //添加当前在线者之一
                        tbm.addRow(tmp);
                    }
                    //提取在线列表的渲染模型
                    DefaultTableCellRenderer tbr = new DefaultTableCellRenderer();
                    //表格数据居中显示
                    tbr.setHorizontalAlignment(JLabel.CENTER);
                    cframe.jtbOnline.setDefaultRenderer(Object.class,tbr);
                }else if(type.equals("Chat")){
                    //得到发送者和发送内容
                    String sender=content.substring(0,content.indexOf("/"));
                    String word=content.substring(content.indexOf("/")+1);
                    //在聊天显示窗展示聊天信息
                    cframe.jtaChat.append(cframe.sdf.format(new Date())
                                           +"\n来自 "+sender+":\n"
                                           +word+"\n\n");
                    //展示最新消息
                    cframe.jtaChat.setCaretPosition(cframe.jtaChat
                                      .getDocument().getLength());
                }
            }
        }catch (Exception e) {
            cframe.jtaChat.append("服务器挂了.....\n");
            e.printStackTrace();
        }
    }
}

ClientFrame.java

package com.myjavachatclient.view;

import com.myjavachatclient.main.Client1;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ClientFrame extends JFrame {
    //时间显示格式
    public SimpleDateFormat sdf=new SimpleDateFormat("MM-dd hh:mm:ss");

    //窗口宽度
    public final int WIDTH=700;
    //窗口高度
    public final int HEIGHT=700;

    //创建发送按钮
    JButton btnSend=new JButton("发送");
    //创建清屏按钮
    JButton btnClear=new JButton("清屏");
    //创建退出按钮
    JButton btnExit=new JButton("退出");

    //创建消息接收者标签
    JLabel lb1Receiver=new JLabel("右边选择聊天对象");

    //创建文本输入框,参数分别为行数和列数(用来输入聊天信息)
    public JTextArea jtaSay=new JTextArea();

    //创建聊天信息框(用来显示聊天记录)
    public JTextArea jtaChat=new JTextArea();

    //当前在线列表的列标题
    String[] colTitles={"网名","IP","端口"};
    //当前在线列表的数据
    String[][] rowData=null;
    //创建当前在线列表
    public JTable jtbOnline=new JTable(
              new DefaultTableModel(rowData,colTitles){
                     //表格不可编辑,只可以显示
                     //构造这个对象时重写isCellEditable方法
                     @Override
                     public boolean isCellEditable(int row, int column) {
                           return false;
                     }
            }
    );
    //创建聊天信息框的滚动窗(通过jtaChat)
    JScrollPane jspChat=new JScrollPane(jtaChat);

    //创建当前在线列表的滚动窗
    JScrollPane jspOnline=new JScrollPane(jtbOnline);

    //设置默认窗口属性,连接窗口组件
    public ClientFrame(){
        //标题
        this.setTitle("蝙蝠通讯");
        //大小
        this.setSize(WIDTH,HEIGHT);
        //不可缩放
        this.setResizable(false);
        //设置布局,不适用默认布局,完全自定义
        this.setLayout(null);

        //设置按钮大小和位置
        addButton(btnSend,20,600,100,60,
                new Font("黑体", Font.BOLD,18));
        addButton(btnClear,140,600,100,60,
                new Font("黑体", Font.BOLD,18));
        addButton(btnExit,260,600,100,60,
                new Font("黑体", Font.BOLD,18));

        //设置标签大小和位置
        lb1Receiver.setBounds(20,420,300,30);
        //添加标签
        this.add(lb1Receiver);

        //添加文本输入框
        //设置文本输入框大小和位置
        jtaSay.setBounds(20,460,360,120);
        //设置文本输入框字体
        jtaSay.setFont(new Font("楷体",Font.BOLD,16));
        //添加文本输入框
        this.add(jtaSay);

        //聊天消息框自动换行
        jtaChat.setLineWrap(true);
        //聊天消息框不可编辑,只可显示
        jtaChat.setEditable(false);
        //设置聊天消息框字体
        jtaChat.setFont(new Font("楷体",Font.BOLD,16));

        //设置聊天框滚动区(内容就是聊天消息文字域)
        addScrollPane(jspChat,20,20,360,400);

        //设置在线用户滚动区(内容就是在线用户文字域)
        addScrollPane(jspOnline,420,20,250,400);

        //添加按钮们的监听方法
        addListenerOfBtnSend();
        addListenerOfBtnClear();
        addListenerOfBtnExit();
        addListenerOfOnlineUser();
    }
    private void addButton(JButton btn,int x,int y,int width,int height,Font f){
        btn.setBounds(x,y,width,height);
        btn.setFont(f);
        //添加按钮
        this.add(btn);
    }
    private void addScrollPane(JScrollPane jsp,int x,int y,int width,int height){
        //设置滚动窗的水平滚动条属性:不可见
        jsp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        //设置滚动窗的垂直滚动条属性:需要时自动出现
        jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        //设置滚动窗的大小和位置
        jsp.setBounds(x,y,width,height);
        //添加滚动窗
        this.add(jsp);
    }
    private void addListenerOfBtnSend(){
        btnSend.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //显示最新消息
                jtaChat.setCaretPosition(jtaChat
                        .getDocument()
                        .getLength());
                try{
                    //有收信人才发送
                    if(Client1.uidReceiver.toString()
                            .equals("")==false){
                        //在聊天消息窗打印发送动作信息
                        jtaChat.append(sdf.format(new Date())
                                +"\n发往 "+Client1.uidReceiver.toString()
                                +":\n");
                        //显示要发送的信息(输入区内容复制给显示区域)
                        jtaChat.append(jtaSay.getText()+"\n\n");
                        //向服务端发送聊天信息
                        OutputStream out=Client1.socket.getOutputStream();
                        out.write(("Chat/"+Client1.uidReceiver.toString()
                                +"/"+jtaSay.getText()).getBytes());
                    }
                } catch (IOException ex) {
                    ex.printStackTrace();
                }finally{
                    //文本输入框清除
                    jtaSay.setText("");
                }
            }
        });
    }
    //注册清屏按钮的响应事件
    private void addListenerOfBtnClear(){
        btnClear.addActionListener(
                new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        //聊天框清屏
                        jtaChat.setText("");
                    }
                }
        );
    }
    //添加退出按钮的响应事件
    private void addListenerOfBtnExit(){
        btnExit.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try{
                    //向服务器发送信息
                    OutputStream out=Client1.socket.getOutputStream();
                    out.write("Exit/".getBytes());
                    //退出
                    System.exit(0);
                }catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }
    //添加在线列表的选择的响应事件(通过MouseListener接口)
    private void addListenerOfOnlineUser(){
        jtbOnline.addMouseListener(new MouseListener() {
            @Override
            public void mouseClicked(MouseEvent e) {
                //获取在线列表的数据模型
                DefaultTableModel tbm=(DefaultTableModel)jtbOnline.getModel();
                //提取鼠标选中的行作为消息目标,最少一个人,最多全体在线者接受消息
                int[] selectedIndex=jtbOnline.getSelectedRows();
                //将所有消息目标的uid拼接成一个字符串,以逗号分隔
                StringBuilder uidRec=new StringBuilder("");
                for(int i=0;i<selectedIndex.length;i++){
                    uidRec.append((String)tbm.getValueAt(
                            selectedIndex[i],1));
                    uidRec.append(":");
                    uidRec.append((String)tbm.getValueAt(
                            selectedIndex[i],2));
                    if(i!=selectedIndex.length-1){
                        uidRec.append(",");
                    }
                }
                //将uidRec赋值给静态的uidRecever
                Client1.uidReceiver=uidRec;
                //修改标签
                lb1Receiver.setText("发送给:"
                        +Client1.uidReceiver.toString());
            }
            @Override
            public void mousePressed(MouseEvent e){}
            @Override
            public void mouseReleased(MouseEvent e) {}
            @Override
            public void mouseEntered(MouseEvent e) {}
            @Override
            public void mouseExited(MouseEvent e) {}
        });
    }
}

 

最后效果:


基础功能就实现了,如果有我还想法的话,会在升级一下这个项目,感谢阅读。

 

 

 

商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

 

发表评论