网络编程初识

注:此博文为本人学习过程中的笔记

1.socket api

这是操作系统提供的一组api,由传输层向应用层提供。

2.传输层的两个核心协议

传输层的两个核心协议分别是TCP协议和UDP协议,它们的差别非常大,编写代码的风格也不同,因此socket提供了两套api。TCP协议是有连接,可靠传输,面向字节流,全双工。UDP协议是无连接,不可靠传输,面向数据报,全双工。

1.有连接/无连接

这里的有无连接是抽象的概念,对于网络通信来说,物理上(网线)的连接时必须的。这里的连接是指在通信的时候有没有保存对方的信息。

对于TCP协议来说,A和B通信,会让A保存B的信息,B保存A的信息,让它们彼此知道谁是和它建立连接的那一个。

对于UDP协议来说,不保存对方的协议。当然程序员可以在自己的代码中保存对方的信息,但这不属于UDP协议的行为。

2.可靠传输/不可靠传输

网络上,数据是非常容易出现丢失的情况,用来传输的光/电信号很容易受到外界的影响。所以我们不能指望一个数据包发送之后,能百分之一百到达对方。

可靠传输的意思不是保证数据包百分之一百到达,而是尽可能提高传输成功的概率,如果丢包了,可以感知到。

不可靠传输就是指数据包发送之后就不管了。

3.面向字节流/面向数据报

面向字节流是指读写数据的时候以字节为单位。它可以灵活地控制读写的长度,但是容易出现粘包问题。

面向数据报是指读写数据的时候以数据报为单位。一次必须读取一个数据报的长度,但不容易出现粘包问题。

4.全双工/半双工

全双工是指一个通信链路支持双向通信

半双工是指一个通信链路只支持单向通信

3.使用socket api进行编程

1.UDP服务器

这是操作系统提供的功能,Java进行了封装。这里我们先讲解基于UDP协议的写法。

1.DatagramSocket

计算机中的文件广义上还能代指硬件设备,将它们抽象成文件。这里我们将网卡抽象成socket文件,操作网卡的时候和普通的文件差不多,打开(也会在文件描述符表分配一个表项)->读写->关闭。直接操作网卡不好操作,把网卡操作转换成socket文件操作更加方便。

1.构造方法

DatagramSocket()

创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)

DatagramSocket(int port)

创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务器)

2.DatagramPacket

这个类表示一个完整的UDP数据报

1.构造方法

UDP数据报的载荷可以通过构造方法来指定

DatagramPacket(byte[] buf, int length)

构造一个DatagramPacket用来接收数据报,接收的数据报保存在字节数组,指定长度

DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)

构造一个DatagramPacket用来接收数据报,接收的数据报保存在字节数组,指定数组下标,数组长度,目的IP和端口号

2.receive/send

1.void receive(DatagramPacket p)

接收数据报,没有接收到会阻塞等待

2.void send(DatagramPacket p)

发送数据报

3.代码示例

这里我们使用回显服务器,回显服务器是指响应和请求都是相同的服务器。我们实现的功能是用户输入一个字符串,服务器返回这个字符串。

1.服务器代码

代码展示

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;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就表示一个UDP数据报,此处传入的字节数组保存UDP的载荷部分DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket);//把读取到的二进制数据转化成字符串,只读取有效的部分String request = new String(requestPacket.getData(), 0,         requestPacket.getLength());//2.根据请求,计算响应(服务器最关键的逻辑)//因为我们写的是回显服务器,所以这个步骤省略了String response = process(request);//3.把响应返回给客户端//根据response构造DatagramPacket返回给客户端DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), 0, response.getBytes().length, requestPacket.getSocketAddress());socket.send(responsePacket);}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer udpEchoServer = new UdpEchoServer(9090);udpEchoServer.start();}
}

解析

1. socket对象代表网卡文件,读这个文件相当于从网卡收数据,写这个文件相当于向网卡发数据

2.启动服务器,在循环中做三件事

3. 读取请求并解析

a)构造DatagramPacket对象,这个对象就代表UDP数据报,有表头和载荷(创建字节数组来保存数据) 

b)调用receive接收数据,这里使用的是输出型参数,所以虽然我们是接收一个UDP数据报,但我们还是创建了一个空的DatagramPacket对象。

c)根据字节数组构造纯出一个String

4.根据请求计算响应

5.把响应返回给客户端

这里构造响应的数据报时,传入了字节数组作为载荷,指定了数组下标和有效长度,传入了目的端口和IP。 

6.socket不用close

一个文件是否要关闭,需要考虑这个文件的生命周期,这里的socket对象自始至终都会伴随整个UDP服务器,如果服务器关闭,会自动释放PCB的文件描述附表里面的所有资源,所以就不用手动关闭了。

2.客户端代码
import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UdpEchoClient {private DatagramSocket socket = null;//UDP本身不保存对端的信息,所以我们在自己的代码保存一下private String serverIp;private int serverPort;//和服务器不同,这里的构造方法需要指定访问服务器的地址public UdpEchoClient(String serverIp, int serverPort) throws SocketException {this.serverIp = serverIp;this.serverPort = serverPort;//这里的DatagramSocket不推荐使用固定端口号,如果客户端是固定端口,//很可能在这个程序运行的时候指定的端口被其他程序占用了,客户端在用户手上,//程序员不能控制socket = new DatagramSocket();}public void start() throws IOException {Scanner scanner = new Scanner(System.in);//1.从控制台读取用户输入的内容System.out.println("请输入要发送的内容");String request = scanner.next();//2.把请求放松给服务器,需要构造DatagramPacket对象//构造过程中,不光要构造载荷,还要指定服务器的IP和端口号DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,InetAddress.getByName(serverIp), serverPort);//3.发送数据报socket.send(requestPacket);//4.接收服务器的响应DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);socket.receive(responsePacket);//5.把从服务器读取出来的数据开始解析,打印出来String response = new String(responsePacket.getData(), 0, responsePacket.getLength());System.out.println(response);}public static void main(String[] args) throws IOException {//127.0.0.1是一个环回ip,非常特殊,无论你的主机是什么,都可以用这个ip表示当前主机,相当于thisUdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);udpEchoClient.start();}
}

2.TCP服务器 

因为TCP服务器进行网络通信的基本单位是字节,所以它不像UDP服务器的那样有DatagramPacket类作为基本单位,可以直接使用InputStream/OutputStream。

1.ServerSocket

这个类是专门给服务器用的,作为一开始的牵头人,和客户端建立连接后,就会使用Socket和客户端进行通信

ServerSocket(int port)

创建一个服务器端流套接字,并指定端口号

accept()

和客户端进行连接

2.Socket

这个类服务器和客户端都会使用

Socket(String host, int port)

这两个参数指的是需要指定的服务器IP和端口号

InputStream getInputStream()/OutputStream getOutputStream()

这两个方法是字节流对象

3.代码解析和修改

初始服务器代码

public class TcpEchoServer {private ServerSocket serverSocket= null;//这里和Udp服务器类似,也是在构造的时候指定端口号public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("启动服务器");while(true) {//对于tcp来说,需要向处理客户端发过来的连接//通过读写clientSocket和客户端进行通信//如果没有客户端发送连接,那么accept就会阻塞Socket clientSocket = serverSocket.accept();processConnection(clientSocket);}}//处理一个客户端的连接//可能涉及多个客户端的连接和响应,这里暂不涉及private void processConnection(Socket clientSocket) {try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {Scanner scanner = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);//处理三个步骤while(true) {//1.读取请求并解析,可以直接read,也可以借助scannerif(!scanner.hasNext()) {break;}String request = scanner.next();//2.根据请求计算响应String response = procoss(request);//3.返回响应到客户端writer.println(response);}} catch (IOException e) {e.printStackTrace();}}private String procoss(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);tcpEchoServer.start();}
}

初始客户端代码

public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp, int port) throws IOException {//这里可以直接使用字符串的ip作为参数socket = new Socket(serverIp, port);}public void start() {Scanner scanner = new Scanner(System.in);try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {//为了方便使用,套壳操作Scanner scannerNet = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);//从控制台读取请求String request = scanner.next();//发送给服务器printWriter.println(request);//获取服务器的响应String response = scannerNet.next();//打印到控制台System.out.println(response);} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9090);tcpEchoClient.start();}
}

问题1

当我们把客户端关闭再启动时,输入数据服务器没有响应。原因在于println只是把数据写入“发送缓冲区”,并没有真正写入网卡,此时我们需要用flush方法来属性缓冲区,让数据真正写入网卡。

问题2

这里的pringln里的ln是加上了换行,如果这里我们把ln删去,那么数据能发送过去,而服务器接收不到。

因为hasNext是以空白符(换行,回车,制表符,翻页符...)为基准,遇到空白符则是一个完整的next(),否则就会阻塞。 

编写客户端代码的时候是需要约定请求和响应之间的分隔符的,这里我们使用的是\n

问题3

如果没有客户端连接,就会阻塞在accept这里

如果客户端不发送请求, 就会阻塞在hasNext这里

我们的服务器无法同时等待accept和客户端请求,当我们在等待客户端发送请求时,如果这时有新的客户端想要连接进来,就无法连接。

在这个场景下,我们就能引入多线程,让一个线程专门负责连接服务器。同时可以引入线程池优化效率。

问题4

服务器的socket要记得及时关闭,因为这个socket的生命周期不再是跟随整个服务器了

问题5

当我们的客户端多到一定程度时,服务器无法承担,此时我们就可以使用操作系统中内置的IO多路复用,这个操作本质上是让一个线程处理多个客户端的请求。多个客户端发送数据大概率不是同时的,客户端很可能在阻塞等待。

优化后服务器代码

public class TcpEchoServer {private ServerSocket serverSocket= null;//这里和Udp服务器类似,也是在构造的时候指定端口号public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {ExecutorService executorService = Executors.newCachedThreadPool();System.out.println("启动服务器");while(true) {//对于tcp来说,需要向处理客户端发过来的连接//通过读写clientSocket和客户端进行通信//如果没有客户端发送连接,那么accept就会阻塞//主线程负责进行accept,每次accept到一个新客户端,就创建一个新线程来处理客户端的请求Socket clientSocket = serverSocket.accept();executorService.submit(() -> {processConnection(clientSocket);});}}//处理一个客户端的连接//可能涉及多个客户端的连接和响应,这里暂不涉及private void processConnection(Socket clientSocket) {try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {Scanner scanner = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);//处理三个步骤while(true) {//1.读取请求并解析,可以直接read,也可以借助scannerif(!scanner.hasNext()) {break;}String request = scanner.next();//2.根据请求计算响应String response = procoss(request);//3.返回响应到客户端writer.println(response);writer.flush();}} catch (IOException e) {e.printStackTrace();}}private String procoss(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);tcpEchoServer.start();}
}

优化后客户端代码

public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp, int port) throws IOException {//这里可以直接使用字符串的ip作为参数socket = new Socket(serverIp, port);}public void start() {Scanner scanner = new Scanner(System.in);try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {//为了方便使用,套壳操作Scanner scannerNet = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);//从控制台读取请求String request = scanner.next();//发送给服务器printWriter.println(request);//加上刷新缓冲区操作,才是真正写入网卡printWriter.flush();//获取服务器的响应String response = scannerNet.next();//打印到控制台System.out.println(response);} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9090);tcpEchoClient.start();}
}

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

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

相关文章

【质量管理】现代TRIZ问题识别中的功能分析——功能模型

功能模型的定义 功能模型是对工程系统进行功能分析的一个阶段,目的是建立工程系统的功能模型。功能模型描述了工程系统和超系统组件的功能,包括有用功能、性能水平和成本等。 在文章【质量管理】现代TRIZ中问题识别中的功能分析——相互接触分析-CSDN博客…

广告事件聚合系统设计

需求背景 广告事件需要进行统计,计费,分析等。所以我们需要由数据接入,数据处理,数据存储,数据查询等多个服务模块去支持我们的广告系统 规模上 10000 0000个点击(10000 00000 / 100k 1wQPS) …

C语言中,sizeof关键字(详细介绍)

目录 ‌1. 基本用法‌(1) ‌基本数据类型‌(2) ‌变量‌(3) ‌数组‌(4) ‌指针‌ ‌2. 特殊用法‌(1) ‌结构体与内存对齐‌(2) ‌动态内存分配‌(3) ‌表达式‌ ‌3. 注意事项‌‌1)sizeof 与 strlen 的区别‌:‌2)变长数组(VLA…

ADK 第三篇 Agents (LlmAgent)

Agents 在智能体开发套件(ADK)中,智能体(Agent)是一个独立的执行单元,旨在自主行动以实现特定目标。智能体能够执行任务、与用户交互、使用外部工具,并与其他智能体协同工作。 在ADK中&#x…

【深度学习】典型的 CNN 网络

目录 一、LeNet-5 (1)LeNet-5 网络概览 (2)网络结构详解 (3)关键组件与数学原理 3.1 局部感受野与卷积运算 3.2 权重共享 3.3 子采样(Pooling) 3.4 激活函数 (4…

4.8/Q1,中山大学用NHANES:膳食烟酸摄入量与非酒精性脂肪肝之间的关联

文章题目:Association between Dietary Niacin Intake and Nonalcoholic Fatty Liver Disease: NHANES 2003-2018 DOI:10.3390/nu15194128 中文标题:膳食烟酸摄入量与非酒精性脂肪肝之间的关联:NHANES 2003-2018 发表杂志&#xf…

高效管理远程服务器Termius for Mac 保姆级教程

以下是 Termius for Mac 保姆级教程,涵盖安装配置、核心功能、实战案例及常见问题解决方案,助你高效管理远程服务器(如Vultr、AWS等)。 一、Termius 基础介绍 1. Termius 是什么? 跨平台SSH客户端:支持Ma…

理解数学概念——支集(支持)(support)

1. 支集(support)的定义 在数学中,一个实函数 f 的支集(support)是函数的不被映射到 0 的元素域(即定义域)的子集。若 f 的(定义)域(domain)是一个拓扑空间(即符合拓扑的集合),则 f 的支集则定义为包含( f 的元素域中)不被映射到0的所有点之最小闭集…

Vue 3 Element Plus 浏览器使用例子

Element Plus 是一个基于 Vue 3 的流行开源 UI 库,提供了一系列的组件,帮助开发者快速构建现代化的用户界面。它的设计简洁、现代,包含了许多可定制的组件,如按钮、表格、表单、对话框等,适合用于开发各种 Web 应用。 …

SSR vs SSG:前端渲染模式终极对决(附 Next.js/Nuxt.js 实战案例)

一、引言:前端渲染模式的进化之路 随着互联网的发展,用户对于网页的加载速度和交互体验要求越来越高。前端渲染技术作为影响网页性能的关键因素,也在不断地发展和演进。从最初的客户端渲染(CSR),到后来的服…

算法笔记.分解质因数

代码实现&#xff1a; #include<iostream> using namespace std; void breakdown(int x) {int t x;for(int i 2;i < x/i;i){if(t%i 0){int counts 0;while(t % i 0){t/i;counts;}cout << i <<" "<< counts<<endl;}}if(t >…

CUDA Error: the provided PTX was compiled with an unsupported toolchain

CUDA程序编译时生成的PTX代码与系统上的CUDA驱动版本不兼容 CUDA 编译器版本&#xff1a; CUDA 12.6 (nvcc 编译器版本) CUDA 驱动版本&#xff1a; CUDA 12.3 (nvidia-smi 驱动版本) 解决方法&#xff1a; 驱动版本下载参考&#xff1a;Your connected workspace for wiki…

计算机组成原理实验(7) 堆指令部件模块实验

实验七 堆指令部件模块实验 一、实验目的 1、掌握指令部件的组成方式。 2、熟悉指令寄存器的打入操作&#xff0c;PC计数器的设置和加1操作&#xff0c;理解跳转指令的实现过程。 二、实验要求 按照实验步骤完成实验项目&#xff0c;掌握数据打入指令寄存器IR1、PC计数器的…

2022 年 6 月大学英语四级考试真题(第 2 套)——阅读版——仔细阅读题

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;目前中南大学MBA在读&#xff0c;也考取过HCIE Cloud Computing、CCIE Security、PMP、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &…

磁盘文件系统

磁盘文件系统 一、磁盘结构1.1 认识一下基础的硬件设备以及真实的机房环境1.2 磁盘物理结构与存储结构1、磁盘物理结构2、磁盘的存储结构3、CHS地址定位4、磁盘的逻辑结构&#xff08;LBA&#xff09;5 磁盘真实过程5 CHS && LBA地址 二、理解分区、格式化1 引⼊"…

基于LangChain 实现 Advanced RAG-后检索优化(下)-上下文压缩与过滤

摘要 Advanced RAG 的后检索优化&#xff0c;是指在检索环节完成后、最终响应生成前&#xff0c;通过一系列策略与技术对检索结果进行深度处理&#xff0c;旨在显著提升生成内容的相关性与质量。在这些优化手段中&#xff0c;上文压缩与过滤技术是提升检索结果质量的重要手段。…

为什么 Vite 速度比 Webpack 快?

一、webpack会先进行编译&#xff0c;再运行&#xff0c;vite会直接启动&#xff0c;再按需编译文件。 首先看两张图&#xff0c;可以清晰的看到&#xff0c;上面的图是webpack编译过的&#xff0c;而下面的图是vite直接使用工程内文件。 二、区别于Webpack先打包的方式&am…

C# 操作符

C# 操作符 一、操作符概览二、优先级与运算顺序三、各类操作符的实例 一、操作符概览 操作符&#xff08;运算符&#xff09;的本质是函数的简记法 操作符不能脱离与它关联的数据类型 int x 5; int y 4; int z x / y; Console.WriteLine(z);//输出1double a 5.0; double b…

C++设计模式:面向对象的八大设计原则之四

里氏替换原则&#xff08;Liskov Substitution Principle&#xff0c;LSP&#xff09;是面向对象设计中的一个重要原则&#xff0c;它指出子类必须能够替换它的基类&#xff0c;并且程序的行为不会发生改变。也就是说&#xff0c;在任何使用基类对象的地方&#xff0c;都可以透…

网络通信领域的基础或流行协议

一、TCP(传输控制协议) 1. 宏观介绍 TCP:全称“Transmission Control Protocol”——传输控制协议,是互联网最基础的传输协议之一。传输层协议,提供面向连接、可靠的字节流传输服务。它通过三次握手建立连接、四次挥手断开连接,确保数据有序、完整地传输作用:让两个设备…