【JAVA】TCP通信——发弹幕案例

Java实现TCP通信——发弹幕案例

TCP通信可以实现双方相互接收发送消息,初学TCP通信,从简入难,先实现一方可以接收多方消息(一方作为服务端,多方作为客户端),即类似于观看视频的用户发送多条弹幕,同时其他用户也可以发送多条弹幕,可视作客户端;屏幕展现的所有弹幕,可视作服务端。
发弹幕案例,主要包含IO流知识点、线程知识点、TCP通信知识点。接下来先总结一些网络编程、TCP协议、UDP协议基础知识。通过编写发弹幕案例,进行复习巩固。

实现网络编程关键的三要素


1. IP地址:设备在网络中的地址,是唯一的标识。
2. 端口:应用程序在设备中唯一的标识。
3. 协议: 数据在网络中传输的规则,常见的协议有UDP协议和TCP协议。


  • 传输层的2个常见协议
  • TCP(Transmission Control Protocol) :传输控制协议
  • UDP(User Datagram Protocol):用户数据报协议

TCP协议特点

  1. 使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
  2. 传输前,采用“三次握手”方式建立连接,所以是可靠的 。
  3. 在连接中可进行大数据量的传输 。
  4. 连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。
  • TCP 协议通信场景
  • 对信息安全要求较高的场景,例如:文件下载、金融等数据通信。

UDP协议特点

  1. UDP是一种无连接、不可靠传输的协议。
  2. 将数据源IP、目的地IP和端口封装成数据包,不需要建立连接
  3. 每个数据包的大小限制在64KB内,数据不安全,易丢失数据
  4. 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
  5. 可以广播发送 ,发送数据结束时无需释放资源,开销小,速度快。
  • UDP协议通信场景
  • 语音通话,视频会话等。

本文重点学习TCP通信,TCP是一种面向连接的可靠通信协议,对于它的“三次握手"确定连接、”四次挥手“断开连接的具体情况,如下图

-TCP三次握手确立连接

请添加图片描述
原理转换小剧场:
在这里插入图片描述

-TCP三次握手确立连接TCP四次挥手断开连接

请添加图片描述
原理转换小剧场:
在这里插入图片描述

TCP通信模式

请添加图片描述

TCP通信的基本原理
  1. 客户端怎么发,服务端就应该怎么收。
  2. 客户端如果没有消息,服务端会进入阻塞等待。
  3. Socket一方关闭或者出现异常、对方Socket也会失效或者出错。
  • 了解TCP通信模式后,用Java实现一个TCP通信,模拟发弹幕案例。

需求:多个客户端可以发多个弹幕给一个服务端

根据TCP通信模式、基本原理,可尝试编写代码:分客户端、服务端两部分进行编写代码

步骤分析

a. 客户端发送弹幕步骤分析

  1. 创建客户端的Socket对象,请求与服务端的连接。
  2. 使用socket对象调用getOutputStream()方法得到字节输出流。
  3. 使用字节输出流完成数据的发送。
  4. 使用死循环,客户端可以不断发送弹幕。

b. 服务端发送弹幕步骤分析

  1. 创建ServerSocket对象,注册服务端端口。
  2. 调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象。
  3. 通过Socket对象调用getInputStream()方法得到字节输入流、完成数据的接收。
  4. 主线程定义了循环负责接收客户端Socket管道连接
  5. 每接收到一个Socket通信管道后分配一个独立的线程负责处理它。

注意:Socket一方关闭或者出现异常、对方Socket也会失效或者出错。

1. 客户端代码

选择合适的IO流,提高程序性能,客户端此时只是输出文本,OutputStream 低级流转换为PrintStream 打印流最佳。
注意:写出数据要即使刷新 flush()

public class ClientDemo {
    public static void main(String[] args) {
        try {
            System.out.println("=============客户端=====================");

            //创建一个通道,请求有服务端的连接,参数需要填写要连接的管道
            Socket socket = new Socket("127.0.0.1",7777);

            //创建一个输出流
            OutputStream os = socket.getOutputStream();

            //将字节输出流转换为高级流,此处打印流为最佳选项
            PrintStream ps = new PrintStream(os);

            Scanner scanner = new Scanner(System.in);
            //进行多次输出
            while (true) {
                System.out.println("请说:");
                //进行添加要打印的数据
                String msg = scanner.nextLine();
                ps.println(msg);
                //进行刷新数据
                ps.flush();
            }

            //此时不需要关闭,因为TCP 通信,可能数据没有传输过去,
            // 现在进行关闭,会造成数据的丢失
//        //关闭管道
//        ps.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
2. 服务端代码

2.1 创建 ServerReaderThread 类,主线程每接收到一个Socket通信管道后,均创建ServerReaderThread 类的对象,目的是为其分配一个独立的线程负责处理它,这样可以实现多个客户端同时发送多个弹幕(消息)给服务端。
在传输内容方式,选择合适的IO流进行编写代码,以提高程序性能。在此处,只是进行文字传输,可以将InputStream 转换为BufferedReader,将字节输入流转换为缓存字符输入流,不仅读取速度更快,而且BufferedReader 包含独有的API,readLine()读取字符内容更加方便。

public class ServerReaderThread extends Thread{
    //定义接收通道
    private Socket socket;

    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //创建一个字节输入流,进行流入数据
            InputStream is = socket.getInputStream();
            //将低级的字节输入流,转换为高级流,此时缓存字符输入流最为合适
            //注意缓存字符输入流,不能直接接受字节输入流,应将其转换为字符输入流
            BufferedReader bis = new BufferedReader(new InputStreamReader(is));

            //定义一个字符串,利用字符串进行输出一行数据
            String msg;
            //进行检验,输出数据
            while ((msg = bis.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress()+"发送了:"+msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

2.2 服务端代码
注意:继承Thread创建的线程,需要调用start(),进行启动,否则不会开始这个线程。

public class ServerDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("=============服务端=====================");

        //服务端创建一个端口号
        ServerSocket serverSocket = new ServerSocket(7777);

        //定义一个死循环,由主线程不断的接收客户端的socket管道连接
        while (true) {
            //每次收到一个客户端的socket管道,交给一个独立的子线程负责读取信息
            Socket socket = serverSocket.accept();
            //开始创建独立的子线程处理socket
            new ServerReaderThread(socket).start();
        }
    }
}
3. 多个客户端并发配置操作

发弹幕案例,是允许多个客户端一起发送内容,所以重新编辑配置,可以多个客户端并发进行。具体修改操作如下图
请添加图片描述

请添加图片描述
先运行服务端,在运行客户端。
此时,我只开了四个客户端,看个人需求需要开几个客户端均可。
在这里插入图片描述

4. 运行结果
=============服务端=====================
/127.0.0.1:1646发送了:锦瑟无端五十弦
/127.0.0.1:1651发送了:一线一柱思华年
/127.0.0.1:1659发送了:庄生晓梦迷蝴蝶
/127.0.0.1:1672发送了:望帝春心托杜鹃
/127.0.0.1:1646发送了:沧海月明珠有泪
/127.0.0.1:1651发送了:蓝田日暖玉生烟
/127.0.0.1:1659发送了:此情可待成追忆
/127.0.0.1:1672发送了:只是当时已惘然

=============客户端=====================
请说:
锦瑟无端五十弦
请说:
沧海月明珠有泪
请说:
=============客户端=====================
请说:
庄生晓梦迷蝴蝶
请说:
此情可待成追忆
请说:

版权声明:本文为qq_47156956原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。