Python 网络编程3:tcp客户端与tcp服务器以及下载器

一、TCP介绍

  • 1.1、TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。
  • 1.2、TCP通信需要经过 创建连接、数据传送、终止连接 三个步骤。
  • 1.3、TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,"打电话",而DCP犹如生活中的 "写信"
    TCP

二、TCP特点

  • 2.1、面向连接
    • 通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统内核资源,以管理连接的状态和连接上的传输。
    • 双方间的数据传输都可以通过这一个连接进行。
    • 完成数据交换后,双方必须断开此连接,以释放系统资源。
    • 这种连接是一对一的,因此TCP不适用于广播的应用程序,基于广播的应用程序请使用UDP协议
  • 2.2、可靠传输
    • (1)、TCP采用发送应答机制
      • TCP发送的每个报文段都必须得到接收方的应答才认为这个TCP报文段传输成功
    • (2)、超时重传
      • 发送端发出一个报文段之后就启动定时器,如果在定时时间内没有收到应答就重新发送这个报文段。
      • TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。
    • (3)、错误校验
      • TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。
    • (4)、流量控制和阻塞管理
      • 流量控制用来避免主机发送得过快而使接收方来不及完全收下。

三、TCP与UDP的不同点

  • 3.1、有如下不同点

    • 面向连接(确认有创建三方交握,连接已创建才作传输。)
    • 有序数据传输
    • 重发丢失的数据包
    • 舍弃重复的数据包
    • 无差错的数据传输
    • 阻塞/流量控制
  • 3.2、udp通信模型
    udp通信模型中,在通信开始之前,不需要建立相关的链接,只需要发送数据即可,类似于生活中,"写信""


    udp通信模型

    udp通信模型
  • 3.3、TCP通信模型
    tcp通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,"打电话""


    TCP通信模型

四、tcp客户端构建流程

  • 4.1、tcp的客户端要比服务器端简单很多,如果说服务器端是需要自己买手机、查手机卡、设置铃声、等待别人打电话流程的话,那么客户端就只需要找一个电话亭,拿起电话拨打即可,流程要少很多

  • 4.2、实例代码(四步)

    • (1)、创建TCP套接字

      tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
      
    • (2)、创建链接服务器,一个元组:服务器的IP与端口

      server_ip = input("请输入要连接的服务器ip:")
      server_port = input("请输入要连接的服务器port:")
      server_addr = (server_ip,server_port)
      tcp_socket.connect(server_addr)
      
    • (3)、发送内容或者接收内容

      # 发送内容
      send_data = input("亲输入要发送的内容:")
      tcp_socket.send(send_data.encode("utf-8"))
      
      # 接收对方发送过来的数据,最大接收1024个字节
      recvData = tcp_client_socket.recv(1024)
      print('接收到的数据为:', recvData.decode("utf-8"))
      
    • (4)、关闭套接字

      tcp_socket.close()
      
  • 4.3、完整的tcp客户端代码

    import socket
    
    
    def run_client():
          """tcp客户端代码"""
    
          # 1、创建TCP套接字
          tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
          # 2、创建链接服务器,一个元组:服务器的IP与端口
          server_ip = input("请输入要连接的服务器ip:")
          server_port = int(input("请输入要连接的服务器port:"))
          server_addr = (server_ip,server_port)
          tcp_socket.connect(server_addr)
    
          # 3、发送内容或者接收内容
          send_data = input("亲输入要发送的内容:")
          tcp_socket.send(send_data.encode("utf-8"))
    
          # 接收对方发送过来的数据,最大接收1024个字节
          recvData = tcp_socket.recv(1024)
          print('接收到的数据为:', recvData.decode("utf-8"))
    
          # 4、关闭套接字
          tcp_socket.close()
    
    
    if __name__ == '__main__':
    
          run_client()
    

五、tcp服务器端构建流程

  • 5.1、服务器端主要有以下6步(固定)

    • (1)、socket创建一个套接字
    • (2)、bind绑定ip和port
    • (3)、listen使套接字变为可以被动链接
    • (4)、accept等待客户端的链接
    • (5)、recv/send接收发送数据
    • (6)、关闭套接字
  • 5.2、socket创建一个tcp服务器套接字

    tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
  • 5.3、bind绑定ip和port

    tcp_server_socket.bind(("服务器的ip",服务器的端口))
    
  • 5.4、改变服务器套接字由默认的主动变被动 listen

    tcp_server_socket.listen(128)
    
  • 5.5、等待别人发信息过来(等待客户端的链接 accept)
    如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务

    # new_client_socket:用来为这个客户端服务
    # tcp_server_socket:就可以省下来专门等待其他新客户端的链接
    new_client_socket,client_addr = tcp_server_socket.accept()
    
  • 5.6、接收客户端发送过来的数据

    receive_data = new_client_socket.recv(1024)
    print(receive_data.decode("utf-8"))
    
  • 5.7、回复一部分数据给客户端

    new_client_socket.send("哈哈哈哈😆😆😆😆😆😆".encode("utf-8"))
    
  • 5.8、关闭套接字

    new_client_socket.close()
    tcp_server_socket.close()
    
  • 5.9、完整的服务器端代码

    import socket
    
    def run_server():
    
           # 1、创建tcp服务器套接字
           tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
           # 2、绑定服务器信息
           tcp_server_socket.bind(("192.168.3.223",7890))
    
           # 3、改变服务器套接字由默认的主动变被动 listen
           tcp_server_socket.listen(128)
    
           print('---1---')
    
           # 4、等待别人发信息过来(等待客户端的链接 accept)
           # 如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务
           # new_client_socket:用来为这个客户端服务
           # tcp_server_socket:就可以省下来专门等待其他新客户端的链接
           new_client_socket,client_addr = tcp_server_socket.accept()
    
           print('---2---')
    
           # 5、接收客户端发送过来的数据
           receive_data = new_client_socket.recv(1024)
           print(receive_data.decode("utf-8"))
    
           # 6、回复一部分数据给客户端
           new_client_socket.send("哈哈哈哈😆😆😆😆😆😆".encode("utf-8"))
    
           # 7、关闭套接字
           new_client_socket.close()
           tcp_server_socket.close()
    
    
    if __name__ == '__main__':
    
          run_server()
    

六、tcp服务器循环为多个客户端服务(对上面tcp服务器代码的改进)

  • 6.1、分析:
    在我们开启tcp服务器后,服务器的套接字进入等待客户端的状态,当客户端进行了连接服务器,服务器将为其服务,等待客户端的数据,接收到用户的数据后为其服务,服务完再等待客户端的新的指令,服务器接到客户端的数据后为其服务,再次等待客户端的数据,如果客户端关闭了自己的套接字,服务器端将收到一个空数据,这时服务器也将关闭为这个客户端服务的套接字,一个客户端与服务器的链接结束

  • 6.2、完善后的代码

    import socket
    
    
    def run_server():
    
         # 1、创建tcp服务器套接字
         tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
         # 2、绑定服务器信息
         tcp_server_socket.bind(("192.168.3.6",7890))
    
         # 3、改变服务器套接字由默认的主动变被动 listen
         tcp_server_socket.listen(128)
    
         # 4、等待别人发信息过来(等待客户端的链接 accept)
         # new_client_socket:用来为这个客户端服务
         # tcp_server_socket:就可以省下来专门等待其他新客户端的链接
    
        # 为多个客户端服务
        while True:
    
              print("等待一个新的客户端的到来")
              new_client_socket, client_addr = tcp_server_socket.accept()
              print("一个新的客户端已经到来%s"%str(client_addr))
    
              # 循环的目的:为同一个客户端 服务多次
              while True:
    
                    # 5、接收客户端发送过来的请求数据
                    receive_data = new_client_socket.recv(1024)
                    print(receive_data.decode("utf-8"))
    
                    # 如果recv解堵塞,那么有2种方式:
                    # 1、客户端发送过来数据
                    # 2、客户端调用 close 导致了 这里 recv 堵塞
                    if receive_data:
    
                         # 6、回复一部分数据给客户端
                         new_client_socket.send("哈哈哈哈😆😆😆😆😆😆".encode("utf-8"))
                         print("给客户端回复了:哈哈哈哈😆😆😆😆😆😆")
                    else:
                         print("客户端关闭了自己的套接字或则断开了链接")
                         break
    
               # 7、关闭套接字,关闭accept 返回的套接字 意味着 不会在为这个客户端服务
               new_client_socket.close()
    
        # 如果将监听套接字 关闭了,那么会导致 不能再次等待新客户端的到来,也就是 套接字.accept()会失败
        tcp_server_socket.close()
    
    
    if __name__ == '__main__':
    
               run_server()
    

    上面两个while的作用:

    • 第1个: 为多个客户端服务
    • 第2个: 为同一个客户端 服务多次

七、TCP下载器:客户端服务器端(注释都在代码里面)

  • 7.1、客户端

    import socket
    
    
    def main():
    
          # 1、创建tcp套接字
          tcp_client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
          # 2.创建与服务器的链接
          server_ip = input("请输入连接服务器的ip:")
          server_port = int(input("请输入服务器对应的端口:"))
          tcp_client_socket.connect((server_ip,server_port))
    
          # 3、发送下载的文件名字给服务器
          file_name = input("请输入要下载的文件名字:")
          tcp_client_socket.send(file_name.encode("utf-8"))
    
          # 4、接收服务器返回的文件的数据 ,接收一个小的试试
          receive_data = tcp_client_socket.recv(1024)
    
          # 5、判断数据是否有(保存接收到的数据到一个文件夹中)
          if receive_data:
    
               with open("[new]"+file_name,'wb') as file:
    
                     file.write(receive_data)
    
          # 6、关闭tcp套接字
          tcp_client_socket.close()
    
    if __name__ == '__main__':
    
            main()
    
  • 7.2、服务器

    import socket
    
    
    def exec_file(new_tcp_socket,client_addr):
    
           print("等待客户端的指令")
           # 1、接收客户端要下载的文件名
           file_name = new_tcp_socket.recv(1024).decode("utf-8")
           print("客户端:%s 要下载的文件名是:%s"%(str(client_addr),file_name))
    
           # 2、在服务器找到这个文件,打开读取,发送给客户端
           file_data = None
           try:
    
               file = open(file_name,'rb')
               file_data = file.read()
               file.close()
           except:
               print("% 文件不存在" % file_name)
    
          # 3、发送文件内的数据给客户端
          new_tcp_socket.send(file_data)
    
    def server_down():
    
          # 1、创建服务器套接字
          tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
          # 2、绑定本地的服务器与端口
          tcp_server_socket.bind(("192.168.3.6",7893))
    
          # 3、让tcp套接字由主动变被动
          tcp_server_socket.listen(128)
    
          while True:
    
               print("等待客户端的连接")
    
               # 4、等待客户端的连接
               new_tcp_socket,client_addr = tcp_server_socket.accept()
    
               # 5、处理客户端的内容
               exec_file(new_tcp_socket,client_addr)
    
               # 6.关闭为这个客户端服务的套接字
               new_tcp_socket.close()
    
          # 7.关闭监听服务器套接字
          tcp_server_socket.close()
    
    if __name__ == '__main__':
    
          server_down()
    
  • 7.3、说一下 写与读文件的注意点,在往文件里面写内容的时候,文件可以不存在,可以自己根据文件名在当前目录下创建,然后如果是读取文件的内容的时候,文件是必须存在的才可以进行读取,否则会报错,还有一个注意点是:with 的使用,with可以保证文件打开后,一定会关闭,如上面客户端的代码

    with open("[new]"+file_name,'wb') as file:
                 file.write(receive_data)
    

八、tcp注意点

  • 8.1、tcp服务器一般情况下都需要绑定,否则客户端找不到这个服务器
  • 8.2、tcp客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机
  • 8.3、tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的
  • 8.4、当客户端需要链接服务器时,就需要使用connect进行链接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信
  • 8.5、当一个tcp客户端连接服务器时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务
  • 8.6、listen后的套接字是被动套接字,用来接收新的客户端的链接请求的,而accept返回的新套接字是标记这个新客户端的
  • 8.7、关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信。
  • 8.8、关闭accept返回的套接字意味着这个客户端已经服务完毕
  • 8.9、当客户端的套接字调用close后,服务器端会recv解堵塞,并且返回的长度为0,因此服务器可以通过返回数据的长度来区别客户端是否已经下线
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容