UDP和TCP中的网络编程

在我们初识完网络知识后,我们了解了关于TCP/IP五层模型

分别是:应用层,传输层,网络层,数据链路层,物理层

这篇文章,我们主要了解关于传输层中相关的俩种协议:UDP TCP

1.UDP和TCP的特点

2.UDP网络编程

3.TCP网络编程

在传输层中,传输层为应用层提供了编程接口(即一组类或者方法)给我们使用 ---- socket

提供了俩组socket,分别是UDP和TCP

UDP和TCP的特点

传输层涉及到了俩种最主要的协议,UDP协议和TCP协议

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

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

有连接 / 无连接

连接就是让俩台主机能够互相的进行通信,互相保存了对方的核心信息(IP和端口号)

断开连接就是删除对方的核心信息

TCP在正式通信之前,就需要保存好双方的关键信息,通信完毕之后再进行删除

UDP在正式通信之前,就不需要

可靠传输 / 不可靠传输

我们在进行网络通信的过程,往往有时候会出现丢包的现象(正常现象)

TCP是可靠传输,他就是在对抗丢包现象的出现,尽可能的将数据包传输给对方,但是有时候还是会出现丢包的现象,无法达到100%的传输,但也可以知道对方是否接收到了数据包

UDP则不考虑是否丢不丢包的情况,爱咋咋地吧~~~~这样传输效率就会比较高,而TCP传输效率就会比较低,毕竟他需要要关注丢包的情况

面向字节流 / 面向数据报

面向字节流:与读文件类似,读写的方式非常的灵活,可以按照规定的字节,分多次进行读取

面向数据报:每次读和写,要以一个UDP数据报为单位进行

全双工 / 半双工

全双工:一个通信,可以进行双向传输

半双工:一个通信,只能单向传输

UDP网络编程

其中DatagramPacket就是UDP数据传输的基本单位

1.)实现一个回显的UDP客户端和服务器

当客户端发送一串字符串,服务器应该原封不动的返回,然后输出在显示器上 ---- 回显

服务器端Code:

java 复制代码
package UDP;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;

//服务端
public class UdpServer {
    private DatagramSocket socket = null;//先不初始化

    //构造方法 --- port就是端口号
    public UdpServer(int port) throws SocketException {
        socket = new DatagramSocket(port) ;
    }

    //启动服务器
    public void start() throws IOException {
        System.out.println("Server Start!!!!");

        //因为服务器是7*24h不断工作,等待客户端发来的请求~~~所以使用死循环
        while(true) {
            //1.接收请求并解析
            //接收请求,需要先申请一个DatagramPacket内存空间
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);//DatagramPacket是以数据包为单位进行读取~~~
            //receive的构造参数时DatagramPacket,所以需要传给他Datagrampacket
            socket.receive(requestPacket);//客户端请求接收

            //因为客户端发送的请求中是二进制数组,我们需要将DatagramPacket转换为字符串 ---- 获取二进制数据,并从下标0到length转换为字符串
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());

            //2.根据请求计算响应(因为我们只需要在显示器打印东西,所以不需要计算)
            //根据请求计算相应,这里我们实现的是回显操作,客户端发送什么,服务器就返回什么即可~~~~
            String response = process(request);

            //3.将响应重新返回给客户端
            //需要DatagramPacket,传字节数组,长度,getSocketAddress(会返回ip和port),相应也是按照DatagramPocket数据包为单位进行发送~~~~
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
            //send构造参数也是DatagramPacket,所以需要我们将字符串转换为DatagramPacket
            socket.send(responsePacket);

            //4.打印(打印ip,端口号,请求内容,相应内容)
            System.out.printf("[%s : %d] request : %s, resopnse : %s\n", requestPacket.getAddress().toString(), requestPacket.getPort(), request, response);
        }
    }

    public String process(String request) {
        return request;//因为这里是回显操作,客户端发送什么,服务器返回什么即可,不需要计算的操作
    }


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

客户端Code:

java 复制代码
package UDP;

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

//客户端
public class UdpClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;

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

    public void start() throws IOException {
        System.out.println("client start!!!");
        Scanner scanner = new Scanner(System.in);

        while(true) {
            //1.让用户从控制台输入字符串
            System.out.print("请输入一串字符串:");
            String request = scanner.next();
            if(request.equals("exit")) {
                break;
            }

            //2.将用户输入的字符串转换为UDP数据包类型,因为send的类型是数据包类型~~~
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(this.serverIp), this.serverPort);
            socket.send(requestPacket);//将请求发送给服务器

            //3.获取服务器的响应 --- receive构造也是DatagramPacket,所以也需要构造一个DatagramPacket,注意这里获取的是字节数组
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
            //接收服务器响应
            socket.receive(responsePacket);
            //转换为字符数组,因为服务器发送的响应是一个二进制字节数组,我们需要转换为String,然后打印的显示器上
            String response =  new String(responsePacket.getData(), 0, responsePacket.getLength());
            //4.显示响应
            System.out.println("响应数据为:" + response);
        }

    }

    public static void main(String[] args) throws IOException {
        UdpClient client = new UdpClient("127.0.0.1", 10086);//告诉客户端服务器的ip和port
        client.start();
    }



}

这里有几点要注意:

1.我们让用户输入的是字符串,但是DatagramPacket参数是二进制,所以需要将字符串转换为二进制

2.服务器返回的是二进制,客户端需要接收并且转换为字符串形式,才能显示在显示器上,否则就是乱码

3.服务器应该设置为While死循环,因为服务器是7*24小时服务的

4.在编写服务器的时候,需要我们程序员指定的给定一个端口号,而客户端是不需要给定端口号的,是一个无参数的构造方法,操作系统会自动分配一个空闲的端口号

5.服务器需要接收客户端的请求并且解析 ---> 根据请求计算响应 ----> 将响应返回给客户端

6.客户端需要从控制台读取用户输入的字符串 ---> 构造请求发送给服务器 ---> 读取服务器返回的响应 ---> 显示响应结果到显示器上

2.)实现一个查字典的UDP客户端和服务器

客户端输入一个中文单词,服务器要求返回这个中文对应的英文单词,然后客户端接收,并且显示到显示器上

这里就会使用Map保存好中文和对应的英文单词~~~

服务器Code:

java 复制代码
package UDP;

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

//翻译服务器
public class UdpDictionaryServer {
    private DatagramSocket socket = null;

    //创建词典
    public Map<String, String> map = new HashMap<>();

    public UdpDictionaryServer (int port) throws SocketException {
        socket = new DatagramSocket(port);
        map.put("苹果", "apple");
        map.put("香蕉", "banana");
        map.put("猪", "pig");
        map.put("鸭子", "duck");
        map.put("老虎", "tiger");
        map.put("鱼", "fish");
        map.put("你好", "hello");
    }

    public void start() throws IOException {
        System.out.println("Server Start~~~");

        while(true) {
            //1.接收请求并解析 --- 需要使用receive接收,receive的构造方法是DatagramPakcet
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);
            socket.receive(requestPacket);

            //receive接收到的是一个二进制字节数组,需要转换为字符串
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());

            //2.根据请求计算响应
            String response = process(request);

            //3.计算好的响应返回给客户端 -- 需要使用send,但是注意,send构造也是需要DatagramPacket的~~~,这里参数是获取字节数组,字节数组长度,以及接收到请求的IP和Port
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());
            socket.send(responsePacket);

            //4.显示打印
            System.out.printf("[%s : %d] request : %s, resopnse : %s\n", requestPacket.getAddress().toString(), requestPacket.getPort(), request, response);

        }
    }

    public String process(String request) {
        return map.getOrDefault(request, "很抱歉没查找到此单词~~~");
    }

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


}

客户端Code:

java 复制代码
package UDP;

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

//翻译客户端
public class UdpDictionaryClient {
    DatagramSocket socket = null;
    private String ip;
    private int port;

    public UdpDictionaryClient(String ip, int port) throws SocketException {
        socket = new DatagramSocket();
        this.ip = ip;
        this.port = port;
    }

    public void start() throws IOException {
        System.out.println("Client Start~~~");
        Scanner scanner = new Scanner(System.in);

        while(true) {
            System.out.print("请输入中文:");
            String dictionary = scanner.next();
            if(dictionary.equals("exit")) {
                break;
            }

            //1.发送请求给服务器 --- 需要使用到send,构造方法是DatagramPacket
            DatagramPacket request = new DatagramPacket(dictionary.getBytes(), dictionary.getBytes().length, InetAddress.getByName(this.ip), this.port);
            socket.send(request);

            //2.接收服务器的响应 --- 需要使用到receive,构造方法是DatagramPacket
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
            socket.receive(responsePacket);

            //因为服务器响应回来的是一个二进制字节数组,我们需要转换为字符串
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());

            System.out.println("翻译后结果为:" + response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpDictionaryClient client = new UdpDictionaryClient("127.0.0.1",10086);
        client.start();
    }
}

TCP网络编程

TCP提供了俩个类:ServerSocket(给服务器使用) 和 Socket(即给客户端,也给服务器使用)

ServerSocket的任务是:负责将连接到服务器的客户端进行统一处理

Socket的任务是:针对每一个客户端,提供具体的服务(读写数据)

1.)实现回显的TCP客户端和服务器

服务器Code:

java 复制代码
package TCP;

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 {
    //创建ServerSocket对象
    private ServerSocket serverSocket = null;

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

    public void start() throws IOException {
        System.out.println("Server Start~~~");

        while(true) {
            //需要接收客户端的连接 --- 需要使用到accept,如果接收到了,会返回,否则就一直阻塞等待
            Socket socket = serverSocket.accept();//这里的socket会保存客户端的ip和port,每一次连接客户端,就会创建一个socket连接,但是很快就执行完,所以执行完后需要关闭连接~~

            //处理连接过程,但是服务器只有一个,当我们启动多个客户端,一个服务器无法接收多个客户端的请求,这时就需要创建多线程,让多线程去实现多个服务器,才能接收不同客户端的请求
            Thread t = new Thread(() -> {
                try {
                    processConnection(socket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
    }


    public void processConnection(Socket socket) throws IOException {
        //一次连接的过程中,客户端和服务器可能会传输多组数据
        System.out.printf("【%s : %d】 客户端上线\n", socket.getInetAddress(), socket.getPort());

        //因为TCP使用的是字节流,所以我们可以使用InputStram---读,OutputStream---写
        try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            PrintWriter write = new PrintWriter(outputStream);

            while(true) {
                //1.处理多次请求,这里一次循环就处理一个请求
                if(!scanner.hasNext()) {
                    System.out.printf("【%s : %d】 客户端下线\n",socket.getInetAddress(), socket.getPort());
                    break;
                }

                String request = scanner.next();

                //2.根据请求计算响应 --- 这里实现的是回显操作,直接返回客户端请求即可
                String response = process(request);


                //3.将响应返回给客户端
                write.println(response);
                //刷新缓冲区
                write.flush();

                //4.打印日志
                System.out.printf("【%s : %d】 request ->%s  response ->%s\n", socket.getInetAddress(), socket.getPort(), request, response);
            }

        }catch (IOException e) {
            e.printStackTrace();
        } finally {
            socket.close();//当客户端和服务器连接完毕,并执行完上面代码,这个连接就失效了,我们需要手动关闭socket的连接,防止资源泄露!!!!
        }

    }

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

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

每次服务器收到一个客户端的连接,都是通过ServerSocket的进行accept

ServerSocket只需要进行accept即可,不负责提供读写的操作

客户端Code:

java 复制代码
package TCP;

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 {
    private Socket socket = null;

    public TcpClient(String ip, int port) throws IOException {
        socket = new Socket(ip, port);
    }

    public void start() {
        System.out.println("Client Start~~~");

        try(InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {
            Scanner scanner = new Scanner(System.in);
            PrintWriter write = new PrintWriter(outputStream);
            Scanner read = new Scanner(inputStream);

            while(true) {
                //1.让用户输入字符串
                System.out.print("请输入一串字符串 ->");
                String request = scanner.next();

                //2.将请求发送给服务器
                write.println(request);
                //刷新缓冲区
                write.flush();

                //3.接收服务器响应
                if(!read.hasNext()) {
                    break;
                }
                String response = read.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", 10086);
        client.start();
    }
}

对于TCP的网络编程,有三点需要注意:

1.)TCP是面向字节流的,即InputStream / OutputStream,会对文件进行操作,所以文件内部是设置了缓冲区,我们写入的文件,并不是直接写入文件,而是写入到了一个缓冲区的地方,当缓冲区满了,才会真正写入到文件中 ---- 所以在write的时候,需要使用flush对缓冲区进行刷新,即使缓冲区没有满,也是可以真正写入的

2.)当前代码中是存在文件资源泄露的情况,因为当客户端每次连接上服务器,都会触发一次accept,都会创建一个Socket,就会占用一个文件资源描述符表的位置!!!当客户端源源不断的过来,每一次的连接都会产生一个新的socket文件,而我们只使用,不关闭,就会一直占用文件描述符表的位置 ---- 这时就需要我们进行close操作

3.)我们需要使用多线程,才能让多个客户端同时进行操作,如果随着客户端数量不断增加,我们后续还可以继续考虑线程池,减小线程的创建与销毁的操作

2.)实现翻译字典的TCP客户端和服务器

服务器Code:

java 复制代码
package TCP;

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.HashMap;
import java.util.Map;
import java.util.Scanner;

public class TcpDictionaryServer {
    private ServerSocket serverSocket = null;
    private Map<String, String> map = new HashMap<>();

    public TcpDictionaryServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
        map.put("你好", "hello");
        map.put("鸭子", "duck");
        map.put("鱼", "fish");
        map.put("猪", "pig");
        map.put("狗", "dog");
        map.put("猫", "cat");
    }

    public void start() throws IOException {
        System.out.println("Server Start~~~");

        //与客户端产生连接,连接成功会返回
        Socket socket = serverSocket.accept();

        Thread t = new Thread(() -> {
            //使用多线程,让多个客户端可以运行,避免一个服务器只能服务一个客户端
            try {
                processConnection(socket);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
        t.start();
    }

    public void processConnection(Socket socket) throws IOException {
        //客户端和服务器一次连接,之间可能传输较多数据
        System.out.printf("【%s : %d】 客户端上线\n", socket.getInetAddress(), socket.getPort());

        try(InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            PrintWriter write = new PrintWriter(outputStream);

            while(true) {
                if(!scanner.hasNext()) {
                    System.out.printf("【%s : %d】 客户端下线\n",socket.getInetAddress(), socket.getPort());
                    break;
                }

                //接收请求
                String request = scanner.next();

                //计算响应
                String response = process(request);

                //将请求返回给客户端
                write.println(response);
                //刷新
                write.flush();

                //打印日志
                System.out.printf("【%s : %d】 request ->%s  response ->%s\n", socket.getInetAddress(), socket.getPort(), request, response);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            socket.close();//当客户端与服务器连接,执行完上面的代码,连接就失效了,这时需要释放socket,防止资源泄露!!!!
        }
    }

    public String process(String request) {
        return map.getOrDefault(request, "很抱歉,没查找到此单词~~~");
    }

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

}

客户端Code:

java 复制代码
package TCP;

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 TcpDictionaryClient {
    private Socket socket = null;

    public TcpDictionaryClient(String ip, int port) throws IOException {
        socket = new Socket(ip, port);
    }


    public void start() {
        System.out.println("Client Start~~~");

        try(InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {
            Scanner scanner = new Scanner(System.in);
            PrintWriter write = new PrintWriter(outputStream);
            Scanner read = new Scanner(inputStream);

            while(true) {
                //让用户输入内容
                System.out.print("请输入一个中文:");
                String request = scanner.next();

                //发送请求
                write.println(request);
                write.flush();//刷新缓冲区

                //接收服务器响应
                if(!read.hasNext()) {
                    break;
                }
                String response = read.next();

                //将服务器响应输入到显示器上
                System.out.println("英文是:" + response);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpDictionaryClient client = new TcpDictionaryClient("127.0.0.1", 10086);
        client.start();
    }
}
相关推荐
木辰風1 小时前
启动了不安全的HTTP方法(漏洞、springmvc)
网络·网络协议·http
yu_zhidun1 小时前
企业电脑数据防泄密软件选型:内网终端 DLP 落地实战指南
服务器·网络·电脑
rcms152702692181 小时前
Kensington 0190-23563W伺服控制器
网络
147API1 小时前
蒸馏模型回退率突然升高,先查容量、路由还是任务分布
网络·智能路由器
abcyuu1 小时前
福州中小企业业财一体化系统权限管理与数据安全实践方案
大数据·网络·数据库
ITxiaobing20231 小时前
技术选型参考:从IPinfo出发,聊聊IP情报服务的替代方案与选型逻辑
网络·网络协议·tcp/ip
z_mazin2 小时前
逆向一种私有二进制序列化格式:从零到字节级解析器
网络·python·网络协议·算法·rpc
luj_17682 小时前
罚球线右移破防新策略
c语言·开发语言·网络·经验分享·算法
Jeremy_WW2 小时前
QSFP/QSFP-DD/OSFP 通用管理接口规范(CMIS)解读:11 Page 13h IV
网络·网络协议·信息与通信·光模块·cmis