【网络编程】理解客户端和服务器并使用Java提供的api实现回显服务器

目录

一、网络编程

二、客户端和服务器

三、客户端和服务器的交互模式

四、TCP 和 UDP

UDP socket api 的使用

1、DatagramSoket

2、DatagramPacket

TCP socket api 的使用

1、ServerSocket

2、Socket 



一、网络编程

本质上就是学习传输层给应用层提供的 api,通过 api 把数据交给传输层,进一步地层层封装将数据通过网卡发送出去,这也是网络程序的基本工作流程。

掌握了基础 api 就能更好的理解实际开发中使用的框架(spring,dubbo)的工作过程,也提供了魔改/自己实现框架的能力。

二、客户端和服务器

在网络中,主动发起通信的一方称为“客户端”,被动接受的一方称为“服务器”。同一个程序在不同的场景中,可能是客户端也可能是服务器。

客户端给服务器发送的数据,称为“请求”(request)

服务器给客户端返回的数据,称为“响应”(response)

三、客户端和服务器的交互模式

1、“一问一答”

一个请求对于一个响应,这是交互模式是最常见的,后续进行的“网站开发”(web开发)都是这种模式。

2、“一问多答”

主要在“下载”场景中涉及

3、“多问一答”

主要在“上传”场景中涉及

4、“多问多答”

主要在“远程控制/远程桌面”场景中涉及

四、TCP 和 UDP

进行网络编程,需要使用系统的 API,【本质上是传输层提供的协议】

传输层主要涉及到两个协议:TCP 和 UDP。

连接性可靠性面向数据传输方式
TCP面向连接可靠传输面向字节流全双工
UDP无连接不可靠传输面向数据报全双工
  • 连接:此处说的“连接”不是物理意义的连接,是抽象虚拟的“连接”。所谓计算机中的“网络连接”是指通信双方各自保存对方的信息。客户端的数据结构中记录了谁是它的服务器;服务器的数据结构中记录了谁是它的客户端;本质上就是记录对方的信息
  • 可靠传输/不可靠传输:无论如何都不能保证100%的信息传输。可靠传输主要是指发送方能够感知数据有没有传输给接收方,如果没接收到,可以采取相应的措施补救,例如重传机制。
  • 面向字节流:与文件中的字节流完全一致,网络中传输数据的基本单位就是字节
  • 面向数据报:每次传输的基本单位是一个数据报(有一系列字节构成)。
  • 全双工:一个信道,可以双向通信,就叫全双工。可以理解成马路的多车道,就是全双工。
  • 半双工:可以理解为吸管,同一时刻只能吸或者呼。

UDP socket api 的使用

Java 把系统原生 api 封装了,UDP socket 提供的两个核心的类

1、DatagramSoket

操作系统中有一类文件,就叫 socket 文件,这类文件抽象地表示了“网卡”这样的硬件设备。而进行网络通信最核心的硬件设备就是网卡。

DatagramSocket 类就是负责对 socket 文件进行读写,从而借助网卡发送接收数据。

2、DatagramPacket

UDP 面向数据报,每次发送接收数据的基本单位是一个 UDP 数据报

DatagramPacket 类就表示了一个 UDP 数据报。

关于 receive 接收数据报的底层实现过程

UdpEchoServer 实例

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);
            // 将读到的字节数组转换成 String 方便后续操作
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            //2. 根据请求计算响应
            String response = process(request);
            //3. 把响应返回到客户端
            // 与请求数据报创建不同,请求数据报是使用空白字节数组,而此处直接把 String 里包含的字节数组作为参数创建,
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());  // 因为 UDP 无连接,因此必须从【请求数据报】中获取对应客户端的 ip 和端口
            socket.send(responsePacket);

            //打印日志
            System.out.printf("[%s:%d] req: %s, resp: %s\n",requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }

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

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

 上述代码中:

1、可以看到 23 行需要从【请求数据报】中获取对应客户端 ip 和端口号才能完成发送响应,证明了 UDP socket 自身不保存对端的 ip 和端口号,体现了无连接

2、不可靠传输,代码中没有体现。

3、receive 和 socket 都是以DatagramPacket 为单位,体现了面向数据报

4、一个 socket 既能发送(send)有能接收(receive),体现了全双工

UdpEchoClient 示例 

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;  // 客户端对应的服务器端口号
    }

    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner sc = new Scanner(System.in);
        while (true) {
            //1. 从控制台读取要发送的数据
            System.out.print("-> "); //表示提示用户输入
            if (!sc.hasNext()) {   //hasNext 具有阻塞功能
                break;
            }
            String request = sc.next();
            //2. 构造请求并发送
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIp), serverPort);
            socket.send(requestPacket);
            //3. 读取服务器的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            // 阻塞等待响应数据返回
            socket.receive(responsePacket);
            //4. 把响应显示到控制台
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.println(response);
        }
    }

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

TCP socket api 的使用

由于 TCP 是面向字节流的,传输的基本单位是字节,因此没有像 UDP 中 DatagramPacket 这样的类。
Java 把系统原生 api 封装了,TCP socket 提供的两个核心的类

1、ServerSocket

这是Socket 类,同样抽象地表示了“网卡”但是这个类与 UDP 中使用的 DatagramSocket 不同,这个类只能给服务器进行使用。只负责处理对客户端的连接,主要 api 是 accept()

2、Socket 

对应到“网卡”,既能给服务器使用,又能给客户端使用。相当于电话的两个听筒,通过 Socket 完成对端之间的通信。主要的 api 是 getInputStream 和 getOutputStream
需要注意:由于服务器端的 Socket 对象与客户端时一一对应的,为了避免无限占用文件描述符表,使用完毕后需要 close 关闭。

TcpEchoServer 示例

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("服务器启动!");
        while (true) {
            // 当客户端创建出 socket 后(new socket),就会和对应的服务器进行 tcp 连接建立流程
            // 此时通过 accept 方法来“接听电话”,然后才能进行通信
            Socket clientSocket = serverSocket.accept();
            Thread t = new Thread(() -> {
                processConnection(clientSocket);
            });
            t.start();
        }
    }

    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
        // 循环读取客户端的请求并返回响应
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()){
            // 可以使用 inputStream 原本的 read 方法进行读取
            // 但是比较繁琐,为了【方便读入】,这里使用 Scanner 对输入流进行输入
            Scanner sc = new Scanner(inputStream);
            while (true) {   // 长连接
                if (!sc.hasNext()) {
                    // 读取完毕,客户端断开连接
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
                    break;
                }
                //1. 读取请求并解析,此处使用 next ,需要注意 next 的读入规则
                String request = sc.next();
                //2. 根据请求计算响应
                String response = process(request);
                //3. 把响应返回给客户端

                /*  通过这种方式也可以写回,但是这种方式不方便添加 \n
                outputStream.write(response.getBytes(),0,response.getBytes().length);*/

                // 因此为了【方便写入】,给 outputStream 也套一层,即使用 printWriter
                // 此处的 printWriter 就类似于 Scanner 将输入流包装了一下,而 printWriter 对输出流包装了一下
                PrintWriter printWriter = new PrintWriter(outputStream);
                // 通过 println 在末尾添加了 \n,与客户端的 scNetwork.next 呼应
                printWriter.println(response);
                // 刷新缓冲区,确保数据能够发送出去
                printWriter.flush();

                // 打印日志
                System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress(), clientSocket.getPort(),
                        request,response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
                clientSocket.close();
        }

    } 

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

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

需要注意的是:
1、理解ServerSocket 和Socket 的不同作用,Socket作为接收对象。
2、只有当客户端 new Socket 时,ServerSocket 才能通过 accept 完成连接。
3、Scanner 和 PrintWriter 。
4、flush 刷新缓冲区。
5、finaly{ clientSocket.close(); } 每个客户端对应一个Socket,因此每个客户端完成任务后,需要关闭文件,从而销毁文件描述符表。而 try()自动关闭的是流对象,而没有释放文件本体。

TcpEchoClient 示例

public class TcpEchoClient {

    private Socket socket = null;

    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // 这里直接将 ip 和 port 传入,是由于 tcp 是有连接的,socket 里能够保存 ip 和 port
        socket = new Socket(serverIp,serverPort);
        // 因此也不需要额外创建【类成员对象】来保存 ip 和 port
    }

    public void start() {
        System.out.println("客户端启动!");
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()){
            // 此处的 scanner 用于控制台读取数据
            Scanner scConsole = new Scanner(System.in);
            // 此处的 scanner 用于读取服务器响应回来的数据
            Scanner scNetwork = new Scanner(inputStream);
            // 此处 printWriter 用于向服务器写入请求数据
            PrintWriter printWriter = new PrintWriter(outputStream);
            while (true) {
                // 这里流程和 UDP 的客户端类似
                //1. 从控制台读取输入的字符串
                System.out.print("-> ");
                if (!scConsole.hasNext()) {
                    break;
                }
                String request = scConsole.next();
                //2. 把请求发送给服务器,
                // 使用 printWriter 是为了使发送的请求末尾带有 \n,与服务器的 sc.next 呼应
                printWriter.println(request);
                // 刷新缓冲区,确保数据能够发送出去
                printWriter.flush();
                //3. 从服务器读取响应
                String response = scNetwork.next();
                //4. 打印响应
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

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

需要注意的是:
1、TCP 是有连接的,因此 Socket 能够直接保存 ip 和 port。
2、flush 刷新缓冲区。

【博主推荐】

【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/zzzzzhxxx/article/details/136288256?spm=1001.2014.3001.5501【Java多线程】对线程池的理解并模拟实现线程池-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/zzzzzhxxx/article/details/136160003?spm=1001.2014.3001.5501【Java多线程】分析线程加锁导致的死锁问题以及解决方案-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/zzzzzhxxx/article/details/136150237?spm=1001.2014.3001.5501

如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!

如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!

如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/418701.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL之事务详解

华子目录 什么是事务银行转账案例方式1方式2具体操作 事务的四大特性并发事务问题脏读不可重复读幻读 事务的隔离级别查看事务隔离级别设置事务隔离级别 session与global的区别 什么是事务 事务(transaction),一个最小的不可再分的工作单元&…

实例:NX二次开发抽取平面以及标准柱面中心线

一、概述 最近体验许多外挂,包括胡波外挂、星空外挂及模圣等都有抽取面的中心线,由于刚刚学习,我尝试看看能不能做出来,本博客代码没有封装函数,代码有待改进,但基本可以实现相应的功能。 二、案例实现的功…

Sora 原理与技术实战笔记一

b 站视频合集 【AIX组队学习】Sora原理与技术实战:Sora技术路径详解 Sora 技术报告(OpenAI) huggingsd 文生图视频系列的一个开源项目 最强视频生成模型Sora相关技术解析 https://github.com/lichao-sun/SoraReview 惊艳效果: 长…

Ps:路径面板

Ps菜单:窗口/路径 Window/Paths “路径”面板 Paths Panel提供了一系列功能,使用户能够创建、编辑、保存和利用路径。 ◆ ◆ ◆ 路径分类 在“路径”面板上的路径可分为五大类。 常规路径 Saved Path 也称“已保存的路径”,指的是已经存储在…

Python进阶学习:Pandas--DataFrame--如何把几列数据合并成新的一列

Python进阶学习:Pandas–DataFrame–如何把几列数据合并成新的一列 🌈 个人主页:高斯小哥 🔥 高质量专栏:Matplotlib之旅:零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1…

SpringMVC的配置2种(本质上还是一样的,实现的接口不同)

第一种SpringInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer 看第一种配置 package com.xxx.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class SpringInitConfig ext…

减少页面加载时间:提升用户体验的关键

✨✨ 祝屏幕前的您天天开心,每天都有好运相伴。我们一起加油!✨✨ 🎈🎈作者主页: 喔的嘛呀🎈🎈 目录 引言 一、为什么页面加载时间重要? 二、如何减少页面加载时间? …

Google发布Genie硬杠Sora:通过大量无监督视频训练最终生成可交互虚拟世界

前言 Sora 问世才不到两个星期,谷歌的世界模型也来了,能力看似更强大(嗯,看似):它生成的虚拟世界自主可控 第一部分 首个基础世界模型Genie 1.1 Genie是什么 Genie是第一个以无监督方式从未标记的互联网视频中训练的生成式交互…

UDP数据报套接字编程入门

目录 1.TCP和UDP的特点及区别 1.1TCP的特点 1.2UDP的特点 1.3区别 2.UDP Socket的api的介绍 2.1DatagramSocket API 2.2DatagramPacket API 3.回显客户端与服务器 3.1回显服务器 3.1.1UdpEchoServer类的创建 3.1.2服务器的运行方法start() 3.1.3main部分 3.1.4.完整…

nginx反向代理之缓存 客户端IP透传 负载均衡

一 缓存功能 缓存功能可以加速访问,如果没有缓存关闭后端服务器后,图片将无法访问,缓存功能默认关闭,需要开启。 相关选项: ​ proxy_cache zone_name | off; 默认off #指明调用的缓存,或关闭缓存机制;C…

【C++初阶】第四站:类和对象(下)(理解+详解)

前言: 本篇知识点:初始化列表、explicit关键字、static成员、友元、内部类、匿名对象、编译器的优化 专栏:C初阶 目录 再谈构造函数 1️⃣构造函数体赋值 2️⃣初始化列表 explicit关键字 static成员 1.static概念 2.static特性 面试…

Docker技术概论(4):Docker CLI 基本用法解析

Docker技术概论(4) Docker CLI 基本用法解析 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.netMy WebSite:http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:http…

NC65 零预算控制规则 数据库表关系

NC65 零预算控制规则 数据库表关系 SELECT t1.createdby, t1.objname, t2.ctrlname, t2.pk_parent, t3.billtype, t3.nameidx, t3.pk_obj FROM tb_rule_formula t1 left join tb_ctrlformula t2 on t1.pk_obj t2.pk_parent left join tb_ctrlscheme t3 on t3.pk_ctrlformula …

Mysql安装教程

一、下载 点开下面的链接:https://dev.mysql.com/downloads/mysql/ 点击Download 就可以下载对应的安装包了, 安装包如下: 二、解压 下载完成后我们得到的是一个压缩包,将其解压,我们就可以得到MySQL 8.0.31 的软件本体了(就是一个文件夹…

Tomcat布署及优化-----JDK和Tomcat

1.Tomcat简介 Tomcat 是 Java 语言开发的,Tomcat 服务器是一个免费的开放源代码的 Web 应用服务器,Tomcat 属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试 JSP 程序的首选。一般来说&…

Vivado Vitis 2023.2 环境配置 Git TCL工程管理 MicroBlaze和HLS点灯测试

文章目录 本篇概要Vivado Vitis 环境搭建Vivado 免费标准版 vs 企业版Vivado Windows 安装Vivado 安装更新 Vivado 工程操作GUI 创建工程打开已有工程从已有工程创建, 重命名工程GUI导出TCL, TCL复原工程TCL命令 Vivado 版本控制BlinkTcl脚本新建导出重建工程纯Verilog BlinkTc…

抖音视频批量下载软件说明|视频采集挖掘工具

操作步骤如下: 打开抖音批量下载工具,进入软件界面的第一个选项卡页面。在搜索框中输入需要搜索的视频关键词,例如"汽车配件",然后点击"开启抓取"按钮开始搜索。软件将开始搜索并显示与关键词相关的视频内容…

祖传代码:历史的宝藏与现代的挑战

程序员是如何看待“祖传代码”的? 程序员眼中的“祖传代码”,就像一本古老而神秘的魔法书,藏着无穷的智慧和技巧,有些代码像家传宝贝,有些像祖传秘方。快来分享一下你遇到的“祖传代码”吧~ 一、祖传代码的历史与文…

算法------(13)KMP

例题:(1)AcWing 831. KMP字符串 。。其实写完也不太理解。。随便写点吧 KMP就是求next数组和运用next的数组的过程。相比传统匹配模式一次更新一单位距离的慢速方法,next数组可以让下表字符串一次更新n - next【n】个距离&#x…

三天学会阿里分布式事务框架Seata-seata事务日志mysql持久化配置

锋哥原创的分布式事务框架Seata视频教程: 实战阿里分布式事务框架Seata视频教程(无废话,通俗易懂版)_哔哩哔哩_bilibili实战阿里分布式事务框架Seata视频教程(无废话,通俗易懂版)共计10条视频&…