Java实现本地Socket通信(三)

今天任务较少也较为简单

本节任务

  • 账号登录(半成品)
  • 可视化界面

一、账号登陆

我们知道,账号输入操作是需要在客户端进行的,所以这里我们需要在客户端封装登陆方法login,在这里读取输入并传递给服务端账号信息

java 复制代码
public void login(OutputStream os){
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入账号:");
        String account=sc.nextLine();
        byte[] ac=account.getBytes();
        try {
            os.write(ac.length);//不可以发送字符串的长度,必须发送字节的长度
            os.write(ac);
            os.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

接着我们需要在服务端读取传递过来的信息,用封装函数proLogein实现,并返回读取到的用户名

java 复制代码
public String proLogein(Socket socket){
        try {
            InputStream is=socket.getInputStream();
            int len=is.read();
            byte[] msgs=new byte[len];
            is.read(msgs);
            String userName=new String(msgs);
            System.out.println("用户"+userName+"登录了");
            return userName;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

tips.这里proLogein方法应该在消息处理方法proClintMsg处创建线程时调用(如下)

java 复制代码
 public void proClintMsg(Socket socket){

        //注意:循环速度太快,导致cpu被占满,io流根本没机会运行,所以这里加一个println,让cpu空闲下来
            new Thread(()->{
                //登录
                //读取用户名
                String UserName=proLogein(socket);
                ClientUser cu=new ClientUser(UserName,socket);
                //保存ClientUser对象
                SocketList.add(cu);
                while(true) {
                    try {
                        InputStream is=cu.socket.getInputStream();
                        System.out.println("监听"+cu.socket.getPort()+"消息中:");//顺序不对?
                        String msg=receive(is);
                        replyMsg(cu,msg);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }).start();
    }

用户信息类Client内部定义了账号account(用户名)和套接字socket(这里就不展示代码了)

至此,用户登录的功能雏形便完成了

二、实现界面可视化

这里我们需要创建一个UI类:ClientUI

首先,先创建窗口对象,并调用内部函数设置相关参数(尺寸、关闭方法、显示格式、布局等)

java 复制代码
JFrame jf=new JFrame("聊天室");
        jf.setSize(500,600);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭
        jf.setLocationRelativeTo(null);//居中显示
        //默认边框布局

随后添加相关组件(最后记得设置可见)

  • 文本显示JTextArea
java 复制代码
JTextArea msgArea=new JTextArea();//支持多行显示所以不用jtext(显示文字)
JScrollPane scrollPane=new JScrollPane(msgArea);//滚动显示面板
jf.add(scrollPane, BorderLayout.NORTH);
  • 输入框JTestField
java 复制代码
JTextField msgField=new JTextField();//输入框
        msgField.setPreferredSize(new Dimension(400,0));
        msgField.setBackground(Color.LIGHT_GRAY);
jf.add(msgField, BorderLayout.WEST);
  • 发送键JButton
java 复制代码
JButton sendButton=new JButton("发送");
        sendButton.setPreferredSize(new Dimension(100,0));
jf.add(sendButton, BorderLayout.EAST);

tips.这里不要忘记为按钮添加监听方法(下面是新版本的简便写法)

java 复制代码
sendButton.addActionListener(e -> sendMsg(msgArea,msgField));
//e相当于是内部类,,创建一个临时内部类可以直接调用本类中的函数,不需要单独写一个监听类

到这里界面基本就创建完成了,接下来需要做的便是功能迁移

三、功能迁移

我们的代码大体上是不需要做什么改动的,现在我们需要做的是读取可视化界面内输入框里的内容,并让其显示在文本框上

消息发送方法如下

java 复制代码
public void sendMsg(JTextArea msgArea,JTextField msgField){
        String msg=msgField.getText();
        //send(msg);
        System.out.println("发送中...");
        //设置到自己的界面上
        msgArea.append("我:"+msg+"\n");
        //发送消息
        client.send(msg);
    }

显示如下(在showui中添加新线程)

java 复制代码
new Thread(()->{
            while(true) {
                String msg=client.readMsg();
//                System.out.print("接收消息:");
//                System.out.println(msg);
//                System.out.println("请输入:");
                msgArea.append(msg+"\n");
            }
        }).start();

四、完整代码

·Client

java 复制代码
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    //由于外部需要访问socket,所以定义为全局变量
    Socket socket=null;
    public void init(){
        try {
            socket=new Socket("127.0.0.1",54321);
            login(socket.getOutputStream());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void send(String  message){
        try {
            OutputStream os=socket.getOutputStream();

            byte[] msgs=message.getBytes();
            int len=msgs.length;
            os.write(len);
            os.write(msgs);
            os.flush();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    public String readMsg(){
        try {
            InputStream is=socket.getInputStream();
            int len=is.read();
            byte[] msgs=new byte[len];
            is.read(msgs);
            return new String(msgs);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void login(OutputStream os){
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入账号:");
        String account=sc.nextLine();
        byte[] ac=account.getBytes();
        try {
            os.write(ac.length);//不可以发送字符串的长度,必须发送字节的长度
            os.write(ac);
            os.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

·ClientUI

java 复制代码
import javax.swing.*;
import java.awt.*;
import java.util.Scanner;

public class ClientUI extends JFrame {
    Client client=new Client();
    public void showUI(){
        //先加载客户端 并进行登录
        client.init();

        JFrame jf=new JFrame("聊天室");
        jf.setSize(500,600);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭
        jf.setLocationRelativeTo(null);//居中显示
        //默认边框布局

        JTextArea msgArea=new JTextArea();//支持多行显示所以不用jtext(显示文字)
//        JTextPane msgPane=new JTextPane(msgArea);
        JScrollPane scrollPane=new JScrollPane(msgArea);//滚动显示

        scrollPane.setPreferredSize(new Dimension(0,500));

        JTextField msgField=new JTextField();//输入框
        msgField.setPreferredSize(new Dimension(400,0));
        msgField.setBackground(Color.LIGHT_GRAY);

        JButton sendButton=new JButton("发送");
        sendButton.setPreferredSize(new Dimension(100,0));

        //JLabel label=new JLabel("当前状态");

        jf.add(scrollPane, BorderLayout.NORTH);
        jf.add(msgField, BorderLayout.WEST);
        jf.add(sendButton, BorderLayout.EAST);

        jf.setVisible(true);

        sendButton.addActionListener(e -> sendMsg(msgArea,msgField));//e相当于是内部类,,创建一个临时内部类可以直接调用本类中的函数,不需要单独写一个监听类

        new Thread(()->{
            while(true) {
                String msg=client.readMsg();
//                System.out.print("接收消息:");
//                System.out.println(msg);
//                System.out.println("请输入:");
                msgArea.append(msg+"\n");
            }
        }).start();
    }

    public void sendMsg(JTextArea msgArea,JTextField msgField){
        String msg=msgField.getText();
        //send(msg);
        System.out.println("发送中...");
        //设置到自己的界面上
        msgArea.append("我:"+msg+"\n");
        //发送消息
        client.send(msg);
    }

    public static void main(String[] args) {
        ClientUI clientUI=new ClientUI();
        clientUI.showUI();
    }

}

·ClientUser

java 复制代码
import java.net.Socket;

public class ClientUser {
    String account;
    Socket socket;
    public ClientUser(String account,Socket socket){
        this.account=account;
        this.socket=socket;
    }
    public ClientUser(){
    }
}

·Server

java 复制代码
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class Server {
    ServerSocket ss=null;
    ArrayList<ClientUser> SocketList=new ArrayList<>();
    public void init(){
        try {
            ss=new ServerSocket(54321);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
    public Socket listen(){
        try {
            return ss.accept();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void send(String message, OutputStream  os){
        try {
            //mission:通过os把这个消息发送出去
            byte[] msgs=message.getBytes();
            int len=msgs.length;
            os.write(len);//发送长度
            os.write(msgs);
            os.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    public String receive(InputStream is){
        try {
            int len=is.read();
            byte[] msgs=new byte[len];
            is.read(msgs);
//            for(int i=0;i<len;i++){
//                msgs[i]= (byte) is.read();
//            }
            String msg=new String(msgs);
            System.out.println("收到:"+msg);
            return msg;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void replyMsg(ClientUser  client,String msg){
        //转发消息
        for(int i=0;i<SocketList.size();i++){
            ClientUser cu=SocketList.get(i);
            Socket s=cu.socket;
            if (s!=client.socket){
                try {
                    String m= client.account+"说:"+msg;//注意拼接逻辑
                    send(m,s.getOutputStream());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public void proClintMsg(Socket socket){

        //注意:循环速度太快,导致cpu被占满,io流根本没机会运行,所以这里加一个println,让cpu空闲下来
            new Thread(()->{
                //登录
                //读取用户名
                String UserName=proLogein(socket);
                ClientUser cu=new ClientUser(UserName,socket);
                //保存ClientUser对象
                SocketList.add(cu);
                while(true) {
                    try {
                        InputStream is=cu.socket.getInputStream();
                        System.out.println("监听"+cu.socket.getPort()+"消息中:");//顺序不对?
                        String msg=receive(is);
                        replyMsg(cu,msg);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }).start();


    }

    public String proLogein(Socket socket){
        try {
            InputStream is=socket.getInputStream();
            int len=is.read();
            byte[] msgs=new byte[len];
            is.read(msgs);
            String userName=new String(msgs);
            System.out.println("用户"+userName+"登录了");
            return userName;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Server server=new Server();

        //初始化
        server.init();
        //匿名内部类放到线程中(监听连接部分)
        new Thread(()->{
            while(true) {
                System.out.println("等待连接...");
                Socket s = server.listen();
                System.out.println("有新的连接:"+s.getInetAddress()+"("+s.getPort()+")");

                server.proClintMsg(s);//处理客户端消息 ,每个客户端一个线程
            }
        }).start();


        }

    }
相关推荐
一水1 小时前
java运行排错,新码旧jar
java·开发语言·jar
wuqingshun3141592 小时前
JAVA中的注解原理是什么?
java
茯苓gao2 小时前
嵌入式开发笔记:电机参数辨识与自学习完全指南——从电气参数到机械特性的深度解析
笔记·嵌入式硬件·学习
笨鸟先飞的橘猫2 小时前
游戏后端分布式学习——消息队列在游戏的用法
分布式·学习·游戏
Python+992 小时前
Java 编程语言入门指南
java·开发语言
优化Henry2 小时前
学习笔记之VoNR语音业务注册流程
网络·笔记·学习·5g·信息与通信
程序喵大人2 小时前
【C++进阶】STL容器与迭代器 - 05 map 和 set 为什么按键保持有序
开发语言·c++·容器·迭代器·stl
完美火龙篇 四月的友2 小时前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
开发语言·php
冻柠檬飞冰走茶3 小时前
PTA基础编程题目集 7-15 计算圆周率(C语言实现)
c语言·开发语言·数据结构·算法