UDP和TCP的区别、网络编程(UDP回显服务器、TCP回显服务器)

目录

一、什么是网络编程

二、网络编程的内容概念

接受端和发送端

请求和响应

服务端和客户端

三、UDP和TCP协议的区别

四、UDP网络编程的类和函数(回显服务器)

DatagramSocket

DatagramPacket

InetSocketAddress

基于UDP的回显服务器和客户端:

UDP回显服务器

UDP回显客户端

五、TCP网络编程的类和函数(回显服务器)

ServerSocket

Socket

基于TCP的回显服务器和客户端:

TCP回显服务器

Tcp回显客户端


一、什么是网络编程

指⽹络上的主机,通过不同的进程,以编程的⽅式实现⽹络通信(或称为⽹络数据传输)。也就是客户端和服务器之间的通信。

++我们如果去xx视频看电视剧,也就是发出请求(我要看这个电视剧,请服务器给我响应),然后服务器就会给你这个视频的地址(响应),然后你就能看到电视剧了。++

当然,我们只要满⾜进程不同就⾏;所以即便是同⼀个主机,只要是不同进程,基于⽹络来传输数 据,也属于⽹络编程。 (由于设备有限,这里都用一个主机来示范)

++如上图:++

服务器就是个进程,客户端也是个进程。服务器为客户端提供数据。

二、网络编程的内容概念

接受端和发送端

在⼀次⽹络数据传输时:

接收端:收数据的一方,也就是网络通信的源主机。

发送端:发数据的一方,也就是网络通信的目的主机。

请求和响应

获取⼀个⽹络资源,涉及到两次⽹络数据传输,如下:

请求:请求数据的发送。

响应:响应数据的发送。

++比如:你去一个餐馆,要点餐。++

请求:你说老板我要一份酸菜鱼。响应:老板听完后做了份酸菜鱼 。

服务端和客户端

在常⻅的⽹络数据传输场景下:

服务端:提供服务的⼀⽅进程,可以提供对外服务。

客户端:获取服务的⼀⽅进程,称为客⼾端。

三、UDP和TCP协议的区别

UDP:无连接、不可靠传输、面向数据报、全双工

TCP:有连接、可靠传输、面向字节流、全双工

有无连接:这里的"连接"是一种抽象的连接,是绑定对方信息。若双方都绑定对方的信息,则是有连接,没有绑定对方的信息则是无连接。就像结婚证,一式两份上面都有自己和爱人的信息。

可不可靠传输:这个也不是我们说这个人可不可靠的那种可靠,而是这个协议它有没有"尽可能"的去传输数据。就比如要传一个信息,可能会失败,这时候采取什么样的行动呢?如果可靠,它可能会进行重新发送啊,或者过一会儿发送什么什么的;但是不可靠传输只管传,其他什么都不会做,所以是不可靠传输。

面向字节流,面向数据报:这里指的是传输的基本单位是什么,面向字节流的基本是字节流,而面向数据报的基本单位是数据报。两者不同,传输的方式也会不一样。

全双工:同个时间内,A->B和B->A可以同步进行。而半双工则是同个时间,只能单向进行。

四、UDP网络编程的类和函数(回显服务器)

DatagramSocket

DatagramSocket 是UDP Socket,⽤于发送和接收UDP数据报。

DatagramSocket 构造⽅法:

|--------------------------|----------------------------------------------|
| 方法签名 | 方法说明 |
| DatagramSocket() | 创建⼀个UDP数据报套接字的Socket,绑定到本机任 意⼀个随机端⼝(⼀般⽤于客⼾端) |
| DatagramSocket(int port) | 创建⼀个UDP数据报套接字的Socket,绑定到本机指 定的端⼝(⼀般⽤于服务端) |

DatagramSocket ⽅法:

|--------------------------------|---------------------------------|
| 方法签名 | 方法说明 |
| void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该⽅法会阻塞等待) |
| void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
| void close() | 关闭此数据报套接字 |

DatagramPacket

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

DatagramPacket 构造⽅法:

|-----------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
| 方法签名 | 方法说明 |
| DatagramPacket(byte[] buf, int length) | 构造⼀个DatagramPacket以⽤来接收数据报,接收的 数据保存在字节数组(第⼀个参数buf)中,接收指定 ⻓度(第⼆个参数length) |
| DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) | 构造⼀个DatagramPacket以⽤来发送数据报,发送的 数据为字节数组(第⼀个参数buf)中,从0到指定⻓ 度(第⼆个参数length)。address指定⽬的主机的IP 和端⼝号 |

DatagramPacket ⽅法:

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

构造UDP发送的数据报时,需要传⼊ SocketAddress ,该对象可以使⽤ InetSocketAddress 来创建。

InetSocketAddress

InetSocketAddress ( SocketAddress 的⼦类 )构造⽅法:

|-----------------------------------------------|-------------------------|
| 方法签名 | 方法说明 |
| InetSocketAddress(InetAddress addr, int port) | 创建⼀个Socket地址,包含IP地址和端⼝号 |

基于UDP的回显服务器和客户端:

UDP回显服务器

java 复制代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpEchoServer {
    private DatagramSocket socket=null;

    public UdpEchoServer(int port) throws SocketException {
        socket=new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器开启!");
        while(true){
            // 1. 读取客户端的请求并解析
            DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            // 上述收到的数据, 是二进制 byte[] 的形式体现的. 后续代码如果要进行打印之类的处理操作
            // 需要转成字符串才好处理.
            String request=new String(requestPacket.getData(),
                    0,requestPacket.getLength());
            // 2. 根据请求计算响应, 由于此处是回显服务器. 响应就是请求.
            String respond=process(request);
            // 3. 把响应写回到客户端
            DatagramPacket respondPacket =new DatagramPacket(respond.getBytes(),
                    0,respond.getBytes().length,requestPacket.getSocketAddress());
            socket.send(respondPacket);
            // 4. 把日志打印一下.
            System.out.printf("[%s:%d] req=%s,res=%s\n",requestPacket.getAddress(),
                    requestPacket.getPort(),request,respond);
        }
    }

    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer udpEchoServer=new UdpEchoServer(9090);
        udpEchoServer.start();
    }
}

1)用DatagramSocket创建出一个UDP服务器,参数为端口号(比如9090,就是约定客户端找到服务器的地方,就像约会地点);这里不需要ip地址,因为每个机子才有ip地址,所以直接在客户端new一个对象,参数输入服务器的ip地址就行了。

2)开始服务器,由于服务器基本上都是7*24小时运行的,所以我们用个while循环来一直给它运行;

3)我们用DatagramPacket来接受服务器传来的数据报,传满这个byte数组,然后包装成DatagramPacket对象,让服务器来接收;

4)我们现在是学习,为了方便查看里面的数据,所以我们把数据报转成字符串,给服务器去响应。

5)写一个process响应函数(回显服务器,其实就是return客户端发来的信息),来响应客户端发来的信息,这里我们就把刚刚的字符串传进去,然后返回同样的数据。

6)把这个返回来的信息,我们拿DatagramPacket来包装起来,发回给客户端,这样就完成了响应。(注意的是,发回去的时候要传客户端的ip和端口号,不然都不知道要传给谁)

7)打印日志。

UDP回显客户端

java 复制代码
import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;

    public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

    private void start() throws IOException {
        System.out.println("启动客户端");
        // 1. 从控制台读取到用户的输入.
        Scanner scanner = new Scanner(System.in);

        while (true) {

            System.out.print("->");
            String request = scanner.next();
            // 2. 构造出一个 UDP 请求, 发送给服务器.
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes()
                    , request.getBytes().length, InetAddress.getByName(serverIP), this.serverPort);
            socket.send(requestPacket);
            // 3. 从服务器读取到响应
            DatagramPacket respondPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(respondPacket);
            String respond = new String(respondPacket.getData(), 0, respondPacket.getLength());
            // 4. 把响应打印到控制台上.
            System.out.println(respond);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);
        udpEchoClient.start();
    }
}

1)我们UDP的客户端的构造函数是要有服务器IP和端口号的,因为客户端要主动发数据报给服务器,但是new客户端对象的时候却不需要,因为服务器最开始不需要主动找客户端,而且指定端口号的话,可能会和客户的电脑中的端口号冲突,就会产生bug;

2)因为客户端可能7*24小时都有人发数据,所以我们也是做一个while循环,输入字符串,这时候我们构建DatagramPacket来接受一下,所以这个字符串传进来的时候要getByte()一下,然后send发送这个datagrampacket对象;

3)然后等待响应,接收服务器传来的响应,也是用DatagramPacket来接收;

4)为了方便观看,也是转成字符串,打印出来。

五、TCP网络编程的类和函数(回显服务器)

ServerSocket

ServerSocket 是创建TCP服务端Socket的API。

ServerSocket 构造⽅法:

|------------------------|----------------------------|
| 方法签名 | 方法说明 |
| ServerSocket(int port) | 创建⼀个服务端流套接字Socket,并绑定到指定端⼝ |

ServerSocket ⽅法:

|-----------------|------------------------------------------------------------------------|
| 方法签名 | 方法说明 |
| Socket accept() | 开始监听指定端⼝(创建时绑定的端⼝),有客⼾端连接后,返回⼀个服务端Socket对象,并基于该 Socket建⽴与客⼾端的连接,否则阻塞等待 |
| void close() | 关闭此套接字 |

因为TCP是有连接的,所以需要accept来连接一下,但这里的连接是抽象的连接(这里的连接我们是察觉不到的,是系统内核做的),这里的accept类似于做了一个"确认连接"的作用。

就像打电话,A打给B,会经历一系列的数据传输(比如给基站发数据说我要大给B,但这里我们是察觉不到的,很快B就响铃了)这时B接通了,就相当于accept连接完成了。

Socket就像去揽客,,然后accept连接出来的是一个客户端对象(网卡),把它揽进来,相当于我们对它进行一对一的服务。

Socket

Socket 是客⼾端Socket,或服务端中接收到客⼾端建⽴连接(accept⽅法)的请求后,返回的服 务端Socket。

不管是客⼾端还是服务端Socket,都是双⽅建⽴连接以后,保存的对端信息,及⽤来与对⽅收发数据的。

Socket 构造⽅法:

|-------------------------------|-------------------------------------------|
| 方法签名 | 方法说明 |
| Socket(String host, int port) | 创建⼀个客⼾端流套接字Socket,并与对应IP的主机 上,对应端⼝的进程建⽴连接 |

Socket ⽅法:

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

基于TCP的回显服务器和客户端:

TCP回显服务器

java 复制代码
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {
    private ServerSocket serverSocket=null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket=new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务器!");
        ExecutorService service= Executors.newCachedThreadPool();
        while (true){
            Socket clientSocket=serverSocket.accept();
            //不止可以创建线程,还可以使用线程池
//            Thread t=new Thread(()->{
//                processConnection(clientSocket);
//            });
//            t.start();
            //可以使用线程池,效率更高
            service.submit(()->{
                processConnection(clientSocket);
            });
        }
    }
    // 针对一个连接, 提供处理逻辑
    private void processConnection(Socket clientSocket) {
        // 先打印一下客户端的信息
        System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
        // 获取到 socket 中持有的流对象.
        try(InputStream inputStream=clientSocket.getInputStream();
            OutputStream outputStream=clientSocket.getOutputStream()) {
            // 使用 Scanner 包装一下 inputStream. 就可以更方便的读取这里的请求数据了.
            Scanner scanner=new Scanner(inputStream);
            PrintWriter writer=new PrintWriter(outputStream);
            while (true){
                // 1. 读取请求并解析
                if (!scanner.hasNext()){
                    // 如果 scanner 无法读取出数据, 说明客户端关闭了连接, 导致服务器这边读取到 "末尾"
                    break;
                }

                String request=scanner.next();
                // 2. 根据请求计算响应
                String respond=process(request);
                //3. 把响应写回给客户端
                // 此处可以按照字节数组直接来写, 也可以有另外一种写法.
                // outputStream.write(response.getBytes());
                writer.println(respond);
                writer.flush();

                // 4. 打印日志
                System.out.printf("[%s:%d],req=%s res=%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,respond);

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());


    }

    private String process(String request) {
        return request;
    }

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

1)用ServerSocket创建出一个TCP服务器,参数为端口号(比如9090,就是约定客户端找到服务器的地方,就像约会地点),这里不需要ip地址,因为每个机子才有ip地址,所以直接在客户端new一个对象,参数输入服务器的ip地址就行了。

2)因为服务器都是7*24小时运行的,所以我们用while循环把他们框起来,然后因为客户很多,频繁的创建和开销很消耗资源,所以我们考虑使用线程池来解决这个问题,注意这里使用的是newCachedThreadPool(),因为这个方法的线程池数量很大,21亿可以解决这个问题。

3)我们通过服务器的accept()方法来接收客户端对象,用Socket接收,得到的就是操作客户端的网卡;

4)因为TCP是面向字节流的,所以我们使用IO操作,来获取这些字节流,为了方便,我们把inputstream对象读到的字节传到scanner里面,这样非常的方便读取。同理outputstream作为参数传入printwriter里面,为什么用这个呢,因为这个输出的时候是需要结尾为空白符作为结束标志的,而printwriter有个方法println,会有\n作为结束标志,就不要手动打\n了。这样数据流就取一个完整的数据流了,就不会和下一个数据流混一起了。

5)其余的看看源代码,其实也差不多和UDP类似,就那么一些出入而已。

6)flush()是"冲刷缓存区",IO操作时间长,很消耗资源,所以内核优化了一下,得攒一堆IO才会一起发送,不然的话就会卡着,发不出去。

7)打印日志

Tcp回显客户端

java 复制代码
import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    private Socket clientSocket=null;

    public TcpEchoClient(String serverIP,int serverPort) throws IOException {
        clientSocket=new Socket(serverIP,serverPort);
    }

    public void start(){
        System.out.println("客户端启动!");
        try(InputStream inputStream=clientSocket.getInputStream();
            OutputStream outputStream=clientSocket.getOutputStream()) {
            Scanner scanner=new Scanner(inputStream);
            Scanner scannerIn=new Scanner(System.in);
            PrintWriter writer=new PrintWriter(outputStream);
            while (true){
                // 1. 从控制台读取到用户的输入.
                System.out.print("->");
                String request=scannerIn.next();
                // 2. 构造出一个 UDP 请求, 发送给服务器.
                writer.println(request);
                writer.flush();
                // 3. 从服务器读取响应
                if(!scanner.hasNext()){
                    break;
                }
                String respond=scanner.next();
                // 4. 打印响应结果
                System.out.println(respond);
            }

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

    public static void main(String[] args) throws IOException {
        TcpEchoClient tcpEchoClient=new TcpEchoClient("127.0.0.1",9090);
        tcpEchoClient.start();
    }
}

1)我们UDP的客户端的构造函数是要有服务器IP和端口号的,因为客户端要主动发数据报给服务器,但是new客户端对象的时候却不需要,因为服务器最开始不需要找客户端,而且指定端口号的话,可能会和客户的电脑中的端口号冲突,就会产生bug。

2)接下来的基本上与前面几个同理,看看代码,这里就不赘述了。

相关推荐
hzyyyyyyyu11 分钟前
内网安全隧道搭建-ngrok-frp-nps-sapp
服务器·网络·安全
刽子手发艺38 分钟前
WebSocket详解、WebSocket入门案例
网络·websocket·网络协议
速盾cdn5 小时前
速盾:CDN是否支持屏蔽IP?
网络·网络协议·tcp/ip
yaoxin5211235 小时前
第二十七章 TCP 客户端 服务器通信 - 连接管理
服务器·网络·tcp/ip
内核程序员kevin5 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
PersistJiao6 小时前
Spark 分布式计算中网络传输和序列化的关系(一)
大数据·网络·spark
黑客Ash9 小时前
【D01】网络安全概论
网络·安全·web安全·php
->yjy9 小时前
计算机网络(第一章)
网络·计算机网络·php
摘星星ʕ•̫͡•ʔ10 小时前
计算机网络 第三章:数据链路层(关于争用期的超详细内容)
网络·计算机网络