星期三, 八月 08, 2007

python socket buffers and blocking

前面 python socket write/read only 讨论了使用服务器端只写、客户端只读的方式来进行 mirrord/fs_mirror 之间的同步,那么服务器端如下:
...
fconn = conn.makefile('w', 0)
...
while True:
try:
try:
self.wmCond.acquire()
while self.manager.serial == serial: self.wmCond.wait()
sn = serial
serial = self.manager.serial
finally:
self.wmCond.release()
while sn < serial:
action, target = self.manager.wmlog[sn]
# #1:
fconn.write("%s:%s\n" % (action, target)) # OR:
# conn.sendall("%s:%s\n" % (action, target))
if debug:
print "after writing"
sn += 1
self.manager.svPool[self] = sn
客户端现在主要用在测试中,是这样的:
def __check_protocol(self, conn, fconn, md, expect, incr, func, *args):
svCurrent = md.Status.svCurrent()
sn = md.manager.serial
if mirrord.debug: print "sn:", sn
func(*args)
nloop = md.Status.nloop()
while md.Status.nloop() <= nloop + 1: time.sleep(1)
# Before the wmlog being read, the server should reserve it for the client
self.failUnlessEqual(md.manager.svPool[svCurrent], sn
result = []
for i in range(incr):
line = fconn.readline()[:-1]
result.append(line)
result.sort()
expect.sort()
self.failUnlessEqual(result, expect)
nloop = md.Status.nloop()
while md.Status.nloop() <= nloop + 1: time.sleep(1)
# After the wmlog being transported, the server should delete the overdue records
self.failUnlessEqual(md.manager.svPool[svCurrent], sn + incr)
运行测试时,会在上面的
self.failUnlessEqual(md.manager.svPool[svCurrent], sn
处报错,实际是 md.manager.svPool[svCurrent] > sn!

从输出来分析原因,可以发现在服务器端代码的 #1 处并没有被阻塞!也就是说,fconn.write() 或 conn.sendall() 并不会等待客户端收到数据才返回。实际上,socket 和文件操作一样,使用缓冲 buffers 方法,只有数据被 write() 或 sendall() 到缓冲区以后就会立即返回。

这里虽然在前面使用了 fconn = conn.makefile('w', 0),也就是不使用缓冲,按道理来说,应该是要等待数据成功发送才返回的,但因为这个 fconn 只是一个文件对象,使用的是自己的缓冲,它在底层调用 socket 对象的 sendall() 方法,而这个 sendall() 方法在 Python 中一定会隐含使用 buffers,所以这里还是会立即返回。


《Python 网络编程基础》

对于很多操作系统来说,有时候在网络上发送的数据的调用会在远程服务器确保已经收到信息之前返回,因此,很有可能一个来自对 sendall() 成功调用返回的数据,实际上永远都没有被收到。


或者不完整或顺序不对?

总之,recv() 在缓冲是空的时候阻塞,而 sendall() 等应该是在 buffers 满的时候阻塞!

因此,上面的测试代码,在 func(*args) 被调用之后(通常是 os.mkdir,open('...', 'w') 或 shutil.move 之类的),导致文件系统变化,这样服务器端 fconn.write() 之后立刻返回,则该服务线程的 serial 必然增加,而不会等到下面 line = fconn.readline() 的时候,所以比较 serial 的测试会抛出错误。

所以除非使用一开始那种客户端发送确认回执的方法,否则在这里不能进行这种比较(或者也没必要进行这种比较?)。


但这是不是也就意味着在服务器和客户端之间实际上缺乏一种校准机制?
例如服务器端有一个 serial 为 11138 的 wmlog 记录,但客户端可能并没有收到,一种情况是客户端丢掉了这个记录,另一种情况是客户端不得不进行一次完全的初始化操作?

那么是否使用回执的方法就是正确的校准机制呢?

或者我应该向客户端传递 serial 号?

没有评论: