栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

创建专属聊天室练习(客户端与服务端通信|客户端界面交互)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

创建专属聊天室练习(客户端与服务端通信|客户端界面交互)

一、知识回顾与补充
package frame;

import javax.swing.*;

//只是发现以后所有的框都是按照刚才那套流程画出来的
//只不过具体的组件,位置,布局都不一样,但是需要做的事情抽象来看都是一样的
//设计一个抽象的规则

//模板方法模式  23个设计模式当中的一个

//还有一种是:缺省适配器模式 Myframe(接口 10个) baseframe(挑一些不重要的实现掉{} 子类去实现一些必要的方法)
public abstract class baseframe extends Jframe {
    public baseframe(String title){
        super(title);
    }

    protected abstract void setOther();
    protected abstract void addElements();
    protected abstract void setSelf();
}

package frame;

import javax.swing.*;
import java.awt.*;

//简单梳理一下关于Swing的技术
//GUI 图形用户接口(规则)  Client/Server
//GUI下面有两个接口:AWT Swing。  可以做但很少用来基于Browser/Server  HTML  
//想要利用Swing技术来实现一个窗口的绘制
//里面有Jframe窗体   可看成类似于
//还有JPanel面板    可看成类似于 无色透明的容器  做布局管理
//各种组件  按钮  文本框  文本域  理解为HTMl中那些
//JButton按钮  JTextField
//最终肯定是需要有功能  事件-方法  HTML(布局)+CSS(样式)+JS(事件)  Tomcat
//事件背后才是我们真正需要实现的强大逻辑/功能

//============================================================
//需要自己描述一个类
//可能需要描述属性,方法
//类与类的关系
//继承  泛化(实现) A is a B
// 聚合  组合   A has-a  B
// 包含  依赖  A use-a  B
public class QQframe extends baseframe{//是一个窗口  类和类之间的关系  A  is-a  B  继承
    //1.需要一个窗体/窗口
    private Jframe frame=new Jframe("聊天框");//GC  边界管理

    //2.需要一个无色透明的容器--面板
    private JPanel panel=new JPanel();//    流式管理    可以有多个

    //3.需要一些聊天窗口的组件
    //这是两个带滚动条的文本域
    private Jtextarea messArea=new Jtextarea();
    private JScrollPane messPane=new JScrollPane(messArea);//滚动条范围大,里面是包着文本域的
    private Jtextarea sendArea=new Jtextarea();
    private  JScrollPane sendPand=new JScrollPane(sendArea);
    //两个按钮
    private JButton sendButton=new JButton("发送");
    private JButton cancelButton=new JButton("取消");

    //构造方法  里面可以放这三个方法,或者这三个方法可以放在代码块,让它在构造方法之前加载
    public QQframe(String title){
        super(title);
        //2.调用里面的方法
        this.setOther();
        this.addElements();
        this.setSelf();
    }

    //设计一个方法 做一些设置 布局 字体。。
    @Override
    protected void setOther(){
        //4.为了让布局更好的好看一点,我们采用一个自动布局的方式
        //  自定义布局需要将原有的布局清空
        panel.setLayout(null);
        //所有的组件按照自己的设计位置放置
        messPane.setBounds(10, 10, 320, 220);
        sendPand.setBounds(10, 240, 320, 140);
        sendButton.setBounds(180, 390, 60, 30);
        cancelButton.setBounds(260, 390, 60, 30);
        //设置上面接收框的信息不能更改
        messArea.setEnabled(false);
        //设置文本域中字体的效果
        messArea.setFont(new Font("宋体", Font.BOLD,18));
        sendArea.setFont(new Font("宋体", Font.BOLD,18));
    }

    //设计一个方法 做一些组件之间的相互添加
    @Override
    protected void addElements(){
        //5.将这些组件放在panel里,panel放在frame里
        panel.add(messPane);
        panel.add(sendPand);
        panel.add(sendButton);
        panel.add(cancelButton);
        this.add(panel);
    }

    //设计一个方法  用来做窗体自己的一些设置  初始位置  不可拖拽
    @Override
    protected void setSelf(){
        //需要在创建出来的同时,设置一下窗口的展示
        this.setBounds(500, 200, 350, 480);

        //可以设置窗口不可以拖拽改变大小
        this.setResizable(false);

        //设置点击右上角关闭按钮的同时,让程序结束
        this.setDefaultCloseOperation(Jframe.EXIT_ON_CLOSE);//  private static final int xxx=3;

        //其实窗口已经创建出来了,默认效果是隐藏的
        //需要设置那个窗口的状态  隐藏--->显示
        //面向对象  窗口的状态-----对象的属性
        //          设置状态----事情----方法   setXXXX
        this.setVisible(true);
    }
}

package frame;

public class Test {
    public static void main(String[] args) {
        //1.创建一个QQframe窗口对象
        new QQframe("聊天框");//无参数构造方法
    }
}


二、客户端与服务器通信
package client;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class TestClient {
    public static void main(String[] args) {
        try {
            System.out.println("我是客户端");
            //1.主动访问服务端,访问成功后,即连接成功,获取一个socket对象
            Socket socket=new Socket("127.0.0.1",9999);//服务器地址(IP) 服务器开放的端口号(port)
            System.out.println("我成功连接到服务器啦?");
            //======================================
            Scanner input=new Scanner(System.in);

            //2.读取服务器发送过来的消息  读消息/输入流对象
            //不能自己创建,需要通过socket对象来回去
            InputStream is = socket.getInputStream();
            //is其实可以拿去读取消息,is是一个字节型输入流
            //为了读取字符更加方便,做一个包装
            InputStreamReader isr=new InputStreamReader(is);//字节--->字符转换流--->最终是个字符
            //isr其实可以读取中文啦,但是由于对面发送过来的是一行为单位的,isr无法读取一行数据
            //为了读取一行比较方便,再做一个包装
            BufferedReader reader = new BufferedReader(isr);//包装本身体现出来一种设计模式——————装饰者模式  23
            //3.客户端回写点什么    写消息/输出流对象
            OutputStream os = socket.getOutputStream();
            PrintWriter writer=new PrintWriter(os);

            while (true){
                String value = reader.readLine();
                System.out.println("消息"+value);

                System.out.println("回写点什么吧:");
                String mess=input.nextLine();
                writer.println(mess);
                writer.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

package server;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TestServer {
    public static void main(String[] args) {
        try {
            System.out.println("===Server start===");
            //1.开启一个服务
            //调用构造方法,缺少参数
            //没法构造对象,接口/抽象类
            //类中构造方法虽然有,但是都是private(私有化),提供的都是类似工厂一样的方法
            //构建对象的时候,出现了编译时异常
            //  ArrayIndexOutofBoundsException
            //  NullPointerException
            //  InputMisMatchException
            //  NumberFormatException
            //  ArithmeticException
            //  ClassCastException
            //  StringIndexOutOfBoundsException
            ServerSocket server=new ServerSocket(9999);//0——65535//服务器开发的端口号,为了让客户的数据从这个端口进入到服务器来的
            //2.开启服务之后,等待某一个客户端过来访问我,需要服务器统一/接收
            Socket socket = server.accept();
            System.out.println("有一个客户端跟我连接成功啦");
            //================================================================

            Scanner input=new Scanner(System.in);

            //3.先让服务器跟客户端说一句话————写消息/输出流
            //不是自己随意创建的输出流,得通过socket来获取
            OutputStream os = socket.getOutputStream(); //流(读 I/写O) 字节/字符(Reader、Writer)
            //上述os对象可以写消息了,但是os是一个字节流,发送中文的时候不方便
            //字节流进行一个包装---->字符流
            PrintWriter writer=new PrintWriter(os);


            //4.服务器读取回写的消息  读信息/输入流对象
            InputStream is = socket.getInputStream();
            InputStreamReader isr=new InputStreamReader(is);
            BufferedReader reader=new BufferedReader(isr);

            while(true){
                //4.可以发送数据啦
                System.out.println("说点什么吧:");
                String mess = input.nextLine();
                writer.println(mess);
                writer.flush();//刷新  清空流管道

                String value = reader.readLine();
                System.out.println("消息:"+value);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

三、客户端发起聊天

四、完成客户端界面交互

优化代码

package frame;

import javax.swing.*;

//只是发现以后所有的框都是按照刚才那套流程画出来的
//只不过具体的组件,位置,布局都不一样,但是需要做的事情抽象来看都是一样的
//设计一个抽象的规则

//模板方法模式  23个设计模式当中的一个

//还有一种是:缺省适配器模式 Myframe(接口 10个) baseframe(挑一些不重要的实现掉{} 子类去实现一些必要的方法)
public abstract class baseframe extends Jframe {
    public baseframe(String title){
        super(title);
    }

    protected abstract void setOther();
    protected abstract void addElements();
    protected abstract void addListener();
    protected abstract void setSelf();
}

package frame;

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.Socket;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;

//简单梳理一下关于Swing的技术
//GUI 图形用户接口(规则)  Client/Server
//GUI下面有两个接口:AWT Swing。  可以做但很少用来基于Browser/Server  HTML  
//想要利用Swing技术来实现一个窗口的绘制
//里面有Jframe窗体   可看成类似于
//还有JPanel面板    可看成类似于 无色透明的容器  做布局管理
//各种组件  按钮  文本框  文本域  理解为HTMl中那些
//JButton按钮  JTextField
//最终肯定是需要有功能  事件-方法  HTML(布局)+CSS(样式)+JS(事件)  Tomcat
//事件背后才是我们真正需要实现的强大逻辑/功能

//============================================================
//需要自己描述一个类
//可能需要描述属性,方法
//类与类的关系
//继承  泛化(实现) A is a B
// 聚合  组合   A has-a  B
// 包含  依赖  A use-a  B
public class QQframe extends baseframe{//是一个窗口  类和类之间的关系  A  is-a  B  继承

    //构造方法  里面可以放这三个方法,或者这三个方法可以放在代码块,让它在构造方法之前加载
    public QQframe(String uid){
        super(uid);
        this.uid=uid;
        //1.真的跟服务器产生一个连接
        this.start();
        //2.调用里面的方法
        this.setOther();
        this.addElements();
        this.addListener();
        this.setSelf();
    }


    //1.需要一个窗体/窗口
    private Jframe frame=new Jframe("聊天框");//GC  边界管理

    //2.需要一个无色透明的容器--面板
    private JPanel panel=new JPanel();//    流式管理    可以有多个

    //3.需要一些聊天窗口的组件
    //这是两个带滚动条的文本域
    private Jtextarea messArea=new Jtextarea();
    private JScrollPane messPane=new JScrollPane(messArea);//滚动条范围大,里面是包着文本域的
    private Jtextarea sendArea=new Jtextarea();
    private  JScrollPane sendPand=new JScrollPane(sendArea);
    //两个按钮
    private JButton sendButton=new JButton("发送");
    private JButton cancelButton=new JButton("取消");

    //设计一个方法 做一些设置 布局 字体。。
    @Override
    protected void setOther(){
        //4.为了让布局更好的好看一点,我们采用一个自动布局的方式
        //  自定义布局需要将原有的布局清空
        panel.setLayout(null);
        //所有的组件按照自己的设计位置放置
        messPane.setBounds(10, 10, 320, 220);
        sendPand.setBounds(10, 240, 320, 140);
        sendButton.setBounds(180, 390, 60, 30);
        cancelButton.setBounds(260, 390, 60, 30);
        //设置上面接收框的信息不能更改
        messArea.setEnabled(false);
        //设置文本域中字体的效果
        messArea.setFont(new Font("黑体", Font.BOLD,18));
        sendArea.setFont(new Font("黑体", Font.BOLD,18));
    }

    //设计一个方法 做一些组件之间的相互添加
    @Override
    protected void addElements(){
        //5.将这些组件放在panel里,panel放在frame里
        panel.add(messPane);
        panel.add(sendPand);
        panel.add(sendButton);
        panel.add(cancelButton);
        this.add(panel);
    }

    //设计一个方法 给窗口中的某些组件添加事件
    @Override
    protected void addListener(){
        //给取消按钮绑定一个事件---对象(帮你去做)    观察者模式Observer   按钮-目标  做事-观察者对象
        cancelButton.addActionListener(e->{
            sendArea.setText("");
        });
        //给发送按钮绑定一个事件---
        sendButton.addActionListener(e -> {
            try {
                PrintWriter writer=new PrintWriter(socket.getOutputStream());
                String message = sendArea.getText();
                //获取一个系统当前时间
                Date date=new Date();
                DateFormat df=new SimpleDateFormat("yyyy-MM-dd kk:mm:ss");
                String time = df.format(date);

                writer.println(time+"##@##"+uid+":"+message);//客户端真正发送的消息
                writer.flush();
                //发送框文字清空
                sendArea.setText("");
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        });
    }
    //设计一个方法  用来做窗体自己的一些设置  初始位置  不可拖拽
    @Override
    protected void setSelf(){
        //需要在创建出来的同时,设置一下窗口的展示
        this.setBounds(500, 200, 350, 480);

        //可以设置窗口不可以拖拽改变大小
        this.setResizable(false);

        //设置点击右上角关闭按钮的同时,让程序结束
        this.setDefaultCloseOperation(Jframe.EXIT_ON_CLOSE);//  private static final int xxx=3;

        //其实窗口已经创建出来了,默认效果是隐藏的
        //需要设置那个窗口的状态  隐藏--->显示
        //面向对象  窗口的状态-----对象的属性
        //          设置状态----事情----方法   setXXXX
        this.setVisible(true);
    }

    //=============================================================================
    //因为当前的QQframe就是那个客户端,
    // 所以客户端需要有一个uid
    private String uid;
    //所以客户端需要一个socket
    private Socket socket;

    //====================================================
    public void start(){
        try {
            //1.主动访问服务端
            socket=new Socket("127.0.0.1",9999);
            //2.客户端将自己的uid写给服务器
            PrintWriter writer=new PrintWriter(socket.getOutputStream());
            writer.println(uid);
            writer.flush();
            //3.将socket分别交给两个小弟,读/写
            ClientReader cr=new ClientReader();
            cr.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //====================================================

    //客户端读线程(剩下的那个小弟)---只管客户端自己用
    //1.不想让别人看见我客户端自己不干活,还有一个小弟帮我
    //2.节省一个类文件
    //3.客户端读线程,读取的数据不是在控制台展示的,是需要展示在客户端的文本域中
    //  最主要才是这个问题,内部类中直接使用外部类成员
    private class ClientReader extends Thread{
        //频繁的追加新的字符串,不能让聊天框中的文字清空
        StringBuilder result=new StringBuilder();

        @Override
        public void run(){
            //读取
            try {
                InputStream is=socket.getInputStream();
                InputStreamReader isr=new InputStreamReader(is);
                BufferedReader reader=new BufferedReader(isr);//可以读取一行
                while (true){
                    String value=reader.readLine();
                    //time##@##uid:message
                    value.replace("##@##", "rn");
                    result.append(value);
                    result.append("n");
                    //找到那个文本域,value放里即可
                    messArea.setText(result.toString());
                }
            } catch (IOException e) {
                //e.printStackTrace();
                System.out.println("服务器宕机啦");
            }
        }
    }


}

package frame;

import frame.QQframe;


public class TestClient {
    public static void main(String[] args) {
        //1.创建客户端
        new QQframe("zzt");
        new QQframe("dmc");
        new QQframe("Ella");
    }
//    public static void main(String[] args) {
//        try {
//            System.out.println("我是客户端");
//            //1.主动访问服务端,访问成功后,即连接成功,获取一个socket对象
//            Socket socket=new Socket("127.0.0.1",9999);//服务器地址(IP) 服务器开放的端口号(port)
//            System.out.println("我成功连接到服务器啦?");
//            //======================================
//            Scanner input=new Scanner(System.in);
//
//            //2.读取服务器发送过来的消息  读消息/输入流对象
//            //不能自己创建,需要通过socket对象来回去
//            InputStream is = socket.getInputStream();
//            //is其实可以拿去读取消息,is是一个字节型输入流
//            //为了读取字符更加方便,做一个包装
//            InputStreamReader isr=new InputStreamReader(is);//字节--->字符转换流--->最终是个字符
//            //isr其实可以读取中文啦,但是由于对面发送过来的是一行为单位的,isr无法读取一行数据
//            //为了读取一行比较方便,再做一个包装
//            BufferedReader reader = new BufferedReader(isr);//包装本身体现出来一种设计模式——————装饰者模式  23
//            //3.客户端回写点什么    写消息/输出流对象
//            OutputStream os = socket.getOutputStream();
//            PrintWriter writer=new PrintWriter(os);
//
//            while (true){
//                String value = reader.readLine();
//                System.out.println("消息"+value);
//
//                System.out.println("回写点什么吧:");
//                String mess=input.nextLine();
//                writer.println(mess);
//                writer.flush();
//            }
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//
//    }
}

package server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

//服务器类
public class Server {

    //属性集合----Map
    static HashMap userBox=new HashMap();
    public void start(int port){
        try {
            System.out.println("====server start===");
            ServerSocket server=new ServerSocket(port);
            while (true){//服务器开启,一直等待客户端
                Socket socket = server.accept();
                //将socket对象存入map集合
                //读取客户端发来的Uid       服务端可以获取socket,但是没有Uid(客户端发送过来)
                //
                InputStream is = socket.getInputStream();
                InputStreamReader isr=new InputStreamReader(is);
                BufferedReader reader=new BufferedReader(isr);
                String uid = reader.readLine();
                //构建一个User对象
                User user=new User(uid, socket);
                userBox.put(uid, user);
                System.out.println(uid+"成功连接到服务器啦");
                //将这个socker交给一个小弟来负责处理
                ServerThread st=new ServerThread(user);
                st.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

package server;

import java.io.*;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

//服务端线程
public class ServerThread extends Thread{

    private User user;
    public ServerThread(User user){
        this.user=user;
    }

    @Override
    public void run(){

        try {
            //1.读取刚才那个socket客户端发送过来的数据
            InputStream is = user.getSocket().getInputStream();
            InputStreamReader isr=new InputStreamReader(is);
            BufferedReader reader=new BufferedReader(isr);
            while (true){
                //服务器从客户端那里读取过来的真实数据/消息
                String message =reader.readLine();
                System.out.println("接收到"+user.getUid()+"xxx发送过来的"+message);
                //2.将数据转发给其他socket对应的客户端------群聊  map遍历 User(socket)
                //想要转发出去,需要找到那个map
                Map map=Server.userBox;//
                //将map集合遍历,获取里面全部的user,拿到每一个user里面的socket--->
                Set keySet = map.keySet();
                Iterator it = keySet.iterator();
                while (it.hasNext()){
                    String uid= (String)it.next();
                    User user=map.get(uid);
                    PrintWriter pw=new PrintWriter(user.getSocket().getOutputStream());
                    pw.println(message);
                    pw.flush();
                }
            }


        } catch (IOException e) {
            //e.printStackTrace();
            System.out.println(user.getUid()+"下线啦");
        }
    }
}

package server;

public class TestServer {

    public static void main(String[] args) {
        //1.启动一个服务
        Server server=new Server();
        //2.启动即可
        server.start(9999);
    }
//    public static void main(String[] args) {
//        try {
//            System.out.println("===Server start===");
//            //1.开启一个服务
//            //调用构造方法,缺少参数
//            //没法构造对象,接口/抽象类
//            //类中构造方法虽然有,但是都是private(私有化),提供的都是类似工厂一样的方法
//            //构建对象的时候,出现了编译时异常
//            //  ArrayIndexOutofBoundsException
//            //  NullPointerException
//            //  InputMisMatchException
//            //  NumberFormatException
//            //  ArithmeticException
//            //  ClassCastException
//            //  StringIndexOutOfBoundsException
//            ServerSocket server=new ServerSocket(9999);//0——65535//服务器开发的端口号,为了让客户的数据从这个端口进入到服务器来的
//            //2.开启服务之后,等待某一个客户端过来访问我,需要服务器统一/接收
//            Socket socket = server.accept();
//            System.out.println("有一个客户端跟我连接成功啦");
//            //================================================================
//
//            Scanner input=new Scanner(System.in);
//
//            //3.先让服务器跟客户端说一句话————写消息/输出流
//            //不是自己随意创建的输出流,得通过socket来获取
//            OutputStream os = socket.getOutputStream(); //流(读 I/写O) 字节/字符(Reader、Writer)
//            //上述os对象可以写消息了,但是os是一个字节流,发送中文的时候不方便
//            //字节流进行一个包装---->字符流
//            PrintWriter writer=new PrintWriter(os);
//
//
//            //4.服务器读取回写的消息  读信息/输入流对象
//            InputStream is = socket.getInputStream();
//            InputStreamReader isr=new InputStreamReader(is);
//            BufferedReader reader=new BufferedReader(isr);
//
//            while(true){
//                //4.可以发送数据啦
//                System.out.println("说点什么吧:");
//                String mess = input.nextLine();
//                writer.println(mess);
//                writer.flush();//刷新  清空流管道
//
//                String value = reader.readLine();
//                System.out.println("消息:"+value);
//            }
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//
//
//
//    }
}

package server;

import java.net.Socket;

//服务器端自己创建的一个类型
//目的:为了将一个用户名和这个用户对应的socket对象包括包装在一起
//每一个User对象,其实就是一个存储数据的容器
public class User {
    private String uid;
    private Socket socket;

    public User(String uid, Socket socket) {
        this.uid = uid;
        this.socket = socket;
    }

    public String getUid() {
        return uid;
    }

    public Socket getSocket() {
        return socket;
    }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/667051.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号