为什么说UDP基于数据报,TCP基于流传输?

为什么说UDP基于数据报,TCP基于流传输?

​ 在一些八股当中,经常会见到TCP和UDP的不同,其中一点就是“TCP基于流,而UDP基于数据报”,此篇就用java socket来解释为什么会这样

一、UDP为什么基于数据报?

​ 我们都知道socket的底层就是TCP/UDP,在我们编写UDP相关代码时,会有如下代码:

//创建一个socket
DatagramSocket socket=new DatagramSocket();
//重要:创建一个报文段
DatagramPacket packet=new DatagramPacket(new byte[255],0,255);
//发送报文段
socket.send(packet);
//接收报文段
socket.receive(packet);

​ 由以上代码可以得出UDP是以数据报的形式传输的。

1.UDP具有接收缓冲区

​ UDP是有接收缓冲区的,当我们去写一个服务端socket去接收数据时,我们会有如下代码:

//创建一个socket
DatagramSocket socket=new DatagramSocket();
//创建接收缓冲区
byte[] buf=new byte[255];
//创建一个报文段
DatagramPacket packet=new DatagramPacket(buf,0,buf.length);
//接收数据
//重要:接收报文段时,会将数据放在buf数组中
socket.receive(packet);

​ 这时我们只能看到用户层面,感觉确实好像是有一个byte数组当作缓冲区,那到底有没有使用呢?看一下receive()方法的源码:
在这里插入图片描述

在这里插入图片描述

​ 通过以上两幅源码图可以看到,在recive()方法中,会通过byte数组去接收,最终到了一个本地方法中。

2.UDP没有发送缓冲区

​ UDP是没有发送缓冲区的,同样由代码说明:

//创建一个socket
DatagramSocket socket=new DatagramSocket();
//创建发送的数据
String response="response data";
//创建一个报文段
//参数分别是:相应内容的字节数组,响应内容字节数组大小,目标IP地址和端口号
DatagramPacket packet=new 		DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
//接收数据
//重要:接收报文段时,会将数据放在buf数组中
socket.send(packet);

​ 可以从上述代码中看到,在创建DatagramPacket的时候,并没有传入byte数组作为缓冲区,现在进入send()方法的源码中:
在这里插入图片描述

在这里插入图片描述

​ 以上两张源码图中可以看到,send()方法内调用实现类的send()方法时,直接将我们在用户层面的传入的DatagramPacket传进入了send()方法中,并没有使用缓冲区。

二、TCP为什么是基于流传输?

​ 在socket编程中,TCP编程所需要的socket和UDP是不同的,UDP使用DatagramPacket,而TCP使用serverSocket(服务器)和Socket,在TCP中通过socket想要获取数据,只能通过socket.getInputStream()方法,即通过流的方法进行传输,如以下代码是服务端进行读取数据和响应的一个过程,服务端先得需要获取输入输出流,通过流来读取数据:

public void process(Socket serverSocket) throws IOException {
        //1.获取输入输出流
        InputStream inputStream = serverSocket.getInputStream();
        OutputStream outputStream = serverSocket.getOutputStream();
        try{
            while(true){
                //2.读取请求数据,并解析
                Scanner sc=new Scanner(inputStream);
                if(!sc.hasNext()){
                    //如果没有了数据,则关闭
                    System.out.println("客户端已下线"+",ip:"+serverSocket.getInetAddress()+",port:"+serverSocket.getPort()+"客户端已下线");
                    break;
                }
                //读取请求数据
                String requestData=sc.next();
                //3.查询词典,返回响应
                String responseData=analysis(requestData);
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(responseData);
                printWriter.flush();
                //4.打印响应请求
                System.out.println("[ip:"+serverSocket.getInetAddress()+",port:"
                        +serverSocket.getPort()+"]"+"-->"+"req:"+requestData+",resp:"+responseData);
            }
        } finally {
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
三、基于UDP的简易回显服务器
1.客户端
package com.drl.socket.socket_udp.udp2;

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

/**
 * @Description:客户端
 * @Author:dongRuiLong
 * @Date 创建时间: 2022/5/13 21:23
 **/
public class Client {
    private DatagramSocket client =null;
    private String serverIp;
    private int serverPort;

    /**
     * 创建客户端socket
     * @param serverIp 服务器Ip
     * @param serverPort 服务器端口号
     */
    public Client(String serverIp,int serverPort) throws Exception{
        this.serverIp=serverIp;
        this.serverPort=serverPort;
        //客户端在创建socket对象时,不能把端口号传入进去,在构造方法中传入的端口是自己的端口,并不是发送目的端口
        //当不指定端口的话,就相当于操作系统会分配一个空闲的端口号
        client=new DatagramSocket();
    }

    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true){
            //1.从键盘读取数据
            System.out.println("->");
            //输入一行请求数据
            String requestData = scanner.nextLine();
            if("exit".equals(requestData)){
                System.out.println("bye bye ~");
                return;
            }
            //2.把数据创建成一个UDP请求
            DatagramPacket datagramPacket = new DatagramPacket(requestData.getBytes(),requestData.getBytes().length,
                    InetAddress.getByName(serverIp),serverPort);
            //3.发送数据
            client.send(datagramPacket);
            //4.尝试从服务器读取响应
            byte[] responseBuf=new byte[4099];
            DatagramPacket responsePacket = new DatagramPacket(responseBuf, 0, responseBuf.length);
            client.receive(responsePacket);
            //5.显示读取的结果
            String responseData=new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.println("收到的响应:"+responseData);
        }
    }


    public static void main(String[] args) throws Exception{
        Client client = new Client("127.0.0.1",8888);
        client.start();
    }
}
2.服务端
package com.drl.socket.socket_udp.udp2;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * @Description:服务器
 * @Author:dongRuiLong
 * @Date 创建时间: 2022/5/13 21:29
 **/
public class Server {
    private DatagramSocket server=null;

    public Server(int port) throws Exception{
        server=new DatagramSocket(port);
    }


    /**
     * 启动服务器
     */
    public void start() throws Exception{
        System.out.println("服务器已启动...");
        //服务器一般都是持续运行的,当前服务器也不知道客户端什么时候发送请求,所以会在这块阻塞住,直到接收到数据
        while(true){
            //1.读取请求
            byte[] requestBuf=new byte[4096];
            DatagramPacket requestPacket = new DatagramPacket(requestBuf,0,requestBuf.length);
            server.receive(requestPacket);
            //拿到请求数据
            String requestData=new String(requestPacket.getData(),0,requestPacket.getLength());
            System.out.println("收到的请求:"+requestData);
            //2.针对请求来计算响应
            String responseData=process(requestData);
            //3.返回响应,也要构造一个DatagramPacket
            //直接传入响应的数据二进制数组,注意:设置长度时,要设置byte数组的长度,而不是响应数据字符串的长度
            //DatagramPacket()有三个参数,分别是:要传入数据的二进制数组,二进制数组的长度,目标IP地址和端口号(被封装到SocketAddress中了)
            DatagramPacket responsePacket = new DatagramPacket(responseData.getBytes(),responseData.getBytes().length,requestPacket.getSocketAddress());
            //发送数据
            server.send(responsePacket);
            //4.日志打印
            System.out.println("已向:"+requestPacket.getSocketAddress()+"响应数据");
        }
    }

    /**
     * 根据请求内容返回响应
     * @param requestData
     * @return
     */
    public String process(String requestData){
        return requestData;
    }

    public static void main(String[] args) throws Exception {
        Server server = new Server(8888);
        server.start();
    }
}
四、基于UDP的简易查找字典服务器
1.客户端
package com.drl.socket.socket_udp.udp3;

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

/**
 * @Description:
 * @Author:dongRuiLong
 * @Date 创建时间: 2022/5/14 18:45
 **/
public class UDPDictClient {
    private DatagramSocket client=null;
    private String serverIp;
    private int serverPort;


    public UDPDictClient(String serverIp,int serverPort) throws Exception{
        client=new DatagramSocket();
        this.serverIp=serverIp;
        this.serverPort=serverPort;
    }

    public void start() throws IOException {
        //1.创建发送请求
        Scanner sc = new Scanner(System.in);
        while(true){
            String requestData = sc.next();
            if ("exit".equals(requestData)) {
                System.out.println("bye bye ~");
                return;
            }
            //创建发送数据报
            DatagramPacket requestPacket = new DatagramPacket(requestData.getBytes(),0,
                    requestData.getBytes().length,InetAddress.getByName(serverIp),serverPort);
            client.send(requestPacket);
            //2.接收返回的响应
            byte[] buf=new byte[4096];
            DatagramPacket responsePacket = new DatagramPacket(buf,0,buf.length);
            client.receive(responsePacket);
            String responseData = new String(responsePacket.getData(), 0, responsePacket.getLength());
            //3.打印日志
            System.out.println(requestData+"-->"+responseData);
        }
    }

    public static void main(String[] args) throws Exception{
        UDPDictClient client = new UDPDictClient("127.0.0.1", 8888);
        client.start();
    }
}
2.服务器
package com.drl.socket.socket_udp.udp3;


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.HashMap;

/**
 * @Description:
 * @Author:dongRuiLong
 * @Date 创建时间: 2022/5/14 18:45
 **/
public class UDPDictServer {
    private DatagramSocket server=null;
    private HashMap<String,String> dict=new HashMap<>();

    public UDPDictServer(int port) throws Exception{
        server=new DatagramSocket(port);
        //初始化dict
        dict.put("hello","你好");
        dict.put("cat","小猫");
        dict.put("dog","小狗");
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            //1.读取请求并解析
            byte[] buf=new byte[4096];
            DatagramPacket requestPacket = new DatagramPacket(buf,0,buf.length);
            server.receive(requestPacket);
            String requestData=new String(requestPacket.getData(),0,requestPacket.getLength());
            //2.根据请求计算响应
            String responseData=process(requestData);
            //3.将响应数据封装成数据报,响应到客户端
            DatagramPacket responsePacket = new DatagramPacket(responseData.getBytes(),
                    0,responseData.getBytes().length);
            //发送响应的目的ip和端口号
            responsePacket.setSocketAddress(requestPacket.getSocketAddress());
            server.send(responsePacket);
            System.out.println("reqIp:"+requestPacket.getAddress().getHostAddress()+",reqPort:"+requestPacket.getPort()+",responseData:"+responseData);
        }
    }

    /**
     * 根据请求获取响应
     * @param request
     * @return
     */
    public String process(String request){
        return dict.getOrDefault(request,"没有找到哦");
    }

    public static void main(String[] args) throws Exception {
        UDPDictServer server = new UDPDictServer(8888);
        server.start();
    }
}
五、基于TCP的简易回显服务器
1.客户端
package com.drl.socket.socket_tcp.tcp2;

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

/**
 * @Description: tcp回显服务器客户端
 * @Author:dongRuiLong
 * @Date 创建时间: 2022/5/14 19:44
 **/
public class TcpEchoClient {
    private Socket client=null;
    private String serverIp;
    private int serverPort;


    public TcpEchoClient(String serverIp,int serverPort) throws IOException {
        this.serverIp=serverIp;
        this.serverPort=serverPort;
        //让socket创建的同时,就和服务器尝试创建连接
        client=new Socket(serverIp,serverPort);
    }

    public void start() throws IOException {
        Scanner sc = new Scanner(System.in);
        try{
            OutputStream outputStream = client.getOutputStream();
            InputStream inputStream = client.getInputStream();
            while (true){
                //1.从键盘读取用户输入的数据
                System.out.print("-->");
                String requestData = sc.next();
                if("exit".equals(requestData)){
                    break;
                }
                //2.把输入的数据构造成请求,发送给服务器
                PrintWriter writer = new PrintWriter(outputStream);
                //注意:使用PrintWriter发送数据时,服务器想要读入,则需要以空白行为标志,所以需要使用println
                writer.println(requestData);
                //注意:在写完之后,只是将数据写入了缓冲区,需要使用flush()方法刷新缓冲区
                writer.flush();
                //3.从服务器读取响应并解析
                Scanner respScanner = new Scanner(inputStream);
                String responseData=respScanner.next();
                //4.将结果显示在界面上
                System.out.println("发送请求:"+requestData+",响应了:"+responseData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            client.close();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1",8888);
        client.start();
    }
}
2.服务器
package com.drl.socket.socket_tcp.tcp2;

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;

/**
 * @Description: tcp回显服务器服务器
 * @Author:dongRuiLong
 * @Date 创建时间: 2022/5/14 19:44
 **/
public class TcpEchoServer {
    private ServerSocket listenSocket=null;

    /**
     *
     * @param port
     * @throws IOException
     */
    public TcpEchoServer(int port) throws IOException {
        listenSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动...");
        while (true){
            //TCP是有连接的,所以先需要使用accept()来进行连接
            //  1.如果客户端没有建立连接,accept()就是阻塞等待
            //  2.如果有客户端建立连接,accept()就是返回一个socket对象
            //进一步的服务器和客户端之间的交互,就交给clientSocket来完成了
            Socket clientSocket = listenSocket.accept();
            processConnection(clientSocket);
        }
    }


    public void processConnection(Socket clientSocket) throws IOException {
        //打印日志
        System.out.println("客户端上线了,ip:"+clientSocket.getInetAddress()+
                ",port:"+clientSocket.getPort());
        try{
            //获取输入输出流
            InputStream inputStream=clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream();
            while(true){
                //1.读取请求并解析
                Scanner sc = new Scanner(inputStream);
                //如果没有数据了,则结束循环
                if(!sc.hasNext()){
                    System.out.println("客户端已下线...");
                    break;
                }
                String requestData = sc.next();
                //2.根据请求计算响应
                String responseData=process(requestData);
                //3.将响应写回给客户端
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(responseData);
                writer.flush();
                //打印响应信息
                System.out.println("client:ip:"+clientSocket.getInetAddress().toString()
                        +",port:"+clientSocket.getPort()+",responseData:"+responseData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关闭
            clientSocket.close();
        }
    }

    /**
     * 回显服务器响应处理
     * @param requestData
     * @return
     */
    public String process(String requestData){
        return requestData;
    }

    public static void main(String[] args) throws Exception{
        TcpEchoServer server = new TcpEchoServer(8888);
        server.start();
    }
}
六、基于TCP的简易回显服务器(多线程版)

​ 使用多线程主要是解决了多个客户端连接服务器的问题,服务器可以处理多个客户端的请求。

1.客户端
package com.drl.socket.socket_tcp.tcp2;

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

/**
 * @Description: tcp回显服务器客户端
 * @Author:dongRuiLong
 * @Date 创建时间: 2022/5/14 19:44
 **/
public class TcpEchoClient {
    private Socket client=null;
    private String serverIp;
    private int serverPort;


    public TcpEchoClient(String serverIp,int serverPort) throws IOException {
        this.serverIp=serverIp;
        this.serverPort=serverPort;
        //让socket创建的同时,就和服务器尝试创建连接
        client=new Socket(serverIp,serverPort);
    }

    public void start() throws IOException {
        Scanner sc = new Scanner(System.in);
        try{
            OutputStream outputStream = client.getOutputStream();
            InputStream inputStream = client.getInputStream();
            while (true){
                //1.从键盘读取用户输入的数据
                System.out.print("-->");
                String requestData = sc.next();
                if("exit".equals(requestData)){
                    break;
                }
                //2.把输入的数据构造成请求,发送给服务器
                PrintWriter writer = new PrintWriter(outputStream);
                //注意:使用PrintWriter发送数据时,服务器想要读入,则需要以空白行为标志,所以需要使用println
                writer.println(requestData);
                //注意:在写完之后,只是将数据写入了缓冲区,需要使用flush()方法刷新缓冲区
                writer.flush();
                //3.从服务器读取响应并解析
                Scanner respScanner = new Scanner(inputStream);
                String responseData=respScanner.next();
                //4.将结果显示在界面上
                System.out.println("发送请求:"+requestData+",响应了:"+responseData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            client.close();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1",8888);
        client.start();
    }
}
2.服务器
package com.drl.socket.socket_tcp.tcp2;

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;

/**
 * @Description: tcp回显服务器服务器
 * @Author:dongRuiLong
 * @Date 创建时间: 2022/5/14 19:44
 **/
public class TcpEchoServer {
    private ServerSocket listenSocket=null;

    /**
     *
     * @param port
     * @throws IOException
     */
    public TcpEchoServer(int port) throws IOException {
        listenSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动...");
        while (true){
            //TCP是有连接的,所以先需要使用accept()来进行连接
            //  1.如果客户端没有建立连接,accept()就是阻塞等待
            //  2.如果有客户端建立连接,accept()就是返回一个socket对象
            //进一步的服务器和客户端之间的交互,就交给clientSocket来完成了
            Socket clientSocket = listenSocket.accept();
            //使用多线程实现多个客户端连接服务器
            if(clientSocket!=null){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            processConnection(clientSocket);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
    }


    public void processConnection(Socket clientSocket) throws IOException {
        //打印日志
        System.out.println("客户端上线了,ip:"+clientSocket.getInetAddress()+
                ",port:"+clientSocket.getPort());
        try{
            //获取输入输出流
            InputStream inputStream=clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream();
            while(true){
                //1.读取请求并解析
                Scanner sc = new Scanner(inputStream);
                //如果没有数据了,则结束循环
                if(!sc.hasNext()){
                    System.out.println("客户端已下线...");
                    break;
                }
                String requestData = sc.next();
                //2.根据请求计算响应
                String responseData=process(requestData);
                //3.将响应写回给客户端
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(responseData);
                writer.flush();
                //打印响应信息
                System.out.println("client:ip:"+clientSocket.getInetAddress().toString()
                        +",port:"+clientSocket.getPort()+",responseData:"+responseData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关闭
            clientSocket.close();
        }
    }

    /**
     * 回显服务器响应处理
     * @param requestData
     * @return
     */
    public String process(String requestData){
        return requestData;
    }

    public static void main(String[] args) throws Exception{
        TcpEchoServer server = new TcpEchoServer(8888);
        server.start();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值