美文网首页网络I/O模型总结
socket编程的几个问题

socket编程的几个问题

作者: Spring_Bear | 来源:发表于2017-07-22 23:16 被阅读276次

    网络编程离不开socket,接触不多时总觉得简单,无非是bind();listen();accept();send();recv()几个函数痴痴用上,能跑就不管三七二十一了。最近需要写个http代理,遇上几个问题。

    socket的本质

    从前认死理,怎么就recv()收到网络报文,功能还这么强大,嗅探,代理,服务器都用上它,就差没往硬件上想,现在想来也算是和自己和解了,在我的理解,socket是从传输层提取出的一套程序接口,供应用层使用,使我们不必面对实际的tcp/ip复杂的传输过程。它是一套规则和机制,灵感来源于Unix一切皆文件的设计,socket就像一个文件夹,创建,读取,写出,关闭,只不过这里对应的是网络数据。

    socket中的accept()

    accept()函数返回值是客户端socket和客户端地址信息,它创建了一个新的socket,称它为cli_socket,而原先绑定的socket称为soc。这里使我疑惑的地方就在于为什么同一个端口下有两个socket的存在。不是一个端口标识唯一的socket吗?
    事实上,不是一个端口唯一标识,而是五元组标识一个socket,(source ip, source port, destination ip, destination port, protocol),而本地绑定的socket并不算一个完整的socket,它只包含三元组,即(destination ip, destination port, protocol),而accept()函数接受了来自客户端的信息,至此完成一个完整的socket链接,这个cli_socket用于接收和发送数据,而soc则继续用于监听客户端的connect请求。
    再说明一点,虽说五元组唯一标识一个socket,并不是说这个端口就只能给一个socket使用,若是五元组中有不同的元素,比如说客户端ip和端口不同,那就是另一个socket,它和服务器端相同的端口和ip构成了新的五元组,比如说服务器端的80端口供多个客户端使用。
    总的来说这些都是前人留下的规定,有的也不见得多明智,但目前还是主流,所以还是有必要知道。
    参考1
    参考2

    socket中的数据接收问题

    因为写的是一个代理,所以既要当客户端也要当服务端,在充当客户端过程中,需要和web服务器通信,很简单的一个建立连接,接收数据,是这样的:

    SerSock = socket(AF_INET, SOCK_STREAM)
    SerSock.connect(SerAddr)
    SerSock.send(CMessage)
    message = SerSock.recv(4096)
    SerSock.close()
    

    就是创建,连接,发送,接收,关闭的过程,但是报错,broken pipe。说是服务端还没传完数据,客户端就关闭了连接。
    将接收到的数据输出了一下,发现数据都没传完,是这种:

    Screenshot from 2017-07-12 22-10-34.png

    细想,才知服务器传送数据并不是一次性传完的,学过计算机网络的都知道,传输层会将数据进行分片传输,选择不同的路径传送过来再组织到一起。那么分片的大小是多大呢?每个以太网帧都有最小的大小64bytes最大不能超过1518bytes,参考

    将代码一改:

        SerSock = socket(AF_INET, SOCK_STREAM)
        SerSock.connect(SerAddr)
        SerSock.send(CMessage)
        FromSMessage = ''
        while True:
            message = SerSock.recv(4096)
            if not message:
                break
            FromSMessage += message
            SerSock.close()
    

    可顺利运行。

    socket中的非阻塞问题

    代理程序虽然能运行,但访问一多就会崩掉。这里需要知道的是,socket中的send(),recv()函数都是阻塞函数,也就是说若数据没有发送完毕或者没有接收完毕,函数是不会返回的,那么随着accept的客户端请求多了,内存占用溢出,就会崩掉。怎样设计一个非阻塞的socket程序呢?
    这里主要讲一下select这个对象,利用select对象的select()函数可以实现非阻塞,简单看一下:

    select.select(rlist, wlist, xlist[, timeout])
    

    select()的参数为3个列表:第一列表为读取输入数据的对象;第2个接收要发送的数据,第3个存放errors,加上一个超时设置。
    返回值有三个:readable,writable,exceptional
    readable有3种可能:对于用来侦听连接主服务器socket,表示已准备好接受一个到来的连接;对于已经建立并发送数据的链接,表示有数据到来;如果没数据到来,表示链接已经关闭。
    writable的情况:连接队列中有数据,发送下一条消息。如果队列中无数据,则从output队列中删除。
    socket有错误,也要从output队列中删除。
    所以这里实现非阻塞大体流程就是,select函数轮循,看有那个socket队列有需要读或写的数据,有就全部接收或发送,没有就忙别的。代码大体如下:

    def nonblocking(self):
            inputs=[self.client,self.target]
            while True:
                readable,writeable,errs=select.select(inputs,[],inputs,3)
                if errs:
                    break
                for soc in readable:
                    data=soc.recv(self.BUFSIZE)
                    if data:
                        if soc is self.client:
                            self.target.send(data)
                        elif soc is self.target:
                            self.client.send(data)
                    else:
                        break
            self.client.close()
            self.target.close()
    

    参考1
    参考2

    至此,socket问题肯定不止于此,经事尚少,暂且留意了这些,做个总结,也希望能对他人有所帮助,不对之处还望指正。

    相关文章

      网友评论

        本文标题:socket编程的几个问题

        本文链接:https://www.haomeiwen.com/subject/heoohxtx.html