1. 网络通信概述
1.1. 相关概念
-
服务器:服务器就是一系列硬件或软件,为一个或多个客户端提供所需的服务。目的是等待客户端的请求,并响应他们,然后等待更多请求。
-
客户端: 客户端因特定的请求而联系服务器,并发送必要的数据,然后等待服务器的回应。
注: 服务器无限地运行下去,并不断地处理请求,而客户端会对服务器进行一次性请求,接收服务,最后结束与服务器之间的事务。客户端可能会再次发出请求,这些都被当做不同的事务。
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练习
-
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() -
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() -
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练习
-
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() -
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() -
案例
循环为多个客户端服务并且多次服务一个客户端
在虚拟机执行代码,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()