Python网络编程

206 阅读5分钟

1. 网络通信概述

1.1. 相关概念

  1. 服务器:服务器就是一系列硬件或软件,为一个或多个客户端提供所需的服务。目的是等待客户端的请求,并响应他们,然后等待更多请求。

  2. 客户端: 客户端因特定的请求而联系服务器,并发送必要的数据,然后等待服务器的回应。

    注: 服务器无限地运行下去,并不断地处理请求,而客户端会对服务器进行一次性请求,接收服务,最后结束与服务器之间的事务。客户端可能会再次发出请求,这些都被当做不同的事务。

1.2 通信基础

  • 服务器在响应客户端请求之前,必须先创建一个通信端点,能够使服务器监听请求。客户端也必须知道这个通信端点才能与服务器进行通信。

1.3 套接字

  • 套接字(socket) 是计算机网络数据结构,即 “通信端点”。任何类型的通信开始之前,网络应用程序必须先创建套接字。

    • 基于文件的套接字: AF_UNIX
    • 基于网络的套接字: AF_INET。在网络编程中,我们使用这个套接字
  • 套接字地址: 主机-端口对

  • 套接字分类:

    • 面向连接的套接字

      面向连接,意味着在通信之前必须先建立一个连接。每条消息可以拆分成多个片段,并且都能确保到达目的地,然后按照顺序组合在一起,最后将完整信息传递给正在等待的应用程序。

      实现这种连接的主要协议是 传输控制协议,即 TCP。使用 TCP 套接字,就必须使用 SOCK_STREAM 作为套接字类型。

    • 无连接的套接字

      通信开始之前不需要建立连接,消息以整体发送,可能重复或丢失,相比 TCP 来说更加低廉。

      实现这种连接的主要协议是 用户数据报协议,即 UDP。使用 UDP 套接字,就必须使用 SOCK_DGRAM 作为套接字类型。

2. 编程练习

2.1 基础模块

  • 网络编程使用的模块就是 socket 模块。使用前需要导入。

    import socket
    
  • socket() 模块函数

    创建套接字,必须使用 socket.socket() 函数,用法如下:

    socket(socket_family, socket_type, protocol=0)
    

    socket_family 是 AF_UNIX 或 AF_INET
    socket_type 是 SOCK_STREAM 或 SOCK_DGRAM
    protocol 通常省略,默认为0。

  • 创建 TCP/IP 套接字

    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
  • 创建 UDP/IP 套接字

    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    

2.2 UDP练习

  1. UDP发送数据

    步骤:

    • 创建 upd 套接字
    • 绑定本地端口,发送方一般不需要绑定
    • 使用套接字发送数据 参数(发送的数据(字节), (ip + 端口)(元组形式))
    • 关闭套接字

    代码:

     import socket
    
     def main():
         #  创建 upd 套接字
         udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
         # 绑定本地端口,发送发一般不需要绑定
         # udp_socket.bind(("", 7890))
         
         send_data = input("请输入要发送的数据: ")
    
         # 使用套接字发送数据  参数(发送的数据(字节),  (ip + 端口)(元组形式))
         udp_socket.sendto(send_data.encode('gbk'), ('192.168.1.120', 8080))
     
         # 关闭套接字
         udp_socket.close()
     
     if __name__ == '__main__':
         main()
    
  2. UDP接受数据

    步骤:

    • 创建 upd 套接字
    • 绑定本地信息, ip 一般不用写
    • 接收数据
    • 输出
    • 关闭 upd 套接字

    代码:

     import socket
    
     def main():
         # 1. 创建 upd 套接字
         udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
         # 2.绑定本地信息, ip 一般不用写
         localaddr = ('', 9955)
         udp_socket.bind(localaddr)
         # 3. 接收数据
         recv_data = udp_socket.recvfrom(1024)   # 1024 为接收的默认最大字节数
         # 4. 输出
         # recv_data这个变量中存储的是一个元组(接收到的数据,(发送方的ip, port))
         # print(recv_data) # (b'123', ('192.168.1.120', 8080))
         recv_msg = recv_data[0]
         send_addr = recv_data[1]
         print("来自 %s 的消息:  %s" % (str(send_addr), recv_msg.decode('gbk')))  # windows 默认为gbk
         # 5. 关闭 upd 套接字
         udp_socket.close()
     
     if __name__ == '__main__':
         main()
    
  3. UDP聊天案例

    在虚拟机执行代码,windows下用过 网络调试助手收发信息。

    代码:

     import socket
    
     def send_msg(udp_socket):
         send_data = input("请输入您要发送的数据:")
         send_ip = input("请输入对方的ip:")
         send_port = int(input("请输入对方的port:"))
         udp_socket.sendto(send_data.encode('gbk'), (send_ip, send_port))
     
     def recv_msg(udp_socket):
         recv_data = udp_socket.recvfrom(1024)
         recv_addr = recv_data[1]
         recv_info = recv_data[0]
         print("来自 %s 的消息: %s" % (str(recv_addr), recv_info.decode('gbk'))) # 来自 ('192.168.1.120', 8080) 的消息: 你好
     
     def main():
         udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
         localaddr = ('', 9955)
         udp_socket.bind(localaddr)
     
         while(True):
             print('----------UDP聊天器----------')
             print('     1. 发送数据')
             print('     2. 接受数据')
             print('     0. 退出系统')
             op = input("请输入您要进行的操作: ")
     
             if op == '1':
                 send_msg(udp_socket)
             elif op == '2':
                 recv_msg(udp_socket)
             elif op == '0':
                 break
             else:
                 print("您输入的信息有误, 请重新输入")
     
     if __name__ == '__main__':
         main()
    

2.3 TCP练习

  1. tcp客户端

    步骤:

    • 创建 tcp 的套接字
    • 链接服务器
    • 发送数据
    • 关闭套接字

    代码:

     import socket
    
     def main():
         # 1. 创建 tcp 的套接字
         tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         # 2. 链接服务器
         sever_ip = input("请输入要链接的服务器的ip: ")
         sever_port = int(input("请输入要链接的服务器的端口: "))
         sever_addr = (sever_ip, sever_port)
         tcp_client.connect(sever_addr)
         # 3. 发送数据
         send_data = input("请输入你要发送的数据: ")
         tcp_client.send(send_data.encode('gbk'))
         # 4. 关闭套接字
         tcp_client.close()
     
     
     if __name__ == '__main__':
         main()
    
  2. tcp服务端

    步骤:

    • 创建 tcp 套接字
    • 绑定本地信息
    • 让默认的套接字由主动变为被动 listen
    • 等待客户端的连接
    • 接收发送的数据
    • 回送一部分数据给客户端
    • 关闭套接字

    代码:

     import socket
    
     def main():
         # 1. 创建 tcp 套接字
         tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         # 2. 绑定本地信息
         tcp_server.bind(('', 9955))
         # 3. 让默认的套接字由主动变为被动 listen
         tcp_server.listen(128)
         # 4. 等待客户端的连接
         client_socket, client_addr = tcp_server.accept()
         # 5. 接收发送的数据
         recv_data = client_socket.recv(1024)
         print(recv_data.decode('gbk'))
         # 6. 回送一部分数据给客户端
         client_socket.send('收到了'.encode('gbk'))
         # 7. 关闭套接字
         client_socket.close()
         tcp_server.close()
     
     
     if __name__ == '__main__':
         main()
    
  3. 案例

    循环为多个客户端服务并且多次服务一个客户端

    在虚拟机执行代码,windows下用过 网络调试助手收发信息。

     import socket
     
     def main():
         # 1. 建立链接
         tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         # 2. 绑定本地信息
         tcp_server.bind(('', 9955))
         # 3. listen
         tcp_server.listen(128)
         # 4.
         # 此处循环是为了 为多个客户端服务
         while True:
             print('-----等待客户端连接-----')
             recv_client, recv_addr = tcp_server.accept()
             print('客户端已连接, 详细信息 %s' % str(recv_addr))
     
             # 此处循环是为了 为同一个客户端服务多次
             while True:
                 recv_data = recv_client.recv(1024)
                 print('客户端的消息为: %s' % (recv_data.decode('gbk')))
     
                 # 如果recv解堵塞,那么有2种方式:
                 # 1. 客户端发送过来数据
                 # 2. 客户端调用close导致而了 这里 recv解堵塞
                 if recv_data:
                     # 回送给客户端信息
                     recv_client.send('收到了'.encode('gbk'))
                 else:
                     break
             # 关闭accept返回的套接字 意味着 不会在为这个客户端服务
             recv_client.close()
             print('-----已经服务完毕-----')
     
         # 如果将监听套接字 关闭了,那么会导致 不能再次等待新客户端的到来,即xxxx.accept就会失败
         tcp_server.close()
     
     
     if __name__ == '__main__':
         main()