一、socket

socket概念
socket(俗称:套接字)本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 我们知道网络 通信 都 是基于 ip+port 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
socket和file的区别:
- file模块是针对某个指定文件进行【打开】【读写】【关闭】
- socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】
建立一个socket必须至少有2端, 一个服务端,一个客户端, 服务端被动等待并接收请求,客户端主动发起请求, 连接建立之后,双方可以互发数据。

代码示例:
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port = ('127.0.0.1',9999) sk = socket.socket() #创建socket对象 sk.bind(ip_port) #绑定地址 sk.listen(5) #开启端口监听, #设置5个监听,(监听的队列为5)那么超过5个,就会将第6个拒绝掉 while True: #使用死循环实现不间断监听 print ('server waiting...') # 客户端向服务端发送sk.connect(ip_port),连接服务器通过该语句在此处形成阻塞,知道有客户端发起连接请求,并同意客户端的连接。并返回双方的连接通道,和客户端的地址 conn,addr = sk.accept() #阻塞。阻塞时长:永远,直到有客户端连接 #获取数据时,使用连接通道去接收。 #每次接收的最大字节数为1024。 client_data = str(conn.recv(1024),encoding="utf-8") print (client_data) conn.sendall(bytes('不要回答,不要回答,不要回答',encoding="utf-8")) conn.close()
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port = ('127.0.0.1',9999) #定义的是一个元组 sk = socket.socket() #创建socket对象 sk.connect(ip_port) #连接服务器 sk.sendall(bytes('请求占领地球',encoding="utf-8")) #发送数据到服务器。注意只能是字节。 server_reply = str(sk.recv(1024),encoding="utf-8") #定义是每次接收长度最长为1024字节。 print (server_reply) sk.close()
注意:所有的web框架都是基于socket建立的。
web服务器的应用:
#!/usr/bin/env python #coding:utf-8 import socket def handle_request(client): buf = client.recv(1024) client.send("HTTP/1.1 200 OK\r\n\r\n") #发送请求头 client.send("Hello, World") #发送内容 def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost',8080)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close() if __name__ == '__main__': main()
更多功能
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
参数一:地址簇
socket.AF_INET IPv4(默认)
socket.AF_INET6 IPv6socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
参数二:类型
socket.SOCK_STREAM 流式socket , for TCP (默认)
socket.SOCK_DGRAM 数据报式socket , for UDPsocket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
socket.SOCK_SEQPACKET 可靠的连续数据包服务参数三:协议
0 (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
import socket ip_port = ('127.0.0.1',9999) sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) sk.bind(ip_port) while True: data = sk.recv(1024) print( data) import socket ip_port = ('127.0.0.1',9999) sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) while True: inp = input('数据:').strip() if inp == 'exit': break sk.sendto(bytes(inp,encoding="utf-8"),ip_port) sk.close()
方法解析:
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()
套接字的文件描述符
练习:
#!/usr/bin/env python # _*_ coding:utf-8 _*_ __author__ = 'liujianzuo' import socket sk = socket.socket() # sk.bind(('127.0.0.1',9999,)) sk.listen(5) #接收客户端请求 # 连接,客户端地址信息 while True: conn,address = sk.accept()#accept阻塞 如果客户端不连接的话,就会阻塞 conn.sendall(bytes('欢迎致电老男孩', encoding='utf-8')) # py2 直接传即可,而3是字节类型 while True: ret = conn.recv(1024) ret_str = str(ret,encoding="utf-8") if ret_str == "q": break conn.sendall(bytes(ret_str+"好",encoding="utf-8")) print(conn,address)
#!/usr/bin/env python # _*_ coding:utf-8 _*_ __author__ = 'liujianzuo' import socket obj = socket.socket() obj.connect(("127.0.0.1",9999)) ret_bytes = obj.recv(1024) # 这一步如果服务端不sendall 就不会反应,会阻塞 ret_str = str(ret_bytes,encoding="utf-8") print(ret_str) while True: inp = input("请输入内容:") if inp == "q": obj.sendall(bytes(inp,encoding="utf-8")) break else: obj.sendall(bytes(inp,encoding="utf-8")) ret_str = str(obj.recv(1024),encoding="utf-8") print(ret_str) obj.close() # 关闭 # 阻塞
2.文件传输
#############服务端
#!/usr/bin/env python # _*_ coding:utf-8 _*_ __author__ = 'liujianzuo' import socket sk = socket.socket() sk.bind(('127.0.0.1',9999,)) #绑定端口,为服务器完成该任务指定端口 sk.listen(5) #接收客户端请求 while True: # 连接,客户端地址信息 conn,address = sk.accept()#accept阻塞 如果客户端不连接的话,就会阻塞 conn.sendall(bytes('欢迎致电老男孩', encoding='utf-8')) # py2 直接传即可,而3是字节类型
#先去获取文件大小。通过获取文件大小来判断文件传输结束了没有,传完了就可以断开连接。从而避免了服务器的长时间占用 file_size = str(conn.recv(1024),encoding="utf-8") conn.sendall(bytes("ack", encoding='utf-8')) #获取文件大小后,回复确认 #为的解决粘包问题
"""
在该程序里,如果没有获取文件大小后,回复确认这一步,那么就是客户端传了一个文件大小的数值,然后就接着传文件内容,因服务器接收数据时,是内容从端口进入服务器,
会进入缓冲区中,然后操作系统再每隔固定时间去缓冲区中后去数据并解析。这时就有可能出现问题:我们本想文件大小传给服务器后,然后服务器就获取到这个值,然后服务器
再获取文件内容,但是,此时会可能出现,文件内容和文件大小因间隔时间太短,服务器还没接收文件大小,文件内容也跟着传了服务器缓冲区,那么服务器在接收到数据后,
就会不知道如何出来。这种现象即为:粘包
""" print(file_size) has_recv = 0 #指定接收到的文件的大小 f = open("n.jpg","wb") while True: if int(file_size) == has_recv: #如果接收到的文件内容大小等于文件大小 break print(111) data = conn.recv(1024) f.write(data) has_recv += len(data) #因为文件内容传输的时候就是字节。所以直接使用字节长度,就是接收到的文件大小
#######客户端 #!/usr/bin/env python # _*_ coding:utf-8 _*_ __author__ = 'liujianzuo' import socket obj = socket.socket() obj.connect(("127.0.0.1",9999)) ret_bytes = obj.recv(1024) # 这一步如果服务端不sendall 就不会反应,会阻塞 ret_str = str(ret_bytes,encoding="utf-8") print(ret_str) import os file_size = os.stat("f.jpg").st_size #获取文件大小,得到的数字是:字节数 print(file_size) obj.sendall(bytes(str(file_size),encoding="utf-8")) obj.recv(1024) # 接收确认包,解决粘包。 无需确认是什么包 with open("f.jpg","rb") as f: for line in f: obj.sendall(line) #一行一行的发送 obj.close() # 关闭
3、实现简单的ssh
#_*_coding:utf-8_*_ __author__ = 'Alex Li' import socket import os server = socket.socket() #获得socket实例 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("localhost",9999)) #绑定ip port server.listen() #开始监听 while True: #第一层loop print("等待客户端的连接...") conn,addr = server.accept() #接受并建立与客户端的连接,程序在此处开始阻塞,只到有客户端连接进来... print("新连接:",addr ) while True: data = conn.recv(1024) if not data: print("客户端断开了...") break #这里断开就会再次回到第一次外层的loop print("收到命令:",data) res = os.popen(data.decode()).read() #py3 里socket发送的只有bytes,os.popen又只能接受str,所以要decode一下 #执行shell命令的 if len(res) == 0: res = "cmd exec success,has not output!".encode("utf-8") conn.send(str(len(res)).encode("utf-8")) #发送数据之前,先告诉客户端要发多少数据给它 print("等待客户ack应答...") client_final_ack = conn.recv(1024) #等待客户端响应 print("客户应答:",client_final_ack.decode()) print(type(res)) conn.sendall(res.encode("utf-8")) #发送端也有最大数据量限制,所以这里用sendall,相当于重复循环调用conn.send,直至数据发送完毕 server.close()
import socket client = socket.socket() client.connect(("localhost",9999)) while True: msg = input(">>:").strip() if len(msg) == 0:continue client.send( msg.encode("utf-8") ) res_return_size = client.recv(1024) #接收这条命令执行结果的大小 print("getting cmd result , ", res_return_size) total_rece_size = int(res_return_size.decode()) client.send(b"ready go") now_recv_size=0 while now_recv_size<total_rece_size: data=client.recv(1024) now_recv_size+=len(data) print(data.decode()) #print(data.decode()) #命令执行结果 client.close()
socketserver 实现1个进程相应多个请求
内部 select 多线程 等待
2 版本SocketServer 3 版本 socketserver
#!/usr/bin/env python # _*_ coding:utf-8 _*_ __author__ = 'liujianzuo' # import socketserver import SocketServer # class Mysock(socketserver.BaseRequestHandler): class Mysock(SocketServer.BaseRequestHandler): #必须创建的类,继承的类也不能变 def handle(self): # 必须叫这个方法名称,socketserver内写死了要调这个方法 # self 封装了属性,可以生成多个服务端响应请求 # self.request, self.client_address,self.servers conn = self.request #创建连接通道 print("客户端地址:",self.client_address) #获取客户端的ip地址 conn.sendall(bytes("欢迎来点",encoding="utf-8")) while True: ret = conn.recv(1024) ret_str = str(ret,encoding="utf-8") if ret_str == "q": break conn.sendall(bytes(ret_str+"好",encoding="utf-8")) print(conn) if __name__ == '__main__': # server = socketserver.TCPServer(('127.0.0.1',9999,),Mysock) #如果使用的是TCPServer()那么就和socket方法一样了。只能一对一 # server = socketserver.ThreadingTCPServer(('127.0.0.1',9999,),Mysock) # 继承baserver init 其需要两个参数 。使用ThreadTCPServer()就是为了实现多线程 server = SocketServer.ThreadingTCPServer(('127.0.0.1',9999,),Mysock) # 继承baserver init 其需要两个参数 server.serve_forever() #创建永久连接
#!/usr/bin/env python # _*_ coding:utf-8 _*_ __author__ = 'liujianzuo' import socket obj = socket.socket() obj.connect(("127.0.0.1",9999)) ret_bytes = obj.recv(1024) # 这一步如果服务端不sendall 就不会反应,会阻塞 ret_str = str(ret_bytes,encoding="utf-8") print(ret_str) while True: inp = input("请输入内容:") if inp == "q": obj.sendall(bytes(inp,encoding="utf-8")) break else: obj.sendall(bytes(inp,encoding="utf-8")) ret_str = str(obj.recv(1024),encoding="utf-8") print(ret_str) obj.close() # 关闭
注意点:1.使用socketserver,必须写类,类里面的handle方法也必须实现。
2.socketserver可以实现一个服务器连接多个客户端。socket只能一对一连接,一个连接不断开,其他连接只能一直等待,直到当前连接断开
浙公网安备 33010602011771号