-
socket(简称
套接字
) 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信。 - 在 Python 中 使用socket 模块的函数 socket 就可以完成socket对象的创建:
import socket socket.socket(AddressFamily, Type) """ 函数 socket.socket 创建一个 socket,该函数带有两个参数: Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议 """
- 使用流程
- 创建套接字
- 使用套接字发送//接受数据
- 关闭套接字
UDP链接
无连接的
- 通讯流程图如图(udp链接通讯流程图)
- 创建udp客户端(用于发送数据)
from socket import * # 1.创建udp套接字 udp_socket = socket(AF_INET, SOCK_DGRAM) # 2.准备接收方的ip和端口、元组形式 dest_addr = ('192.168.1.103', 8080) # 3.准备要发送的数据 send_data = 'hello word' # 4.发送到指定的ip地址的指点端口中(应用对应的端口) # 网络通信中只能传输二进制所以进行先encode编码 udp_socket.sendto(send_data.encode('utf-8'), dest_addr ) # 5.关闭套接字 udp_socket.close()
- 创建udp客户端(用于发送数据后接受数据)
from socket import * # 1.创建udp套接字 udp_socket = socket(AF_INET, SOCK_DGRAM) # 2.准备接收方的ip和端口、元组形式 dest_addr = ('192.168.1.103', 8080) # 3.准备要发送的数据 send_data = 'hello word' # 4.发送到指定的ip地址的指点端口中(应用对应的端口) # 网络通信中只能传输二进制所以进行先encode编码 udp_socket.sendto(send_data.encode('utf-8'), dest_addr ) # 5.等待接收对方发送的数据 # 1024表示本次接收的最大字节数 recv_data = udp_socket.recvfrom(1024) # 6.显示对方发送的数据(当前位置会阻塞进程直到接收到数据) # 接收到的数据recv_data是一个元组 # 第1个元素是对方发送的数据,对方用什么编码自己用什么解码 # 第2个元素是对方的ip和端口 print(recv_data[0].decode('utf-8')) print(recv_data[1]) # 7.关闭套接字 udp_socket.close()
- 创建udp服务器端用于接受客户端发送的数据
""" 一般情况下,在一台电脑上运行的网络程序有很多,为了不与其他的网络程序占用同一个端口号,往往在编程中, udp的端口号一般不绑定,每一次运行建立udp链接都会使用不同的端口。由于服务器端端口需要固定。 所以服务器端需要绑定固定的端口方便客户端链接 """ from socket import * # 1. 创建套接字 udp_socket = socket(AF_INET, SOCK_DGRAM) # 2. 设置绑定的ip地址和端口号,ip一般不用写,表示可以通过访问的本机的任何一个ip发送数据 local_addr = ('', 7788) # 3. 绑定本地的相关信息,如果一个网络程序不绑定,则系统会随机分配 udp_socket.bind(local_addr) # 4. 等待接收对方发送的数据(此处会阻塞进程等待客户端发送数据) recv_data = udp_socket.recvfrom(1024) # 1024表示本次接收的最大字节数 # 5. 显示接收到的数据 print(recv_data[0].decode('utf-8')) # 6. 关闭套接字 udp_socket.close()
- 小结
- 一个udp网络程序,可以不绑定,此时操作系统会随机进行分配一个端口,如果重新运行此程序端口可能会发生变化
- 一个udp网络程序,也可以绑定信息(ip地址,端口号),如果绑定成功,那么操作系统用这个端口号来进行区别收到的网络数据是否是此进程的
TCP链接
TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。
TCP通信需要经过创建连接、数据传送、终止连接三个步骤。
TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,"打电话""
- 特点:
- 面向链接
- 可靠传输
- TCP和UDP的不同
- 面向连接(确认有创建三方交握,连接已创建才作传输。)
- 有序数据传输
- 重发丢失的数据包
- 舍弃重复的数据包
- 无差错的数据传输
- 阻塞/流量控制
- 通讯流程图(如图:TCP链接通讯流程图)
- 搭建简单TCP客户端
from socket import * # 创建socket tcp_client_socket = socket(AF_INET, SOCK_STREAM) # 目的信息 server_ip = '190.168.1.103' server_port = 8000 # 链接服务器 tcp_client_socket.connect((server_ip, server_port)) # 需要发送的数据 send_data = 'hello word' # 编码并发送 tcp_client_socket.send(send_data.encode("utf-8")) # 此处会阻塞进程等待服务器端发送数据 # 接收对方发送过来的数据,最大接收1024个字节 recvData = tcp_client_socket.recv(1024) # 解码并打印接受数据 print('接收到的数据为:', recvData.decode('utf-8')) # 关闭套接字 tcp_client_socket.close()
-
搭建简单的TCP服务器端
from socket import * # 创建socket tcp_server_socket = socket(AF_INET, SOCK_STREAM) # 本地信息 address = ('', 7788) # 绑定 bind绑定ip和port tcp_server_socket.bind(address) # 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动套接字用于接收别人的链接 #128 指可以有多少个客户端可以链接 tcp_server_socket.listen(128) # 如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务 # client_socket用来为这个客户端服务(新的用于通讯的TCP链接)】、 # clientAddr 客户端的ip和端口 # tcp_server_socket 就可以省下来专门等待其他新客户端的链接 client_socket, clientAddr = tcp_server_socket.accept() # 接收对方发送过来的数据 recv_data = client_socket.recv(1024) # 接收1024个字节 # 对接受数据解码并打印 print('接收到的数据为:', recv_data.decode('utf-8')) # 发送一些数据到客户端 client_socket.send("thank you !".encode('utf-8')) # 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接 client_socket.close() # 关闭服务器端套接字,服务器端用于接受链接的套接字一般不关闭 # tcp_server_socket.close()
-
TCP注意点
- tcp服务器一般情况下都需要绑定,否则客户端找不到这个服务器
- tcp客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机
- tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的
- 当客户端需要链接服务器时,就需要使用connect进行链接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信
- 当一个tcp客户端连接服务器时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务
- listen后的套接字是被动套接字,用来接收新的客户端的链接请求的,而accept返回的新套接字是标记这个新客户端的
- 关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信。
- 关闭accept返回的套接字意味着这个客户端已经服务完毕
- 当客户端的套接字调用close后,服务器端会recv解堵塞,并且返回的长度为0,因此服务器可以通过返回数据的长度来区别客户端是否已经下线
-
文件下载器
-
服务器端
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()
-
错误之处欢迎指出