Java实现本地Socket通信(二)

引言

上一章我们学习了如何连接两个对象并相互传递消息,今天我们来继续拓展这个项目

本节我们有以下目标

  • 实现多人交互
  • 收发信息互不影响
  • 优化信息传递方式

一、实现多人连接

在上一篇笔记中,我们使用了A、B两个类,这种一个类代表一个对象的方式虽然简单,但是当对象数量偏多时编写起来就会很麻烦,每两个类之间都需要连接一次,这就会创建很多个socket

那么如何更简洁地实现它呢

其实很简单

这里我们只需要使用两个类:服务端Server (中转站)和客户端Client

1.服务端Server

首先,我们需要先创建serversocket对象并初始化,便于后面客户端识别身份并连接

java 复制代码
public class Server {
    ServerSocket ss=null;
    public void init(){
        try {
            ss=new ServerSocket(54321);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

其次,还需要创建listen监听函数,用于监测是否有新的客户端连接

java 复制代码
public Socket listen(){
        try {
            return ss.accept();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

接着,写主函数并创建线程,用死循环持续监听,每有一个新客户连接,就存到动态数组当中

这里两个get方法分别是ip和端口号

java 复制代码
ArrayList<Socket> SocketList=new ArrayList<>();
public static void main(String[] args) {
        Server server=new Server();
        //初始化
        server.init();
        //匿名内部类放到线程中(监听连接部分)
        new Thread(()->{
            while(true) {
                System.out.println("等待连接...");
                Socket s = server.listen();
                server.SocketList.add(s);
                System.out.println("有新的连接:"+s.getInetAddress()+"("+s.getPort()+")");
            }
        }).start();
        }

tips.这里选择了用匿名内部类的方式创建线程,更方便(如下)

java 复制代码
new Thread(()->{ 
               //代码写这里 
               }).start();

2.客户端Client

跟服务端一样,Client也需要先创建对象并初始化

不过client里创建的是socket对象,且代表的是客户端,server中初始化是初始化成自己,而这里则是初始化成server对象

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

接下来我们写主函数,尝试连接

java 复制代码
public static void main(String[] args) {
        Client client=new Client();
        client.init();
}

tips.Client类记得更改配置,使其能够允许多个实例同时运行(如下图,idea为例),否则可能需要多个client类

二、信息传递优化

这里为了方便使用我将各个功能进行了封装

1.服务端Server

·发送消息send

由于数字不方便存储,字符又无法直接通过write传递,所以这里使用byte数组

java 复制代码
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);
        }

    }

·接收消息receive

java 复制代码
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);
        }
    }

·消息中转replyMsg

java 复制代码
public void replyMsg(Socket socket,String msg){
        //转发消息
        for(int i=0;i<SocketList.size();i++){
            Socket s=SocketList.get(i);
            if (s!=socket){
                try {
                    send(msg,s.getOutputStream());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

·客户端消息处理proClintMsg(接收和转发)

java 复制代码
public void proClintMsg(Socket socket){
        //注意:循环速度太快,导致cpu被占满,io流根本没机会运行,所以这里加一个println,让cpu空闲下来
            new Thread(()->{
                while(true) {
                    try {
                        InputStream is=socket.getInputStream();
                        System.out.println("监听"+socket.getPort()+"消息中:");//顺序不对?
                        String msg=receive(is);
                        replyMsg(socket,msg);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }).start();


    }

2.客户端Client

·发送消息send

这里和服务端的区别便是参数的不同,客户端socket固定为服务端,而服务端需要对接多个客户端,所以参数多一个socket

java 复制代码
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);
        }
    }

·接收消息readMsg

java 复制代码
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);
        }
    }

三、收发信息互不影响

在上一篇笔记中,仔细观察我们可以发现,发消息是有顺序的,a发完b才能够发,那么如何实现收发自由呢?

其实很简单,需要用到线程

将收发消息的操作分别放到两个线程中即可

如下(client-主函数)

java 复制代码
new Thread(()->{
            while(true) {
                String msg=client.readMsg();
                System.out.print("接收消息:");
                System.out.println(msg);
                System.out.println("请输入:");
            }
        }).start();
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入账号:");
        while (true){
            System.out.println("请输入:");
            String message=sc.nextLine();
            client.send(message);
        }
    }

四、完整代码

·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<Socket> 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(Socket socket,String msg){
        //转发消息
        for(int i=0;i<SocketList.size();i++){
            Socket s=SocketList.get(i);
            if (s!=socket){
                try {
                    send(msg,s.getOutputStream());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

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


    }

    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.SocketList.add(s);
                server.proClintMsg(s);//处理客户端消息 ,每个客户端一个线程
            }
        }).start();


        }

    }

·Client

java 复制代码
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);
        } 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 static void main(String[] args) {
        Client client=new Client();
        client.init();

        new Thread(()->{
            while(true) {
                String msg=client.readMsg();
                System.out.print("接收消息:");
                System.out.println(msg);
                System.out.println("请输入:");
            }
        }).start();
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入账号:");
        while (true){
            System.out.println("请输入:");
            String message=sc.nextLine();
            client.send(message);
        }
    }

}
相关推荐
dadaobusi2 小时前
学习:RV lkvm SBI
java·学习·spring
djarmy3 小时前
MAIN.c(1): warning C318: can’t open file ‘STC8G.H’ 报错 解决 分析
c语言·开发语言·mongodb
HEJOO94 小时前
深入理解 Java volatile 关键字:原理、用法与常见误区
java·开发语言·spring
曹牧4 小时前
Java:no content to map due to end of input
java·开发语言
阿维的博客日记4 小时前
Example 类全场景实战指南
java·mybatis
梦梦代码精4 小时前
《回收租赁系统技术选型避坑指南:业务闭环与二开自由度详解》
开发语言·低代码·docker·开源·代码规范
两点王爷4 小时前
SpringBoot 项目集成 GeoServer 源码,实现地图服务发布
java·spring boot·后端
隔窗听雨眠4 小时前
记一次SQL Server数据库性能分析:从CPU100%到单配置修复的完整诊断
开发语言·数据库·php
David猪大卫4 小时前
【C++修炼】智能指针使用及原理
开发语言·c++·经验分享·笔记·学习·考研·面试
charlie1145141914 小时前
现代Qt开发之图像处理进阶:像素操作与格式转换
开发语言·图像处理·qt