TCP通信

一、TCP协议回顾

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


二、TCP通信模式演示

  • TCP是点到点通信,因此需要建立一个点到点通信的管道来进行数据传输。

  • 以下是客户端传输数据给服务端:

java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_java


  • 以下是服务端传输数据给客户端:

java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_网络_02


注意:在Java中只要是使用java.net.Socket类实现通信,底层就是使用了TCP协议



三、Socket类

1、构造器

构造器

说明

public Socket(String host, int port)

创建发送端的Socket对象与服务端连接,参数为服务端程序的IP和端口。


2、API

方法名称

说明

OutputStream getOutputStream()

得到字节输出流对象

InputStream getInputStream()

得到字节输入流对象



四、ServerSocket类

1、构造器

构造器

说明

public ServerSocket(int port)

注册服务端端口


2、API

方法名称

说明

public Socket accept()

等待接收客户端的Socket通信连接

连接成功返回Socket对象与客户端建立端到端通信(点到点通信)



五、TCP通信入门案例:一发一收

1、客户端发送消息

(1)实现步骤
  1. 创建客户端的Socket对象,请求与服务端的连接。
  2. 使用Socket对象调用getOutputStream()方法得到字节输出流。
  3. 使用字节输出流完成数据的发送。
  4. 释放资源:关闭socket管道。
package com.app.d6_tcp_socket1;

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

/**
    客户端(发送端)
    需求:创建客户端与服务端建立连接,完成一发一收
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        System.out.println("-------------客户端启动--------------");
        try {
            /**
                1、创建Socket对象,请求与服务端连接
                    构造器:public Socket(String host, int port)
                    参数一:服务端的IP地址
                    参数二:服务端的端口
             */
            Socket socket = new Socket("127.0.0.1", 6666);

            // 2、获得字节输出流对象
            OutputStream os = socket.getOutputStream();

            // 3、将低级的字节输出流包装成高级的打印流,负责发送数据
            PrintStream ps = new PrintStream(os);

            // 4、发送数据
            ps.print("我是TCP通信客户端,已与你建立了连接,现发出邀请:约吗?");
            ps.flush(); // 刷新数据

            /**
                释放资源:
                    不建议在这里使用,因为客户端已经与服务端建立了通信管道,
                    在这里释放资源,容易出现丢失数据的情况,
                    因为必须是客户端确认离线了才释放资源、关闭管道。
             */
            // socket.close();

        } catch (Exception e) { // 捕获异常
            e.printStackTrace();
        }
    }
}

(2)小结

1、TCP通信的客户端的代表类是?

  • Socket类
  • public Socket(String host, int port)

2、TCP通信如何使用Socket管道发送、接收数据?

  • getOutputStream()方法:获得字节输出流对象(发)
  • getInputStream()方法:获得字节输入流对象(收)


2、服务端接收消息

(1)实现步骤
  1. 创建ServerSocket对象并注册端口。
  2. 使用ServerSocket对象的accept方法,等待客户端的Socket连接请求,建立Socket通信管道。
  3. 从socket通信管道中得到字节输入流,负责读取接收数据。
  4. 将低级的字节输入流转换成字符输入流。
  5. 将字符输入流包装成高级的缓冲字符输入流。
  6. 使用while循环按照行读取消息
package com.app.d6_tcp_socket1;

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

/**

 */
public class ServerDemo2 {
    public static void main(String[] args) {
        System.out.println("-------------服务端启动--------------");
        try {
            // 1、创建ServerSocket对象并注册端口
            ServerSocket serverSocket = new ServerSocket(6666);

            // 2、使用ServerSocket对象的accept方法,等待客户端的Socket请求,建立Socket通信管道
            Socket socket = serverSocket.accept();

            // 3、从socket通信管道中得到字节输入流
            InputStream is = socket.getInputStream();

            // 4、将低级的字节输入流转换成字符输入流
//            InputStreamReader isr = new InputStreamReader(is);

            // 5、将低级的字符输入流包装成高级的缓冲字符输入流:负责读取接收数据
//            BufferedReader br = new BufferedReader(isr);
            BufferedReader br = new BufferedReader(new InputStreamReader(is));  // 一步到位,省一行代码

            // 6、使用while循环按照行读取数据
            String msg; // 定义消息变量,用于存储按照行读取接收到的消息数据
            while ((msg = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
            }

        } catch (Exception e) { // 捕获异常
            e.printStackTrace();
        }
    }
}

(2)小结

1、TCP通信服务端用的代表类是?

  • ServerSocket类,注册端口
  • 调用accept()方法阻塞等待接收客户端的连接请求,得到Socket对象

2、TCP通信的基本原理?

  • 客户端怎么发,服务端就应该怎么收
  • 客户端如果没有消息,服务端会进入阻塞等待
  • Socket一方关闭或者出现异常,对方Socket也会失效或者出错


3、测试

java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_java_03


java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_java tcp服务端如何判断连接断开_04


java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_开发语言_05


java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_java_06


java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_java_07


java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_开发语言_08


java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_java tcp服务端如何判断连接断开_09




六、TCP通信案例:多发多收

1、需求

  • 使用TCP通信方式实现:多发多收消息。

2、分析

  • 客户端
  • 1、创建Socket对象,指定服务端IP地址和端口,请求与服务端连接。
  • 2、调用Socket对象的getOutputStream()方法,得到字节输出流。
  • 3、将低级的字节输出流包装成高级的打印流。
  • 4、创建键盘输入对象,用于客户端输入消息。
  • 5、使用while死循环不断让客户端输入消息,如果输入的是exit则说明客户端已离线!!释放资源;
  • 6、如果客户端输入的消息不是exit,则将客户端输入的消息发送给服务端。
package com.app.d7_tcp_socket2;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
    需求:开发客户端多发消息给服务端,输入exit表示离线、终止程序!
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        System.out.println("-=-=-=-=-客户端启动-=-=-=-=-");
        try {
            // 1、创建Socket对象,指定服务端的IP地址和端口,请求与服务端连接
            Socket socket = new Socket("127.0.0.1", 6565);

            // 2、调用Socket对象的getOutputStream()方法,得到字节输出流。
            OutputStream os = socket.getOutputStream();

            // 3、将低级的字节输出流包装成高级的打印流
            PrintStream ps = new PrintStream(os);

            // 4、创建键盘输入对象,用于客户端输入消息
            Scanner sc = new Scanner(System.in);

            // 5、使用while死循环不断让客户端输入消息,
            while (true) {
                // 让客户端输入消息
                System.out.println("请说:");
                String msg = sc.nextLine();

                // 如果输入的是exit说明客户端已离线!!释放资源。
                if (msg.equals("exit")) {
                    System.out.println("您已离线!!");
                    socket.close(); // 释放资源!
                    return;
                }

                // 6、如果客户端输入的消息不是exit,程序将会走到这里,则将客户端输入的消息发送给客户端。
                ps.println(msg);
                ps.flush(); // 必须刷新数据
            }

        } catch (Exception e) { // 捕获异常!
            e.printStackTrace();
        }
    }
}

  • 服务端
  • 1、创建ServerSocket对象,注册端口。
  • 2、调用ServerSocket对象的accept方法,等待客户端的Socket请求,建立Socket通信管道。
  • 3、从Socket通信管道中得到字节输入流。
  • 4、将低级的字节输入流转换成字符输入流,并包装成高级的缓冲字符输入流。
  • 5、使用while死循环不断等待客户端发送消息,接收后展示。
package com.app.d7_tcp_socket2;

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

/**
    需求:开发服务端多收客户端发送过来的消息!
 */
public class ServerDemo2 {
    public static void main(String[] args) {
        System.out.println("-=-=-=-=-服务端启动-=-=-=-=-");
        try {
            // 1、创建ServerSocket对象,注册端口
            ServerSocket serverSocket = new ServerSocket(6565);

            // 2、调用ServerSocket对象的accept方法,等待客户端的Socket请求,建立Socket通信管道
            Socket socket = serverSocket.accept();

            // 3、从Socket通信管道中得到字节输入流
            InputStream is = socket.getInputStream();

            // 4、将低级的字节输入流转换成字符输入流,并包装成高级的缓冲字符输入流。
            BufferedReader br = new BufferedReader(new InputStreamReader(is));

            // 5、使用while死循环不断等待客户端发送消息,接收后展示。
            String msg; // 定义变量,存储按照行读取到的消息。
            while ( (msg = br.readLine()) != null ) {
                System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


3、测试

java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_网络_10



4、问题??

本案例实现了多发多收,那么是否可以同时接收多个客户端的消息??

  • 不可以的
  • 为什么?
  • 因为服务端现在只有一个线程,只能与一个客户端进行通信

java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_java tcp服务端如何判断连接断开_11


java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_网络_12


java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_java tcp服务端如何判断连接断开_13


java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_tcp/ip_14



总结

1、本次多发多收是如何实现的?

  • 客户端使用循环反复地发送消息
  • 服务端使用循环反复地接收消息

2、现在服务端为什么不可以同时接收多个客户端的消息?

  • 因为目前服务是单线程的,每次只能处理一个客户端发送的消息



七、TCP通信案例:同时接收多个客户端消息【重点】

1、问题??

(1)之前我们的TCP通信是否可以同时与多个客户端通信,为什么??

  • 不可以
  • 因为TCP通信的服务端是单线程,每次只能处理一个客户端的Socket通信

(2)如何才可以让服务端可以处理多个客户端的通信需求?

  • 引入多线程


2、同时处理多个客户端消息

(1)思路模型
  • 让主线程不断接收客户端的Socket连接;
  • 将接收到的多个客户端的Socket连接交给独立的线程去处理。

(2)分析实现
  • 客户端
  1. 创建Socket对象,指定服务端的IP和端口,请求与服务端连接。
  2. 调用Socket对象的getOutputStream()方法,得到字节输出流。
  3. 将低级的字节输出流包装成高级的打印流。
  4. 创建键盘录入对象,用于客户端输入数据。
  5. 使用while死循环不断让客户端输入数据,如果输入的是exit说明客户端已离线!释放资源!
  6. 如果输入的不是exit,说明程序走到这里,则将客户端输入的数据发送给服务端。
  7. 每发送完一条数据,必须刷新数据。
package com.app.d8_tcp_socket3;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
    需求:开发客户端可以不断的发送数据给服务端,输入exit说明离线。
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        System.out.println("--==--==--客户端启动--==--==--");
        try {
            // 1、创建Socket对象,指定服务端的IP和端口,与服务端建立连接
            Socket socket = new Socket("127.0.0.1", 8888);

            // 2、使用Socket对象的getOutputStream()方法,得到字节输出流
            OutputStream os = socket.getOutputStream();

            // 3、将低级的字节输出流包装成高级的打印流,负责发送数据给服务端
            PrintStream ps = new PrintStream(os);

            // 4、创建键盘录入对象,用于客户端输入数据
            Scanner sc = new Scanner(System.in);

            // 5、使用while死循环不断让客户端输入数据
            while (true) {
                // 客户端开始输入数据
                System.out.println("请说:");
                String msg = sc.nextLine();

                // 如果输入的是exit,则说明客户端已离线!释放资源
                if (msg.equals("exit")) {
                    System.out.println("您已离线!!");
                    socket.close();
                    return;
                }

                // 6、如果输入的不是exit,说明程序走到这里,则将客户端输入的数据发送给服务端
                ps.println(msg);
                // 7、客户端每发送完一条数据,必须刷新数据。
                ps.flush();
            }

        } catch (Exception e) { // 捕获异常!
            e.printStackTrace();
        }
    }
}

  • 服务端
  1. 创建ServerSocket对象,注册端口。
  2. 使用while死循环由主线程负责不断接收多个客户端的Socket连接请求。
  3. 调用ServerSocket对象的accept方法,等待客户端的Socket连接请求,建立Socket通信管道。
  4. 创建线程任务类ServerReaderThread(服务端读取线程),重写run方法:
  1. 从Socket通信管道中得到字节输出流。
  2. 将低级的字节输入流转换成字符输入流,并包装成高级的缓冲字符输入流,负责接收客户端发来的数据。
  3. 使用while死循环不断按照行读取客户端发来的数据。
  4. 展示读取到的数据。
  1. 每接收到一个客户端的Socket连接请求,主线程将其交给独立的线程去处理。
package com.app.d8_tcp_socket3;

import java.net.ServerSocket;
import java.net.Socket;

/**
    需求:开发服务端可以同时接收多个客户端发送过来的数据。
 */
public class ServerDemo2 {
    public static void main(String[] args) {
        System.out.println("--==--==--服务端启动--==--==--");
        try {
            // 1、创建ServerSocket对象,注册端口。
            ServerSocket serverSocket = new ServerSocket(8888);

            // 2、使用while死循环由主线程负责不断接收多个客户端发出的Socket连接请求
            while (true) {
                // 3、调用ServerSocket对象的accept方法,等待客户端的Socket连接请求,建立Socket通信管道。
                Socket socket = serverSocket.accept();

                // 5、每接收到一个客户端的Socket连接请求,主线程将其交给独立的线程去处理。
                new ServerReaderThread(socket).start();
            }

        } catch (Exception e) { // 捕获异常!
            e.printStackTrace();
        }
    }
}
package com.app.d8_tcp_socket3;

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

/**
    4、创建线程任务类:ServerReaderThread(服务端读取线程)
 */
public class ServerReaderThread extends Thread {
    // 2、定义一个socket变量,用于存储主线程交过来的客户端的Socket连接请求
    private Socket socket;
    // 3、定义一个有参数构造器,用于将主线程交过来的客户端的Socket连接请求初始化到私有的socket变量
    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }


    /**
        1、重写run方法
      */
    @Override
    public void run() {
        try {
            // a.从Socket通信管道中得到字节输入流
            InputStream is = socket.getInputStream();

            // b.将低级的字节输入流转换成字符输入流,并包装成高级的缓冲字符输入流,负责读取接收客户端发送过来的数据。
            BufferedReader br = new BufferedReader(new InputStreamReader(is));

            // c.使用while死循环不断按照行读取客户端发送过来的数据
            String msg;
            while ( (msg = br.readLine()) != null ) {
                // d.展示读取到的数据
                System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
            }

        } catch (Exception e) { // 捕获异常!
            e.printStackTrace();
        }
    }
}


(3)测试

java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_tcp/ip_15



(4)拓展
  • 跟踪某个客户端上线和下线。
  • 跟踪上线逻辑
  • 直接在接收到客户端的Socket连接请求的地方,打印输出一个上线日志:
  • 跟踪下线逻辑
  • 直接将输入exit的判断逻辑代码注释掉,然后在线程任务类的run方法中的捕获异常处做客户端下线提醒

java tcp服务端如何判断连接断开 java tcp服务端连接多个客户端_网络_16


  • 测试



总结

1、本次是如何实现服务端接收多个客户端的消息的?

  • 主线程使用了循环负责接收客户端Socket管道连接
  • 每接收到一个Socket通信管道分配一个独立的线程负责处理