socket
通信三要素
A:IP地址 (1) 用来标识网络上一台独立的主机
(2) IP地址 = 网络地址 + 主机地址(网络号:用于识别主机所在的网络/网段。主机号:用于识别该网络中的主机)
(3) 特殊的IP地址:127.0.0.1(本地回环地址、保留地址,点分十进制)可用于简单的测试网卡是否故障。表示本机。
B:端口号: (1) 用于标识进程的逻辑地址。不同的进程都有不同的端口标识。
(2) 端口:要将数据发送到对方指定的应用程序上,为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识。为了方便称呼这些数字,则将这些数字称为端口。(此端口是一个逻辑端口)
C: 传输协议:通讯的规则。例如:TCP、UDP协议(好比两个人得用同一种语言进行交流)
①、UDP:User Datagram Protocol用户数据报协议
特点:
- 面向无连接:传输数据之前源端和目的端不需要建立连接。
- 每个数据报的大小都限制在64K(8个字节)以内。
- 面向报文的不可靠协议。(即:发送出去的数据不一定会接收得到)
- 传输速率快,效率高。
- 现实生活实例:邮局寄件、实时在线聊天、视频会议…等。
②、TCP:Transmission Control Protocol传输控制协议
特点:
- 面向连接:传输数据之前需要建立连接。
- 在连接过程中进行大量数据传输。
- 通过“三次握手”的方式完成连接,是安全可靠协议。
- 传输速度慢,效率低。
注意:在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接。
SOCK_STREAM : TCP
SOCK_DGRAM : UDP
family = AF_INET : 服务器之间的通信
family = AF_UNIX :unix不同进程间通信
默认为:SOCK_STREAM : TCP family = AF_INET
<socket.socket fd=240, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
<socket.socket fd=240, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
<socket.socket fd=232, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8000), raddr=('127.0.0.1', 56702)>
server 下的方法
blind() 为socket绑定IP和端口号
listen() 监听设置窗口等待用户请求
accept() 接受来自客户端的连接
recv() 接收数据
send(string) 发送数据
sendall() “使劲”发
close() 关闭连接
client下的方法
connect() 连接服务器
recv() 接收数据
send(string) 发送数据
sendall() 发送内容一定是bytes类型
close() 关闭连接
server端
import socket #建立对象 sk = socket.socket() print(sk)#<socket.socket fd=240, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0> #建立连接地址,127.0.1.1是本地IP,8000是自己设的端口号,必须为元组 address = ('127.0.0.1', 8000) #为socket绑定IP地址和端口号 sk.bind(address) #等待连接的人只能有3个 sk.listen(3) print('waiting') #accept阻塞,直到有客户端连接过来 conn, addr = sk.accept() #print(conn) #<socket.socket fd=232, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8000), raddr=('127.0.0.1', 56702)> inp = input('>>>') #python3只能传输Byte类型的数据 conn.send(bytes(inp, 'utf8')) #用conn接收信息 data = conn.recv(1024) #打印时转化为字符串 print(str(data,'utf8')) # print(conn)
client端
import socket #建立对象,与server端的没有关系 sk = socket.socket() #给出IP和端口号,这是server端的信息 #print(sk) #<socket.socket fd=240, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0> address = ('127.0.0.1',8000) #连接 sk.connect(address) #接收信息,设定为最大接收1024字节 data = sk.recv(1024)#阻塞 print(str(data,'utf8')) inp = input('>>>') #给服务端发送一条信息 sk.send(bytes(inp,'utf8'))
# 流程描述: # # 1 服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket # # 2 服务器为socket绑定ip地址和端口号 # # 3 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开 # # 4 客户端创建socket # # 5 客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket # # 6 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直等到客户端返回连接信息后才返回,开始接收下一个客户端连接请求 # # 7 客户端连接成功,向服务器发送连接状态信息 # # 8 服务器accept方法返回,连接成功 # # 9 客户端向socket写入信息(或服务端向socket写入信息) # # 10 服务器读取信息(客户端读取信息) # # 11 客户端关闭 # # 12 服务器端关闭
sk.bind(address) #s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。 sk.listen(backlog) #开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。 #backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5 #这个值不能无限大,因为要在内核中维护连接队列 sk.setblocking(bool) #是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。 sk.accept() #接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。 #接收TCP 客户的连接(阻塞式)等待连接的到来 sk.connect(address) #连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。 sk.connect_ex(address) #同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061 sk.close() #关闭套接字 sk.recv(bufsize[,flag]) #接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。 sk.recvfrom(bufsize[.flag]) #与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。 sk.send(string[,flag]) #将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。 sk.sendall(string[,flag]) #将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。 #内部通过递归调用send,将所有内容发送出去。 sk.sendto(string[,flag],address) #将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。 sk.settimeout(timeout) #设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s ) sk.getpeername() #返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。 sk.getsockname() #返回套接字自己的地址。通常是一个元组(ipaddr,port) sk.fileno() #套接字的文件描述符
传输命令
client端
import socket sk = socket.socket() address = ('127.0.0.1',8000) sk.connect(address) while True: inp = input('>>>') if inp == 'exit': break sk.send(bytes(inp, 'utf8')) result_len = int(str(sk.recv(1024),'utf8')) print(result_len) sk.send('go on') #用于偶然出现的粘包现象 data = bytes() while len(data) != result_len: recv = sk.recv(1024) data += recv print(str(data, 'gbk')) #因为通过subprocess, stdout.read()过来的为gbk编码的bytes sk.close()
server端
#向服务器发送一个命令,返回命令处理结果,用subprocess模块处理命令 import socket import subprocess sk = socket.socket() address = ('127.0.0.1', 8000) sk.bind(address) sk.listen(3) print('waiting') while True: conn, addr = sk.accept() print(addr) while True: try: data = conn.recv(1024) except Exception: break if not data:break #print(str(data, 'utf8')) #这里必须加上stdout=subprocess.PIPE,只有这样才能把subprocess进程里的 #其中第一个参数为传入的命令,必须为str格式 obj = subprocess.Popen(str(data,'utf8'), shell=True, stdout=subprocess.PIPE) #返回值为bytes类型,windows系统默认为gbk编码的bytes cmd_result = obj.stdout.read() result_len = bytes(str(len(cmd_result)),'utf8') conn.send(result_len) print(result_len) conn.recv(1024) #用于偶然出现的粘包现象 conn.send(cmd_result) sk.close()
传输文件
client端
#传送一个文件 import socket,os sk = socket.socket() address = ('127.0.0.1',8000) sk.connect(address) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) while True: inp = input('>>>') #识别输入命令及路径 cmd, path = inp.split('|') #拼接绝对路径 path = os.path.join(BASE_DIR,path) #获取文件名称 filename = os.path.basename(path) #获取文件大小 file_size = os.stat(path).st_size #把两项内容打包 file_info = 'post|%s|%s'%(filename, file_size) sk.sendall(bytes(file_info, 'utf8')) f = open(path, 'rb') has_sent = 0 while has_sent != file_size: data = f.read(1024) sk.send(data) has_sent += len(data) f.close() print('上传成功') sk.close()
server端
#接收一个文件 import socket,os import subprocess sk = socket.socket() address = ('127.0.0.1', 8000) sk.bind(address) sk.listen(3) print('waiting') BASE_DIR = os.path.dirname(os.path.abspath(__file__)) while True: conn, addr = sk.accept() while True: data = conn.recv(1024) cmd,filename,file_size = str(data,'utf8').split('|') path = os.path.join(BASE_DIR,'yuan',filename) file_size = int(file_size) f = open(path,'ab') has_rev = 0 while has_rev != file_size: data = conn.recv(1024) f.write(data) has_rev += len(data) f.close() conn.close() sk.close()
socketserver
创建一个socketserver 至少分以下几步
- First, you must create a request handler class by subclassing the
BaseRequestHandler
class and overriding itshandle()
method; this method will process incoming requests. - Second, you must instantiate one of the server classes, passing it the server’s address and the request handler class.
- Then call the
handle_request()
orserve_forever()
method of the server object to process one or many requests. - Finally, call
server_close()
to close the socket.
简单并发
client端
#简单并发 import socket ip_port = ('127.0.0.1',8091) sk = socket.socket() sk.connect(ip_port) print ("客户端启动:") while True: inp = input('>>>') sk.sendall(bytes(inp,"utf8")) if inp == 'exit': break server_response=sk.recv(1024) print (str(server_response,"utf8")) sk.close()
server端
#简单并发 import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): print ("服务端启动...") while True: conn = self.request print (self.client_address) while True: client_data=conn.recv(1024) print (str(client_data,"utf8")) print ("waiting...") conn.sendall(client_data) conn.close() if __name__ == '__main__': server = socketserver.ThreadingTCPServer(('127.0.0.1',8091),MyServer) server.serve_forever()
聊天并发实例
import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): print ("服务端启动...") while True: conn = self.request print (self.client_address) while True: client_data=conn.recv(1024) print (str(client_data,"utf8")) print ("waiting...") server_response=input(">>>") conn.sendall(bytes(server_response,"utf8")) # conn.sendall(client_data) conn.close() # print self.request,self.client_address,self.server if __name__ == '__main__': server = socketserver.ThreadingTCPServer(('127.0.0.1',8098),MyServer) server.serve_forever() ########################################## import socket ip_port = ('127.0.0.1',8098) sk = socket.socket() sk.connect(ip_port) print ("客户端启动:") while True: inp = input('>>>') sk.sendall(bytes(inp,"utf8")) server_response=sk.recv(1024) print (str(server_response,"utf8")) if inp == 'exit': break sk.close()
import socketserver class MyTCPHandler(socketserver.BaseRequestHandler): """ The request handler class for our server. It is instantiated once per connection to the server, and must override the handle() method to implement communication to the client. """ def handle(self): # self.request is the TCP socket connected to the client self.data = self.request.recv(1024).strip() print("{} wrote:".format(self.client_address[0])) print(self.data) # just send back the same data, but upper-cased self.request.sendall(self.data.upper()) if __name__ == "__main__": HOST, PORT = "localhost", 9999 # Create the server, binding to localhost on port 9999 server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) # Activate the server; this will keep running until you # interrupt the program with Ctrl-C server.serve_forever()
让你的socketserver并发起来, 必须选择使用以下一个多并发的类
class socketserver.ForkingTCPServer class socketserver.ForkingUDPServer class socketserver.ThreadingTCPServer class socketserver.ThreadingUDPServer
所以:
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) #替换为 server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)