TCL客户端在向Python服务器发送“无效命令”时卡住
我有一个Python服务器和一个TCL客户端,它们都在同一台机器上运行,使用的是8020端口。
在Python服务器上,我实现了一些功能来执行特定的操作。
现在,我想在Python服务器中添加一些异常处理,特别是当TCL客户端尝试调用一些尚未实现的功能时。服务器应该能够提醒TCL客户端,这个功能还不可用。
我把我的代码粘贴在下面了:
PYTHON服务器代码
import socket
import sys
import os
DUT_Browser = ""
host = 'localhost'
port = 8020
print 'Current working Directory:',os.getcwd()
def LogError(message):
try:
logfile = open("log.txt", "a")
try:
logfile.write(message)
finally:
logfile.close()
except IOError:
pass
def GetParam(sParams, i):
params = sParams.split(",")
p = params[i].strip()
p = p.strip("\"")
return p
def Login(sParams):
print "In method Login with parameters:", sParams
return 0
def ExecuteKeyword(sCommand):
vals = sCommand.partition("(")
sKeyword = vals[0].strip()
sParams = vals[2].strip()
# Remove the trailing ")" from the parameters string
sParams = sParams[:-1]
#print "KEYWORD: ", sKeyword, "PARAMETERS: ", sParams
func = getattr(sys.modules[__name__],sKeyword)
failed = 1
failed=func(sParams)
if failed:
return 1
else:
return 0
def clientthread(conn):
while True:
keyword = conn.recv(1024)
errorMsg = 'none'
failed = 1
try:
failed=ExecuteKeyword(keyword)
except (Exception, FindFailed), err:
#except Exception, err:
errorMsg=str(err)
LogError(errorMsg)
if failed:
reply = 'FAIL START' + errorMsg + 'FAIL END'
print 'ERROR MSG:',reply
else:
reply = 'PASS: ' + keyword
if not keyword:
break
print 'SERVER REPLY',reply
conn.sendall(reply)
print 'SERVER REPLY SENT'
conn.close()
sdServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'TCP (SOCK_STREAM) Socket created'
try:
sdServer.bind((host, port))
except socket.error , msg:
print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
sys.exit()
print 'Socket bind to port:',port,'on host:',host,'is complete'
#Start listening on socket
sdServer.listen(10)
print 'Socket now listening for 10 connections'
while 1:
#wait to accept a connection - blocking call
conn, addr = sdServer.accept()
print 'Connected with ' + addr[0] + ':' + str(addr[1])
#try:
clientthread(conn)
#except socket.error , err:
# print str(err)
sdServer.close()
TCL客户端代码
set port 8020
set host localhost
set ntoIP "http://10.218.164.58/"
set sdClient [socket $host $port]
fconfigure $sdClient -buffering line
set ntoGuiCmd start
while {$ntoGuiCmd != "end"} {
puts "Enter some command for $sdClient"
set ntoGuiCmd [gets stdin]
puts $sdClient $ntoGuiCmd
if {[eof $sdClient]} {close $sdClient}
gets $sdClient serverMessage
puts "From Server : $serverMessage"
}
当我从客户端输入以下命令时,它能够正常通过,并且服务器的消息也会被打印出来。
Login("10.218.164.58",admin,admin,\"FALSE\")
但是,当我使用以下尚未实现的命令时,客户端就会卡住,无法打印出服务器的异常消息,说明这个功能还没有实现。
Logout("10.218.164.58")
我甚至尝试使用Wireshark工具,发现服务器确实发送了消息,而且TCL客户端似乎也发送了一个确认(ACK)作为回应。所以我不太明白为什么“gets”会卡住。
1 个回答
1
我不太确定哪里出了问题,但我怀疑问题出在Tcl代码上。可能有很多问题(比如有没有换行符被传回去?)但关键是这段代码其实并不是特别符合常规写法。这里有一个更符合常规的版本:
set port 8020
set host localhost
set sdClient [socket $host $port]
fconfigure $sdClient -buffering line
while true {
puts "Enter some command for $sdClient"
gets stdin ntoGuiCmd
if {[eof stdin] || $ntoGuiCmd eq "end"} {
break
}
if {[catch {puts $sdClient $ntoGuiCmd}]} {
break
}
### Replacement discussed below starts here
gets $sdClient serverMessage
if {[eof $sdClient]} {
break
}
puts "From Server: $serverMessage"
# If multiline messages are possible, extra work is required here!
### Replacement ends here
}
close $sdClient
如果你的响应是多行的,你需要更加小心。最好的方法是一直读取,直到你知道已经到达响应的末尾(例如,因为服务器告诉你字节数或行数),但这并不总是可能的。如果你假设响应总是能放在一个数据包里,你可以用这个方法来读取可用的行(它通过在有未读字节时以非阻塞模式从套接字读取):
fconfigure $sdClient -blocking 0
while {[gets $sdClient serverMessage] >= 0} {
puts "From Server: $serverMessage"
}
fconfigure $sdClient -blocking 1
if {[eof $sdClient]} {
break
}
如果响应足够大(这取决于操作系统的缓冲区等),那么它就不会在一个数据包中写入。这时你真的需要在你的消息协议中有一个清晰的标记,来指明响应的结束位置。(反方向也是如此,只不过那边是大请求导致的问题。)
如果这些都是我自己的代码,我会倾向于做一个完全异步的非阻塞客户端:
proc transfer {from to} {
if {[gets $from line] >= 0} {
puts $to $line
} elseif {[eof $from]} {
exit
}
}
set sock [socket "localhost" 8020]
fconfigure stdin -blocking 0
fconfigure $sock -blocking 0
fileevent stdin readable [list transfer stdin $sock]
fileevent $sock readable [list transfer $sock stdout]
vwait forever
不过,这种客户端风格非常不同(而且不容易封装成前端API)。