Python特洛伊木马病毒程序设计(加强版)(三)

网络连接-Socket-01

本小节来学习网络连接socket

本小节的目标分为4部分:

1.简单介绍socket

2.Python的socket模块

3.UDP和TCP

4.TCP函数:socket(),listen()。。。等

首先来简单介绍一下socket

socket的网络应用程序的界面最早是在Unix上使用,最早在1980年到后面不同的操作系统,以及Windows等各家系统系统也有提供给winsock2。

不同的操作系统都遵循相同的标准界面,使用应用程序在移值到不同的系统可以减少许多负担,位于不同系统的应用程序才得以进行畅通。

关于socket的历史,在网络Google或百度就一大堆这里就不重复做那些重复的操作了。

socket在使用上,和文件的存取有几分相识,这是因为当初Berkeley在设计时,就将它做得很像在使用文件一样,甚至读取和发送可以用文件相关的函数read(),write(),所有使用还算比较简单

网络传输比想象中还复杂

虽然使用上在用read()和write(),很像是在使用文件,感觉很亲切,但是文件的存取是只要给个文件名称,由open()开始就可以了,然后就是以read()或write()来使用。

网络的存取就复杂多了,要和网络上的某一台机器沟通,当然不像存取文件时open文件名就有,而是先要有网址IP,如果是网址还不能直接使用,要先要转成IP才能确认机器的位置。然而这不是网络存取和硬盘的存取最大的不同处,它们最大的差异就是网路连线要比文件需要更多的考虑,像是传输延迟或是封包遗失等等一些状况。

两台电脑联机,并不是双方拉一条线直接通讯,中间会经过许多gateway、router等,所以和每一台机器传送的速度常常不一样,有的较久有的较快,也有可能根本就中断无法相连。

在传输时,还得要设定「超时」,发觉传输过久的时候,就要判断封包是不是在中间遗失,或是仅仅只是较慢、没到达而已。如果一定时间内没得到回报,应用程式就要考虑是否当做封包失,然后重新传送刚才的封包。

但是,如果那封包其实没有遗失,这种状况却重传的话,又会造成接收方收到两次相同的封包,如果接收方没检查就使用或存盘,数据就不对了。

接收方可能等半天没收到封包,认为没收到就送出重传的要求,结果这时封包到了,而发送方收到重传要求,又重传一次,也会变成重复收到两个封包……

这些可能出现的突发状况,就像排列组合一样,出现状况一时,考虑有出现状况二和没出现状况二,有出现状况二时考虑有出现或没出现状况三……再说下去连我自己也乱了。

简单地说,网络上有太多突发的状况要考虑,但在硬盘的存取上是不会遇到的,所以网络的函式设计会较复杂,就没办法像文件一样,只要open()就可以不用考虑太多直接就可以存取。

Python的socket模组-socket

Python要使用socket面,一样要通过import导入模块,无论是Unix还是Linux或Windows都是一样的,

socket名字的由来

记不记得不知道,有点牛头不对马嘴,网络是网络,而socket翻译过过来是插座,那网络和插座又有什么关系呢?

socket虽然翻译过来是插座的意思,但是大部分的人不会叫插座,而是直接说socket,很多人会觉得难以理解,为什么要用插座socket对应网络

那么这里就不在提插座,先打开电脑看看,在系统里面这些虚拟的物件名称都是来源于现实,首先开机看到的Windows,文件file,文件夹directoy等等,还有程序设计师使用堆栈(通常指码放整齐的)一叠,执行绪thread,虽然执行绪这译名不像现实中的东西,但英文thead翻译过来就是线的意思,管线pipe等等,很明确这些名词都是来源象形,也就是现实的造型

依靠它们的特性由现实中相似的事物,作为它们的名称,也是工程师的浪漫吧。

关于网络方面,所有的应用程序在进行网络连接时,都是通过操作系统的网络应用界面来连通的,这个界面就像一个插座,只要在应用程序上提前安装好这个插座,这个插座就可以让应用程序和另一方也插上插座的应用程序通讯,这两个应用程序不一定需要一样,但是插座的规格(通讯协议)一定要一样才能通过,这就是socket插座,名字的由、

socket函数裡函式(简体翻译为里函式)相当多,而且网络的响应不仅仅只有两个,而且太多的情况要考虑进去,但是不一定需要写到那么专业的程度,现在要考虑的能做到连通的最低要求就可以了,以最少的代码来达成,从最少的代码开始,以后需要再学习增加需要的代码。

后面会讲解一下网络连接的类型,毕竟不是每个人都学过网络程序设计的经验,虽然不是百分百用到,但是这是基本的概念,还是需要学习的,已经学过TCP和UDP的朋友完全可以跳过这一段,没有学过的朋友,如果看不明白,看不下去的,或者简单看过的也可以跳过,以后有时间在看也可以,无论何时都要学的,

网络上的传输基本上有两种,一种是保证数据传输正确,另一种就是不保证传输能到达目的地,也就是说后者很有可能常常漏出一两个或几个封包

1.2.1不保证传输-UDP

保证数据传输正确,这个都可以理解,传输网络数据当然是要数据能到达正确的位置,不正确的还传什么?那为什么会有不保证传输正确的出现?

并不是所有事物都要求准确,稍有错误也没关系

因为有的东西要求及时,比如看网络的信息,如果要求完全正确,网络慢的时候画面会显示不到,那么后面就会停止,直到缺少的画面显示了,就会继续动

其实,一秒30多张的画面中间少一两个,人类的眼镜是看不出来的,少了就少了,别少太多就可以,有时可以直接放弃漏掉的画面也没有关系,没必要在传送一次,就这样直接传输下去吧

如果用保证传输完全正确的协议,最网络状态不好的时候,就会常常停下来等画面,我想没有人会喜欢在网络上看电影,看到精彩的地方,突然跟你玩123木头人吧

这种协议被称之为 UDP(User Datagram Protoocol),或者是使用者质量包协议,暂时不做多的介绍

1.2.2保证传输正确-TCP

这次要做的是文件传输,没有要求及时,但是要正确,所有我们当然是介绍能够正确传输的通讯协议-TCP(Transmisson Control Protcol)。虽然翻译成了 “传输控制协议”,但是大部分的人还是喜欢说TCP比较快

这种传输在封包传送时会检查是否到达,没有到达它可能会自己重新传输,这些决策无需烦恼,都交给TCP底层运行就行,如果没有TCP就要用UDP然后做出保证传输正确的程序会是很痛苦的事,总而言之记得用这种协议就可以当作资料传输送达正确就可以了

下面就是在使用这种保证传输正确的网络连接时,程序用到的socket函数代码

上图是我翻译的,下图是原版可自行参考

这几个程序中,服务器和客户端都有用到的有socket(),send(),recv(),shutdown(),close())五个,事后给服务端使用的有三个,bind(),lister(),accept()),事后给客户端用的就一个connect(),这些就是要用的代码,那下面开始介绍这些代码

1.3TCP代码

1.3.1开启-socket()

ocket()有点类似于open()函数时传回的handle。最呼叫socket()时,决定是哪一种传输,前面有提到过的,不保证传输UDP和保证传输正确的TCP

socket()有两个传输,一个是family表示是什么协议

1.AF_INET:常用的IPv4协议

2.AF_INET6:新的IPv6协议

3.AF_UNIX:本地端(少人用,你可以不用去管它)

示例程序里用的是AF_INET也就是IPv4.它们前面有AF_是adress Family的意思

第二个参数是socket的形态,也就是前面说过的保证和不保证传输
1.SOCK_STREAM:就是TCP,也就是这次要使用的
2.SOCK_DGRAM:就是UDP
3.SOCK_RAW:原始通讯,较少的使用,而且需要非常高权限,这次也可以不管它

当然不只这三个,其他基本很少使用就不介绍了

使用socket

一般使用socket的代码是这样的

serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

SOCK_STREAM表示选择的是TCP

1.3.2 绑定信号-bind()

bind()是 “绑定”(这是象形嘛?比较音译吧)

初次接触socket,没有网络程序写作的朋友,可能会对上面的功能表里所说的bind() ‘绑定’,接下来还有listen() “聆听,等待” accept() “接受,受理”等没有办法去理解,因为这些信息是非常重要的,因为是网络编程写作的基础,所有最这里特别加大幅度的说明,要是没有弄明白的话,后面会很难进行下去的。

所以 “绑定” 是要绑定什么东西嘛? 是的只不过得先说明什么是port了。

埠(bu)

Port,埠,现实是港口的意思,而最电脑里面被翻译成端口或者埠,很明确的又是另一个象形字,一台电脑里面可能有许多程序都要做网络连接,如果所有人都将封包传出去,然后对方的封包经过网络路线传输到电脑里面,操作系统看着封包,然后再看到一堆正在程序的执行时,那么这个封包要给谁?请问这个操作系统该将这个封包交个那个程序?

因此就有了埠的出现。

系统定了数万个编号等待应用程序的申请,申请了这个编号,有关这个编号的传输,全部就会送往申请这个编号的应用程序,所有一个最连接的程序至少会占住一个端口,无论传输端或接受端都会有它们的埠号,就像一艘船的起点港口和终点港口,传输时,封包里面会标记端口号码,所有也称埠号

每次只能有一艘船去占住这个港口,如此一来系统在接收封包时才能依靠封包里面标注埠号将封包送给使用它的应用程序。

固定埠号的服务

许多服务会最服务器端使用搞定的埠号,比如大家熟悉的HTTP和HTTPS,占住的埠号是80,HTTPS是加上保密功能的HTTP,它的埠号是443,其他还有SSH,FFTP都有预设的固定埠号

下面是埠号的讲解

在使用浏览器连接网络时,没有在浏览器上标注埠号,那是因为浏览器自动的帮助我们加上了,如果HTTP不是占80这个预设埠号,而是使用8000或者8080其他埠号,这通常是内部测试或有其他目的另开出来的,像这样的非预设埠号的就要在网址上注明,否则浏览器会连接不到正确的服务器程序

https://www.baidu.com:8080/login

对port的讲解这一大篇、就只要说明bind()是做什么用的,上面的表可以看到在服务器里面使用了bind(),就是前面说的要占的一个埠号,等待客户端连接

客户端需要绑定吗?

那客户端可不可以用bind()呢?当然是可以的,但是最好也不要用,因为没多人这样干过,没有bind()时,系统会分配一个随机的埠号来使用,重复埠号已经被分配完了,否则不用担心拿不到埠号。

“客户端的埠号不是固定的,服务器端怎么知道传给客户端的封包要用那个埠号?”

连接时,都是客户端程序先主动向服务器连线时,这个时候客户端已经将自己的IIP和端口都放封包里面了,所以服务器端传输封包回来时,就会从先收到的封包得到客户端的埠号。

刚才说的客户端主动连接时是content(),这个后面在讲解。

除非服务器要求客户端一定要用特定的埠号,否则客户端最好不要去bind(),不然会有两个程序同时向系统请求相同的端口,那其中一个程序将无法取得埠,系统是不会允许应用程序搞3P的,没拿到埠号就无法连接,所有客户端的应用程序都不会有bind(),都是交给系统分配,PS:我不觉得会有服务器要求客户端用bind()

有用bind()的客户端,像是有的木马是等待服务器端连接的,那这样感觉不太像单纯的客户端,说这木马是服务端也不是不行。

这种开启了socket等待黑客连接的木马,好处是黑客端,(这种情况主要有点混乱就不伺候服务端了) 更容易隐藏自己,黑客不需要留任何的网络资讯在木马端,但是可惜的是有防火墙的存在,黑客要连接到防火墙的木马是困难重重,所有现在都是木马连接黑端,但是也增加了被追踪的风险

服务器端绑定时为什么还是要上host name ?

bind()是向系统要求绑定一个端口,但看到的实际上它的参数是个tuple。tuple里第一个位置是放hostname,但这时指定hostname是做什么用的?建立服务器是在本地机械本地的系统里面进行的事,为何服务端要绑定端口时又要指定hestname ?

其实,服务器端在bind()的时候,依hostname放的不同,会有不同的结果。

hostname放的是local或127.0.0.1,那这个post不会接收本机以外的连接,也是只有本机发出的连接要求会被接受

bind(('localhost',80))

bind(('127.0.0.1',80))

如果放的是本机的主机名称(host name),那么就可以允许本机外的连接请求。

bind((socket.gethostname(),80,))

有时候一台电脑可能不只有一个IP,(像是主机里面不只一张网卡时),如果希望所有的ip的连接请求都可以允许连接,那就放空子串

bind(('',80,))

一般来说,最常用的是bind((socket.gethostname(),80,)) .

bin((",80,))允许所有的IP都可以连上,虽然看起来很方便,但是这样对安全性是有一定的疑惑,所有使用上要谨慎一点(黑客一样要请求资讯安全)。如果在测试过程中一直连不上服务器端时,也可以放空的hostname排除bind()出问题的可能性

1.3.3 等待连接-listen()

服务端bind()一个端口之后,就可以使用listen()开始等待客户端连接,也许有人会觉得等待这个动作,用wait()会比listen()贴切一点,但wait()这个名字已经被别人用掉了,wait()被用在执行程序时,父程序等待子程序结束的等待行为,所有网络上等待连接的动作就选用lissten())这个名字了。

listen()参数标注一个数字,代表同时能让多少连接等待。

listen的作用是设定queue

当使用listen(5)时,系统会建立一个queue,让连接过来的客户端依序排在queue里面,当系统处理完一个客户端请求时,会出queue中取出下一个要服务的客户端。

PS:listen(5)大部分程序放的是5

为什么要有这个queue ?

有了这个queue就是有了个缓解,不会因为先有一个连接后,后面的连接全部一律拒绝,一段短暂的等待和停留,上一个连接完成服务或是建立了新的执行者来服务,就可以从这个queue取得下一个请求。

queue有这么重要嘛?

如果没有这个queue,可以想象一下,就像一个在路边很贴近马路的早餐店,完全没有位置可排队,而老板一次只能服务一个客人,正在服务客户的时候,下一个客人这个时候就来了,而老板只有一个人没有办法在提供服务,而早餐店因为靠近马路,客人不能在原地排队等待,就只能往前离开,然后在从后面绕回来,一直绕到前一个客户结束才能上前买东西,但是这个客人前脚刚走,后面正好刚才正在服务的客人已经结束,老板可以接收下一个请求,刚走的客人还没绕回店,后面又来一个客人,那就变成后面的人运气好,刚到就可以得到服务,而先来的入太霉,又没赶上被后面的抢先一步了,只能再绕一下再试着连接

queue该设多大?

5就是最好的数字嘛?

先看看queue可以设定的最大值是多少,这个最大值是依靠系统来决定的,最Linux的原始程序里面就可以看到queue可以定为的最大值:

net /include/linux/socket.h:#define SOMAXCONN 128

但是书中这里我并不知道怎么操作查看,有大佬的可以指点一下,下面是原图

下面讲解如何去Linux查看,我这里的Linux版本为CentOS-7-x86_64

下面进入到 cd /proc/sys/fs/mqueue/

输入ls -l

可以发现queue_max文件

下面通过cat查看

cat queues_max

可以看到256

由于书中年代是128,而笔记现在的我是2024。

也就是它最大允许设定到256这个数量,如果数字超过256这个数字,一律会被给回成这个256,这个数字是写死在操作系统的也就是底层代码,所有别想着动它,如果真的想要操作系统过256该怎么办呢?唯一的办法是修改原始代码里定义的这个数字,重新编程操作系统原始码,否则逃不过256这个牢笼。

一般应用程序设定的queue大小,大部分是写5,也就是建立大小为5的queue来接收连接请求,超过5个连接后,后面的连接不再接收。这个5的数字从那里来的?我个人也不清楚也许是有实际测试过,或者是有过实际测试过,或者是当时的示范程序式这么写,结果后面看着示范程序式来写程序,已经有写过socket程序的人应该知道,开始写的时候,常常是找个示范程序式来抄,也许就是这样你看我我看你,最后大家都用了5这个数字(乱猜的)

没找到相关资料,跟着写5就对了,也就是一个数字而已。

1.3.4 接受连接 - accept()

listen()是告诉系统可同时等待多少连接,它只是设置来queue,其他什么都没有做,还没有开启服务客户端,而queue由listen()设置好了之后就是真的要接客了。

接受是指接受客户端连线

等待listen()建好queue后就可以开始开始等待,开放客人入座,接受客户端的连线,这就是accept()。

于其将它说是 “接受” 其实更应该说是 “受理”或者比较对,受理了客户端的连接之后,才开始进行网络存取的动作,提供服务 “受理。服务” 都知道这就是服务器端用 “server”这个名词的由来。

为何accept()的两个返回值中,其中一个是新的socket

accept()有两个返回值,返回的两个返回值中,第一个返回的是socket,这时有人会觉得奇怪,程序一开始的时候,不就有请求过socket()拿到过socket了?

看看刚刚一开始产生的socket :

serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 不是一开始就有socket?

下面是原图

为什么又要从accept()返回一个新的?直接使用之前最开始请求socket()时得到的socket不就好了吗?

新的socket针对现在的客户端,服务器端继续用原来的socket等待连接

这是因为一个应用程序,其实是可以分出来好几个执行绪的,所以收到新的连接时,原来用的socket(),得到的socket()仍然在accept()等待连接,不再针对已经accept()的客户端连接,而新的socket就是用来和连接的客户端传资料的.

执行绪可以让程序同时工作,最服务已连接的客户端的同时,前段仍继续用accept()等待新的连接。

每个执行绪可以负责一个连接(当然也可能多个,就看应用程序如何设计),所有的执行序可以接受连接,。这样就可以同时处理数个客户端的连接请求了。(执行绪和执行序不一样,一开始以为是作者打错字了)

有执行绪可以同时接受数个连接,不会一次只能服务一个客户端的连接,也不会在服务一个客户端的时候,所有其他的连接只能等在queue里。

之前用listen()设置了queue,已经做好排队的位置,但是一次只能处理一个连接就太没效率了,就像一家很有人气的店不可能只有一个店员忙,而且只有一个店员肯定是忙不过来的,一定会多请几个店员可同时接纳更多的客人。

所以可以看到许多网络程序都伴随着执行绪的使用。

执行绪能更加发挥系统的效能,系统可以同时处理数个连接服务,服务的客户端还没传下一个封包,所以服务器端只能等待,封包的速度远远不如硬盘和存储器的存取,虽然等待封包也算是短时间,也足够先处理另一个客户,就因为会同时接受服务数个客户端,所以才要accept()在受理连接时,返回新的socket,用不同的socket,才能区分是那个执行绪的封包,这点和前面的端口来区分封包属于那个应用程序是差不多的意思。

conn,addr = accept() # conn是新的 socket

下面是原版

accept()返回的结果是两个,一个是和客户端连接的socket,很多书都将这个socket名字定为conn。第二日返回的是客户端的位置addr。这个addr是个tuple,和bind()后面的connect()参数一样,所以看到的addr也是个tuple,就像这个样子

('192.168.0.164',8086,) # addr存放IP

下面是原版

执行绪在accept()之后产生

刚才不只一次提到应用程序式产生执行绪来处理客户端的连接,那执行绪是什么时候建立呢?

执行绪正是这个accept()取得客户端的socket之后,用thread相关的物件产生的,产生的执行绪后就可以互相传送资料了。

1.3.5 传输资料-recv()和send()

在前面说过,网络传输和文件存取很像,可以用常见的read()和write()来传输资料吗?为什么在这里介绍的是recv()和send()?

recv和send比较好吗?

其实前面说的没有错,确实是可以用read和write来对一个socket进行读取和写入,但是这里都是建议用recv和send,毕竟有能力写socket程序了,应该早就有写过文件存取的程序,那么这里就应该学习新的合适用来做网络资料存取的代码。

而且,由于read和write是比较大众化的存取代码,在前面介绍网络时也提到过,网络传输和硬盘的文件传输不同,会有许多意外情况要考虑和设定,recv和send可以设定和网络有关的参数,而大众化的read和write是没办法提供或是设定那些选项的。

另外多说一个,前面提供不保证正确传输的UDP,它所使用的是recvfrom()和sendto(),名字又不同了,最大的不同是参数增加,原因在使用UDP时,要得到或设定的参数比起TCP的recv和send还要更多,也就是recv和send所能得到的信息,仍无法满足UDP的需求,才会出现这两个可以提供更多信息的代码,如果那些参数在程序中不是必要的,那可以不用recvform和recv都可以,那这种情况就不在说明。

recv()和send()参数的返回值只能是bytes

recv和send发送的这里类别只能是bytes,而不能是str,这点和文件的read和write不同,文件的read和write的存取预设是str,如果在open()加上’rb’或’wb’参数,以binar方式开档,那read和write送人和返回的资料类型就会变成是bytes了。read和write可以是str也可以是bytes,但recv和send只能是bytes。

rfp = open(filename, 'rb')
wfp = open(filename, 'wb')

下图是原版

以recv和send发送资料时,都是以bytes类型来发送,那资料不是bytes时怎么办?如果要发str时,在发送前先将str转成bytes,传送到对方后,对方自己将bytes再转成str。

Python将str转成bytes是用str.encode()。如图

bytes_text = str_text.encode('utf-8')

原版:

将bytes转换成str是bytes.decode()。如图

str_text = bytes_text.decode('utf-8')

原版:

下面开始在编译器操作如图

我用的Python编译器是WingIDE

1.3.6关闭连接-close()

这和文件用完就要关闭是一样的道理,原本占用的端口就会释放,这个端口就可以让别的应用程序申请和使用,如果对方没关闭还在发送资料,这时有新的应用程序分配到端口和刚才的应用程序相同时,因为原来的端口已经释放,那些资料并不会存在于新的应用程序里,封包不会送给新的应用程序,所以不用担心这个端口的新应用程序会收到这些资料。

1.3.7 客户端连接-connect()

刚才说明的都是服务器会使用的代码,现在要说明的是客户端的连接,除了bind()和客户端一样要用socket()来产生一个socket,一样用recv和send来发送资料,一样用close(0关闭socket,但在recv和send之前需要有连接的建立,连接是由客户端主动向服务器发起请求,不可能是服务器连接客户端。

客户端用content()连接,服务器用accept()接受连接

客户端要连接时,是用content来和服务器连接,content会将客户端的ip和端口告诉服务器,content的参数是服务器的网址和端口,这样不用多说有能明白吧,客户端在连接前必须要先知道服务器的位置和端口,没有先知道父亲的端口是不可能连接的,连要连接的对象都不知道那要怎么连接?就像店都不知道在那里,那怎么买东西?所以服务器就要用bind来绑定端口,一家店如果天天搬家,那还能做生意嘛?只有将端口占为己有,就是一个固定的地方开店。

这是content()的使用例子。

connet(('www.baidu.com',80,))

原版

个人总结:

这个是我之前写的笔记,复制粘贴过来还花了不少时间,所以下一章直接通过知道的语句翻译成内地看的明白的语言了,比如提到的资料就是数据等等。有时间在更新第二版吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值