互联网跟移动互联网

交流的故事 (A Story Of Communication)

Have you ever wondered how the Internet actually speaks? How does one computer “talk” to another computer via the Internet?

您是否想过互联网实际上是如何说话的? 一台计算机如何通过Internet与另一台计算机“对话”?

When people communicate with one another, we use words strung into seemingly meaningful sentences. The sentences only make sense because we’ve agreed on a meaning for these sentences. We’ve defined a protocol of communication, so to speak.

当人们彼此交流时,我们使用串入看似有意义的句子中的单词。 这些句子才有意义,因为我们已经同意了这些句子的含义。 可以这么说,我们已经定义了通信协议。

Turns out, computers speak to each other in a similar fashion over the Internet. But, we’re getting ahead of ourselves. People use their mouths to communicate, let’s figure out what the mouth of the computer is first.

事实证明,计算机在Internet上以类似的方式相互交谈。 但是,我们正在超越自我。 人们用嘴巴交流,让我们先弄清楚计算机的嘴巴是什么。

输入套接字 (Enter The Socket)

The socket is one of the most fundamental concepts in computer science. You can build entire networks of inter-connected devices using sockets.

套接字是计算机科学中最基本的概念之一。 您可以使用套接字建立互连设备的整个网络。

Like all other things in computer science, a socket is a very abstract concept. So, rather than define what a socket is, it is far easier to define what a socket does.

像计算机科学中的所有其他事物一样,套接字是一个非常抽象的概念。 因此,与其定义套接字是什么,不如定义套接字的作用要容易得多。

So, what does a socket do? It helps two computers communicate with each other. How does it do this? It has two methods defined, called send() and recv() for send and receive respectively.

那么,套接字有什么作用? 它可以帮助两台计算机相互通信。 它是如何做到的? 它定义了两个方法,分别称为send()recv() ,分别用于发送和接收。

Okay, that’s all great, but what do send() and recv() actually send and receive? When people move their mouths, they exchange words. When sockets use their methods, they exchange bits and bytes.

好的,那很好,但是send()recv()实际发送和接收什么呢? 当人们张开嘴巴时,他们交换言语。 当套接字使用其方法时,它们交换位和字节。

Let’s illustrate the methods with an example. Let’s say we have two computers, A and B. Computer A is trying to say something to Computer B. Therefore, Computer B is trying to listen to what Computer A is saying. Here’s what that would look like.

让我们用一个例子来说明这些方法。 假设我们有两台计算机,A和B。计算机A试图对计算机B说些话。因此,计算机B试图听计算机A在说什么。 这就是它的样子。

读取缓冲区 (Reading The Buffer)

Looks a little odd, doesn’t it? For one, both computers are pointing to a bar in the middle, titled ‘buffer’.

看起来有点奇怪,不是吗? 首先,两台计算机都指向中间一个名为“缓冲区”的栏。

What is the buffer? The buffer is a memory stack. It’s where the data for each computer is stored, and is allocated by the kernel.

什么是缓冲区? 缓冲区是一个内存堆栈。 它是存储每台计算机的数据并由内核分配的位置。

Next, why are they both pointing to the same buffer? Well, that’s not quite accurate actually. Each computer has its own buffer allocated by its own kernel and the network transports the data between the two separate buffers. But, I don’t want to get into network details here, so we’ll assume that both computers have access to the same buffer placed “somewhere in the void between”.

接下来,为什么它们都指向同一缓冲区? 好吧,实际上这不是很准确。 每台计算机都有由自己的内核分配的自己的缓冲区,并且网络在两个单独的缓冲区之间传输数据。 但是,我不想在这里讨论网络细节,因此我们假设两台计算机都可以访问位于“中间空隙”中的同一缓冲区。

Okay, now that we know what this looks like visually, let’s abstract it away into code.

好的,现在我们知道了外观,让我们将其抽象为代码。

#Computer A sends data computerA.send(data)
#Computer B receives data computerB.recv(1024)

This code snippet does the exact same thing the image above represents. Except for one curiosity, we don’t say computerB.recv(data). Instead, we specify a seemingly random number in the place of data.

该代码段执行的操作与上图完全相同。 除了一种好奇心,我们不说computerB.recv(data) 。 相反,我们在数据位置指定了一个看似随机的数字。

The reason is simple. Data over a network is transmitted into bits. Therefore, when we recv in computerB, we specify the number of bits we are willing to receive at any one given point in time.

原因很简单。 网络上的数据被传输成比特。 因此,当我们在computerB中接收时,我们指定在任何给定的时间点我们愿意接收的位数

Why did I pick 1024 bytes to receive at once? No specific reason. It is usually best to specify the number of bytes you would receive in a power of 2. I picked 1024 which is 2¹⁰.

为什么选择1024个字节一次接收? 没有具体原因。 通常最好指定以2的幂为单位接收的字节数。我选择了1024,即2¹⁰。

So, how does the buffer figure this out? Well, Computer A writes or sends whatever data is stored with it into the buffer. Computer B decides to read or receive the first 1024 bytes of what is stored in that buffer.

那么,缓冲区如何解决呢? 好吧,计算机A将存储在一起的所有数据写入或发送到缓冲区中。 计算机B决定读取或接收该缓冲区中存储的前1024个字节。

Okay, awesome! But, how do these two computers know to talk to each other? For instance, when Computer A writes to this buffer, how does it know that Computer B is going to pick it up? To rephrase that, how can it ensure that a connection between two computers has a unique buffer?

好吧,太棒了! 但是,这两台计算机如何知道彼此通话? 例如,当计算机A写入此缓冲区时,它如何知道计算机B将要拾取它? 换句话说,如何确保两台计算机之间的连接具有唯一的缓冲区?

移植到IP (Porting Into IPs)

The image above shows the same two computers we’ve been working on all along with one more detail added in. There are a bunch of numbers listed in front of each computer along the length of a bar.

上图显示了我们一直在使用的两台计算机,并添加了更多详细信息。每台计算机的前面都沿着条的长度列出了一堆数字。

Consider the long bar in front of each computer to be the router that connects a specific computer to the internet. Those numbers listed on each bar are called ports. Your computer has thousands of ports available on it right now. Each port allows a socket connection. I’ve only shown 6 ports in the image above, but you get the idea.

假设每台计算机前面的长条是将特定计算机连接到Internet的路由器。 每个栏上列出的数字称为端口 。 您的计算机上现在有数千个可用端口。 每个端口都允许一个套接字连接。 我在上图中仅显示了6个端口,但是您明白了。

Ports below 255 are generally reserved for system calls and low-level connections. It is generally advisable to open up a connection on a port in the high 4-digits, like 8000. I haven’t drawn the buffer in the image above, but you can assume that each port has its own buffer.

低于255的端口通常保留用于系统调用和低级连接。 通常建议在高4位的端口上打开一个连接,例如8000。我没有在上图中绘制缓冲区,但是您可以假定每个端口都有自己的缓冲区。

The bar itself also has a number associated with it. This number is called the IP address. The IP address has a bunch of ports associated with it. Think of it in the following way:

酒吧本身也有一个与之相关的数字。 该号码称为IP地址。 IP地址具有与其关联的一堆端口。 通过以下方式考虑它:

127.0.0.1                             / | \                            /  |  \                           /   |   \                        8000  8001 8002

Great, let’s set up a connection on a specific port between Computer A and Computer B.

很好,让我们在计算机A和计算机B之间的特定端口上建立连接。

# computerA.pyimport socket
computerA = socket.socket()
# Connecting to localhost:8000 computerA.connect(('127.0.0.1', 8000)) string = 'abcd' encoded_string = string.encode('utf-8') computerA.send(encoded_string)

Here is the code for computerB.py

这是computerB.py的代码

# computerB.py import socket
computerB = socket.socket()
# Listening on localhost:8000 computerB.bind(('127.0.0.1', 8000)) computerB.listen(1)
client_socket, address = computerB.accept() data = client_socket.recv(2048) print(data.decode('utf-8'))

It looks like we’ve jumped a little ahead in terms of the code, but I’ll step through it. We know we have two computers, A and B. Therefore, we need one to send data and one to receive data.

看起来我们在代码方面已经领先了一些,但我将逐步介绍。 我们知道我们有两台计算机,A和B。因此,我们需要一台发送数据,一台接收数据。

I have arbitrarily selected A to send data and B to receive data. In this line computerA.connect((‘127.0.0.1’, 8000), I am making computerA connect to port 8000 on IP address 127.0.0.1.

我已经任意选择了A发送数据,B选择接收数据。 在这行中computerA.connect(('127.0.0.1', 8000) ,我正在使computerA连接到IP地址127.0.0.1上的端口8000。

Note: 127.0.0.1 typically means localhost, which references your machine

注意:127.0.0.1通常表示localhost,它引用您的计算机

Then, for computerB, I am making it bind to port 8000 on IP address 127.0.0.1. Now, you’re probably wondering why I have the same IP address for two different computers.

然后,对于computerB,我将其绑定到IP地址127.0.0.1上的端口8000。 现在,您可能想知道为什么我为两台不同的计算机使用相同的IP地址。

That’s because I’m cheating. I’m using one computer to demonstrate how you can use sockets (i’m basically connecting from and to the same computer for the sake of simplicity). Typically two different computers would have two different IP addresses.

那是因为我在作弊。 我正在使用一台计算机来演示如何使用套接字(为简单起见,我基本上是在同一台计算机之间进行连接)。 通常,两台不同的计算机将具有两个不同的IP地址。

We already know that only bits can be sent as part of a data packet, which is why we encode the string before sending it over. Similarly, we decode the string on Computer B. If you decide to run the above two files locally, make sure to run computerB.py file first. If you run the computerA.py file first, you will get a connection refused error.

我们已经知道,只有位可以作为数据包的一部分发送,这就是为什么我们在发送字符串之前先对其进行编码。 同样,我们对计算机B上的字符串进行解码。如果您决定在本地运行上述两个文件,请确保首先运行computerB.py文件。 如果首先运行computerA.py文件,则会收到连接被拒绝的错误。

服务客户 (Serving The Clients)

I’m sure its been pretty clear to many of you that what I’ve been describing so far is a very simplistic client-server model. In fact you can see that from the above image, all I’ve done is replace Computer A as the client and Computer B as the server.

我敢肯定,对你们中的许多人来说很清楚,到目前为止,我所描述的是一个非常简单的客户端-服务器模型。 实际上,您可以从上图中看到,我所做的只是将计算机A替换为客户端,将计算机B替换为服务器。

There is a constant stream of communication that goes on between clients and servers. In our prior code example, we described a one shot of data transfer. Instead, what we want is a constant stream of data being sent from the client to the server. However, we also want to know when that data transfer is complete, so we know we can stop listening.

客户端和服务器之间存在持续不断的通信流。 在我们先前的代码示例中,我们描述了一次数据传输的过程。 相反,我们想要的是从客户端到服务器的持续不断的数据流。 但是,我们也想知道何时完成数据传输,因此我们可以停止监听。

Let’s try to use an analogy to examine this further. Imagine the following conversation between two people.

让我们尝试使用一个类比来进一步研究这一点。 想象下面两个人之间的对话。

Two people are trying to introduce themselves. However, they will not try to talk at the same time. Let’s assume that Raj goes first. John will then wait until Raj has finished introducing himself before he begins introducing himself. This is based on some learned heuristics but we can generally describe the above as a protocol.

两个人正在尝试自我介绍。 但是,他们不会尝试同时讲话。 让我们假设拉吉先行。 然后,约翰将等到Raj开始自我介绍之后,再开始自我介绍。 这是基于一些学到的启发式方法,但是我们通常可以将以上内容描述为协议。

Our clients and servers need a similar protocol. Or else, how would they know when it’s their turn to send packets of data?

我们的客户端和服务器需要类似的协议。 否则,他们怎么知道该何时发送数据包?

We’ll do something simple to illustrate this. Let’s say we want to send some data which happens to be an array of strings. Let’s assume the array is as follows:

我们将做一些简单的说明。 假设我们要发送一些恰好是字符串数组的数据。 让我们假设数组如下:

arr = ['random', 'strings', 'that', 'need', 'to', 'be', 'transferred', 'across', 'the', 'network', 'using', 'sockets']

arr = ['random', 'strings', 'that', 'need', 'to', 'be', 'transferred', 'across', 'the', 'network', 'using', 'sockets']

The above is the data that is going to be written from the client to the server. Let’s create another constraint. The server needs to accept data that is exactly equivalent to the data occupied by the string that is going to be sent across at that instant.

以上是将要从客户端写入服务器的数据。 让我们创建另一个约束。 服务器需要接受与该时刻将要发送的字符串完全相同的数据。

So, for instance, if the client is going to send across the string ‘random’, and let’s assume each character occupies 1 byte, then the string itself occupies 6 bytes. 6 bytes is then equal to 6*8 = 48 bits. Therefore, for the string ‘random’ to be transferred across sockets from the client to the server, the server needs to know that it has to access 48 bits for that specific packet of data.

因此,例如,如果客户端要通过字符串“ random”发送,并假设每个字符占用1个字节,那么字符串本身就占用6个字节。 那么6个字节等于6 * 8 = 48位。 因此,要使字符串“ random”从客户端到服务器跨套接字传输,服务器需要知道它必须访问该特定数据包的48位。

This is a good opportunity to break the problem down. There are a couple of things we need to figure out first.

这是解决问题的好机会。 我们需要首先解决两件事。

我们如何计算一个字符串在Python中占用的字节数? (How do we figure out the number of bytes a string occupies in Python?)

Well, we could start by figuring out the length of a string first. That’s easy, it’s just a call to len(). But, we still need to know the number of bytes occupied by a string, not just the length.

好吧,我们可以首先确定一个字符串的长度。 很简单,这只是对len()的调用。 但是,我们仍然需要知道字符串占用的字节数,而不仅仅是长度。

We’ll convert the string to binary first, and then find the length of the resulting binary representation. That should give us the number of bytes used.

我们将首先将字符串转换为二进制,然后找到所得二进制表示的长度。 那应该给我们使用的字节数。

len(‘random’.encode(‘utf-8’)) will give us what we want

len('random'.encode('utf-8'))会给我们我们想要的

我们如何将每个字符串占用的字节数发送到服务器? (How do we send the number of bytes occupied by each string to the server?)

Easy, we’ll convert the number of bytes (which is an integer) into a binary representation of that number, and send it to the server. Now, the server can expect to receive the length of a string before receiving the string itself.

容易,我们将字节数(是整数)转换为该数字的二进制表示形式,然后将其发送到服务器。 现在,服务器可以期望在接收字符串本身之前先接收字符串的长度。

服务器如何知道客户端何时完成所有字符串的发送? (How does the server know when the client has finished sending all the strings?)

Remember from the analogy of the conversation, there needs to be a way to know if the data transfer has completed. Computers don’t have their own heuristics they can rely on. So, we’ll provide a random rule. We’ll say that when we send across the string ‘end’, that means the server has received all the strings and can now close the connection. Of course, this means that we can’t use the string ‘end’ in any other part of our array except the very end.

从对话的类比中记住,需要有一种方法来知道数据传输是否已完成。 计算机没有可以依靠的启发式方法。 因此,我们将提供一个随机规则。 我们会说,当我们发送字符串'end'时,这意味着服务器已收到所有字符串,现在可以关闭连接。 当然,这意味着我们不能在数组的除结尾之外的任何其他部分使用字符串“ end”。

Here’s the protocol we’ve designed so far:

这是到目前为止我们设计的协议:

The length of the string will be 2 bytes, followed by the actual string itself which will be variable length. It will depend on the string length sent in the previous packet, and we will alternate between sending the string lengths and the string itself. The EOT stands for End Of Transmission, and sending the string ‘end’ means that there is no more data to send.

字符串的长度将是2个字节,然后是实际的字符串本身,该字符串将是可变长度。 这将取决于前一个数据包中发送的字符串长度,我们将在发送字符串长度和字符串本身之间交替进行。 EOT表示传输结束,发送字符串'end'意味着不再有数据要发送。

Note: Before we continue, I want to point something out. This is a very simple and stupid protocol. If you want to see what a well-designed protocol looks like, look no further than the HTTP protocol.

注意:在我们继续之前,我想指出一点。 这是一个非常简单和愚蠢的协议。 如果您想查看设计良好的协议是什么样的,则只需要HTTP协议即可 。

Let’s code this out. I’ve included comments in the code below so it’s self-explanatory.

让我们对此进行编码。 我在下面的代码中包含了注释,因此它是不言自明的。

Great, we have a client running. Next, we need the server.

太好了,我们正在运行一个客户端。 接下来,我们需要服务器。

I want to explain a few specific lines of code in the above gists. The first, from the clientSocket.py file.

我想在以上要点中解释一些特定的代码行。 第一个,来自clientSocket.py文件。

len_in_bytes = (len_of_string).to_bytes(2, byteorder='little')

len_in_bytes = (len_of_string).to_bytes(2, byteorder='little')

What the above does is convert a number into bytes. The first parameter passed to the to_bytes function is the number of bytes allocated to the result of converting len_of_string to its binary representation.

上面所做的是将数字转换为字节。 传递给to_bytes函数的第一个参数是分配给将len_of_string转换为其二进制表示形式的结果的字节数。

The second parameter is used to decide whether to follow the Little Endian format or the Big Endian format. You can read more about it here. For now, just know that we will always stick with little for that parameter.

第二个参数用于确定是遵循Little Endian格式还是Big Endian格式。 您可以在此处了解更多信息。 现在,只知道我们将始终坚持使用该参数。

The next line of code I want to take a look at is:

我要看的下一行代码是:

client_socket.send(string.encode(‘utf-8’))

client_socket.send(string.encode('utf-8'))

We’re converting the string to a binary format using the‘utf-8’ encoding.

我们正在使用'utf-8'编码将字符串转换为二进制格式。

Next, in the serverSocket.py file:

接下来,在serverSocket.py文件中:

data = client_socket.recv(2) str_length = int.from_bytes(data, byteorder='little')

The first line of code above receives 2 bytes of data from the client. Remember that when we converted the length of the string to a binary format in clientSocket.py, we decided to store the result in 2 bytes. This is why we’re reading 2 bytes here for that same data.

上面的第一行代码从客户端接收2个字节的数据。 请记住,当我们在clientSocket.py字符串的长度转换为二进制格式时,我们决定将结果存储在2个字节中。 这就是为什么我们在这里读取2个字节以获取相同数据的原因。

Next line involves converting the binary format to an integer. The byteorder here is “little”, to match the byteorder we used on the client.

下一行涉及将二进制格式转换为整数。 此处的byteorder “很小”,以匹配我们在客户端上使用的byteorder

If you go ahead and run the two sockets, you should see that the server will print out the strings the client sends across. We established communication!

如果继续运行两个套接字,则应该看到服务器将打印出客户端发送的字符串。 我们建立了沟通!

结论 (Conclusion)

Okay, we covered quite a bit so far. Namely, what are sockets, how we use them and how to design a very simple and stupid protocol. If you want to learn more about how sockets work, I highly recommend reading Beej’s Guide To Network Programming. That e-book has a lot of great stuff in it.

好的,到目前为止,我们已经介绍了很多。 即,什么是套接字,我们如何使用它们以及如何设计一个非常简单而愚蠢的协议。 如果您想了解有关套接字如何工作的更多信息,我强烈建议您阅读Beej的网络编程指南 。 那本电子书有很多很棒的东西。

You can of course take what you read in this article so far, and apply it to more complex problems like streaming images from a RaspberryPi camera to your computer. Have fun with it!

当然,您可以将到目前为止阅读的内容应用于其他问题,例如将图像从RaspberryPi相机流式传输到计算机上。 玩得开心!

If you want to, you can follow me on Twitter or GitHub. You can also check out my blog here. I’m always available if you want to reach out to me!

如果愿意,可以在Twitter或GitHub上关注我。 您也可以在这里查看我的博客。 如果您想与我联系,我将随时为您服务!

Originally published at https://redixhumayun.github.io/networking/2019/02/14/how-the-internet-speaks.html on February 14, 2019.

最初于2019年2月14日发布在https://redixhumayun.github.io/networking/2019/02/14/how-the-internet-speaks.html 。

翻译自: https://www.freecodecamp.org/news/how-the-internet-speaks-1ac4ee385e28/

互联网跟移动互联网

互联网跟移动互联网_互联网如何说话相关推荐

  1. 互联网最新开发语言_互联网是多语言的,但您需要学习普通话

    互联网最新开发语言 The internet is becoming the town square for the global village of tomorrow. - Bill Gates. ...

  2. 互联网测试岗位分类_互联网企业都有哪些技术类岗位

    互联网公司中常见的几种岗位有产品类岗位.技术类岗位.设计类岗位.运营类岗位四类岗位.其中技术要求高的要数技术类岗位了,那么技术类岗位有可以细分为哪些呢?沈阳优就业IT培训老师带你了解下技术岗位分类,让 ...

  3. 互联网运营面试题_互联网运营、产品岗一定要看的面试题

    互联网运营.产品岗一定要看的面试题关于网络运营和产品营销策划.想要刺激的小伙伴们可以看看. 下面就展示这些题目,希望求职者们能够get到题目里面的考察点. 1. 为什么要做产品经理/运营 2.日常生活 ...

  4. 凡客副总裁被曝离职:或因IPO受阻|凡客|王春焕|离职_互联网_新浪科技_新浪网...

    凡客副总裁被曝离职:或因IPO受阻|凡客|王春焕|离职_互联网_新浪科技_新浪网 凡客副总裁被曝离职:或因IPO受阻|凡客|王春焕|离职_互联网_新浪科技_新浪网 凡客副总裁被曝离职:或因IPO受阻 ...

  5. IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深藏功与名)_互联网的一些事...

    IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深藏功与名)_互联网的一些事 IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深藏功与名)_互联网的一些事 IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深 ...

  6. connertone怎么远程连接服务器,离形得似_互联网艺术与化身网络建设.pdf

    离形得似_互联网艺术与化身网络建设 ( ) 第 8卷 第 4 期 南 京 邮 电 大 学 学 报 社 会 科 学 版 Vo l. 8 No. 4 2006年 12 月 Journal of N anj ...

  7. 纳米材料在计算机专业的应用,纳米材料论文_互联网_IT计算机_专业资料

    纳米材料论文_互联网_IT计算机_专业资料 (4页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 9.9 积分 纳米材料的性质.制备与表征班级:材料125 ...

  8. 概要设计 英文_互联网知识大全:软件开发中和文档常见的英文缩写,还不快收藏...

    概要设计 英文_互联网知识大全:软件开发中和文档常见的英文缩写,还不快收藏... https://blog.csdn.net/weixin_39914243/article/details/11123 ...

  9. 【网络协议从入门到底层原理】【00】课程大纲_互联网与网络协议

    持续学习&持续更新中- 学习态度:守破离 [网络协议从入门到底层原理][00]课程大纲_互联网与网络协议 课程大纲 互联网(Internet) 为什么要学习网络协议 基本常识 C/C++的跨平 ...

  10. 控制台应用和空项目有什么区别_互联网小程序的应用以及APP的应用有什么区别及发展...

    随时移动互联网进入的千家万户,互联网的手机应用程序也渐渐的在市场上流行起来了.今天主要跟大家谈一下互联网小程序的应用以及APP的应用有什么区别以及未来的发展趋.未来会流行什么手机应用或者APP应用,我 ...

最新文章

  1. Windows下程序打包发布时的小技巧
  2. Python 3 利用 subprocess 实现管道( pipe )交互操作读/写通信
  3. 010_Spring Data JPA一对多关系
  4. string转map集合_Map、斗地主案例
  5. 连续 3 天,企业容器应用实战营上海站来啦!
  6. Camel:构建基于消息的应用程序
  7. 2020快手移动游戏行业玩家数据价值报告
  8. vivo Y66的usb调试模式在哪里,打开vivo Y66usb调试模式的流程
  9. 由浅入深逐步了解 Synchronized
  10. Fill-倒水问题(Uva-10603-隐式图路径寻找问题)
  11. 时序数据在滴滴实时数据开发平台中的处理和应用...
  12. SQLyog 注册码记录
  13. MacOS升级Big Sur后32位锐捷客户端排坑历程
  14. Java精进-20分钟学会mybatis使用
  15. maven仓库清理缓存文件(dos工具)
  16. (搞笑)经典!一些很彪悍的句子 !!
  17. Android版优酷网闪亮登场
  18. Leiden算法介绍
  19. Opencv每日函数 图像分割模块 watershed分水岭算法
  20. 三维电子沙盘无人机倾斜摄影开发教程第22课

热门文章

  1. char 与Unicode编码
  2. 忘记电子商务吧,很快一切都将与元宇宙有关
  3. 深度学习中的normalization总结(BN、LN、WN、IN、GN)
  4. ASCII字符代码表,python 生成字母a到z字母,生产倒序列表
  5. 大数据技术期末复习习题-前两章 大数据概述及Hadoop概述
  6. ZMQ特点及消息模式
  7. Shellmo:用于娱乐和教育的Aquatic 3D打印机器人
  8. 毕节大班装饰安师傅的四大装修经验之谈
  9. 程序人生 | 程序员感觉技术停滞了怎么办?找个师傅引导架构之路
  10. ocdma相干非相干_相干成像与非相干成像系统的比较