2022Java学习笔记七十九(网络编程:TCP通信,TCP通信:多个客户端消息【重点】,追踪客户端的上线和下线功能、线程池优化)

2022Java学习笔记七十九(网络编程:TCP通信,TCP通信:多个客户端消息【重点】,追踪客户端的上线和下线功能、线程池优化)

一、TCP通信快速入门

TCP协议回顾:
1、TCP是一种面向连接,安全、可靠的传输数据的协议
2、传输前,采用“三次握手”方式,点对点通信,是可靠的
3、在连接中可进行大数据量的传输

2022Java学习笔记七十九(网络编程:TCP通信,TCP通信:多个客户端消息【重点】,追踪客户端的上线和下线功能、线程池优化)
构造器和常用API
2022Java学习笔记七十九(网络编程:TCP通信,TCP通信:多个客户端消息【重点】,追踪客户端的上线和下线功能、线程池优化)
二、TCP客户端发送消息
2022Java学习笔记七十九(网络编程:TCP通信,TCP通信:多个客户端消息【重点】,追踪客户端的上线和下线功能、线程池优化)
示例代码

package com.zcl.d12_tcpDaemo;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;

/*
    // 目标:完成socket网络编程入门案例的客户端开发  一发一收功能
 */
public class ColientDemo {
    public static void main(String[] args) {
        System.out.println("-------- 客户端启动成功 --------");
        try {
            // 1、创建socket通信管道请求服务器的连接1
            /**
             * 参数一:服务端的IP地址
             * 参数二:服务端的端口
             */
            Socket socket = new Socket("127.0.0.1",7777);
            // 2、从socket通道中得到一个字节输出流,负责发送数据
            OutputStream ops = socket.getOutputStream();
            // 把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(ops);
            // 发送消息
            ps.println("我对您发起邀请"); // 必须是发一行的消息
            ps.flush();

            // 不要关闭打印流,需要用户发送关闭消息才关闭
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

三、服务端代码编写
2022Java学习笔记七十九(网络编程:TCP通信,TCP通信:多个客户端消息【重点】,追踪客户端的上线和下线功能、线程池优化)

2022Java学习笔记七十九(网络编程:TCP通信,TCP通信:多个客户端消息【重点】,追踪客户端的上线和下线功能、线程池优化)
示例代码

package com.zcl.d12_tcpDaemo;

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

/*
    目标:开发socket网络编程入门代码的服务端,实现接收消息
 */
public class ServerDemo {
    public static void main(String[] args) {
        System.out.println("-------- 服务端启动成功 --------");
        try {
            // 1、注册服务端
            ServerSocket serverSocket = new ServerSocket(7777);
            // 2、必须调用accept方法,等待接收客户端的socket连接请求,建立socket通信管道
            Socket socket = serverSocket.accept();
            // 3、从socket通信管道中得到一个字节输入
            InputStream is = socket.getInputStream();
            // 4、把字符输入流包转成缓存字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            // 5、按照缓存字节输入流按行读取消息
            String msg;
            if ((msg = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress()+"说了:"+msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

需要严格遵守一发一收的原则,如果客户端不是使用一行消息来发送的化,而服务端使用的是接收一行数据就会报错,因为客户端不是发送一行服务端就接收到不完整的消息
如果客户端只发送一条行数据,而服务端使用循环while()接收数据也会报错,因为客户端已经完成了一次消息发送已经关掉了,服务器还在接收数据就会报错

三、TCP通信:多发多收案例
2022Java学习笔记七十九(网络编程:TCP通信,TCP通信:多个客户端消息【重点】,追踪客户端的上线和下线功能、线程池优化)

在上面的代码基础上分别个客户端和服务端添加反复循环就可以了

客户端代码修改

while (true) {
      System.out.println("请输入需要发送的消息:");
      String msg = sc.nextLine();
      // 发送消息
      ps.println(msg); // 必须是发一行的消息
      ps.flush(); // 刷新
}

服务端代码修改

while ((msg = br.readLine()) != null) {
     System.out.println(socket.getRemoteSocketAddress()+"说了:"+msg);
}

现在写好的服务端目前自能同时一收一个客户端的消息,原因是:目前的服务端是单线程的,每次只能处理一个客户端的消息

四、实现同时接收多个客户端消息【重点】
引用多线程
实现代码
1、客户端的代码不要动
2、修改服务端的代码

package com.zcl.d13_socket4;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/*
    目标:开发socket网络编程入门代码的服务端,实现服务端可以同时接收多个客户端消息
 */
public class ServerDemo {
    public static void main(String[] args) {
        System.out.println("-------- 服务端启动成功 --------");
        try {
            // 1、注册服务端
            ServerSocket serverSocket = new ServerSocket(7777);
            while (true) {
                // 2、每接收到一个客户端的socket管道,交给一个独立的子线程负责读取消息
                Socket socket = serverSocket.accept();
                // 3、开始创建独立线程处理socket
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3、创建一个多线程的ServerReaderThread类实现Thread类

package com.zcl.d13_socket4;

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

public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // 3、从socket通信管道中得到一个字节输入
            InputStream is = socket.getInputStream();
            // 4、把字符输入流包转成缓存字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            // 5、按照缓存字节输入流按行读取消息
            String msg;
            while ((msg = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress()+"说了:"+msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

多客户端接收消息就可以完成了

关于如何追踪客户端的上线和下线功能

1、在服务端的while循环里面通过socket的IP地址可以知道哪台客户端上线了
// 判断可客户端谁上线了 System.out.println(socket.getRemoteSocketAddress()+"他上线了");
2、在定义的ServerReaderThread多线程类里面,将捕获catch中最终IP地址判断下线,如果客户端报错了服务端就会给那个客户端报错

// 用户下线通知
System.out.println(socket.getRemoteSocketAddress() + "他下线了");

2022Java学习笔记七十九(网络编程:TCP通信,TCP通信:多个客户端消息【重点】,追踪客户端的上线和下线功能、线程池优化)

在定义的ServerReaderThread多线程类
2022Java学习笔记七十九(网络编程:TCP通信,TCP通信:多个客户端消息【重点】,追踪客户端的上线和下线功能、线程池优化)
五、TCP通信:线程池优化
1、客户端代码不需要动
2、修改服务端代码

package com.zcl.d14_scoket5;

import com.zcl.d13_socket4.ServerReaderThread;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

/*
    目标:开发socket网络编程入门代码的服务端,实现服务端可以同时接收多个客户端消息
 */
public class ServerDemo {
    // 使用静态变量记住一个线程池对象
    private static ExecutorService pool = new ThreadPoolExecutor(3,5,6,
            TimeUnit.SECONDS,new ArrayBlockingQueue<>(2),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {
        System.out.println("-------- 服务端启动成功 --------");
        try {
            // 1、注册服务端
            ServerSocket serverSocket = new ServerSocket(6666);
            while (true) {
                // 2、每接收到一个客户端的socket管道,交给一个独立的子线程负责读取消息
                Socket socket = serverSocket.accept();
                // 判断可客户端谁上线了
                System.out.println(socket.getRemoteSocketAddress()+"他上线了");
                // 任务对象负责读取消息
                Runnable target = new ServerReaderRunnable(socket); // 提交线程池排队
                pool.execute(target);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

添加了ExecutorService 线程池对象,通过线程池提交客户端发起的信息,在线程池排队

3、创建ServerReaderRunnable任务对象实现Runnable接口

需要重写构造器接收客户端发送对象

package com.zcl.d14_scoket5;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;

public class ServerReaderRunnable implements Runnable{
    // 接收对象
    private Socket socket;
    // 有参构造器接收对象
    public ServerReaderRunnable(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // 3、从socket通信管道中得到一个字节输入
            InputStream is = socket.getInputStream();
            // 4、把字符输入流包转成缓存字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            // 5、按照缓存字节输入流按行读取消息
            String msg;
            while ((msg = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress()+"说了:"+msg);
            }
        } catch (Exception e) {
            // e.printStackTrace();
            // 用户下线通知
            System.out.println(socket.getRemoteSocketAddress() + "他下线了");
        }
    }
}

使用线程池的优势在哪里
1、服务器端可以复用线程池处理多个客户端,可以避免系统瘫痪
2、适合客户端通信时长较短的场景

上一篇:java实现微信公众号消息推送


下一篇:Java集合中的Set集合必备文章