python socket
从比赛的一道PPC题中对send和recv方法的认识
服务端源码:
1 #!/usr/bin/env python 2 # coding: utf-8 3 4 import random 5 import time 6 import SocketServer 7 8 from congratulation import FLAG 9 10 11 banner = r''' 12 ___| _) ___| 13 | _ \ | __ \ | _` | __ `__ \ _ \ 14 | ( | | | | | | ( | | | | __/ 15 \____|\___/ _|_| _| \____|\__,_|_| _| _|\___| 16 ''' 17 18 19 class HandleClient(SocketServer.StreamRequestHandler): 20 timeout = 3 21 22 def handle(self): 23 req = self.request 24 req.sendall(banner + '\n\n') 25 26 iround = 0 27 allround = 1000 28 start = int(time.time()) 29 seed = int(time.time()) 30 random.seed(seed) 31 t = time.localtime(seed) 32 req.sendall('Server time: [%s]\n\n' % time.asctime(time.localtime(seed))) 33 req.sendall('Welcome to CCTF final battle.\n\nI think you are good at programing, so let\'s play coin game.\n') 34 req.sendall('Rules: just guess positive(1) or negative(0) of coin status in each round.\n\n') 35 req.sendall('I will print flag to you, if you guess all status in 1000 rounds. :)\n\n') 36 37 while iround < allround: 38 """ 39 if int(time.time()) - start > 30: 40 req.sendall("Time Out...") 41 req.close() 42 return 43 """ 44 answer = random.choice(['0', '1']) 45 req.sendall('::::: Round %d :::::\nguess:' % (iround + 1)) 46 47 try: 48 t = req.recv(8).strip() 49 except Exception: 50 req.close() 51 return 52 53 if t == answer: 54 req.sendall('Nice job, go on.\n') 55 iround += 1 56 else: 57 req.sendall("Wrong :(\n") 58 req.close() 59 return 60 61 req.sendall("You hit it! Flag is %s" % FLAG) 62 print 'Got flag from', self.client_address 63 64 65 class ThreadedServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): 66 pass 67 68 69 if __name__ == '__main__': 70 host, port = "", int(55554) 71 server = ThreadedServer((host, port), HandleClient) 72 server.allow_reuse_address = True 73 server.serve_forever()
客户端源码:
1 import socket, time, re, random 2 HOST = '' 3 PORT = 55554 4 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 s.connect((HOST, PORT)) 6 time.sleep(0.5) 7 f = s.recv(512) 8 print f 9 t = re.findall(re.compile(r'time:\s\[(.*?)\]'), f)[0] 10 u = int(time.mktime(time.strptime(t, '%a %b %d %H:%M:%S %Y'))) 11 random.seed(u) 12 for i in range(1000): 13 answer = random.choice(['0', '1']) 14 s.send(answer) 15 time.sleep(0.00025) 16 print s.recv(64)
我写客户端脚本时,遇到的问题及认识:
1.客户端代码第6行要挂起一段时间,否则recv()方法因不能完整接收数据无法匹配后续的正则而报错,这是因为要给数据传送一点延迟时间。但是默认情况下socket应该是阻塞的,为什么会出现这种情况?从服务端源码来看,是因为前边的数据是分成几次send的。
2.当服务端在本机运行和在远程运行时会出现截然不同的情况,服务端远程运行时客户端在第15行代码处不需要挂起,而本机运行时则必须挂起一段时间,否则也会报错,从源码发现也是因为数据分成两段send造成的,但是为什么远程和本机运行结果会不同呢?
通过修改客户端源码测试发现,远程运行时,一个recv()方法可以完整接收两次send过来的数据,而本机则不行。解决方法就是在客户端send之后挂起一下,等待两次send的数据都到达接收缓冲区再recv,或者写两个recv,两个recv缓冲区长度分别对应两个send发送的实际长度。
从这里我知道了SOCKET_STREAM是字节流的套接字,传输层发送的应用层信息没有信息边界,信息放到管道里传输到对面的缓冲队列里,对信息边界的处理则由应用层协议完成。
但是还是没有解决本机和远程之间出现这种差异的原因,源码的调试已经无法发现问题,所以抓包测试一下,通过抓包发现服务器端在远程时,TCP协议包里封装了两次send的数据,而在本机时,两次send的数据在两个TCP包里。这就解释了本机和远程的差异。
但是为什么会有这种不同,也就是说在远程发送时发生了粘包,而本机没有。粘包可以在发送方解决,也可以在接收方解决,发送方设置立即发送数据或接收方确定消息边界,而在我写的这个客户端源码中是希望发生粘包的,当在本机时因为几乎没有延迟,缓冲区的数据被立即发送而没有发生粘包,所以会出现不同。
后来又发现非同一主机,也可能发生这种不粘包现象,抓包分析这是由于程序运行速度和TCP发送数据的时机共同决定的