tcp的拆包和粘包

tcp的拆包和粘包

简介

拆包和粘包是在socket编程中经常出现的情况,在socket通讯过程中,如果通讯的一端一次性连续发送多条数据包,tcp协议会将多个数据包打包成一个tcp报文发送出去,这就是所谓的粘包。而如果通讯的一端发送的数据包超过一次tcp报文所能传输的最大值时,就会将一个数据包拆成多个最大tcp长度的tcp报文分开传输,这就叫做拆包。

一些基本概念

MTU

泛指通讯协议中的最大传输单元。一般用来说明TCP/IP四层协议中数据链路层的最大传输单元,不同类型的网络MTU也会不同,我们普遍使用的以太网的MTU是1500,即最大只能传输1500字节的数据帧。可以通过ifconfig命令查看电脑各个网卡的MTU。

MSS

指TCP建立连接后双方约定的可传输的最大TCP报文长度,是TCP用来限制应用层可发送的最大字节数。如果底层的MTU是1500byte,则 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte。

示意图

如图所示,客户端和服务端之间的通道代表TCP的传输通道,两个箭头之间的方块代表一个TCP数据包,正常情况下一个TCP包传输一个应用数据。粘包时,两个或多个应用数据包被粘合在一起通过一个TCP传输。而拆包情况下,会一个应用数据包会被拆成两段分开传输,其他的一段可能会和其他应用数据包粘合。

在这里插入图片描述

场景实例

下面通过简单实现两个socket端通讯,演示粘包和拆包的流程。客户端和服务端都在本机进行通讯,服务端使用127.0.0.1监听客户端,客户端也在127.0.0.1发起连接。

1. 粘包

a. 实现服务端代码,服务监听55533端口,没有指定IP地址默认就是localhost,即本机IP环回地址 127.0.0.1,接着就等待客户端连接,代码如下:

在这里插入图片描述

public class SocketServer {public static void main(String[] args) throws Exception {// 监听指定的端口int port = 55533;ServerSocket server = new ServerSocket(port);// server将一直等待连接的到来System.out.println("server将一直等待连接的到来");Socket socket = server.accept();// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取InputStream inputStream = socket.getInputStream();byte[] bytes = new byte[1024 * 1024];int len;while ((len = inputStream.read(bytes)) != -1) {//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8String content = new String(bytes, 0, len,"UTF-8");System.out.println("len = " + len + ", content: " + content);}inputStream.close();socket.close();server.close();}
}

b. 实现客户端代码,连接服务端,两端连接建立后,客户端就连续发送100个同样的字符串;
public class SocketClient {
public static void main(String[] args) throws Exception {
// 要连接的服务端IP地址和端口
String host = “127.0.0.1”;
int port = 55533;
// 与服务端建立连接
Socket socket = new Socket(host, port);
// 建立连接后获得输出流
OutputStream outputStream = socket.getOutputStream();
String message = “这是一个整包!!!”;
for (int i = 0; i < 100; i++) {
//Thread.sleep(1);
outputStream.write(message.getBytes(“UTF-8”));
}
Thread.sleep(20000);
outputStream.close();
socket.close();
}
}

c. 先运行服务端代码,运行到server.accept()时阻塞,打印“server将一直等待连接的到来”来等待客户端的连接,接着再运行客户端代码;
d. 客户端代码运行后,就能看到服务端的控制台打印结果如下:

server将一直等待连接的到来
len = 21, content: 这是一个整包!!!
len = 168, content: 这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!
len = 105, content: 这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!
len = 42, content: 这是一个整包!!!这是一个整包!!!
len = 42, content: 这是一个整包!!!这是一个整包!!!
len = 63, content: 这是一个整包!!!这是一个整包!!!这是一个整包!!!
len = 42, content: 这是一个整包!!!这是一个整包!!!
len = 21, content: 这是一个整包!!!
len = 42, content: 这是一个整包!!!这是一个整包!!!
len = 21, content: 这是一个整包!!!
len = 147, content: 这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!
len = 63, content: 这是一个整包!!!这是一个整包!!!这是一个整包!!!
len = 21, content: 这是一个整包!!!
len = 252, content: 这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!这是一个整包!!!

按照原来的理解,在客户端每次发送一段字符串“这是一个整包!!!”, 分别发送了50次。服务端应该也会是分50次接收,会打印50行同样的字符串。但结果却是这样不寻常的结果,这就是由于粘包导致的结果。加粗样式
总结出现粘包的原因

  • 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去;
  • 接收数据端的应用层没有及时读取接收缓冲区中的数据;
  • 数据发送过快,数据包堆积导致缓冲区积压多个数据后才一次性发送出去(如果客户端每发送一条数据就睡眠一段时间就不会发生粘包);

在这里插入图片描述

2. 拆包

如果数据包太大,超过MSS的大小,就会被拆包成多个TCP报文分开传输。所以要演示拆包的情况,就需要发送一个超过MSS大小的数据,而MSS的大小是多少呢,就要看数据所经过网络的MTU大小。由于上面socket中的客户端和服务端IP都是127.0.0.1, 数据只在回环网卡间进行传输,所以客户端和服务端的MSS都为回环网卡的 MTU - 20(IP Header) -20 (TCP Header),沿用粘包的例子,下面是拆包的处理步骤。

a. mac电脑可以通过ifconfig查看本地的各个网卡的MTU,以下我的电脑运行ifconfig后输出的一部分,其中lo0就是回环网卡,可看出mtu是16384:

lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>inet 127.0.0.1 netmask 0xff000000inet6 ::1 prefixlen 128inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1nd6 options=201<PERFORMNUD,DAD>
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500ether 88:e9:fe:76:dc:57inet6 fe80::18d4:84fb:fa10:7f8%en0 prefixlen 64 secured scopeid 0x6inet 192.168.1.8 netmask 0xffffff00 broadcast 192.168.1.255inet6 240e:d2:495f:9700:182a:c53f:c720:5f63 prefixlen 64 autoconf securedinet6 240e:d2:495f:9700:d96:48f2:8108:2b33 prefixlen 64 autoconf temporarynd6 options=201<PERFORMNUD,DAD>media: autoselectstatus: active
en1: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500options=60<TSO4,TSO6>ether 7a:00:5c:40:cf:01media: autoselect <full-duplex>status: inactive
en2: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500options=60<TSO4,TSO6>ether 7a:00:5c:40:cf:00media: autoselect <full-duplex>status: inactive
......

b. 服务端代码和粘包时一样,将客户端代码改为发送一个超过16384字节的字符串,假设使用UTF-8编码的中文字符一个文字3个字节,那么就需要发送一个大约5461字的字符串,TCP才会拆包,为了篇幅不会太长,发送的字符串我只用一小段文字代替。客户端代码如下:

public class SocketClient {private final static String CONTENT = "这是一个很长很长的字符串这是一个很长很长的字符串这是一个很长很长的字符串这是一个很.....长很长的字符串这是一个很长很长的字符串这是一个很长很长的字符串这是一个很长很长的字符串这是一个很长很长的字符串这是一个很长很长的字符串这是一个很长很长的字符串";//测试时大于5461文字,由于篇幅所限,只用这一段作为代表public static void main(String[] args) throws Exception {// 要连接的服务端IP地址和端口String host = "127.0.0.1";int port = 55533;// 与服务端建立连接Socket socket = new Socket(host, port);// 建立连接后获得输出流OutputStream outputStream = socket.getOutputStream();for (int i = 0; i < 1; i++) {outputStream.write(CONTENT .getBytes("UTF-8"));}Thread.sleep(20000);outputStream.close();socket.close();}

c. 和粘包的代码示例一样,先运行原来的的服务端代码,接着运行客户端代码,看服务端的打印输出。

server将一直等待连接的到来
len = 22328, content: 这是一个很长很长的字符串这是一个很长很长的字符串这是一个很长很长的字符串这是一个很.....长很长的字符串这是一个很长很长的字符串这是一个很长很长的字符串这是一个很长很长的字符串这是一个很长很长的字符串这是一个很长很长的字符串这是一个很长很长的字符串...(22328字节数组的文字)

通过输出的log,可发现客户端发送的字符串并没有在服务端被拆开,而是一次读取了客户端发送的完整字符串。是不是就没有被拆包呢,其实不是的,这是因为字符串被分拆成两个TCP报文,发送到了服务端的缓冲数据流中,服务端一次性读取了流中的数据,显示的结果就是两个tcp数据报串接在一起了。我们可以通过tcpdump抓包查看数据的传送细节:

在控制台输入sudo tcpdump -i lo0 ‘port 55533’,作用是监听回环网卡lo0上在55533端口传输的数据包,有从这个端口出入的数据包都会被抓获并打印出来,这个命令需要管理员权限,输入用户密码后,开始监听数据。这时我们按照刚才的测试步骤重新运行一遍,抓包的结果如下:

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo0, link-type NULL (BSD loopback), capture size 262144 bytes
23:15:44.641208 IP 192.168.1.8.58748 > 192.168.1.8.55533: Flags [S], seq 2331897419, win 65535, options [mss 16344,nop,wscale 6,nop,nop,TS val 261991443 ecr 0,sackOK,eol], length 0
23:15:44.641261 IP 192.168.1.8.55533 > 192.168.1.8.58748: Flags [S.], seq 3403812509, ack 2331897420, win 65535, options [mss 16344,nop,wscale 6,nop,nop,TS val 261991443 ecr 261991443,sackOK,eol], length 0
23:15:44.641270 IP 192.168.1.8.58748 > 192.168.1.8.55533: Flags [.], ack 1, win 6379, options [nop,nop,TS val 261991443 ecr 261991443], length 0
23:15:44.641279 IP 192.168.1.8.55533 > 192.168.1.8.58748: Flags [.], ack 1, win 6379, options [nop,nop,TS val 261991443 ecr 261991443], length 0
23:15:44.644808 IP 192.168.1.8.58748 > 192.168.1.8.55533: Flags [.], seq 1:16333, ack 1, win 6379, options [nop,nop,TS val 261991446 ecr 261991443], length 16332
23:15:44.644812 IP 192.168.1.8.58748 > 192.168.1.8.55533: Flags [P.], seq 16333:22329, ack 1, win 6379, options [nop,nop,TS val 261991446 ecr 261991443], length 5996
23:15:44.644835 IP 192.168.1.8.55533 > 192.168.1.8.58748: Flags [.], ack 22329, win 6030, options [nop,nop,TS val 261991446 ecr 261991446], length 0
  • 第三行中,客户端发起连接请求,options参数中有一个mss 16344的参数,就表示连接建立后,客户端能接收的最大TCP报文大小,超过后就会被拆包分开传送;
  • 前四行都是两端的连接过程;
  • 第五行客户端口58748向服务端口55533传输了16332字节大小的数据包;
  • 第六行客户端口58748向服务端口55533传输了5996字节大小的数据包;

从抓包过程就能看出,客户端发送一个字符串,被拆成了两个TCP数据报进行传输。

解决方案
对于粘包的情况,要对粘在一起的包进行拆包。对于拆包的情况,要对被拆开的包进行粘包,即将一个被拆开的完整应用包再组合成一个完整包。比较通用的做法就是每次发送一个应用数据包前在前面加上四个字节的包长度值,指明这个应用包的真实长度。如下图就是应用数据包格式。

在这里插入图片描述
下面我修改前文的代码示例,来实现解决拆包粘包问题,有两种实现方式:

  1. 一种方式是引入netty库,netty封装了多种拆包粘包的方式,只需要对接口熟悉并调用即可,减少自己处理数据协议的繁琐流程;
  2. 自己写协议封装和解析流程,相当于实现了netty库拆粘包的简易版本,本篇文章是为了学习需要,就通过这个方式实现:

a. 客户端。每次发送一个字符串前,都将字符串转为字节数组,在原数据字节数组前再加上一个四个字节的代表该数据的长度,然后将组合的字节数组发送出去;

public class SocketClient {public static void main(String[] args) throws Exception {// 要连接的服务端IP地址和端口String host = "127.0.0.1";int port = 55533;// 与服务端建立连接Socket socket = new Socket(host, port);// 建立连接后获得输出流OutputStream outputStream = socket.getOutputStream();String message = "这是一个整包!!!";byte[] contentBytes = message.getBytes("UTF-8");System.out.println("contentBytes.length = " + contentBytes.length);int length = contentBytes.length;byte[] lengthBytes = Utils.int2Bytes(length);byte[] resultBytes = new byte[4 + length];System.arraycopy(lengthBytes, 0, resultBytes, 0, lengthBytes.length);System.arraycopy(contentBytes, 0, resultBytes, 4, contentBytes.length);for (int i = 0; i < 10; i++) {outputStream.write(resultBytes);}Thread.sleep(20000);outputStream.close();socket.close();}
}
public final class Utils {//int数值转为字节数组public static byte[] int2Bytes(int i) {byte[] result = new byte[4];result[0] = (byte) (i >> 24 & 0xFF);result[1] = (byte) (i >> 16 & 0xFF);result[2] = (byte) (i >> 8 & 0xFF);result[3] = (byte) (i & 0xFF);return result;}//字节数组转为int数值public static int bytes2Int(byte[] bytes){int num = bytes[3] & 0xFF;num |= ((bytes[2] << 8) & 0xFF00);num |= ((bytes[1] << 16) & 0xFF0000);num |= ((bytes[0] << 24)  & 0xFF000000);return num;}
}

b. 服务端。接收到客户端发送过来的字节数组后,先提取前面四个字节转为int值,然后再往后取该int数值长度的字节数,再转为字符串就是客户端端发送过来的数据,详见代码:

public class SocketServer {public static void main(String[] args) throws Exception {// 监听指定的端口int port = 55533;ServerSocket server = new ServerSocket(port);// server将一直等待连接的到来System.out.println("server将一直等待连接的到来");Socket socket = server.accept();// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取InputStream inputStream = socket.getInputStream();byte[] bytes = new byte[1024 * 128];int len;byte[] totalBytes = new byte[]{};int totalLength = 0;while ((len = inputStream.read(bytes)) != -1) {//1. 将读取的数据和上一次遗留的数据拼起来int tempLength = totalLength;totalLength = len + totalLength;byte[] tempBytes = totalBytes;totalBytes = new byte[totalLength];System.arraycopy(tempBytes, 0, totalBytes, 0, tempLength);System.arraycopy(bytes, 0, totalBytes, tempLength, len);while (totalLength > 4) {byte[] lengthBytes = new byte[4];System.arraycopy(totalBytes, 0, lengthBytes, 0, lengthBytes.length);int contentLength = Utils.bytes2Int(lengthBytes);//2. 如果剩下数据小于数据头标的长度,则出现拆包,再次获取数据连接if (totalLength < contentLength + 4) {break;}//3. 将数据头标的指定长度的数据取出则为应用数据byte[] contentBytes = new byte[contentLength];System.arraycopy(totalBytes, 4, contentBytes, 0, contentLength);//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8String content = new String(contentBytes, "UTF-8");System.out.println("contentLength = " + contentLength + ", content: " + content);//4. 去掉已读取的数据totalLength -= (4 + contentLength);byte[] leftBytes = new byte[totalLength];System.arraycopy(totalBytes, 4 + contentLength, leftBytes, 0, totalLength);totalBytes = leftBytes;}}inputStream.close();socket.close();server.close();}
}

c. 打印结果:

server将一直等待连接的到来
contentLength = 21, content: 这是一个整包!!!
contentLength = 21, content: 这是一个整包!!!
contentLength = 21, content: 这是一个整包!!!
contentLength = 21, content: 这是一个整包!!!
contentLength = 21, content: 这是一个整包!!!
contentLength = 21, content: 这是一个整包!!!
contentLength = 21, content: 这是一个整包!!!
contentLength = 21, content: 这是一个整包!!!
contentLength = 21, content: 这是一个整包!!!
contentLength = 21, content: 这是一个整包!!!

客户端连续发送十个字符串,服务端也收到了分开的十个字符串,不再出现多个数据包连在一起的情况了。

好文

TCP粘包拆包的产生原因分析及解决思路
TCP粘包/拆包问题

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

TCP粘包和拆包

TCP粘包和拆包 &#xff08;1&#xff09;TCP是面向连接的&#xff0c;面向流的&#xff0c;提供可靠性服务。收发两端&#xff08;客户端和服务端&#xff09;都要有一一成对的socket&#xff0c;因此&#xff0c;发送端为了将多个发给接收端的包&#xff0c;更有效的发给对方…

Python拆包

Python拆包 怎么元组拆包&#xff1f;怎么字典拆包&#xff1f;怎么拆包赋值&#xff1f; 拆包&#xff1a; 对于函数中的多个返回数据, 去掉元组, 列表 或者字典直接获取里面数据的过程 &#xff08;只能对可迭代对象进行拆包&#xff09; Python拆包&#xff1a; 就是把元组…

python 拆包_python 拆包

广告关闭 腾讯云11.11云上盛惠 &#xff0c;精选热门产品助力上云&#xff0c;云服务器首年88元起&#xff0c;买的越多返的越多&#xff0c;最高返5000元&#xff01; 为了查明原因&#xff0c;我去查了 matplotlib 的源码&#xff0c;发现 plot 函数返回的是一个列表&#…

【JavaEE初阶】 线程安全

文章目录 &#x1f334;线程安全的概念&#x1f333;观察线程不安全&#x1f384;线程不安全的原因&#x1f6a9;修改共享数据&#x1f4cc;原子性&#x1f4cc; 可见性&#x1f4cc;代码顺序性 &#x1f332;解决之前的线程不安全问题⭕总结 &#x1f334;线程安全的概念 线程…

深度学习基础知识 学习率调度器的用法解析

深度学习基础知识 学习率调度器的用法解析 1、自定义学习率调度器**&#xff1a;**torch.optim.lr_scheduler.LambdaLR2、正儿八经的模型搭建流程以及学习率调度器的使用设置 1、自定义学习率调度器**&#xff1a;**torch.optim.lr_scheduler.LambdaLR 实验代码&#xff1a; i…

基于Java+SpringBoot+Vue民宿管理系统的设计与实现 前后端分离【Java毕业设计·文档报告·代码讲解·安装调试】

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

【Java】什么是API

API (Application Programming Interface,应用程序编程接口) Java中的API 指的就是 JDK 中提供的各种功能的 Java类&#xff0c;这些类将底层封装起来&#xff0c;我们不需要关心这些类是如何实现的&#xff0c;只需要学习这些类如何使用即可&#xff0c;我们可以通过帮助文档…

【C语言】阶乘实现

&#x1f389;博客主页&#xff1a;Luo-Kuang-何 &#x1f389;座右铭&#xff1a;一起走向人生巅峰的路上&#x1f601; &#x1f389;学习进度&#xff1a;【C语言】 &#x1f389;博客声明&#xff1a;我将尽我所能&#xff0c;用心写好每一份博客&#xff0c;让更多小伙伴能…

WatchOS开发教程之四: Watch与 iPhone的通信和数据共享

WatchOS 开发教程系列文章: WatchOS开发教程之一: Watch App架构及生命周期 WatchOS开发教程之二: 布局适配和系统Icon设计尺寸 WatchOS开发教程之三: 导航方式和控件详解 WatchOS开发教程之四: Watch与 iPhone的通信和数据共享 WatchOS开发教程之五: 通知功能开发 WatchOS开发…

Kali Linux 秘籍 第九章 无线攻击

第九章 无线攻击 作者&#xff1a;Willie L. Pritchett, David De Smet 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 简介 当今&#xff0c;无线网络随处可见。由于用户四处奔走&#xff0c;插入以太网网线来获取互联网访问的方式非常不方便。无线网络为了使用便利…

python3遍历目录查找文件

一直有一部分软件&#xff0c;他们的主要功能就是方便用户查找本地文件位置。python当然也可以完成这项功能&#xff0c;所以我写了一个简短的代码。 写完发现&#xff0c;python真的是一门简洁的语言啊&#xff01; 我完成这个功能主要就是用了os模块的功能&#xff0c;直接…

WinSvr:在 Windows Server 中启用无线连接

默认情况下,所有 Windows Server 2022/2019/2016/2012R2 版本都禁用无线 (Wi-Fi) 支持。如果将 Wi-Fi 网络适配器(USB 或 PCI)插入运行 Windows Server 的主机,则无法在控制面板中启用它。本文将在这篇简短的说明中展示如何在 Windows Server 上启用无线支持。 注意,在 W…

【运维笔记】Docker 部署Kibana-7.4.0(在线Docker版)

Docker 部署Kibana-7.4.0&#xff08;在线Docker版&#xff09; 一、准备工作&#xff1a; Centos 7.5 安装 Docker-24.0.6 详细步骤&#xff08;避坑版&#xff09;&#xff1a; https://blog.csdn.net/seesun2012/article/details/133674191注意1&#xff1a;本文的命令使用…

短视频账号矩阵系统源码saas===独立部署

前言&#xff1a; 短视频账号矩阵是指在不同的短视频平台上&#xff0c;一个个人或企业所拥有的账号数量和分布情况。由于不同的短视频平台受众人群和内容类型等因素不同&#xff0c;因此拥有更多账号可以在更广泛的受众中传播内容&#xff0c;提高曝光度和流量。短视频账号矩阵…

管理类联考——逻辑——真题篇——按知识分类——第十章 数学相关

第十章 数学相关 第一节 集合 真题&#xff08;2010-53&#xff09;-数学相关-集合-画饼集能力-朴素逻辑 53.参加某国际学术研讨会的 60 名学者中&#xff0c;亚裔学者 31 人&#xff0c;博士 33 人&#xff0c;非亚裔学者中无博士学位的 4 人。根据上述陈述&#xff0c;参…

2017年全国硕士研究生入学统一考试管理类专业学位联考逻辑试题——解析版

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&…

爬虫小白系列01期: 从李白杜甫,来看爬虫本质 、 浏览器访问网页原理 、 请求头的概念

众所周知&#xff0c;爬虫的本质是&#xff0c;模拟浏览器打开网页&#xff0c;获取网页中我们需要的那部分数据。 那首先我们应该清楚&#xff0c;普通一般浏览器打开网页的流程和原理是怎样的&#xff1f; 根据生活经验&#xff0c;我们使用浏览器打开网页的步骤一般是这样…

神犇营my0001:春晓

本题来源于神犇营 题目: [my0001] 唐代诗人孟浩然所作的《春晓》是一首家喻户晓的诗,但是校园里更流行改编版的《春晓》。 春眠不觉晓, 处处蚊子咬。 夜里嗡嗡声, 脓包知多少。 现在我们要用刚才所学的知识来输出这首诗的前两句。首先在右边的输入C++程序的基本框架…

世界十大名诗

世界十大名诗 时间&#xff1a;2011-01-07 来源&#xff1a;网络 点击&#xff1a;318次 When You Are Old by William Butler Yeats (1865-1939) WHEN you are old and gray and full of sleep, And nodding by the fire, take down this book, And slowly re…

9月火气大,能认真写代码么?

不羡鸳鸯不羡仙&#xff0c;一行代码调半天。原创&#xff1a;小姐姐味道&#xff08;微信公众号ID&#xff1a;xjjdog&#xff09;&#xff0c;欢迎分享&#xff0c;转载请保留出处。 我在风中藏把刀&#xff0c;斩尽世间秋色。 这句注定要流传千古的名诗&#xff0c;是xjjdo…