【网络编程】理解客户端和服务器并使用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.tpcf.cn/news/711038.html

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

相关文章

Leetcode 128. 最长连续序列

最长连续序列 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1: 输入:nums [100,4,200,1,3,2] 输出&am…

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 一:安装环…

node如何解析前端传递过来的命令行字符串

node如何解析前端传递过来的命令行字符串 在Node.js中,如果你想处理从前端传递过来的命令行字符串,你可以根据你的应用程序的架构来决定如何接收这些字符串,然后进行解析。一般来说,命令行字符串可能会通过HTTP请求(如…

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

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

Linux(CentOS)学习

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

深入理解Lucene:开源全文搜索引擎的核心技术解析

1. 介绍 Lucene是什么? Lucene是一个开源的全文搜索引擎库,提供了强大的文本搜索和检索功能。它由Apache软件基金会维护和开发,采用Java语言编写,因其高性能、可扩展性和灵活性而备受欢迎。 Lucene的作用和应用场景 Lucene主要…

Linux下创建用户并且赋root权限

背景:好几次都要求自己在服务器上创建用户,并且赋权限给这个用户的root权限,因为生产服务器上不让用root用户操作,之前没怎么记录,因为这个操作不多,但是又记不住这个操作,一到用上,…

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

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逆向感兴趣的朋…