一、socket的简介
socket(简称:套接字)进程间通信的一种方式,它与其他进程间通信的一个主要不同是:能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,如qq聊天、微信聊天等。
二、socke的基本使用
在python中使用socket模块就可以创建套接字:
import socket socket.socket(AddressFamily, Type)
函数说明:
- Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET
- Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)
import socket # 创建tcp的套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # ...这里是使用套接字的功能(省略)... # 不用的时候,关闭套接字 s.close()
创建一个tcp套接字流程
import socket # 创建udp的套接字 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # ...这里是使用套接字的功能(省略)... # 不用的时候,关闭套接字 s.close()
创建一个udp套接字流程
说明:
套接字的使用流程与文件的使用流程非常的相似:
1.创建套接字
2.使用套接字收、发数据
3.关闭套接字
三、udp网络程序
创建基于udp的网络程序的步骤如下:
udp的发送数据(在python3中socket发送数据用的是字节类型byte):
#coding=utf-8 from socket import * # 1. 创建udp套接字 udp_socket = socket(AF_INET, SOCK_DGRAM) # 2. 准备接收方的地址 # '192.168.1.103'表示目的ip地址 # 8080表示目的端口 dest_addr = ('192.168.1.103', 8080) # 注意 是元组,ip是字符串,端口是数字 # 3. 从键盘获取数据 send_data = input("请输入要发送的数据:") # 4. 发送数据到指定的电脑上的指定程序中 udp_socket.sendto(send_data.encode('utf-8'), dest_addr) # 5. 关闭套接字 udp_socket.close()
udp的接收数据(此时作为服务端,需要绑定ip、端口):
import socket # 创建socket套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 本地的端口和ip local_ip = ('', 7788) # 绑定端口 udp_socket.bind(local_ip) while True: # 接受数据 recv_data = udp_socket.recvfrom(1024) print(recv_data) recv_msg = recv_data[0].decode("gbk") print("接受的数据:%s" % recv_msg) udp_socket.close()
说明:
- 一个udp网络程序,可以不绑定,此时操作系统会随机进行分配一个端口,如果重新运行此程序端口可能会发生变化
- 一个udp网络程序,也可以绑定信息(ip地址,端口号),如果绑定成功,那么操作系统用这个端口号来进行区别收到的网络数据是否是此进程的
import socket def main(): # 创建socket套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) local_ip = ("", 7788) # 绑定端口 udp_socket.bind(local_ip) # 利用循环来进行处理事情 while True: print("请选择你需要的功能:") print("1:发送消息") print("2:接收消息") print("exit:退出") # 输入功能 op_num = input("请输入功能序号:") ": send_msg(udp_socket) ": recv_msg(udp_socket) else: print("退出socket程序") break udp_socket.close() def send_msg(udp_socket): # 请输入要发送的数据 send_data = input("请输入要发送的数据:") ip_addr = input("请输入要发送的ip地址:") ip_port = int(input("请输入对方的端口:")) udp_socket.sendto(send_data.encode("utf-8"), (ip_addr, ip_port)) def recv_msg(udp_socket): recv_data = udp_socket.recvfrom(1024) recv_ip = recv_data[1] recv_data = recv_data[0].decode("gbk") print(">>>%s:%s" % (str(recv_ip), recv_data)) if __name__ == "__main__": main()
udp程序的收、法数据
四、TCP网络程序
1.tcp的简介
TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。
TCP通信需要经过创建连接、数据传送、终止连接三个步骤。
TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,"打电话""。
2.tcp的特点
面向连接
通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统内核资源,以管理连接的状态和连接上的传输。
双方间的数据传输都可以通过这一个连接进行。
完成数据交换后,双方必须断开此连接,以释放系统资源。
这种连接是一对一的,因此TCP不适用于广播的应用程序,基于广播的应用程序请使用UDP协议。
可靠传输
1)TCP采用发送应答机制
TCP发送的每个报文段都必须得到接收方的应答才认为这个TCP报文段传输成功
2)超时重传
发送端发出一个报文段之后就启动定时器,如果在定时时间内没有收到应答就重新发送这个报文段。
TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。
3)错误校验
TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。
4) 流量控制和阻塞管理
流量控制用来避免主机发送得过快而使接收方来不及完全收下。
3.tcp与udp的不同点
- 面向连接(确认有创建三方交握,连接已创建才作传输。)
- 有序数据传输
- 重发丢失的数据包
- 舍弃重复的数据包
- 无差错的数据传输
- 阻塞/流量控制
4.tcp的通信模型
tcp客户端的创建流程:
from socket import * # 创建socket tcp_client_socket = socket(AF_INET, SOCK_STREAM) # 目的信息 server_ip = input("请输入服务器ip:") server_port = int(input("请输入服务器port:")) # 链接服务器 tcp_client_socket.connect((server_ip, server_port)) # 提示用户输入数据 send_data = input("请输入要发送的数据:") tcp_client_socket.send(send_data.encode("gbk")) # 接收对方发送过来的数据,最大接收1024个字节 recvData = tcp_client_socket.recv(1024) print('接收到的数据为:', recvData.decode('gbk')) # 关闭套接字 tcp_client_socket.close()
tcp服务器创建流程:
- socket创建一个套接字
- bind绑定ip和port
- listen使套接字变为可以被动链接
- accept等待客户端的链接
- recv/send接收发送数据
from socket import * # 创建socket tcp_server_socket = socket(AF_INET, SOCK_STREAM) # 本地信息 address = ('', 7788) # 绑定 tcp_server_socket.bind(address) # 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的链接了 tcp_server_socket.listen(128) # 如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务 # client_socket用来为这个客户端服务 # tcp_server_socket就可以省下来专门等待其他新客户端的链接 client_socket, clientAddr = tcp_server_socket.accept() # 接收对方发送过来的数据 recv_data = client_socket.recv(1024) # 接收1024个字节 print('接收到的数据为:', recv_data.decode('gbk')) # 发送一些数据到客户端 client_socket.send("thank you !".encode('gbk')) # 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接 client_socket.close()
注:recv这个函数是阻塞的,如果要解阻塞有两种方式,一种是客户端发送过来数据被接收了,另一种是客户端调用了close方法
import socket def main(): # 1. 买个手机(创建套接字 socket) tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 插入手机卡(绑定本地信息 bind) tcp_server_socket.bind(("", 7890)) # 3. 将手机设置为正常的 响铃模式(让默认的套接字由主动变为被动 listen) tcp_server_socket.listen(128) # 循环目的:调用多次accept,从而为多个客户端服务 while True: print("等待一个新的客户端的到来...") # 4. 等待别人的电话到来(等待客户端的链接 accept) new_client_socket, client_addr = tcp_server_socket.accept() print("一个新的客户端已经到来%s" % str(client_addr)) # 循环目的: 为同一个客户端 服务多次 while True: # 接收客户端发送过来的请求 recv_data = new_client_socket.recv(1024) print("客户端福送过来的请求是:%s" % recv_data.decode("utf-8")) # 如果recv解堵塞,那么有2种方式: # 1. 客户端发送过来数据 # 2. 客户端调用close导致而了 这里 recv解堵塞 if recv_data: # 回送一部分数据给客户端 new_client_socket.send("hahahghai-----ok-----".encode("utf-8")) else: break # 关闭套接字 # 关闭accept返回的套接字 意味着 不会在为这个客户端服务 new_client_socket.close() print("已经为这个客户端服务完毕。。。。") # 如果将监听套接字 关闭了,那么会导致 不能再次等待新客户端的到来,即xxxx.accept就会失败 tcp_server_socket.close() if __name__ == "__main__": main()
循环为多个客户端服务,并多次服务一个客户端
文件下载案例:
from socket import * import sys def get_file_content(file_name): """获取文件的内容""" try: with open(file_name, "rb") as f: content = f.read() return content except: print("没有下载的文件:%s" % file_name) def main(): if len(sys.argv) != 2: print("请按照如下方式运行:python3 xxx.py 7890") return else: # 运行方式为python3 xxx.py 7890 port = int(sys.argv[1]) # 创建socket tcp_server_socket = socket(AF_INET, SOCK_STREAM) # 本地信息 address = ('', port) # 绑定本地信息 tcp_server_socket.bind(address) # 将主动套接字变为被动套接字 tcp_server_socket.listen(128) while True: # 等待客户端的链接,即为这个客户端发送文件 client_socket, clientAddr = tcp_server_socket.accept() # 接收对方发送过来的数据 recv_data = client_socket.recv(1024) # 接收1024个字节 file_name = recv_data.decode("utf-8") print("对方请求下载的文件名为:%s" % file_name) file_content = get_file_content(file_name) # 发送文件的数据给客户端 # 因为获取打开文件时是以rb方式打开,所以file_content中的数据已经是二进制的格式,因此不需要encode编码 if file_content: client_socket.send(file_content) # 关闭这个套接字 client_socket.close() # 关闭监听套接字 tcp_server_socket.close() if __name__ == "__main__": main()
服务端
from socket import * def main(): # 创建socket tcp_client_socket = socket(AF_INET, SOCK_STREAM) # 目的信息 server_ip = input("请输入服务器ip:") server_port = int(input("请输入服务器port:")) # 链接服务器 tcp_client_socket.connect((server_ip, server_port)) # 输入需要下载的文件名 file_name = input("请输入要下载的文件名:") # 发送文件下载请求 tcp_client_socket.send(file_name.encode("utf-8")) # 接收对方发送过来的数据,最大接收1024个字节(1K) recv_data = tcp_client_socket.recv(1024) # print('接收到的数据为:', recv_data.decode('utf-8')) # 如果接收到数据再创建文件,否则不创建 if recv_data: with open("[接收]"+file_name, "wb") as f: f.write(recv_data) # 关闭套接字 tcp_client_socket.close() if __name__ == "__main__": main()
客户端