传输层-第八节2:TCP连接管理实践部分

Posted 快乐江湖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了传输层-第八节2:TCP连接管理实践部分相关的知识,希望对你有一定的参考价值。

此部分为补充内容,主要使用Java实现TCP和UDP通信

一:UDP通信

(1)Java数据报套接字通信模型

Java UDP通信模型:Java中使用UDP协议通信,主要依靠一下两个类

  • DatagramSocket:创建UDP Socket
  • DatagramPacket:是UDP Socket发送和接受的数据报

一次发送和接收UDP数据报流程如下

多个请求流程如下

(2)API接口说明

A:DatagramSocket

DatagramSocket:用于构造UDP Socket,以发送和接收UDP数据报

构造方法如下

方法签名方法说明
DatagramSocket()创建一个UDP数据报 Socket,绑定到本机任意一个随机端口 ,一般用于客户端
DatagramSocket(int port)创建一个UDP数据报 Socket,绑定到本机任意指定端口 ,一般用于服务端

成员方法如下

方法签名方法说明
void receive(DatagramPacket p)从该Socket中接收数据报(如果未收到则会阻塞等待
void send(DatagramPacket p)由该Socket发送数据报(不会阻塞等待,直接发送
void close()关闭此Socket

B:DatagramPacket

DatagramPacket:是UDP Socket发送和接收的数据报

构造方法如下

方法签名方法说明
DatagramPacket(byet[] buf, int length)用于接收数据报,接收的数据保存在buf数组中,length指定接收长度
DatagramPacket(byet[] buf, int offset, int length, SocketAddress address)用于发送数据报,发送的数据为buf数组,范围为从0到lengthaddress是目的主机的IP和端口号

成员方法如下

方法签名方法说明
InetAddress getAddress()从接收的数据报中获取发送端主机IP地址;或从发送的数据报中获取接收端主机IP地址
int getPort()从接收的数据报中获取发送端主机端口号;或从发送的数据报中获取接收端主机端口号
bye[] getData()获取数据报中的数据

(3)示例

  • 注意:这个功能比较简单,但主要目的是为了演示上面所讲API的用法

A:代码

服务端IP地址设置为127.0.0.1,也即本地环回,也即自己发自己收,数据会完整走一遍协议

服务端:UDPServer

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UDPServer 
    // 构造DatagramSocket
    private DatagramSocket socket = null;

    public UDPServer(int port) throws SocketException 
        // 服务端需要绑定一个端口号
        socket = new DatagramSocket(port);
    

    // 服务端启动
    public void start() throws IOException 
        System.out.println("服务器启动!");
        // 服务端随时等待接收客户端请求
        while (true) 
            /*
                 1. 接受请求并解析
                    构造一个DatagramPacket对象requestPacket用于接受客户端发来的数据报,保存在byte[]
                    为了方便查看信息,需要使用 requestPacket的getData()方法将其转换为String
             */
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            /*
                2. 拿到客户端的请求request后,将其交给一个方法process进行处理,process方法会返回响应response
             */
            String response = process(request);
            /*
                3. 将响应回复给客户端
                   构造一个DatagramPacket对象responsePacket用于给客户端回复响应response ,response是String
                   所以需要使用用getBytes()方法将其转化为byte[]
                   客户端发来的requestPacket数据报中携带有客户端的Ip地址和端口号,使用getSocketAddress()获得
             */
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);

            // 打印信息
            System.out.println("【客户端IP: " + requestPacket.getAddress().toString()
                    + "客户端口号:" + requestPacket.getPort() + "】"
                    + ":\\"" + request + "\\"" + ", 服务端回复: " + "\\"" + response + "\\"");
        
    

    // 回显服务器,客户端发什么服务端就回复什么
    public String process(String request) 
        return request;
    

    public static void main(String[] args) throws IOException 
        // 服务端监听9090端口
        UDPServer server = new UDPServer(9090);
        server.start();
    


客户端:UDPClient

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UDPClient 
    DatagramSocket socket = null;
    // 客户端需要指定好服务端的IP和端口号,然后构建一个Socket地址
    private SocketAddress ADDRESS = null;

    public UDPClient(String serverIP, int serverPort) throws SocketException 
        ADDRESS = new InetSocketAddress(serverIP, serverPort);
        socket = new DatagramSocket();
    

    // 客户端启动
    public void start() throws IOException 
        Scanner scanner = new Scanner(System.in);
        while (true) 
            // 1. 读取客户端用户输入
            System.out.print("input: ");
            String request = scanner.next();

            /*
                2. 发送请求给服务端
                   构造一个DatagramPacket对象requestPacket用于给服务端发送数据报,注意需要将String转换为byte[]
                   同时传入服务端的ADDRESS(ip地址和port)
             */
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                   ADDRESS);
            socket.send(requestPacket);

            // 3. 从服务端获取响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());

            // 打印信息
            System.out.println("服务端回复:" + response);

        
    

    public static void main(String[] args) throws IOException 
        UDPClient client = new UDPClient("127.0.0.1", 9090);
        client.start();
    


B:效果展示

一个服务端一个客户端


一个服务端多个客户端

注意如果需要运行多个UDPClient实例,需要在Run/Debug Configurations中选中Allow multiple instances

这里启动三个客户端

C:分析

对于服务端(UDPServer类)

  • 构造方法( public UDPServer(int port)

    • 需要建立一个DatagramSocket并绑定一个指定的端口号port,也即 DatagramSocket socket =new DatagramSocket(port)。服务端绑定端口号的原因是因为程序运行在服务器上是确定的、可控的
  • 服务端处理逻辑(public void start()

    • ①:接受请求并解析

      • 需要构造一个DatagramPacket用于接受客户端发来的数据报,会保存在byte[]中,也即DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096)
      • 接受行为由socket完成,也即socket.receive(requestPacket)
      • 接受到的requestPacket 其类型为byte[],所以需要把它转为String以便于查看,也即String request = new String(requestPacket.getData(), 0, requestPacket.getLength())
    • ②:处理请求

      • 拿到解析后的request后,需要对该request进行处理(交给方法process),不同的业务逻辑会有不同的处理方法。这里我们只是简单的“回显”一下即可,也即客户端发什么服务端就回复什么
    • ③:将处理结果(响应)回复给客户端

      • 需要构造一个DatagramPacket用于给客户端回复响应response,在构造时要将类型为Stringrespnse转化为byte[],同时使用requestPacket.getSocketAddress()获取到客户端的IPport(因为服务端总要知道客户端的具体地址才能发送),也即DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress())
      • 发送行为由socket完成,也即socket.send(responsePacket);
    • ④:打印相关信息

  • main方法

    • 构造UDPServer对象,并绑定指定端口号,如9090,也即UDPServer server = new UDPServer(9090)
    • 启动服务端,也即server.start()

对于客户端(UDPClient类):

  • 构造方法(public UDPClient(String serverIP, int serverPort)

    • 需要建立一个DatagramSocket,但不指定端口号,也即DatagramSocket socket =new DatagramSocket()。客户端无需绑定端口号是因为客户端上运行状况复杂,端口号占用情况各不相同,无法保证所指定的端口号在数据报来临时一定是空闲的,所以这里让系统自动指定空闲端口号即可

    • 需要建立一个InetSocketAddress,传入服务端serverIPserverPort,也即SocketAddress ADDRESS = new InetSocketAddress(serverIP, serverPort),这里的serverPort就是服务端中所指定的那个port。之所以这样做是因为客户端发送时必须要知道服务端的IP地址和port

      • InetSocketAddressSocketAddress的子类
  • 客户端处理逻辑(public void start()

    • ①:读取客户端中用户的输入

      • 使用Scanner接受即可,也即String request = scanner.next()
    • ②:发送请求给服务端

      • 需要构造一个DatagramPacket给服务端发送数据报,注意需要将类型Stringrequest转化为byte[],同时传入服务端的ADDRESS。也即DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, ADDRESS);
      • 发送行为由socket完成,也即socket.send(requestPacket)
    • ③:从服务端获取响应

      • 需要构造一个DatagramPacket用于接受服务端端发来的数据报,会保存在byte[]中,也即DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096)
      • 接受行为由socket完成,也即socket.receive(responsePacket)
      • 接受到的responsePacket 其类型为byte[],所以需要把它转为String以便于查看,也即String response = new String(responsePacket.getData(), 0, resoibsePacket.getLength())
    • ④:打印相关信息

  • main方法

    • 构造UDPClient对象,并给定服务端IPport,也即 UDPClient client = new UDPClient("127.0.0.1", 9090)
    • 启动客户端,也即client.start()

二:TCP通信

(1)Java流套接字通信模型

Java TCP通信模型:Java中使用TCP协议进行通信,主要依靠以下两个类

  • ServerSocket:是创建TCP服务端Socket的API
  • Socket API:是客户端Socket,或服务端中接收到客户端连接(accept方法)的请求后,返回服务端Socket

通信流程如下

(2)API接口说明

A:ServerSocket

ServerSocket:用于创建TCP服务端流套接字Socket

构造方法如下

方法签名方法说明
ServerSocet(int port)创建一个服务端流套接字 Socket,并绑定到指定端口

成员方法如下

方法签名方法说明
Socket accept()开始监听端口,当有客户端连接后会返回一个服务端Socket对象,并基于该Socket 与客户端建立连接,否则阻塞等待
void close()关闭此套接字

B:Socket

Socket :是客户端的Socket(当然也会给服务端用,上面表格说过,当有客户端连接服务端后,会返回一个服务端Socket

构造方法如下

方法签名方法说明
Socket(String host, int port)创建一个客户端流套接字Socket,并和对应IP的主机上、对应端口的进程建立连接

成员方法如下

方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

(3)示例

  • 注意:这个功能比较简单,但主要目的是为了演示上面所讲API的用法

A:代码

服务端IP地址设置为127.0.0.1,也即本地环回,也即自己发自己收,数据会完整走一遍协议

服务端TCPServer:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TCPServer 
    // 创建监听套接字
    private ServerSocket listenSocket = null;

    public TCPServer(int port) throws IOException 
        // 监听套接字绑定指定端口
        listenSocket = new ServerSocket(port);
    

    // 服务器启动
    public void start() throws IOException 
        System.out.println("服务器启动!");
        while (true) 
            // 调用监听套接字的accept()连接客户端,并返回Socket类型的clientSocket
            // 将clientSocket传递给具体处理连接的方法processConnection()进行处理
            Socket clientSocket = listenSocket.accept();
            // 进行处理
            processConnection(clientSocket);
        
    
    // 用于处理连接
    private void processConnection(Socket clientSocket) throws IOException 
        System.out.println("【客户端IP: " + clientSocket.getInetAddress().toString()
                + "客户端口号:" + clientSocket.getPort() + "】"
                + "已上线");

        // 处理请求
        // 打开inputStream和outputStream
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) 
            while (true) 
                // 1. 读取请求并解析
                Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()) 
                    // 如果读完了那么连接可以断开了
                    System.out.println("【客户端IP: " + clientSocket.getInetAddress().toString()
                            + "客户端口号:" + clientSocket.getPort() + "】"
                            + "下线");
                    break;
                
                String request = scanner.next();
                // 2. 根据请求计算响应,具体处理函数为process
                String response = process(request);

                // 3. 响应回复给客户端
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();

                // 打印信息
                System.out.println("【客户端IP: " + clientSocket.getInetAddress().toString()
                        + "客户端口号:" + clientSocket.getPort() + "】"
                        + ":\\"" + request + "\\"" + ", 服务端回复: " + "\\"" + response + "\\"");
            

         catch (IOException e) 
            e.printStackTrace();
         finally 
            // 关闭套接字
            // listenSocket在TCP服务端程序中只有一个,所以不太可能把文件描述符占满
            // 而clientSocket 每遇到一个客户端都要创建一个,所以一定要注意关闭
            clientSocket.close();
        
     

     // 业务逻辑函数
    public String process(String request) 
        return request;
    

    public static void main(String[] args) throws IOException 
        TCPServer server = new TCPServer(9090);
        server.start();
    


客户端TPCClient:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TCPClient 
    // 建立Socket对象
    private Socket socket = null;

    public TCPClient(String serverIP, int serverPort) throws IOException 
        // 指定服务端IP和端口号
        socket = new Socket(serverIP, serverPort);
    

    // 客户端启动
    public void start () throws IOException 
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) 
            while (true) 
                // 1. 获取用户输入
                System.out.print("input: ");
                String request = scanner.next();
                // 2. 发送请求给服务端
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                printWriter.flush();
                // 3. 从服务端获得响应
                Scanner responseScanner = new Scanner(inputStream);
                String response = responseScanner.next();

                // 打印信息
                System.out.println("服务端回复:" + response);
            
         catch (IOException e) 
            e.printStackTrace();
        
    

    public static void main(String[] args) throws IOException 
        TCPClient client = new TCPClient("127.0.0.1", 9090);
        client.start();
    



B:效果展示

C:分析

对于服务端(TCPServer类)

  • 构造方法(public TCPServer(int port)

    • 需要建立一个ServerSocket类型的监听套接字,用于监听客户端的请求连接,也即private ServerSocket listenSocket = new ServerSocket(port)
  • 服务端处理逻辑(public void start()

    • 不断循环一直监听客户端的连接,当有客户端连接之后监听套接字会返回Socket类型的套接字用于处理这个连接,也即Socket clientSocket = listenSocket.accept()
    • 具体处理连接的过程交由processConnection()方法进行,也即processConnection(ClientSocket)
  • 服务端处理连接(private void processConnection(Socket clientSocket)