Java多人视频通信(不定时更新)


前言

2022.1.16

和同学一起做的项目,打算将来写在简历里面,或者就是写着玩玩,前言也会不定时更新,主要是写给自己看,防止自己忘记hhh
以后是打算边写代码边写博客,不然写完再写博客太费劲了,懒

2022.1.19

第二次更新,实现了简单的界面视图,并以直线的形式传递信息,也就是传两个点


一、服务器和客户端

1.创建服务器类和用户类

服务器

package communication2;

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

public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(18888);//需要捕捉异常,好像涉及到网络连接就会强制要求
            System.out.println("等待连接。。。");
            serverSocket.accept();//阻塞式连接,没有连接就不会执行后续代码
            System.out.println("连接成功~");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

用户

package communication2;

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

public class Client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1",18888);//尝试连接
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

先后运行Server和Client

运行Server时
运行Server时

运行了Client后
在这里插入图片描述
第一次连接尝试成功~~

接下来我们尝试让服务器发点东西给用户,看看能不能接收

2.第一次传输数据

更新代码

package communication2;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(18888);//需要捕捉异常,好像涉及到网络连接就会强制要求
            System.out.println("等待连接。。。");
            //获取Socket对象
            Socket socket = serverSocket.accept();//阻塞式连接,没有连接就不会执行后续代码
            System.out.println("连接成功~");
            OutputStream outputStream = socket.getOutputStream();//获取输出流
            outputStream.write(new String("hello my friend").getBytes(StandardCharsets.UTF_8));//写入输出流
            outputStream.close();//关闭输出流,表明输出结束
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

package communication2;

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

public class Client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1",18888);//尝试连接
            //获取从服务器传来的输入
            InputStream inputStream = socket.getInputStream();
            while (true){
                int word = inputStream.read();//这个方法每次只能获取一个字符并为int类型
                if (word == -1)
                    break;
                //转换成char并输出字符
                System.out.print((char)word);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
芜湖~成功了

3.将用户封装

package communication;

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

public class Client {
    private Socket socket;
    private InputStream inputStream;
    public Client(String ip, int port){
        try {
            this.socket = new Socket(ip,port);
            this.inputStream = socket.getInputStream();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //封装读取信息方法
    public void receiveMsg(){
        int perWord = 0;
        while (true){
            try {
                perWord = this.inputStream.read();
                if (perWord == -1)
                    break;
                System.out.print((char) perWord);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

更新Server代码,使其能够持续接收连接

package communication2;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class Server {
    static int index = 0;
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(18888);//需要捕捉异常,好像涉及到网络连接就会强制要求
            System.out.println("等待连接。。。");
            //持续保持等待连接状态
            while (true) {
                //获取Socket对象
                Socket socket = serverSocket.accept();//阻塞式连接,没有连接就不会执行后续代码
                System.out.println("连接成功~");
                index++;
                OutputStream outputStream = socket.getOutputStream();//获取输出流
                outputStream.write(new String("hello my friend you are the "+index+" client").getBytes(StandardCharsets.UTF_8));//写入输出流
                outputStream.close();//关闭输出流,表明输出结束
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

创建一个Test类来写main方法,创建Client对象

package communication2;

public class Test {
    public static void main(String[] args) {
        Client client1 = new Client("127.0.0.1",18888);
        client1.receiveMsg();
        System.out.println();
        Client client2 = new Client("127.0.0.1",18888);
        client2.receiveMsg();
    }
}

![在这里插入图片描述](https://img-blog.csdnimg.cn/8a99f95eb739499aaffb4b534456fdcf.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Y2X5oe_54Oo5pum,size_20,color_FFFFFF,t_70,g_se,x_16
good,现在可以同时连接多个对象了,并且把用户封装好了

二、实现简单可视化界面通信

1.可视化界面和鼠标监听器

可视化界面用JFrame来写,并给该界面加上一个监听器,来监听鼠标按压和释放的点位,以此来画点
Server的封装
连接服务端的connect方法

private Socket connect(){
        Socket socket = null;
        try {
            socket = serverSocket.accept();

        } catch (IOException e) {
            e.printStackTrace();
        }
        return socket;
    }

界面生成方法

private JFrame initUi(Socket socket){
        JFrame jFrame = new JFrame("通信测试界面-服务端");
        jFrame.setSize(400,400);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jFrame.setLocation(790,290);
        jFrame.setVisible(true);
        try {
            //增加监听器,传入画布和输出流
            jFrame.addMouseListener(new ServerUiListener(jFrame.getGraphics(),socket.getOutputStream()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return jFrame;
    }

监听器类的实现

package communication;

import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class ServerUiListener implements MouseListener {
    private Graphics graphics;
    private OutputStream outputStream;
    //确定两个坐标
    private int x1,y1,x2,y2;
    public ServerUiListener(Graphics graphics,OutputStream outputStream){
        this.graphics = graphics;
        this.outputStream = outputStream;
    }
    @Override
    public void mouseClicked(MouseEvent e) {

    }

    @Override
    public void mousePressed(MouseEvent e) {
        x1 = e.getX();
        y1 = e.getY();
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        //鼠标释放后即开始画线
        x2 = e.getX();
        y2 = e.getY();
        graphics.drawLine(x1,y1,x2,y2);
        //将两个坐标出入输出流
        try {
            outputStream.write(new Integer(x1).byteValue());
            outputStream.write(new Integer(y1).byteValue());
            outputStream.write(new Integer(x2).byteValue());
            outputStream.write(new Integer(y2).byteValue());
//            System.out.println(x1+" "+y1+" "+x2+" "+y2);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void mouseEntered(MouseEvent e) {

    }

    @Override
    public void mouseExited(MouseEvent e) {

    }
}

客户端的代码类似,也是加个界面加个监听器,不赘述了

2.运行展示在这里插入图片描述

两边的窗口都可以相互看到画线的地方,在Server窗口画线可以在客户端看到,在客户端画线Server也能接收到

3.写代码时的疑惑

Server的监听器类中有数据传入到输出流,而在Server类中又有Inputstream的接收,同样的Client类里也有Inputstream接收,那到底是哪个Inputstream接收呢?运行后发现只有Client类才会接收,同一个对象不能既发送流又读入流

4.存在的问题

Inputstream在接收信息的时候只能接入0~255的int整数,超过255会溢出,或者小于0也会溢出,
具体表现就是,我在Server的窗口里画一条超范围的直线,在客户端接收到的点位会有明显的偏差
在这里插入图片描述
这个问题我在之后应该会解决

三、解决输入输出流最大只能传送255整数的问题

1.原理即解决方法

由于Inputstream的read()方法只能返回一个0~255的整数,也就是1个字节,8个bit的值,所以,要想准确的传递参数,需要先搞清楚int,byte和bit之间的关系
按理来说这是最基本的东西,但是我以前没咋用,所以记不住,嘿嘿嘿
在java里面,一个int的大小为4个byte即32个bit,所以要想将int完整的表示出来,需要将int拆分成4个部分放入输出流,然后在接收的时候再拼接出来

int用二进制来表示一共有32位,而Outputstream的write方法每次都只取最后8位,所以我们要将int拆分为32-25位,24-17位,16-9位,8-1位,用右移操作即可,接收时再左移回去

先来一个简单的例子

package Test;

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

public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(50000);
            Socket socket = serverSocket.accept();
            OutputStream outputStream = socket.getOutputStream();
            //传10000已经大于255了
            int num = 10000;
            //进行移位操作
            outputStream.write(num>>24);
            outputStream.write(num>>16);
            outputStream.write(num>>8);
            outputStream.write(num>>0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

package Test;

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

public class Client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1",50000);
            InputStream inputStream = socket.getInputStream();
            //读取拆分的4部分
            int b1 = inputStream.read();
            int b2 = inputStream.read();
            int b3 = inputStream.read();
            int b4 = inputStream.read();
            //拼接
            int x = b1<<24|b2<<16|b3<<8|b4;
            System.out.println(b1+" "+b2+" "+b3+" "+b4);
            System.out.println(x);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

然后分别运行,看输出
在这里插入图片描述
表示的是32到25位为0,24到17位为0,16到9位为39,8到1位为16
拼接过后得出了正确的数值
39和16代表啥意思呢,看看计算器
在这里插入图片描述
在这里插入图片描述
再来看看10000对应的二进制
在这里插入图片描述
这就代表了右移过后的结果

2.方法的封装

好了我们接下来将传int的方法封装起来,方便使用

package communication3;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class StreamMethod {
    public static void writeInt(OutputStream outputStream,int num){
        try {
            outputStream.write(num>>24);
            outputStream.write(num>>16);
            outputStream.write(num>>8);
            outputStream.write(num>>0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static int readInt(InputStream inputStream){
        try {
            int b1 = inputStream.read();
            int b2 = inputStream.read();
            int b3 = inputStream.read();
            int b4 = inputStream.read();
            //拼接
            return b1<<24|b2<<16|b3<<8|b4;
        } catch (IOException e) {
            e.printStackTrace();
            return 0;
        }

    }
}

3.更新代码即运行结果

Server中的监听方法

private void startListenMgs(JFrame jFrame,Socket socket){
        int x1,y1,x2,y2;
        try {
            InputStream inputStream = socket.getInputStream();
            Graphics graphics = jFrame.getGraphics();
            while (true) {
                x1 = StreamMethod.readInt(inputStream);
                y1 = StreamMethod.readInt(inputStream);
                x2 = StreamMethod.readInt(inputStream);
                y2 = StreamMethod.readInt(inputStream);
                graphics.drawLine(x1,y1,x2,y2);
                System.out.println(x1+" "+y1+" "+x2+" "+y2);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

监听器中的写入方法

public void mouseReleased(MouseEvent e) {
        //鼠标释放后即开始画线
        x2 = e.getX();
        y2 = e.getY();
        graphics.drawLine(x1, y1, x2, y2);
        //将两个坐标出入输出流
        StreamMethod.writeInt(outputStream, x1);
        StreamMethod.writeInt(outputStream, y1);
        StreamMethod.writeInt(outputStream, x2);
        StreamMethod.writeInt(outputStream, y2);
        System.out.println(x1+" "+y1+" "+x2+" "+y2);

    }

运行结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
现在数据算是传入成功了

四、完善功能

现在属于是只能画直线,我们可以多弄几种类型,加上画笔,圆和矩形,并且优化一下实时显示的功能

1.封装字符串传输的方法

要辨别选择的是哪一种类型,就需要通过字符串来区分,一个字符通常是2字节,但是如果是中文的话就是3字节,要是中英混搭的话就不好区分长度,所以直接调用.getBytes().length方法即可

public static void writeStr(OutputStream outputStream,String str){
        try {
            outputStream.write(str.getBytes().length);
            outputStream.write(str.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

在接收的时候,字符串有个传入byte数组的构造方法可以直接转换成字符串

    public static String readStr(InputStream inputStream){
        try {
            int length =  inputStream.read();
            byte[] strArray = new byte[length];
            inputStream.read(strArray);
            return new String(strArray);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

2.添加按钮并更新监听器

一些ui上的操作就不用赘述了,直接上代码展示

public static JFrame ServerUI(String title, Socket socket) {
        String[] btnStr = {"画笔", "直线", "圆", "矩形"};
        JFrame jFrame = new JFrame(title);
        try {

            jFrame.setSize(1000, 800);
            jFrame.setLocation(200, 100);
            jFrame.setDefaultCloseOperation(jFrame.EXIT_ON_CLOSE);
            jFrame.setLayout(new FlowLayout());
            jFrame.setVisible(true);

            ServerUiListener serverUiListener = new ServerUiListener(jFrame.getGraphics(), socket.getOutputStream());
            jFrame.addMouseListener(serverUiListener);
            jFrame.addMouseMotionListener(serverUiListener);

            for (int i = 0; i < btnStr.length; i++) {
                JButton jButton = new JButton(btnStr[i]);
                jFrame.add(jButton);
                jButton.addActionListener(serverUiListener);
//                System.out.println("加了一个按钮");
            }
            //setVisible必须放在最后面,否则按钮不会显示
            jFrame.setVisible(true);
            return jFrame;
        } catch (IOException e) {
            e.printStackTrace();
            return jFrame;
        }
    }

监听器方面,画笔的话需要实时拖动,故调用mouseDragged方法。

如何动态显示作图痕迹

在拖动的过程中就应该去绘制线条,并在绘制下一个线条的时候擦除前一个线条,这样就感觉是动态绘制了,擦除方法我选择在原来的点位上绘制与背景相同颜色的线条,这样就相当于是擦除了
另外有个问题,在擦除的过程中可能会擦除之前已经画好的线条,所以我们还需要用数组保留画好的线条的点位,在每次擦除的时候重绘一下
我把点的数组放在了UI这个类里面设置成静态变量来使用
在这里插入图片描述
有关UI的方法都在这个UI类里面封装成了静态方法

public void mouseReleased(MouseEvent e) {
        //鼠标释放后即开始画线
        x2 = e.getX();
        y2 = e.getY();
        //在鼠标释放的时候加入点位
        if(shape.equals("直线")) {
//            System.out.println(x2+" "+y2);
            UI.lineStartPoints.add(new Point(x1,y1));
            UI.lineEndPoints.add(new Point(x2,y2));
            graphics.drawLine(x1, y1, x2, y2);

        }
        if(shape.equals("圆")){
            UI.circleStartPoints.add(new Point(x1,y1));
            UI.circleEndPoints.add(new Point(x2,y2));
            graphics.drawOval(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
        }
        if(shape.equals("矩形")){
            UI.recStartPoints.add(new Point(x1,y1));
            UI.recEndPoints.add(new Point(x2,y2));
            graphics.drawRect(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
        }
        if(!shape.equals("画笔")) {
            //加入了图形类就要先传类型,直接调用封装好的方法即可
            StreamMethod.writeStr(outputStream, shape);
            //将两个坐标出入输出流
            StreamMethod.writeInt(outputStream, x1);
            StreamMethod.writeInt(outputStream, y1);
            StreamMethod.writeInt(outputStream, x2);
            StreamMethod.writeInt(outputStream, y2);
            System.out.println(x1 + " " + y1 + " " + x2 + " " + y2);
        }
    }
public void mouseDragged(MouseEvent e) {
		//由于画笔是实时加入的,所以画笔的点位在鼠标拖动的时候就加上了
        if (shape.equals("画笔")){
            UI.Points.add(new Point(e.getX(),e.getY()));
            graphics.fillOval(e.getX(),e.getY(),2,2);
            StreamMethod.writeStr(outputStream,shape);
            StreamMethod.writeInt(outputStream,e.getX());
            StreamMethod.writeInt(outputStream,e.getY());
        }
        if(shape.equals("直线")){
            //将之前的线覆盖
            if(prex!=e.getX()||prey!=e.getY()){
                graphics.setColor(new Color(238,238,238));
                graphics.drawLine(x1,y1,prex,prey);
            }
            graphics.setColor(Color.black);

            graphics.drawLine(x1,y1,e.getX(),e.getY());

        }
        if(shape.equals("圆")){
            //将之前的线覆盖
            if(prex!=e.getX()||prey!=e.getY()){
                graphics.setColor(new Color(238,238,238));
                graphics.drawOval(Math.min(x1,prex),Math.min(y1,prey),Math.abs(x1-prex),Math.abs(y1-prey));

            }
            graphics.setColor(Color.black);

            graphics.drawOval(Math.min(x1,e.getX()),Math.min(y1,e.getY()),Math.abs(x1-e.getX()),Math.abs(y1-e.getY()));

        }
        if(shape.equals("矩形")){
            //将之前的线覆盖
            if(prex!=e.getX()||prey!=e.getY()){
                graphics.setColor(new Color(238,238,238));
                graphics.drawRect(Math.min(x1,prex),Math.min(y1,prey),Math.abs(x1-prex),Math.abs(y1-prey));

            }
            graphics.setColor(Color.black);

            graphics.drawRect(Math.min(x1,e.getX()),Math.min(y1,e.getY()),Math.abs(x1-e.getX()),Math.abs(y1-e.getY()));

        }
        //这里是重绘之前的线条
        for (int i = 0; i < UI.Points.size(); i++) {
            graphics.fillOval(UI.Points.get(i).x,UI.Points.get(i).y,1,1);
        }
        for (int i = 0; i < UI.lineStartPoints.size(); i++) {
            graphics.drawLine(UI.lineStartPoints.get(i).x,UI.lineStartPoints.get(i).y,UI.lineEndPoints.get(i).x,UI.lineEndPoints.get(i).y);
        }
        for (int i = 0; i < UI.circleEndPoints.size(); i++) {
            graphics.drawOval(Math.min(UI.circleStartPoints.get(i).x,UI.circleEndPoints.get(i).x),Math.min(UI.circleStartPoints.get(i).y,UI.circleEndPoints.get(i).y),Math.abs(UI.circleStartPoints.get(i).x-UI.circleEndPoints.get(i).x),Math.abs(UI.circleStartPoints.get(i).y-UI.circleEndPoints.get(i).y));
        }
        for (int i = 0; i < UI.recEndPoints.size(); i++) {
            graphics.drawRect(Math.min(UI.recStartPoints.get(i).x,UI.recEndPoints.get(i).x),Math.min(UI.recStartPoints.get(i).y,UI.recEndPoints.get(i).y),Math.abs(UI.recStartPoints.get(i).x-UI.recEndPoints.get(i).x),Math.abs(UI.recStartPoints.get(i).y-UI.recEndPoints.get(i).y));
        }
        prex = e.getX();
        prey = e.getY();
    }

Server类里的信息接收

 private void startListenMgs(JFrame jFrame,Socket socket){
        int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
        String shape;
        try {
            InputStream inputStream = socket.getInputStream();
            Graphics graphics = jFrame.getGraphics();

            while (true) {
                shape = StreamMethod.readStr(inputStream);
                if(!shape.equals("画笔")) {
                    x1 = StreamMethod.readInt(inputStream);
                    y1 = StreamMethod.readInt(inputStream);
                    x2 = StreamMethod.readInt(inputStream);
                    y2 = StreamMethod.readInt(inputStream);
                }else {
                	//画笔只需要接收两个点位,这个在传输的时候可以先协商好
                    x1 = StreamMethod.readInt(inputStream);
                    y1 = StreamMethod.readInt(inputStream);
                    graphics.fillOval(x1,y1,2,2);
                }
                if (shape.equals("直线")) {
                    graphics.drawLine(x1, y1, x2, y2);
                }
                if(shape.equals("圆")){
                    graphics.drawOval(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
                }
                if (shape.equals("矩形")){
                    graphics.drawRect(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
                }
                System.out.println(x1+" "+y1+" "+x2+" "+y2);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

现在做成这样子了,后续再把消息交流加上去
在这里插入图片描述

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java多人视频通话是指利用Java语言开发的一种技术,可以实现多个用户同时进行视频通话的功能。这种技术可以应用于各种实际场景,比如在线会议、远程教育、远程医疗等。 在Java多人视频通话中,通常会使用一种称为WebRTC的开源项目作为基础。WebRTC是一种实时通信技术,可以在Web浏览器上直接进行音视频通信,而不需要安装额外的插件或软件。 通过Java语言开发的多人视频通话系统,通常需要具备以下功能: 1. 用户注册和登录:用户可以通过注册和登录功能获得自己的账号和密码,用于身份验证和权限管理。 2. 好友管理:用户可以添加好友,并与好友进行视频通话。好友管理功能可以提供搜索、添加、删除好友等操作。 3. 多人视频通话:用户可以发起多人视频通话,并能够接收其他用户的视频流。系统需要提供视频流的传输和实时播放功能,同时还需要考虑视频质量和带宽的管理。 4. 视频会议控制:用户可以对视频会议进行控制,如静音/取消静音、屏幕共享、聊天等。 5. 系统安全保护:为了保护用户的隐私和数据安全,系统需要加强身份验证、加密传输数据等安全措施。 Java多人视频通话系统的实现可以借助一些开源库或框架,如WebRTC、JavaFX等。同时,合理的架构设计、优化的算法和性能调优也是实现一个高效、稳定的多人视频通话系统的关键。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值