帅地

我的眼里只有学习


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

从零打卡leetcode之day 2--两数相加

发表于 2018-08-05 | 分类于 leetcode刷题贴 | 0 comments

前言

深知自己在算法方面上很菜,于是打算通过打卡的方式,逼自己一把。每天在leetcode上打卡,并且把自己的想法分享出来。这将是一段漫长而又艰辛的旅程。如果你也想和我一起走上一条充满艰辛的航路,那么,别犹豫了,上车吧,一起学习一起探讨。


从零打卡leetcode之day 2

1
2
3
4
5
6
7
8
9
10
题目描述:

给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,
它们的每个节点只存储单个数字。将两数相加返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

我的想法

我靠,居然还用到了链表的知识,突然就想起了当初用c语言自学链表的那段日子,真的差点被搞死。各种指针指来指去的。

好吧,我这里需要声明一下,如果你还没接触过链表,或者对链表超级不熟悉,麻烦你先去学好链表再来刷题,因为,链表是真的贼重要啊。

正文

方法1:

说实话,看到这道题的时候,我的想法是不管三七二十一,直接把链表代表的数字给直接算出来,然后在把两个数字给加起来,最后在把得到的和拆分成链表。可谓是暴力思路又简单啊。不过感觉代码量会有点多。

例如:

1
2
3
4
5
6
(2 -> 4 -> 3) => 2 + 4 * 10 + 3 * 100 = 342 
(5 -> 6 -> 4) => 5 + 6 * 10 + 4 * 100 = 465
然后
342 + 465 = 807
接着
807 => 7 -> 0 -> 8

代码如下所示:

先给出链表的结构

1
2
3
4
5
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//方法1
public ListNode addTwoNumbers1(ListNode l1, ListNode l2) {
int num1 = 0;//用来存放第一个链表代表的数字
int num2 = 0;//用来存放第二个链表代表的数字
int sum = 0;//存放和
//t = 1,10,100....用来代表链表中的个位,十位,百位...
int t = 1;//因为是从个位开始遍历的,所以初值为1
//算第一个数字
while(l1 != null){
num1 = num1 + l1.val * t;
t = t * 10;
l1 = l1.next;
}
t = 1;
//算第二个数字
while(l2 != null){
num2 = num2 + l2.val * t;
t = t * 10;
l2 = l2.next;
}
//相加
sum = num1 + num2;
//拆分出来放进链表里;
ListNode head = null;//作为链表的头节点
ListNode temp = head;//跟踪链表
while(sum > 0) {//结束条件为sum等于0
if(head == null){
head = new ListNode(sum % 10);
temp = head;
}else{
temp.next = new ListNode(sum % 10);
temp = temp.next;
}
sum = sum / 10;
}
//由于有可能sum一开始就为0,所以还需要在检查一下
if(head == null){
head = new ListNode(0);
}
return head;
}

大家一起探讨探讨,你们觉得这种思路可行吗?觉得可行的举个爪,不可行的站起来说说为啥不可行。

反正,我是觉得不可行,为什么?

因为题目并没有说这条链表有多长啊,假如这条链表很长的话,一个int型整数根本存不了这个数字,当然,你可能会说,我可以用long long啊。 不好意思,long long也可能存不了。你可能又会说,java或python什么的,不是有大整数咪?好吧,我没去用过这些大整数,不知道具体个什么情况,所以当作没有这么一回事处理,haha。有兴趣的可以去尝试一下。


方法2

刚才方法1已经被告知不可行了。实际上,这道题我是觉得在解法思路上还是比较简单的,我相信大家也都能想到方法2这种方法:

就是我们可以让两个数的个位数相加,十位数相加….然后个位数有进位的话,再把进位给十位数….

例如:

1
2
(1 -> 4 -> 5) + (1 -> 6 -> 4 -> 5)
(我是故意给出两条链表长度不相等的情况的)

解法如下图:

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int cout = 0;//用来检查是否有进位,默认没有进位
ListNode head = null;//用来作为链表头
ListNode temp = head;//用来跟踪链表
//注意循环结束条件
while(l1 != null && l2 != null){
if(head == null){
head = new ListNode((l1.val + l2.val + cout) % 10);
temp = head;
}else{
temp.next = new ListNode((l1.val + l2.val + cout) % 10);
temp = temp.next;
}
//检查是否有进位
cout = (l1.val + l2.val + cout) / 10;
l1 = l1.next;
l2 = l2.next;
}
while(l1 != null){
temp.next = new ListNode((l1.val + cout) % 10);
cout = (l1.val + cout) / 10;
l1 = l1.next;
temp = temp.next;
}
while(l2 != null){
temp.next = new ListNode((l2.val + cout) % 10);
cout = (l2.val + cout) / 10;
l2 = l2.next;
temp = temp.next;

}
//最后还得在检测一下时候最高位有进位
if(cout == 1){
temp.next = new ListNode(cout);
}
return head;

这里需要注意的一些点:

  1. 就是第一个循环的结束条件.
  1. 当循环结束之后,还得把那条比较长的链表剩余的部分再循环一遍。
  2. 最后还得看一下是否最高位有进位。

我觉得这道题在解法思路上还是比较简单,不过并不意味着做起来简单,因为这道题如果不仔细看,还是很容易出错的,细节点不叫多。

不过

除了方法二,我是不知道还要其他什么比较好的方法了,但是我是觉得方法二里面的代码有点多,于是绞尽脑汁,各种简化,写出了一份代码比较简练的精简代码。放出来给各位看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//作为头节点,且最后返回时不要这个头节点
ListNode head = new ListNode(0);
ListNode temp = head;//跟踪
int cout = 0;

while(l1 != null || l2 != null || cout != 0){
if(l1 != null){
cout += l1.val;
l1 = l1.next;
}
if(l2 != null){
cout += l2.val;
l2 = l2.next;
}
temp.next = new ListNode(cout % 10);
temp = temp.next;
cout = cout / 10;
}
return head.next;

先得意一下:虽然大家都能想到方法二,不过我就不信你能比我的简洁。哈哈哈

几点说明:

  1. 先把头节点直接new出来,可以省略判断语句。
  2. 以前的while循环是判断两个链表都不为空,不过我现在换了一种想法,因为i你想我,只要有一个链表是不为空的,或者只要coun=1,那么我们就得继续处理,于是乎…..

此题到此结束


从零打卡leetcode之day 1--两数之和

发表于 2018-08-04 | 分类于 leetcode刷题贴 | 0 comments

前言

深知自己在算法方面上很菜,于是打算通过打卡的方式,逼自己一把。每天在leetcode上打卡,并且把自己的想法分享出来。这将是一段漫长而又艰辛的旅程。如果你也想和我一起走上一条充满艰辛的航路,那么,别犹豫了,上车吧,一起学习一起探讨。


从零打卡leetcode之day 1

1
2
3
4
5
6
7
8
题目描述:
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。

示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

看到这道题的第一想法,暴力暴力,能用暴力解决的事情不要和我谈其他。

于是,两个for循环噼里啪啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//方法一:简单暴力,两个for循环搞定
public int[] twoSum1(int[] nums, int target) {
int[] temp = new int[2];//用来存要找的数的下标
for(int i = 0; i < nums.length; i++){
for(int j = i + 1; j < nums.length; j++){
if(nums[i] + nums[j] == target){
temp[0] = i;
temp[1] = j;
return temp;
}
}
}
return null;
}

不过心里才两个循环时间复杂度可是n的平方,心想肯定得超时,不过还是大胆提交一下提交,呵呵,居然通过了。。。。

我猜这可能是第一道题的原因,让我们开心一下。不过排名你懂的。

不过居然要刷题,要学习算法,那么肯定是不能满足于「第一想法」的,必须得找出我们自己能接受的最优解。

于是我想到了用空间换时间,就是我们可以用哈希表映射的方法,先把数组里所有元素的值作为key,下标作为value存进hashmap里,我们知道从hashmap里查找元素的时间复杂度近似常数,即O(1)。然后我们可以用一个for循环来遍历数组,遍历的过程中一边查找另一个数是否在hashMap里,例如a = nums[i],然后查找b = targert - a是否在hashMap里,如果在,则证明a,b便是要找的数,否则继续查找。代码下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int[] twoSum2(int[] nums, int target){
int[] temp = new int[2];
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
map.put(nums[i], i);
}
//遍历查找
for(int i = 0; i < nums.length; i++){
int a = nums[i];
if(map.containsKey(target - a)){
temp[0] = i;
temp[1] = map.get(target - a);
return temp;
}
}
return null;
}

不过,不知道大家发现问题了没有,题目里只是说会有唯一解,并且一个元素只能用一次,但并没有说,不能两个元素的值相同,也就是说,数组如果有元素的值相同的话,存进hashMap会出现冲突的情况。所以,这样先把数组的所有元素存进hashMap的做法是不严谨的。

我们来分析一下处理方法:

出现这个问题的本质原因是因为我们要找的那两个数刚好相等,导致我们当今哈希表的时候出现了丢失的情况。如何解决?

上面说了,问题的原因是这两个我们要的数刚好相等,并且我们从一开始就想把他们两个硬塞进哈希表里,导致一山容不了二虎。其实,我们可以换一种想法啊,我们可以一边遍历查找一边把数放进哈希表里啊。
先看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public int[] twoSum3(int[] nums, int target){
int[] temp = new int[2];
Map<Integer,Integer> map = new HashMap<>();
//遍历查找
for(int i = 0; i < nums.length; i++){
int a = nums[i];
if(map.containsKey(target - a)){
temp[0] = map.get(target - a);
temp[1] = i;
return temp;
}else {//如果找不到则存进去
map.put(nums[i], i);
}
}
return null;
}

就是取出数a=nums[i],先判断b=target-a在不在哈希表里,如果在,那么a和b就是要找的值了,如果不在,就把a放进哈希表了。
这样,就不会出现上面那种情况的冲突了,因为两个我们要找的数,只有一个会放在哈希表里。


一文看懂https如何保证数据传输的安全性的

发表于 2018-07-30 | 分类于 计算机网络 | 0 comments

大家都知道,在客户端与服务器数据传输的过程中,http协议的传输是不安全的,也就是一般情况下http是明文传输的。但https协议的数据传输是安全的,也就是说https数据的传输是经过加密。

在客户端与服务器这两个完全没有见过面的陌生人交流中,https是如何保证数据传输的安全性的呢?

下面我将带大家一步步了解https是如何加密才得以保证数据传输的安全性的。我们先把客户端称为小客,服务器称为小服。然后一步步探索在小客与小服的交流中(就是一方请求一方响应),https是如何保证他们的交流不会被中间人窃听的。

1. 对称加密

假如现在小客与小服要进行一次私密的对话,他们不希望这次对话内容被其他外人知道。可是,我们平时的数据传输过程中又是明文传输的,万一被某个黑客把他们的对话内容给窃取了,那就难受了。

为了解决这个问题,小服这家伙想到了一个方法来加密数据,让黑客看不到具体的内容。该方法是这样子的:

在每次数据传输之前,小服会先传输小客一把密钥,然后小服在之后给小客发消息的过程中,会用这把密钥对这些消息进行加密。小客在收到这些消息后,会用之前小服给的那把密钥对这些消息进行解密,这样,小客就能得到密文里面真正的数据了。如果小客要给小服发消息,也同样用这把密钥来对消息进行加密,小服收到后也用这把密钥进行解密。 这样,就保证了数据传输的安全性。如图所示:

这时,小服想着自己的策咯,还是挺得意的。

可是,这时候问题来了。这个策略安全的前提是,小客拥有小服的那把密钥。可问题是,小服是以明文的方式把这把密钥传输给小客的,这个时候,如果黑客截取了这把密钥,那就难受了。小服与小客就算是加密了内容,在截取了密钥的黑客老哥眼里,这和明文没啥区别。

2. 非对称加密

小服还是挺聪明的,得意了一会之后,小服意识到了密钥会被截取这个问题。倔强的小服又想到了另外一种方法:用非对称加密的方法来加密数据。该方法是这样的:

小服和小客都拥有两把钥匙,一把钥匙的公开的(全世界都知道也没关系),称之为公钥;而另一把钥匙是保密(也就是只有自己才知道),称之为私钥。并且,用公钥加密的数据,只有对应的私钥才能解密;用私钥加密的数据,只有对应的公钥才能解密。

所以在传输数据的过程中,小服在给小客传输数据的过程中,会用小客给他的公钥进行加密,然后小客收到后,再用自己的私钥进行解密。小客给小服发消息的时候,也一样会用小服给他的公钥进行加密,然后小服再用自己的私钥进行解密。 这样,数据就能安全着到达双方。如图:

想着这么复杂的策略都能想出来,小服可是得意的不能在得意了…..

看着那么得意的小服,小客这时心情就不得好了。还没等小服得意多久,小客就给它泼了一波冷水了。

小客严肃着说:其实,你的这种方法也不是那么的安全啊。还是存在被黑客截取的危险啊。例如:

你在给我传输公钥的过程中,如果黑客截取了你的公钥,并且拿着自己的公钥来冒充你的公钥来发给我。我收到公钥之后,会用公钥进行加密传输(这时用的公钥实际上是黑客的公钥)。黑客截取了加密的消息之后,可以用他自己的私钥来进行解密来获取消息内容。然后在用你(小服)的公钥来对消息进行加密,之后再发给你(小服)。 这样子,我们的对话内容还是被黑客给截取了啊。(倒过来小客给小服传输公钥的时候也一样)。

我靠,这么精妙的想法居然也不行,小服这波,满脸无神。

插讲下

其实在传输数据的过程中,在速度上用对称加密的方法会比非对称加密的方法快很多。所以在传输数据的时候,一般不单单只用非对称加密这种方法(我们先假设非对称密码这种方法很安全),而是会用非对称加密 + 对称加密这两种结合的方法。 你想啊,对于对称加密这种方法来说,之所以不安全是因为密钥在传输的过程中,被别人知道了。基于这个,我们可以用非对称加密方法来安全着传输密钥,之后在用对称加密的方法来传输消息内容(当然,我这里假定了非对称加密传输是安全的,下面会讲如何使之安全)。

数字证书

我们回头想一下,是什么原因导致非对称加密这种方法的不安全性呢?它和对称加密方法的不安全性不同。非对称加密之所以不安全,是因为小客收到了公钥之后,无法确定这把公钥是否真的是小服。

也就是说,我们需要找到一种策略来证明这把公钥就是小服的,而不是别人冒充的。

为了解决这个问题,小服和小客通过绞尽脑汁想出了一种终极策略:数字证书:

我们需要找到一个拥有公信力、大家都认可的认证中心(CA)

小服再给小客发公钥的过程中,会把公钥以及小服的个人信息通过Hash算法生成消息摘要。如图:

为了防止摘要被人调换,小服还会用CA提供的私钥对消息摘要进行加密来形成数字签名。如图:

并且,最后还会把原来没Hash算法之前的信息和数字签名合并在一起,形成数字证书。如图:

当小客拿到这份数字证书之后,就会用CA提供的公钥来对数字证书里面的数字签名进行解密得到消息摘要,然后对数字证书里面小服的公钥和个人信息进行Hash得到另一份消息摘要,然后把两份消息摘要进行对比,如果一样,则证明这些东西确实是小服的,否则就不是。如图:

这时可能有人会有疑问,CA的公钥是怎么拿给小客的呢?小服又怎么有CA的私钥呢?其实,(有些)服务器在一开始就向认证中心申请了这些证书,而客户端里,也会内置这些证书。如图(此图来元阮一峰的网络日志)

当客户端收到服务器返回来的数据时,就会在内置的证书列表里,查看是否有有解开该数字证书的公钥,如果有则…..否则…..

讲到这里,就大概结束了。希望对你有所帮助勒。如果有哪里写得不对的地方,欢迎大家指出。

推荐阅读:

一文读懂一台计算机是如何把数据传送给另外一台计算机的

一文带你简要了解https是如何保证数据传输的安全性的。

互联网协议

发表于 2018-07-27 | 分类于 计算机网络 | 0 comments

一文读懂一台计算机是如何把数据传送给另外一台计算机的

题外话

好久没更新文章了,说真,写文章不难,但坚持原创写文章真的很难。主要是前阵子有其他事,一直没时间写。现在,又打了鸡血来坚持更新文章了,在此感谢那些一直关注着的读者。从今天起,我会每周坚持更新若干篇原创文章,当然,我说的“原创”文章并非所有东西都是原创的,是指参考了大量了资料之后,用自己的话描述总结出来,当然,会有一些总结性的话很类似,因此我觉得作者说的很清晰,才采用了这句话。虽然很花时间,但只要让你有所收获,那便是我坚持写下去动力。

正文

上面说了一些题外话,哈哈。下面我们开始今天的知识点。

互联网相隔n公里路的两台计算机,是如何进行数据的传送的呢?在成千上万台的计算机中,一台计算机是如何正确着找到另外一个计算机,并把数据传给它的呢?

学过计算机网络的同学可能知道,在这互联网中,计算机与计算机之间的数据传送,主要是基于各种“协议”串联起来的。不过今天要讲的,并不会详细去讲各种协议,而是通过各种简化之后,让你大概知道数据之间传送的原理。

模型

互联网中数据的传送,其实分为好几层来处理数据的,每一层有它自己明确的功能。例如就像流水线生产一样,一部分人负责这部分的工作,处理完之后就把剩余的工作扔给另外一部分人来处理……

对于互联网数据传送的分层模型,有分成七层的,有分成5层的,还有分成4层的。例如分成七层模型的如下(从上到下):

  • 应用层
  • 表示层
  • 会话层
  • 传输层
  • 网络层
  • 数据链路层
  • 物理层

七层中,越往下越靠近计算机底层,越往上越靠近用户。

不过,我们今天要讲的,是以分成五层的模型来讲。其分层如下图:

相当于把应用层、表示层、会话层看成是一层的。接下来我们从下往上来一步一步讲,看看如何从一台计算机准确着传给另一台计算机的。

一. 物理层

一台计算机与另一台计算机要进行通信,第一件要做的事是什么?当然是要把这台计算机与另外的其他计算机连起来啊,例如可以通过光纤啊,电缆啊,双绞线啊等物体把他们联起来。然后才能进行通信,也就是说,,物理层负责把两台计算机连起来,然后在计算机之间传送0,1这样的电信号。

二. 数据链路层

前面说了,物理层它只是单纯着负责在计算机之间传输0,1这样的电信号。假如这些0,1组合的传送毫无规则,计算机是解读不了的。因此,我们需要制定一套规则来进行0,1的传送。例如多少个电信号为一组啊,每一组信号应该如何标识才能让计算机读懂啊等。

数据链路层工作在物理层之上,负责给这些0,1制定传送的规则,然后另一方再按照相应的规则来进行解读。

1. 以太网协议

以太网协议规定,一组电信号构成一个数据包,把这个数据包称之为“桢”。每一个桢由标头(Head)和数据(Data)两部分组成。如下:

这个桢的最大长度是1518个字节,最小长度为64字节。假如需要传送的数据很大的话,就分成多个桢来进行传送。

对于表头和数据这两个部分,他们存放的都是一些什么数据呢?我猜你眯着眼睛都能想到他们应该放什么数据。 毫无疑问,我们至少得知道这个桢是谁发送,发送给谁的等这些信息吧?所以标头部分主要是一些说明数据,例如发送者,接收者等信息。而数据部分则是这个数据包具体的,想给接受的内容。

大家想一个问题,一个桢的长度是64~1518个字节,也就是说桢的长度不是固定的,那你觉得标头部分的字节长度是固定的吗?它当然是固定的啊,假如不是固定的,每个桢都是单独发的,那计算机怎么知道标头是几个字节,数据是几个字节。所以标头部分的字节是固定的,并且固定为18个字节。

2. MAC地址

把一台计算的的数据通过物理层和链路层发送给另一台计算机,究竟是谁发给谁的,计算机与计算机之间如何区分,,你总得给他们一个唯一的标识吧?

这就是MAC地址,连入网络的每一个计算机都会有网卡接口,每一个网卡都会一个地址,这个地址就叫做MAC地址。计算机之间的数据传送,就是通过MAC地址来唯一寻找、传送的。MAC地址在网卡生产是就被唯一标识了。

3. 广播与ARP协议

如图,假如计算机A知道了计算机B的MAC地址,然后计算机A想要给计算机B传送数据,虽然计算机A知道了计算机B的MAC地址,可是它要怎么给它传送数据呢?计算机A不仅连着计算机B,而且计算机A也还连着其他的计算机。 虽然计算机A知道计算机B的MAC地址,可是计算机A是无法知道计算机B是分布在哪边路线上的。实际上,计算机A是通过广播的方式把数据发送给计算机B。在同一个子网中,计算机A要向计算机B发送一个数据包,这个数据包包含接收者的MAC地址。这个时候同一个子网中的计算机C,D也会收到这个数据包的,然后收到这个数据包的计算机,会把数据包的MAC地址取出来,与自身的MAC地址对比,如果两者相同,则接受这个数据包,否则就丢弃这个数据包。这种发送方式我们称之为广播,就像我们平时在广场上通过广播的形式呼叫某个人一样。

那么问题来了,计算机A是如何知道计算机B的MAC地址的呢?这个时候就得由ARP协议这个家伙来解决了,不过ARP协议会涉及到IP地址,不过我们下面才会扯到IP地址。因此我们先放着,就当作是有这么一个ARP协议,通过它我们可以知道子网中其他计算机的MAC地址。

三. 网络层

上面我们有说到子网这个关键词,实际上我们所处的网络,是由无数个子网络构成的。广播的时候,也只有同一个子网里面的计算机能够收到。 假如没有子网这种划分的话,计算机A发一个数据包给计算机B,其他所有计算机也都能收到这个数据包,然后进行对比再舍弃。世界上有那么多它计算机,每一台计算机都能收到其他所有计算机的数据包,那就不得了了。那还不得奔溃。 因此产生了子网这么一个东西。

那么问题来了,我们如何区分哪些MAC地址是属于同一个子网的呢?假如是同一个子网,那我们就用广播的形式把数据传送给对方,如果不是同一个子网的,我们就会把数据发给网关,让网关进行转发。

为了解决这个问题我们引入了一套新的地址协议,这个地址协议能够帮助我们区分MAC地址是否处于同一个子网中。这也是网络层负责解决的问题。

1. IP协议

这个协议就是IP协议,它所定义的地址,我们称之为IP地址。IP协议有两种版本,一种是IPv4,另一种是IPv6。不过我们目前大多数用的还是IPv4,我们现在也只讨论IPv4这个版本的协议。

这个IP地址由32为的二进制数组成,我们一般把它分成4段的十进制表示,地址范围为0.0.0.0~255.255.255.255

每一台想要联网的计算机都会有一个IP地址。这个IP地址被分为两部分,前面一部分代表网络部分,后面一部分代表主机部分。并且网络部分和主机部分的二进制位数是不固定的。

假如两台计算机的网络部分是一模一样的,我们就说这两台计算机是处于同一个子网中。例如192.168.43.1和192.168.43.2,假如这两个IP地址的网络部分为24为,主机部分为8位。那么他们的网络部分都为192.168.43,所以他们处于同一个子网中。

可是问题来了,你怎么知道网络部分是占几位。也就是说,单单从两台计算机的IP地址,我们是无法判断他们的是否处于同一个子网中的。

这就引申出了另一个关键词————子码掩码。子码掩码和IP地址一样也是32位二进制数,不过它的网络部分规定全部为1,主机部分规定全部为0.也就是说,假如上面那两个IP地址的网络部分为24为,主机部分为8为的话,那他们的子码掩码都为11111111.11111111.11111111.00000000,即255.255.255.0。

那有了子字码掩码,如何来判端IP地址是否处于同一个子网中呢。显然,知道了子码掩码,相当于我们知道了网络部分是几位,主机部分是几位。我们只需要把IP地址与它的子码掩码做与(and)运算,然后把各自的结果进行比较就行了,如果比较的结果相同,则代表是同一个子网,否则不是同一个子网。

例如,192.168.43.1和192.168.43.2的子码掩码都为255.255.255.0,把IP与子码掩码相与,可以得到他们都为192.168.43.0,进而他们处于同一个子网中。

ARP协议

有了上面IP协议的知识,我们回来讲一下ARP协议。
有了两台计算机的IP地址,我们就可以判断出它们是否处于同一个子网之中。 假如他们处于同一个子网之中,计算机A要给计算机B发送数据时。我们可以通过ARP协议来得到计算机B的MAC地址。ARP协议也是通过广播的形式给同一个子网中的每台电脑发送一个数据包(当然,这个数据包会包含接收方的IP地址)。对方收到这个数据包之后,会取出IP地址与自身的对比,如果相同,则把自己的MAC地址回复给对方,否则就丢弃这个数据包。这样,计算机A就能知道计算机B的MAC地址了。

可能有人会问,知道了MAC地址之后,发送数据是通过广播的形式发送,询问对方的MAC地址也是通过广播的形式来发送,那其他计算机怎么知道你是要传送数据还是要询问MAC地址呢?其实在询问MAC地址的数据包中,在对方的MAC地址这一栏中,填的是一个特殊的MAC地址,其他计算机看到这个特殊的MAC地址之后,就能知道广播想干嘛了。

假如两台计算机的IP不是处于同一个子网之中,这个时候,我们就会把数据包发送给网关,然后让网关让我们进行转发传送

DNS服务器

这里再说一个问题,我们是如何知道对方计算机的IP地址的呢?这个问题可能有人会觉得很白痴,心想,当然是计算机的操作者来进行输入了。这没错,当我们想要访问某个网站的时候,我们可以输入IP来进行访问,但是我相信绝大多数人是输入一个网址域名的,例如访问百度是输入www.baidu.com这个域名。其实当我们输入这个域名时,会有一个叫做**DNS服务器**的家伙来帮我们解析这个域名,然后返回这个域名对应的IP给我们的。

四. 传输层

虽然我们已经把数据成功从计算机A传送到计算机B了,可是,计算机B里面有各种各样的应用程序,计算机该如何知道这些数据是给谁的呢?

这个时候,端口(Port)这个家伙就上场了,也就是说,我们在从计算机A传数据给计算表B的时候,还得指定一个端口,以供特定的应用程序来接受处理。
也就是说,传输层的功能就是建立端口到端口的通信。相比网络层的功能是建立主机到主机的通信。

也就是说,有了IP和端口,我们就可以进行通信了。这个时候可能有人会说,我输入IP地址的时候并没有指定一个端口啊。其实呢,对于有些传输协议,已经有设定了一些默认端口了。例如http的传输默认端口是80,这些端口信息也会包含在数据包里的。

应用层

终于说到应用层了,应用层这一层最接近我们用户了。

虽然我们收到了传输层传来的数据,可是这些传过来的数据五花八门,有html格式的,有mp4格式的,各种各样。你确定你能看的懂?

因此我们需要指定这些数据的格式规则,收到后才好解读渲染。而应用层的功能,就是用来规定应用程序的数据格式的。

五层模型至此讲到这里。对于有些层讲的比较简洁,就随便概况了一下。如果你想详细去了解,可以去买计算机网络相应的资料。希望我的讲解能让你对计算机之间数据的传输有个大概的了解。

12

帅地

关注公众号「苦逼的码农」,获取更多原创文章,后台回复「礼包」送你一份特别的大礼包

14 日志
5 分类
7 标签
© 2018 帅地
访问人数 访问总量