基于UDP和TCP套接字实现简单的回显客户端服务器程序

news/2024/5/18 11:52:28 标签: udp, tcp/ip, 网络

目录

1. 套接字

2. 基于UDP 套接字实现的简单客户端 服务器程序

3. 基于TCP套接字实现的简单客户端 服务器程序


1. 套接字

       之前我们有分享到协议分层这个概念,其中就讲到上层协议调用下层协议,下层协议给上层协议提供支持,这里支持指的是就是socket套接字,它是操作系统给应用程序提供的一组用于网络编程的API(传输层给应用层的支持).


2. 基于UDP 套接字实现的简单客户端 服务器程序

       UDP是传输层协议之一,它的特点是无连接,不可靠,面向数据报传输,半双工(数据传输允许数据在两个方向上传输,但在某一时刻,只允许数据在一个方向传输).

       ● UDP的套接字

          DatagramSocket(指定端口号为服务器,不指定端口号则为客户端)类是用于发送和接收数据报的套接字,而DatagramPacket类是表示UDP数据报,客户端和服务器双方传输的都是数据报.

          1) DatagramSocket常用构造方法有:

//不指定端口号,表示客户端
DatagramSocket socket = new DatagramSocket();
//指定端口号,表服务端
DatagramSocket socket = new DatagramSocket(port);

              其它的构造方法参考jdk文档:

 主要的普通方法有receive():从此套接字接收数据报包 :

它的参数是一个输出型参数,传入一个空的数据报,那么就会将从网卡读取到的数据填充进去.打个比方,就是你拿一个空的饭盒去食堂窗口打菜,有菜食堂就会往你饭盒打菜.等你从窗口处拿到你的饭盒,你的饭盒就已经有菜了.这时你的饭盒就相当于输出型参数.当然如果窗口菜还没炒好,你就需要等待.所以receive()方法可能会产生阻塞.

                              send():从此套接字发送数据报包:

                              ,close():关闭该数据报套接字.

注:要确定该数据报套接字不会再使用才能关闭该资源.还有其它很多该套接字的方法,大家想了解的话可以去查看jdk文档学习.

          2) DatagramPacket常用构造方法有:

               其它的构造方法参考jdk文档:

               其它的方法主要分为获取和设置:包括(数据报发送或接收的计算机IP地址,端口号),数据缓冲区,发送或接收数据的长度等等,根据我们自己的实际需求去调用.这里我们用到的就是以下方法:

       ● 示例代码

          这里先将示例代码展示,然后再根据代码跟大家分享一下我的思路.

//服务器
public class UdpEchoServer {
    DatagramSocket socket;
    // 1.初始化一个带端口号的DatagramSocket对象(作为服务器)
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 2.准备一个数据报(空的饭盒),调用receive方法阻塞等待接收客户端的请求数据
            DatagramPacket request = new DatagramPacket(new byte[2023],2023);
            // 5.收到客户端发送的数据报数据并填入步骤2准备的数据报中
            socket.receive(request);

            // 将请求数据转换为字符串
            String req = new String(request.getData(),0,request.getLength());

            // 6.根据请求计算响应(这里由于是回显服务器,所以请求和响应是一样的)
            String response = process(req);

            // 7.将响应数据放入一个数据报中,使用send()方法返回给客户端
            DatagramPacket resp = new DatagramPacket(response.getBytes(),0,response.getBytes().length,request.getSocketAddress());
            socket.send(resp);

            // (最后打印请求和响应数据)
            System.out.printf("[ip:%s port:%s] req:%s res:%s\n",resp.getAddress(),socket.getPort(),req ,response);
        }
    }

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

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

//客户端
public class UdpEchoClient {
    DatagramSocket socket;
    // 发送请求数据报的服务端ip地址和端口
    public String ip;
    public int port;

    // 1.初始化一个不带端口号的DatagramSocket对象(作为客户端)
    public UdpEchoClient(String ip, int port) throws SocketException {
        socket = new DatagramSocket();
        this.ip = ip;
        this.port = port;
    }

    public void start() throws IOException {
        System.out.println("客户端启动");
        while (true) {
            Scanner s = new Scanner(System.in);
            String temp = s.nextLine();
            // 3.将请求的数据放入一个数据报中,使用send()发送
            DatagramPacket request = new DatagramPacket(temp.getBytes(),temp.getBytes().length,InetAddress.getByName(ip),port);
            socket.send(request);

            // 4.准备一个空的数据报接收响应数据,调用receive()方法阻塞等待服务器返回的响应数据
            DatagramPacket response = new DatagramPacket(new byte[2023],2023);
            // 8.收到响应数据报,将响应数据放入步骤4准备的空数据报中
            socket.receive(response);

            // 将数据报数据转换为字符串
            String res = new String(response.getData(),0,response.getLength());
            // (最后打印请求和响应数据)
            System.out.printf("req:%s res:%s\n", temp,res);
        }
    }

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

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

       ● 根据示例代码分析服务器和客户端的交互流程(图解)

注: ① 应用层可以通过编程实现UDP全双工通信,在某一时刻既可以发送数据,又可以接收数据.但在网络通信中,UDP本身仍然是半双工的协议.

      ② 这里的socket其实是一个特殊的文件,是网卡这个硬件设备的抽象表示(我们可以通过它间接读取或修改网卡数据).


3. 基于TCP套接字实现的简单客户端 服务器程序

      TCP也是传输层协议之一,它的特点是有连接,可靠传输,面向字节流,全双工(允许数据在两个方向同时传输).

       ● TCP的套接字

           可以分为两类,一个是ServerSocket类,它是服务器套接字,给服务器使用(一定要绑定端口号),另一个是Socket类,也是个套接字,但是客户端和服务器都可以使用它(和DatagramSocket相似,没绑定端口号给客户端使用,绑定端口号给服务器使用).

          1) ServerSocket常用构造方法有:

              其它构造方法:

 

 

           它的常用普通方法:accept() 与客户端建立连接(得到的是一个Socket对象):

                                         close() 关闭套接字(一定要确定该套接字不再使用之后再关闭)

          2) Socket常用构造方法有:

  

              其它构造方法:

         Socket中我们在这里用到的普通方法: 

 

         其它方法大家可以根据需求查看jdk文档使用. 

       ● 示例代码

// 服务器
public class TcpEchoServer {
    ServerSocket socket;

    public TcpEchoServer(int port) throws IOException {
        // 该服务器位于哪个端口
        socket = new ServerSocket(port);
    }

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

        while (true) {
            // 请求连接
            Socket clientSocket = socket.accept();
            // 可连接多个客户端,每个客户端在一个线程中处理自己的逻辑
            Thread thread = new Thread(() -> {
                try {
                    processClient(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
        }
    }

    private void processClient(Socket clientSocket) throws IOException {
        System.out.println("客户端上线!");
        // try catch with resource操作会让这里创建的字节流在使用后自动关闭
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 使用字符流处理
            Scanner input = new Scanner(inputStream);
            PrintWriter print = new PrintWriter(outputStream);
            while (true) {
                // 读取到字符流末尾
                if (!input.hasNext()) {
                    System.out.println("客户端下线!");
                    break;
                }
                // 获取请求
                String request = input.next();

                // 处理请求并返回响应
                String response = process(request);

                // 写回响应
                print.println(response);
                //刷新缓冲区
                print.flush();
                //打印结果
                System.out.printf("[ip:%s port:%s] request:%s response:%s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(), request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            clientSocket.close();
        }
    }

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

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

// 客户端
public class TcpEchoClient {
    Socket socket;

    public TcpEchoClient(String ip,int port) throws IOException {
        // 连接对应ip地址及端口的服务器
        socket = new Socket(ip,port);
    }

    public void start() throws IOException {
        System.out.println("客户端开启");
        Scanner s = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()){
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner input = new Scanner(inputStream);
            while (true) {
                // 发送请求
                String request = s.next();
                printWriter.println(request);
                // 刷新缓冲区
                printWriter.flush();

                // 得到响应
                String response = input.next();
                // 打印结果
                System.out.printf("request:%s response:%s\n",request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

注:我们这里约定请求和响应都是字符串,需要使用字符流来处理.

       ● 根据示例代码分析服务器和客户端的交互流程(图解)

注:这里缓冲区策略和线程池策略类似,将数据先写到缓冲区,等缓冲区满了再统一自动写入网卡.我们这里手动刷新缓冲区是为了将数据直接写入网卡中.


 分享完毕~欢迎大家一起学习讨论~


http://www.niftyadmin.cn/n/346904.html

相关文章

VIBRO-METER VM600 MPC4 机械保护卡

4个动态通道和2个转速通道每个动态通道2个处理输出,每个双通道1个处理输出(每个MP C4 2个),每个转速器通道1个处理输出高度可配置的卡支持机械保护应用所需的所有测量,如相对和/或绝对振动高度集成的卡对(带IOC4T)包括传感器电源、缓冲输出、…

springboot+vue摄影跟拍预定管理系统(源码+文档)

风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的摄影跟拍预定管理系统。项目源码以及部署相关请联系风歌,文末附上联系信息 。 💕💕作者&#xff1…

finallshell mac SSH工具

一、FinallShell 是什么 FinalShell是一体化的的服务器,网络管理软件,不仅是ssh客户端,还是功能强大的开发,运维工具,充分满足开发,运维需求. 特色功能: 云端同步,免费海外服务器远程桌面加速,ssh加速,本地化命令输入框,支持自动补全,命令历史,自定义命令参数 二、主要特性 …

Java中的Hash碰撞是什么?该如何解决?

在Java中,哈希碰撞(Hash Collision)是指不同的输入数据产生了相同的哈希值。哈希函数是将输入映射到固定大小的哈希值的函数,而碰撞指的是两个不同的输入映射到了相同的哈希值。 哈希碰撞可能导致哈希表、哈希集合或哈希映射等数据结构的性能下降。当两个…

边缘计算盒子功能介绍,为什么要用边缘计算盒子?

边缘计算盒子(Edge Computing Box)是一种用于边缘计算设备。边缘计算是一种分布式计算模型,它将计算和数据处理能力从传统的集中式云计算数据中心延伸到网络边缘的设备上,以便更快地响应实时数据处理需求和减少对云服务的依赖。 边…

包含短信类等热门API 大全分享

静态活体检测:静态活体检测主要用于针对用户上传图像,返回该图像中的人脸是否为真人;基于图片中人像的破绽(摩尔纹、成像畸形等),判断目标是否为活体,有效防止屏幕二次翻拍等作弊攻击。语音验证…

App Store上线APP流程

现在App Store上已经有数百万款应用,因此对于App的规范要求也越来越高,对于新上线的APP需要满足这些规则并不是件容易的事。今天和大家分享这方面的知识,希望大家喜欢。北京木奇移动技术有限公司,专业的软件外包开发公司&#xff…

银行面试十大得分要点(一)

银行面试的常见形式包括结构化、半结构化和无领导讨论,从如信银行考试中心了解到,结构化和半结构化面试一级维度有四个:态度气场、思维思路、语言声音、心理素质,二级维度有十六个;无领导面试一级维度有八个&#xff1…