day8--socket回顾

    后面学习了线程、协成和异步,它们的框架都是基于socket的协议,基本原理都是一样的,现在把这几个模块重温一下,尽量掌握这些知识更全面一些。

    动态导入模块,知道知道模块名,可以像反射一样,使用字符串来导入模块。

mod = __import__("mulit")
print(mod)
mod.f("alex")
运行结果如下:
<module 'mulit' from '/home/zhuzhu/day10/mulit.py'>
called from child process function f
module name: mulit
parent process: 2092
process id: 3613

hello alex

    上面就实现了动态导入模块的方法,动态导入模块。官方建议使用importlib模块实现模块字符串的动态导入,如下:

import importlib

mulit = importlib.import_module("importy.mulit")

print(mulit)
运行结果如下:
<module 'importy.mulit' from '/home/zhuzhu/day10/importy/mulit.py'>

    上面代码就实现了动态导入importy包下的mulit模块,这样,我们就能够实现,只知道模块的字符串名字,动态的导入模块。

    断言:

 

'''断言'''

name = "alex"
print(name)
assert type(name) is int
print("断言是如何算的")


'''断言:如果是正确的,程序继续执行下面;如果是错误的,程序就执行错误。报错AssertionError,直接停住不执行,后面不管对错'''
#断言的作用:就是没有循环的中断程序。执行错误。

 

    断言这个也在后期用到,即终端操作的时候。

    socket:封装协议底层,现在所有的底层都是基于socket来实现的。网络协议

    conn,addr = socket.accept()

    一定要记住,socket.accept()是接收了一个链接和一个地址,conn去实现链接,conn.recv()来接收,因为conn是链接。

    recv(1024)  官方建议接收数据不能超过8192(8K);

    conn.recv(1024)和conn.send()是实现数据的收发,数据一定要有收发配对,防止粘包。并且只能收发二进制字符串格式。要转换为二进制才能进行发送。

    不能接收和发送空的数据,recv()不能接收空的消息,接收空的消息就会卡主。微信、QQ我们知道,都不允许发送空的消息。

    下面使用类创建了一对sockek客户端和socket服务器端,实现数据的收发,如下所示:

    服务器端:

import socket

class MyServer(object):
    '''创建服务器端'''
    def __init__(self):
        self.server = socket.socket()

    def bind(self,ip,port):
        '''绑定连接'''
        self.server.bind((ip,port))
        self.server.listen()

    def interactive(self):
        while True:
            '''实现接收终止,但是服务器端没有断开,等待新的链接进来'''
            conn, addr = self.server.accept()  # 开始准备接收数据
            while True:
                data = conn.recv(1024)
                if len(data) == 0:
                    print("客户端断开了,接收数据不能为空!!!")
                    break
                conn.send("接收到了数据".encode("utf-8"))
                print(data.decode("utf-8"))

if __name__ == "__main__":
    try:
        server = MyServer()
        server.bind("localhost",9998)
        server.interactive()
    except KeyboardInterrupt as e:
        print("服务器已经断开了!!!",e)

    客户端:

import socket

class Myclient(object):
    '''建立客户端'''
    def __init__(self):
        self.client = socket.socket()

    def communicate(self,ip,port):
        self.client.connect((ip,port))     #建立链接

    def interactive(self):
        '''实现交互'''
        while True:
            mess = input("请输入您要交互的内容>>:").strip()
            if not mess:
                print("消息不能为空!!")
                continue
            self.client.send(mess.encode("utf-8"))
            data = self.client.recv(1024)
            print(data.decode("utf-8"))

if __name__ == "__main__":
    try:
        client = Myclient()
        client.communicate("localhost",9998)
        client.interactive()
    except KeyboardInterrupt as e:
        print("客户端断开了!!!")

    上面就是客户端和服务器端,首先要启动的是服务器端,然后在客户端进行收发数据,从而实现数据的交换,但是不能如何实现,单纯的socket,不能实现多用户交互,只有等一个用户交互完成之后,才能让另一个用户实现交互。

    上面交互如下所示:

    客户端发送:

请输入您要交互的内容>>:客户端不能发送空的消息
接收到了数据
请输入您要交互的内容>>:服务器端也不能发送空的消息
接收到了数据
请输入您要交互的内容>>:客户端和服务器端交互是一对的
接收到了数据
请输入您要交互的内容>>:send和recv()
接收到了数据
请输入您要交互的内容>>:如果主动断开会报错,所以加入了一个异常处理
接收到了数据
请输入您要交互的内容>>:服务器端加入了两层循环,主要目的是在内存循环停止接收数据之后,链接还存在,并没有实现断开
接收到了数据
请输入您要交互的内容>>:客户端断开了!!!

    服务器端接收:

客户端不能发送空的消息
服务器端也不能发送空的消息
客户端和服务器端交互是一对的
send和recv()
如果主动断开会报错,所以加入了一个异常处理
服务器端加入了两层循环,主要目的是在内存循环停止接收数据之后,链接还存在,并没有实现断开
客户端断开了,接收数据不能为空!!!
服务器已经断开了!!! 

    上面就是简单的数据收发,要知道,线程,协成,进程都是基于socket实现的,而且很多方法都是类似的。

    服务器端有两层循环,外层循环是用来不断接收新的链接,内存循环是用来不停接收用户数据,因为如果接收中断,当前链接是要终端的。只能等待新的链接进来,实现数据的交互。

    上面服务器代码中,data = conn.recv(1024),接收的数据大小是1024(1K)个字节,如果超过1K字节会出现什么情况,下面来看一下:

  服务器接收:

 

正常传输数据没有问题 30
当传输数据超过1024的时候,会出现怎样的错误 61
My father was a self-taught mandolin player. He was one of the best string instrument players in our town. He could not read music, but if he heard a tune a few times, he could play it. When he was younger, he was a member of a small country music band. They would play at local dances and on a few occasions would play for the local radio station. He often told us how he had auditioned and earned a position in a band that featured Patsy Cline as their lead singer. He told the family that after he was hired he never went back. Dad was a very religious man. He stated that there was a lot of drinking and cursing the day of his audition and he did not want to be around that type of environment.Occasionally, Dad would get out his mandolin and play for the family. We three children: Trisha, Monte and I, George Jr., would often sing along. Songs such as the Tennessee Waltz, Harbor Lights and around Christmas time, the well-known rendition of Silver Bells. "Silver Bells, Silver Bells, its Christmas time in the city" w 5146
Traceback (most recent call last):
  File "/home/zhuzhu/第八天/socket_server.py", line 38, in <module>
    server.interactive()
  File "/home/zhuzhu/第八天/socket_server.py", line 22, in interactive
    length = int(length.decode("utf-8"))    #接收数据的大小
ValueError: invalid literal for int() with base 10: 'ould ring throughout the house. One of Dad\'s favorite hymns was "The Old Rugged Cross". We learned the words to the hymn when we were very young, and would sing it with Dad when he would play and si

 

    可以看出当传输超过1024字节的时候报错了,这个有时候能接收到报错,有时候接收不到报错,因此尽量不要发超过1024字节的消息,要想发,只能修改服务器的代码,让服务器分段接收数据,如下:

import socket

class MyServer(object):
    '''创建服务器端'''
    def __init__(self):
        self.server = socket.socket()

    def bind(self,ip,port):
        '''绑定连接'''
        self.server.bind((ip,port))
        self.server.listen()

    def interactive(self):
        while True:
            '''实现接收终止,但是服务器端没有断开,等待新的链接进来'''
            conn, addr = self.server.accept()  # 开始准备接收数据
            while True:
                length = conn.recv(1024)
                if len(length) == 0:
                    print("客户端断开了!!!")
                    break
                length = int(length.decode("utf-8"))    #接收数据的大小
                conn.send("数据的长度接收到了!!!".encode("utf-8"))
                receive_size = 0
                received_data = b""
                while receive_size < length:
                    data = conn.recv(1024)
                    receive_size += len(data)
                    received_data += data
                conn.send("接收到了数据".encode("utf-8"))
                print(received_data.decode("utf-8"),length)

if __name__ == "__main__":
    try:
        server = MyServer()
        server.bind("localhost",9999)
        server.interactive()
    except KeyboardInterrupt as e:
        print("服务器已经断开了!!!",e)

    上述代码中,首先服务器接收的不是消息,而是消息的长度,然后才开始接收,通过判断,只要接收的长度小于消息的长度,就一直接收,并且拼接到一起,这样就能接收超过1024字节,并且不会报错,如下:

    服务器端接收:

现在修改了服务器 24
让服务器接收超过1024时,分批接收数据,然后拼接到一起 76
西汉和东汉仍置南阳郡,辖境相当于河南熊耳山以南和湖北大湖 山以北,南阳经济文化的发展达到历史上的鼎盛时期。西汉时,南阳水利与关中郑国渠、成都都江堰齐名,并称全国三大灌区。全国设工官的9个地区和设铁官的46个地区之一。东汉 时,光武帝刘秀起兵南阳,成就帝业,南阳被称为“帝乡”。诸葛亮躬耕南阳卧龙岗,刘备三顾茅庐,诸葛亮提出“三分天下”之计。太守杜诗修治坡池,广拓田土,全郡可灌农田4万顷 ,这时的冶铁用水排, 水力鼓风机鼓风,大大提高了冶铁效率,特别是采用球墨铸铁,提 高了冶铁工艺水平,这一技术的使用比欧州早1000多年。当时南阳郡人口240万,为全国各 郡之冠。郡城周长36公里,比1990年市区面积还大。汉代南阳人才辈出,灿若繁星。不仅刘秀的28个开国元勋大多出自南阳,还涌现出张衡、张仲景闻名世界的伟大科学家和医学家。汉代达官贵人死后流行厚葬,南阳出土众多的画像石和画像砖,是一部“绣像汉代史”,成为中华民族文化艺术宝库中一朵绚丽多彩的奇葩。三国时期:南阳为魏国所有,隶属荆州。晋代:南阳曾为南阳国,辖十四县,都宛。隋朝:(607年)先将郡改州,后又将州改郡,今南阳市辖南阳郡、淯阳郡、淅阳郡、淮安郡(包括平氏、桐柏二县),义阳郡的淮源县、舂陵郡的湖阳县等都在今南阳市内。唐朝,天下设为十道,南阳属山南道管辖。自唐高祖李渊至玄宗李隆基90年间,南阳先后设置纯州、郦州、淅州、北澧州、宛州、淯州、显州、湖州、新州、鲁州和仙州等。玄宗天宝元年(742年)改州为郡,邓州称南阳郡,唐州称淮安郡。肃宗乾 元元年(758年)又改郡称州,县加以合并,今南阳市有泌州淮安郡和邓州南阳郡。经过贞观 、开元之治,南阳农业兴旺,工商业繁荣。李白在《南都行》中说:“清歌遏流云,艳舞有 余闲,邀游盛宛洛,冠盖随风还。”宋朝:南阳归京西南路管辖,南阳叫武胜军,设唐、邓 二州,州下设县,邓州辖穰、南阳、内乡、淅川、顺阳五县。唐州辖有泌阳、湖阳、桐柏、方城等县。元朝:将原南阳郡改为南阳府。属河南江北行中书省。辖五州,其三在今市内; 南阳府自领镇平、南阳二县;邓州领内乡、顺阳、淅川、新野、栾川五县;唐州领泌阳、湖阳、桐柏、方城等县。其他二州辖临汝、伊川、郏县、宝丰、鲁山、叶县、舞阳、卢氏、栾川等县。其间曾将淅川、顺阳合并到内乡。明朝初年,南阳是朱元璋第二十三子唐王朱柽的封地,永乐年间在南阳城内建造了规模宏大的唐王府,成化年间又建造9座郡王府,南阳城 内皇亲贵胄,车水马龙,商业随之活跃,山、陕、江、浙、川、鄂客商纷到沓来,各种商务会馆、公馆在各地兴起,粮食、棉花、生丝、烟草、绸缎、油料、皮毛、木材、药材、铜器、铁器等大量涌入市场,并行销全国各地。当时的南阳可谓百业俱兴,建筑、园林、绘画、雕塑、书法等方面都有新的发展。清朝康熙年间,建筑业尤为发达,武侯、山陕会馆等古建筑巍巍壮观,富丽堂皇,南阳是北京通往湖广和云贵川的交通要道,陆路驿道与水路码头相接,有“南船北马”之称。山、陕、江、浙商贾云之集,工商业兴旺,南阳成了豫西南的经济中心。光绪十年,镇平开始生产丝绸,并远销欧洲及东南亚各国。民国时期:河南设十一个行政区,南阳为第六行政区,辖十三个县:南阳县、南召、唐河、镇平、方城、邓县、内乡、桐柏、新野、淅川、泌阳、叶县、舞阳县。中华人民共和国时期:(1949年)成立后,南阳行政公署划走叶县、舞阳。析出西峡、南阳市,仍辖十三个县市,即南阳市、南召、方城、泌阳、唐河、新野、桐柏 4184

    上面,服务器端接收了超过1024字节的数据,但是一点问题都没有。

    ftp server发送文件给客户端的步骤:

    1、读取文件名;

  2、检测文件是否存在;

  3、打开文件;

  4、检测文件大小;

  5、发送文件大小和md5给客户端;   md5验证

  6、等待客户端确认;

  7、开始边读边发数据;

    使用socket实现文件的传输,下面的例子是从服务器端下载文件,具体代码如下:

    服务器端:

 

import socket,os,json,hashlib

class MyServer(object):
    '''创建server实例'''
    def __init__(self):
        self.server = socket.socket()

    def bind(self,ip,port):
        self.server.bind((ip,port))
        self.server.listen()

    def interactive(self):
        '''实现交互'''
        while True:
            conn,addr = self.server.accept()                                         #循环实现链接不断开,客户端断开不影响
            while True:
                filename = conn.recv(2048).decode("utf-8")                           #接收文件名
                if len(filename) == 0:                                               #判断客户端是否断开,客户端不可能发送空的,接收空,就是客户端断开了
                    print("客户端断开了!!!")
                    break
                if os.path.isfile(filename):                                         #判断客户端文件是否存在
                    '''文件名存在'''
                    conn.send("1".encode("utf-8"))
                    '''文件存在,准备发送文件,首先告知客户端文件名和文件大小'''
                    filesize = os.stat(filename).st_size
                    file_mess = {"filesize":filesize}
                    conn.recv(1024)
                    conn.send(json.dumps(file_mess).encode("utf-8"))                 #发送文件的大小指令
                    conn.recv(1024)
                    '''下面开始发送文件信息'''
                    m = hashlib.md5()
                    with open(filename,"rb") as f:
                        for line in f:
                            m.update(line)
                            conn.send(line)                                          #逐行发送文件
                    '''文件发送完毕之后,发送md5值'''
                    mess = m.hexdigest()
                    conn.send(mess.encode("utf-8"))
                else:
                    '''文件不存在'''
                    conn.send("0".encode("utf-8"))
                    conn.recv(1024)

if __name__ == "__main__":
    try:
        server = MyServer()
        server.bind("localhost",9999)
        server.interactive()
    except KeyboardInterrupt as e:
        print("服务器端断开了!!!")

 

    服务器里面,交互有两层循环,外层循环是保持链接一直存在,防止客户端关闭之后,服务器端也断开,正常不循环链接,客户端断开之后,服务端也会断开的。并且要记住,如果客户端断开,conn.recv(1024)是接收空的数据,因此一定要进行判断,如果接收为空,说明是客户端断开了,因为客户端是不可能发送空给服务器的。

    客户端:

'''通过接收发送数据的大小来训话接收,如果接收完成,则退出'''
import socket,json,hashlib
class MyClient(object):
    '''创建Client实例'''
    def __init__(self):
        self.client = socket.socket()

    def connect(self,ip,port):
        self.client.connect((ip,port))

    def interactive(self):
        '''实现交互,客户端接收文件'''
        while True:
            filename = input("请输入您要下载的文件名>>:").strip()
            if len(filename) == 0:
                print("文件名不能为空!!!")
                continue
            elif filename == "q":
                break
            self.client.send(filename.encode("utf-8"))                       #发送文件名给服务器
            file_exist = int(self.client.recv(1024).decode("utf-8"))         #接收客户端返回文件是否存在的指令
            self.client.send("好的,收到反馈的指令!".encode("utf-8"))

            if file_exist:
                '''文件存在'''
                file_mess = json.loads(self.client.recv(1024).decode("utf-8"))
                filesize = file_mess["filesize"]
                self.client.send("收到文件的信息".encode("utf-8"))
                receive_size = 0
                m = hashlib.md5()
                with open(filename+"1","wb") as f:
                    while receive_size < filesize:
                        if filesize - receive_size > 1024:                  #确保数据完整的接收,这样就能防止粘包
                            size = 1024
                        else:
                            size = filesize - receive_size
                            # print("最后一次接收文件大小:",size)             #由于是逐行发送数据的,因此会出问题
                        data = self.client.recv(size)
                        m.update(data)
                        f.write(data)
                        receive_size += len(data)                           #这里判断长度,一定是len(data)不能是size,
                    server_md5 = self.client.recv(1024).decode("utf-8")
                    print("客户端md5值",m.hexdigest())
                    print("服务器md5值",server_md5)
                    if server_md5 == m.hexdigest():
                        print("数据接收完毕,通过MD5验证!!!")
            else:
                print("对不起,您要下载的文件不存在")
                continue

if __name__ == "__main__":
    try:
        client = MyClient()
        client.connect("localhost",9999)
        client.interactive()
    except KeyboardInterrupt as e:
        print("客户端断开了!!!")

 

    我们知道,为了防止粘包,我们通常使用的方法是,客户端--服务器端发送数据之后,双方要进行确认,确认对方接收到了消息,并把这个结果告知对方,这样就能防止粘包,还有上面这种情况是,告知对方发送数据的大小,然后按照数量进行接收,只要确保全部数据接收完毕,那么就一定不会造成粘包。造成粘包的原因就是,一方没有接收完毕数据,另外一方有发送数据过来,这样就造成了粘包。

    上面的交互如下:

    客户端:

请输入您要下载的文件名>>:file_test
最后一次接收文件大小: 329
最后一次接收文件大小: 304
客户端md5值 576a9a93dc5377b7a204b48df224569d
服务器md5值 576a9a93dc5377b7a204b48df224569d
数据接收完毕,通过MD5验证!!!
请输入您要下载的文件名>>:file_test
最后一次接收文件大小: 329
最后一次接收文件大小: 304
客户端md5值 576a9a93dc5377b7a204b48df224569d
服务器md5值 576a9a93dc5377b7a204b48df224569d
数据接收完毕,通过MD5验证!!!
请输入您要下载的文件名>>:客户端断开了!!!

    上面在客户端和服务器连接之后,客户端发送指令,交互的情况。可以看出,交互没有问题,要注意的是,判断接收长度的时候,一定要len(data),而不能指定数量,因为只要接收数量小于等于规定的值就没有问题。

posted @ 2017-09-06 22:33  (野生程序员)  阅读(390)  评论(0编辑  收藏  举报