一个简单的TCP客户端和服务器

159 阅读3分钟
import argparse
import socket
def recvall(sock,length):
    data=b''    
    while len(data)<length:
        more =sock.recv(length-len(data))
        if not more:
            raise EOFError('was excepting %d bytes but only received %d bytes before the socket closed'%(length,len(data)))
        data=data+more
    return data
def server(interface,port):
    sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    sock.bind((interface,port))
    sock.listen(1)
    print('Listening at',sock.getsockname())
    while True:
        sc,sockname=sock.accept()
        print('we have accepted a connection from',sockname)
        print('Socket name:',sc.getsockname())
        print('Socket peer:',sc.getpeername())
        message=recvall(sc,16)
        print('Incoming sixteen-octet message:',repr(message))
        sc.sendall(b'Farewell,client')
        sc.close()
        print('Reply sent,socket closed')
def client(host,port):
    sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sock.connect((host,port))
    print("Client has been assigned socket name",sock.getsockname())
    sock.sendall(b'Hi,there server')
    reply=recvall(sock,16)
    print('the server said',repr(reply))
    sock.close()
if __name__=='__main__':
    choise={'client':client,'server':server}
    parser=argparse.ArgumentParser(description='send and receive over TCP')
    parser.add_argument('role',choices=choise,help='which role to play')
    parser.add_argument('host',help='interface the server listens at or the host the client sends to')
    parser.add_argument('-p',metavar='PORT',type=int,default=1060,help='TCP port (default is 1060)')
    args=parser.parse_args()
    function=choise[args.role]
    function(args.host,args.p)

**socket.recv(bufsize):**从套接字中返回bufsize大小的字节数据。

**socket.socket(family,type):**定义套接字,第一个参数是地址表,地址表可以定义地址的格式,第二个参数可以定义套接字的类型。SOCK_STREAM代表TCP协议的套接字,TCP可以自动对数据包进行编号,可以在丢包的时候进行重传,可以控制数据传输的流量,但是有时候会因为噪声的扰动使得网络中断。family参数指定地址族,AF_INET代表支持Internet地址族的套接字,代表底层通过TCP/ipv4协议进行通信;类似的又AF_INET6代表TCP/ipv6协议进行通信。

**socket.setsockopt():**第一个参数是即将要设定的套接字选项的级别,可以是IP层,可以是套接字层,这里是SOL_SOCKET代表在套接字层面上对套接字进行设置;第二个参数是SO_REUSEADDR代表允许一个端口在使用完之后马上释放继续使用,因为一个程序如果认为一个TCP对话已经结束的时候,系统的网络栈会把这个会话保持在一个特殊的状态持续一段时间,在这段时间里面,这个TCP会话可以保持正常的对之前与它通信的另一个套接字发送的FIN数据包进行回应,以免FIN数据包丢包而使得TCP客户端没办法关闭,在这个过程里面端口是被占用的,而设置了SO_REUSEADDR参数的套接字会在关闭套接字的时候马上把端口释放出来以供下一次调用,伴随的风险也就是上面说的FIN数据包可能出现丢包的情况导致TCP会话没办法正常关闭。最后一个参数1代表设置数据缓冲区。

**socket.listen():**设置了允许服务器可以对主机进行连接,参数必须是0或者更大的数,表示了服务器在接受某几个连接之前,系统允许的等待的连接的数目,并且服务器不会拒绝相应数目的等待连接。

**socket.getsockname():**返回socket自身的地址,在使用ipv4或者ipv6协议的时候,可以返回端口的值,具体返回什么还得看使用的地址族。

**socket.accept():**开始连接,前提是这个套接字已经绑定好地址(端口和ip)并且套接字已经可以开始进行连接,就是前面的listen函数。返回值有两个,一个是与套接字连接的新套接字,一个是新套接字的地址。

程序启动之后,服务器的程序会自动或手动分配一个端口,并且停留在accept()函数处,等待客户端的连接。这个时候再启动客户端,用服务器的ip和端口进行输入,这个时候系统会给客户端分配一个端口和ip,并且向服务器发送信息,客户端关闭;服务器在进入下一个accept()循环等待下一次的连接。