Java网络编程 - TCP通信

Posted 学全栈的灌汤包

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java网络编程 - TCP通信相关的知识,希望对你有一定的参考价值。

文章目录

TCP通信

快速入门(一发一收)

TCP协议回顾:

TCP是一种面向连接,安全、可靠的传输数据的协议

传输前,采用“三次握手”方式,点对点通信,是可靠的

在连接中可进行大数据量的传输

TCP通信模式:

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

编写客户端代码

Socket(客户端):

构造器说明
Socket(String host , int port)创建发送端的Socket对象与服务端连接,参数为服务端程序的ip和端口。

Socket类成员方法:

方法说明
OutputStream getOutputStream()获得字节输出流对象
InputStream getInputStream()获得字节输入流对象

客户端实现步骤:

  • 创建客户端的Socket对象,请求与服务端的连接。
  • 使用socket对象调用getOutputStream()方法得到字节输出流。
  • 使用字节输出流完成数据的发送。
  • 不建议直接关闭socket管道释放资源, 一般用户退出时才会关闭。
/**
    客户端
 */
public class ClientDemo 
    public static void main(String[] args) 
        try 
            // 1. 创建socket通信管道请求与服务端进行连接
            /**
                参数一: 服务器IP地址
                参数二: 服务器端口号
             */
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2. 从socket通信管道中获取到字节输出流
            OutputStream os = socket.getOutputStream();
            // 包裹低级字节输出流为字节打印流
            PrintStream ps = new PrintStream(os);

            // 3. 打印流发送消息
            ps.println("我是TCP的客户端");
            ps.flush(); // 刷新
         catch (Exception e) 
            e.printStackTrace();
        

    

编写服务器代码

ServerSocket(服务端):

构造器说明
ServerSocket(int port)注册服务端端口

ServerSocket类成员方法:

方法说明
Socket accept()等待接收客户端的Socket通信连接
连接成功返回Socket对象与客户端建立端到端通信

服务端实现步骤:

  • 创建ServerSocket对象,注册服务端端口。
  • 调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象。
  • 通过Socket对象调用getInputStream()方法得到字节输入流、完成数据的接收。
  • 不建议直接关闭socket管道释放资源, 一般用户退出时才会关闭。
/**
    服务器
 */
public class ServerDemo 
    public static void main(String[] args) 
        try 
            // 1. 创建ServerSocket对象注册服务器端口
            ServerSocket serverSocket = new ServerSocket(7777);

            // 2. 调用accept方法, 等待客户端连接, 连接成功返回socket管道对象
            Socket socket = serverSocket.accept();

            // 3. 从socket管道中获取字节输入流, 完成数据接受
            InputStream is = socket.getInputStream();
            // 把字节输入流包装为缓冲字符输入流进行消息接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            // 按照行读取
            String message;
            if ((message = br.readLine()) != null) 
                System.out.println(message);
            

         catch (Exception e) 
            e.printStackTrace();
        

    

多发多收

需求

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

具体要求

  • 可以使用死循环控制服务端收完消息继续等待接收下一个消息。
  • 客户端也可以使用死循环等待用户不断输入消息。
  • 客户端一旦输入了exit,则关闭客户端程序,并释放资源。

客户端

/**
    客户端
 */
public class ClientDemo 
    public static void main(String[] args) 
        try 
            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 inp = scanner.nextLine();
                // 一旦输入了exit,则关闭客户端程序,并释放资源
                if (inp.equals("exit")) 
                    System.out.println("下线成功");
                    ps.close();
                    break;
                
                ps.println(inp);
                ps.flush();
            
         catch (Exception e) 
            e.printStackTrace();
        

    

服务端

/**
    服务器
 */
public class ServerDemo 
    public static void main(String[] args) 
        try 
            ServerSocket serverSocket = new ServerSocket(7777);

            Socket socket = serverSocket.accept();

            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));

            String message;
            // 死循环控制服务端收完消息继续等待接收下一个消息
            while ((message = br.readLine()) != null) 
                System.out.println("收到消息: " + message);
            

         catch (Exception e) 
            e.printStackTrace();
        

    

多发多收(同时接受多个客户端)

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

不可以的。

因为服务端现在只有一个线程,只能与一个客户端进行通信; 并且上面代码中, 我们只连接了一个客户端然后就在死循环接受消息。

那么如何才可以让服务端可以处理多个客户端的通信需求

引入多线程。

同时处理多个客户端消息实现架构如下:

主线程死循环不断地接收socket链接, 每成功链接一个socket, 就交给子线程处理

实现步骤如下:

优化服务器代码即可

创建一个线程类, 用来处理接收消息

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 br = new BufferedReader(new InputStreamReader(is));

            String message;
            // 死循环控制服务端收完消息继续等待接收下一个消息
            while ((message = br.readLine()) != null) 
                System.out.println("收到消息: " + message);
            
         catch (Exception e) 
            e.printStackTrace();
        
    

在服务器主线程中, 每链接到一个socket都要创建一个线程类交给子线程处理

/**
    服务器
 */
public class ServerDemo 
    public static void main(String[] args) 
        try 
            ServerSocket serverSocket = new ServerSocket(7777);
            // 1. 主线程中定义一个死循环由主线程不断地接收客户端socket管道连接
            while (true) 
                // 2. 每接收到一个socket管道, 都交给一个独立的子线程负责读取消息
                Socket socket = serverSocket.accept();
                // 交给子线程处理, 并启动子线程
                new ServerReaderThread(socket).start();
            
         catch (Exception e) 
            e.printStackTrace();
        

    

线程池优化

目前的通信架构存在什么问题

客户端与服务端的线程模型是: 1-1的关系, 有多少客户端就会创建多少线程。

客户端并发越多,系统瘫痪的越快。

引入线程池处理多个客户端消息的架构如下:

线程池优化多发多收, 我们只需要优化服务器的代码即可:

创建一个Runnable任务类

public class ServerReaderRunnable implements Runnable 
    private Socket socket;
    public ServerReaderRunnable(Socket socket) 
        this.socket = socket;
    

    @Override
    public void run() 
        try 
            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));

            String message;
            // 死循环控制服务端收完消息继续等待接收下一个消息
            while ((message = br.readLine()) != null) 
                System.out.println(socket.getRemoteSocketAddress() + "收到消息: " + message);
            
         catch (Exception e) 
            e.printStackTrace();
        
    

优化服务器端代码

/**
    服务器
 */
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) 
        try 
            ServerSocket serverSocket = new ServerSocket(7777);
            while (true) 
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress() + "上线了");
                // 创建Runnable任务交给线程池处理
                pool.execute(new ServerReaderRunnable(socket));
            
         catch (Exception e) 
            e.printStackTrace();
        

    

线程池优势是什么?

服务端可以复用线程处理多个客户端,可以避免系统瘫痪。

适合客户端通信时长较短的场景。
计思想, 客户端将消息发送给服务器, 再由服务器进行转发给其他客户端。

以上是关于Java网络编程 - TCP通信的主要内容,如果未能解决你的问题,请参考以下文章

-1-7 java 网络编程基本知识点 计算机网络 TCP/IP协议栈 通信必备 tcp udp

Java 网络编程 两类传输协议:TCP UDP

Java网络编程:TCP的socket编程

Java网络编程:TCP的socket编程

Java网络编程基础-- 基于TCP/IP的Socket编程

java tcp ip网络编程 套接字的基本使用