TCP协议

TCP协议特点
使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
传输前,采用“三次握手”方式建立连接,所以是可靠的 。
在连接中可进行大数据量的传输 。
连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。

TCP协议通信场景
对信息安全要求较高的场景,例如:文件下载、金融等数据通信

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

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

TCP通信-一发一收消息

客户端发送消息 [一发 一收]
需求:客户端实现步骤
创建客户端的Socket对象,请求与服务端的连接。
使用socket对象调用getOutputStream()方法得到字节输出流。
使用字节输出流完成数据的发送。
释放资源:关闭socket管道。

package com.itheima.d5_socket1;

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

/**
   目标:完成Socket网络编程入门案例的客户端开发,实现1发1收。
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

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

            // 4、发送消息
            ps.println("我是TCP的客户端,我已经与你对接,并发出邀请:约吗?");
            ps.flush();

            // 关闭资源。
            // socket.close();

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

TCP通信的客户端的代表类是谁?
Socket类
public Socket(String host , int port)

TCP通信如何使用Socket管道发送、接收数据。
OutputStream getOutputStream():获得字节输出流对象(发)
InputStream getInputStream():获得字节输入流对象(收)

服务端

服务端实现接收消息
需求:服务端实现步骤
创建ServerSocket对象,注册服务端端口。
调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象。
通过Socket对象调用getInputStream()方法得到字节输入流、完成数据的接收。
释放资源:关闭socket管道 

package com.itheima.d5_socket1;

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

/**
   目标:开发Socket网络编程入门代码的服务端,实现接收消息
 */
public class ServerDemo2 {
    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 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();
        }
    }
}

===服务端启动成功===
/127.0.0.1:49454说了:: 我是TCP的客户端,我已经与你对接,并发出邀请:约吗?

====客户端启动===

TCP通信服务端用的代表类?
ServerSocket类,注册端口。
调用accept()方法阻塞等待接收客户端连接。得到Socket对象。
TCP通信的基本原理?
客户端怎么发,服务端就应该怎么收。
客户端如果没有消息,服务端会进入阻塞等待。
Socket一方关闭或者出现异常、对方Socket也会失效或者出错。

 

TCP通信-多发多收消息

需求:使用TCP通信方式实现:多发多收消息。
具体要求:
可以使用死循环控制服务端收完消息继续等待接收下一个消息。
客户端也可以使用死循环等待用户不断输入消息。
客户端一旦输入了exit,则关闭客户端程序,并释放资源。

package com.itheima.d6_socket2;

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

/**
   目标:实现多发和多收
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

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

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                // 4、发送消息
                ps.println(msg);
                ps.flush();
            }

            // 关闭资源。
            // socket.close();

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

 

package com.itheima.d6_socket2;

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

/**
   目标:开发Socket网络编程入门代码的服务端,实现接收消息
 */
public class ServerDemo2 {
    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            while (true) {
                // 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;
                while ((msg = br.readLine()) != null){
                    System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

===服务端启动成功===
/127.0.0.1:50221说了:: 你好
/127.0.0.1:50221说了:: 在哪呢
/127.0.0.1:50221说了:: 退出
/127.0.0.1:50221说了:: exit

====客户端启动===
请说:
你好
请说:
在哪呢
请说:
退出
请说:
exit
请说

本案例实现了多发多收,那么是否可以同时接收多个客户端的消息?
不可以的。
因为服务端现在只有一个线程,只能与一个客户端进行通信。

本次多发多收是如何实现的
客户端使用循环反复地发送消息。
服务端使用循环反复地接收消息。
现在服务端为什么不可以同时接收多个客户端的消息。
目前服务端是单线程的,每次只能处理一个客户端的消息。

TCP通信-同时接受多个客户端消息

1、之前我们的通信是否可以同时与多个客户端通信,为什么?
不可以的
单线程每次只能处理一个客户端的Socket通信
2、如何才可以让服务端可以处理多个客户端的通信需求?
引入多线程。

 

package com.itheima.d7_socket3;

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

/**
    目标:实现服务端可以同时处理多个客户端的消息。
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

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

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                // 4、发送消息
                ps.println(msg);
                ps.flush();
            }

            // 关闭资源。
            // socket.close();

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

 

package com.itheima.d7_socket3;

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) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
            while (true) {
                // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
                // 3、开始创建独立线程处理socket
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
package com.itheima.d7_socket3;

import java.io.BufferedReader;
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) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
        }
    }
}

===服务端启动成功===
/127.0.0.1:50340它来了,上线了!
/127.0.0.1:50340说了:: 12
/127.0.0.1:50348它来了,上线了!
/127.0.0.1:50348说了:: 12
/127.0.0.1:50340说了:: 13

 

====客户端启动===
请说:
12
请说:
13
请说:

 

====客户端启动===
请说:
12
请说:

本次是如何实现服务端接收多个客户端的消息的。
主线程定义了循环负责接收客户端Socket管道连接
每接收到一个Socket通信管道后分配一个独立的线程负责处理它。

1、目前的通信架构存在什么问题?
客户端与服务端的线程模型是: N-N的关系。
客户端并发越多,系统瘫痪的越快。

线程池处理多个客户端消息

 

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

 

package com.itheima.d8_socket4;

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

/**
    拓展:使用线程池优化:实现通信。
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 6666);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

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

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                // 4、发送消息
                ps.println(msg);
                ps.flush();
            }
            // 关闭资源。
            // socket.close();

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

 

package com.itheima.d8_socket4;
import com.itheima.d7_socket3.ServerReaderThread;

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

/**
   目标:实现服务端可以同时处理多个客户端的消息。
 */
public class ServerDemo2 {

    // 使用静态变量记住一个线程池对象
    private static ExecutorService pool = new ThreadPoolExecutor(300,
            1500, 6, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2)
    , Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(6666);
            // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
            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();
        }
    }
}

 

package com.itheima.d8_socket4;

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) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
        }
    }
}

 

posted @ 2022-11-30 23:10  __破  阅读(267)  评论(0)    收藏  举报