【网络编程】理解客户端和服务器并使用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. 把响应返回给客户端/*  通过这种方式也可以写回,但是这种方式不方便添加 \noutputStream.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 和 portsocket = 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

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ARM简介

ARM:ARM是Advanced RISC Machine的缩写,意为高级精简指令集计算机。 英国ARM公司,2016年被软银创始人孙正义斥资320亿美元收购了。现在是软银旗下的芯片设计公司,总部位于英国剑桥,专注于设计芯片,卖芯片生…

揭秘:头部房企如何借助数据分析实现稳健发展?

房地产行业是我国国民经济中的重要支柱产业之一,在房地产市场供求关系发生重大变化的当下,房企面临多重挑战。Kyligence 服务的这家头部房企把发展的重点聚焦于内生,关注内生的转化率、接管的效率以及内生毛利率的提升,引入 Kylig…

基于springboot实现保险信息网站系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现保险信息网站系统演示 摘要 随着互联网的不断发展,现在人们获取最新资讯的主要途径来源于网上新闻,当下的网上信息宣传门户网站的发展十分的迅速。而保险产品,作为当下人们非常关注的一款能够给人们带来医疗、生活、养老或…

面试笔记系列七之多线程+分布式系统基础知识点整理及常见面试题

目录 多线程 介绍一下线程的生命周期及状态? 线程的sleep、wait、join、yield如何使用? sleep与yield方法的区别在于, 进程调度算法 创建线程有哪些方式? 什么是守护线程? ThreadLocal的原理是什么,…

当大语言模型遇到AI绘画-google gemma与stable diffusion webui融合方法-矿卡40hx的AI一体机

你有想过建一台主机,又能AI聊天又能AI绘画,还可以直接把聊天内容直接画出来的机器吗? 当Google最新的大语言模型Gemma碰到stable diffusion webui会怎么样? 首先我们安装stable diffusion webui(automatic1111开源项目&#xff…

微信小程序构建npm失败解决方式

安装完所需要的依赖后,在微信开发者工具菜单栏中选择:“工具” -> “构建 npm”,但是失败。 解决方法:修改 project.config.json 开发者工具创建的项目,miniprogramRoot 默认为 miniprogram,package.js…

数据迁移DTS | 云上MySQL 数据库迁移至达梦数据库

引入 云上 MySQL 数据库 —> 向达梦国产化数据库迁移 下载&安装 达梦客户端工具 DM->可参考之前国产化专栏达梦文章 创建模式 在客户端分别依次执行以下命令脚本(这里没有通过客户端管理工具去创建达梦数据库的模式,当然也可以通过图形化界…

WordPress通过宝塔面板的入门安装教程【保姆级】

WordPress安装教程【保姆级】【宝塔面板】 前言一:安装环境二:提前准备三:域名解析四:开始安装五:安装成功 前言 此教程适合新手,即使不懂代码,也可轻松安装wordpress 一:安装环…

视频在线转换,四种方法任你选!(建议收藏)

在当今的数字时代,视频已经成为人们日常生活中不可或缺的一部分。我们通过视频分享知识、表达创造力、观看娱乐节目,甚至参与远程学习和工作。然而,随着视频格式的多样化和设备的激增,我们经常会遇到一个常见的问题:视…

Linux(CentOS)学习

一、认识Linux 1、如何修改Linux时区 2、配置固定IP 3、重启网络服务 3、小技巧快捷键 4、环境变量设置 5、Linux文件的上传和下载 6、压缩和解压 二、基础命令 1、目录命令 (1、)查看目录内容(ls) 1、ls //查看当前目录内容 2、- a //显示隐藏内容 3…

【算法】二叉搜索树的插入、删除、转换操作

1 二叉搜索树的插入操作 给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。 注意,可能…

小程序原生 API

微信原生 API 1. API 基础 小程序开发框架提供丰富的微信原生 API,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等,几乎所有小程序的 API 都挂载在 wx 对象底下,例如:wx.c…

#LLM入门|Prompt#2.2_ AI 应用开发的范式_Language_Models,the_Chat_Format_and_Tokens

在本章中,我们将和您分享大型语言模型(LLM)的工作原理、训练方式以及分词器(tokenizer)等细节对 LLM 输出的影响。 我们还将介绍 LLM 的提问范式(chat format),这是一种指定系统消息…

STM32合并烧录IAP+APP

STM32合并烧录IAPAPP 通过查找相关资料 有以下几种合并方法 第一种直接将二进制文件用记事本合并 而要合并的就是就将IAP最后的一行删除,然后将APP程序追加在后面。 (修改前) 把APP的.hex 全部内容拷贝复制到 刚才删掉结束语句的 IAP的.…

Win32汇编ListView控件学习

此控件比较复杂;和基础win32控件不同;需要先初始化Windows公共控件库, invoke InitCommonControls 之后才可使用; lvdemo.asm, .386.model flat, stdcalloption casemap :none ; case sensitiveinclude window…

【OCR识别】使用OCR技术还原加密字体文字

文章目录 1. 写在前面2. 页面分析3. 字符知识4. 加密分析 【作者主页】:吴秋霖 【作者介绍】:Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作! 【作者推荐】:对JS逆向感兴趣的朋…

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

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

华润置地品牌虚拟代言人IP“吉吉”,开启地产数字化营销新场景

在数字化营销时代,房地产品牌通过虚拟人技术,可以有效链接购房者,占领客户心智,优化购房体验,塑造年轻化、数字化的品牌形象。 华润置地积极拥抱数字变革,通过广州虚拟动力「现场虚拟主持技术服务」与「虚…

【Unity】使用Unity实现双屏显示

引言 在使用Unity的时候,有时候会需要使用双屏显示 简单来说就是需要在两个显示器中显示游戏画面 双屏显示注意点: ①双屏显示需要电脑有两个显示 ②双屏显示只能用于PC端 ③不仅仅可以双屏,Unity最大支持8屏显示 1.相机设置 ①我们打开Un…

Untiy webgl iis服务器加载ab包报404.3,需要为AB包添加MIMI映射

首选确定一下文件在不在 这里是缺少对于AB包文件类型的映射,因为AB包没有后缀名,我们为服务器添加通用的映射 1 开始菜单搜索iis管理器,先选中我们的服务器,然后双击进入MIME类型 2 右侧点击添加按钮 3 添加如下内容 文件扩展名为. 类型为…