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发送数据的时机共同决定的

posted @ 2015-05-18 22:24  荆书凝  阅读(655)  评论(0编辑  收藏  举报