Python中Socket的用法及Close方法假关闭Socket连接的问题

最近用python的Socket写了一个传输通讯测试工具,但是发现在Server端调用close方法后,如果循环没有break的话,此连接还可以继续用来发送和接收数据。所以,我就觉得很是奇怪,难道close方法关闭的连接没有起作用吗?经过试验后,确实如此,以下是我的事例代码,

makefile方法介绍:

def makefile(self, mode="r", buffering=None, *, encoding=None, errors=None, newline=None)

makefile方法返回一个与socket套接字相关联的文件对象,在这之后,你就可以像操作一个文件一样去操作socket连接,它的参数解释与open函数的参数解释相同,唯一需要注意的地方是mode只支持r, w, b这三种模式。对于套接字,要求其必须是阻塞的,如果发生了超时, 文件对象的内部缓冲区可能会以不一致的状态结束 。关闭文件对象,不会关闭套接字,仅仅是解除文件对象与套接字的关联。

平时读取文件时,我们常用的是按行读取,但从socket套接字中读取数据时,你最熟悉的方法是读取指定长度的数据,试想一下,你现在写了一个socket,向一个web服务发送了请求,服务器返回response给你,你该如何解析呢?http response通常是下面的样子

HTTP/1.1 200 OK\r\n
Server: nginx/1.16.1\r\n
Date: Fri, 29 May 2020 01:02:01 GMT\r\n
Content-Type: text/html; charset=utf-8\r\n
Content-Length: 57889\r\n
Connection: close\r\n
\r\n
body

如果按照指定字节数量读取数据,该如何解析headers呢?这个问题困扰了我很久,直到阅读了python源代码中的http.client,才明白,原来底层的实现是借用makefile方法。使用makefile方法,创建一个与socket套接字相关联的文件对象,接下来就可以像读取文件一个读取socket套接字了,没错,就是readline,下面的示例代码像你展示如何准确读取解析http response的headers部分

import socket
​
​
url = 'www.coolpython.net'
port = 80
# 创建TCP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务端
sock.connect((url, port))
# 创建请求消息头
request_url = 'GET / HTTP/1.1\r\nHost: www.coolpython.net\r\nConnection: close\r\n\r\n'
# 发送请求
sock.send(request_url.encode())
response = b''
# 接收返回的数据
fp = sock.makefile('rb')
while True:
    line = fp.readline()
    print(line)
    if line in (b'\r\n', b'\n', b''):
        break
​
fp.close()

Server端代码:

from socket import *
import threading,os,time
 
class Server():
    def __init__(self,host='127.0.0.1',port=9990):
        try: 
            addr=(host,port)
            self.tcpSerSock=socket(AF_INET,SOCK_STREAM)
            self.tcpSerSock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
            self.tcpSerSock.bind(addr)
            self.tcpSerSock.listen(5)
        except Exception,e :
            print 'ip or port error :',str(e)
            self.tcpSerSock.close()
            
    def main(self):
        while 1 :
            try :
                print 'wait for connecting ...'
                tcpCliSock,addr = self.tcpSerSock.accept()
                addrStr = addr[0]+':'+str(addr[1])
                print 'connect from',addrStr
            except KeyboardInterrupt:
                self.close=True
                tcpCliSock.close()
                self.tcpSerSock.close()
                print 'KeyboardInterrupt'
                break
            ct = ClientThread(tcpCliSock,addrStr) 
            ct.start()
            
class ClientThread(threading.Thread):
    def __init__(self,tcpClient,addr):
        super(ClientThread,self).__init__()
        
        self.tcpClient = tcpClient
        self.addr = addr
        self.timeout = 60
        tcpClient.settimeout(self.timeout)
        self.cf = tcpClient.makefile('rw',0)
        
    def run(self):
        while 1:
            try:
                data = self.cf.readline().strip()
                if data:
                    if data.find("set time")>=0:
                        self.timeout = int(data.replace("set time ",""))
                        self.tcpClient.settimeout(self.timeout)
                    print self.addr,"client say:",data
                    self.cf.write(str(self.addr)+" recevied ok!"+"\n")
                else:
                    break
            except Exception,e:
                self.tcpClient.close()
                self.cf.write("time out !"+"\n")
                print self.addr,"send message error,",str(e)
		#此处将break注释掉
#                break
 
 
if __name__ == "__main__" :
    ser = Server()
    ser.main()

Client端代码:

from socket import *
class Client():
    def __init__(self):
        pass
    
    def main(self):
        tcpCliSock=socket(AF_INET,SOCK_STREAM)
        tcpCliSock.connect(('127.0.0.1',9990))
        print 'connect server 9999 successfully !'
        cf = tcpCliSock.makefile('rw', 0)
        while 1:
            data=raw_input('>')
            try:
                if data:
                    cf.write(data+"\n")
                    data = cf.readline().strip()
                    if data:
                        print "server say:",data
                    else:
                        break
                else:
                    break
            except Exception,e:
                print "send error,",str(e)
if __name__ == "__main__":
    cl = Client()
    cl.main()

在代码中可以看出,如果timeout后,except肯定能够捕获到timeout异常,这样就会进入到except代码中,在上面我们故意将break注释掉,也就是不让其跳出循环,经过试验,可以得知,虽然在server端已经将连接close掉了,但是client端仍然可以顺利的接收到消息,而且,如果client端发送数据的间隔小于超时时间的话,此连接可以顺利的一直使用,这样,我们close貌似就一点儿效果都没有了,经过在百度搜索,一直米有找到解决办法,最后还是硬着头皮去看鸟语文档,下面是官方解释:

close()releases the resource associated with a connection but does not necessarily close the connection immediately. If you want to close the connection in a timely fashion, callshutdown() beforeclose().

大体意思是:close方法可以释放一个连接的资源,但是不是立即释放,如果想立即释放,那么请在close之前使用shutdown方法,可是shutdown方法是干什么的呢?

还得继续看鸟语,官方解释如下:

Shut down one or both halves of the connection. If how is SHUT_RD, further receives are disallowed. If how isSHUT_WR, further sends are disallowed. Ifhow is SHUT_RDWR, further sends and receives are disallowed. Depending on the platform, shutting down one half of the connection can also close the opposite half (e.g. on Mac OS X, shutdown(SHUT_WR) does not allow further reads on the other end of the connection).

大体意思是:shutdown方法是用来实现通信模式的,模式分三种,SHUT_RD 关闭接收消息通道,SHUT_WR 关闭发送消息通道,SHUT_RDWR 两个通道都关闭

也就是说,想要关闭一个连接,首先把通道全部关闭,然后在release连接,以上三个静态变量分别对应数字常量:0,1,2

所以,要实现我们的功能,只需要改变server端的代码,也就是

	    except Exception,e:
                self.tcpClient.close()
                self.cf.write("time out !"+"\n")
                print self.addr,"send message error,",str(e)
		#此处将break注释掉
#                break

改为:

	    except Exception,e:
		self.tcpClient.shutdown(2)
                self.tcpClient.close()
                self.cf.write("time out !"+"\n")
                print self.addr,"send message error,",str(e)
                break

这样的话,在close后,再调用 self.cf.write(“time out !”+“\n”),应该就会报异常,此时client再使用此连接向server发数据,就会出错,然后就会顺理成章的走except的代码了。

当然,上面的代码,self.cf.write(“time out !”+“\n”)代码可以注释掉,我只是想用它来测试是否会出异常而已。

原文链接:https://blog.csdn.net/ztb3214/article/details/17405385

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值