TCP 和 UDP 协议

 

TCP 和 UDP 协议

1、socket层

  • Socket是应用层与TCP/IP协议族通讯的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来讲,一组简单的接口就是所有,让Socket去组织数据,以符合指定的协议。html

    img

  • 简而言之:node

     
     
     
     
     
     
     
     
    其实站在你的角度上看,socket就是一个模块。咱们经过调用模块中已经实现的方法创建两个进程之间的链接和通讯。
    也有人将socket说成ip+port,由于ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。
    因此咱们只要确立了ip和port就能找到一个应用程序,而且使用socket模块来与之通讯。
     
  • 套接字得发展史: (套接字分为两类,文件类型和网络类型)python

    • (文件)套接字家族的名字:AF_UNIX(起源于UNIX)web

      unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,能够经过访问同一个文件系统间接完成通讯编程

    • (网络)套接字家族的名字:AF_INETwindows

      (还有AF_INET6被用于ipv6,还有一些其余的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是不多被使用,或者是根本没有实现,全部地址家族中,AF_INET是使用最普遍的一个,python支持不少种地址家族,可是因为咱们只关心网络编程,因此大部分时候我么只使用AF_INET)设计模式

  • TCP(Transmission Control Protocol)可靠的、面向链接的协议(eg:打电话)、传输效率低全双工通讯(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。浏览器

    UDP(User Datagram Protocol)不可靠的、无链接的服务,传输效率高(发送前时延小),一对1、一对多、多对1、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)缓存

  • 具体的三次握手,四次挥手过程:服务器

     

     

    • 三次握手:

      • 第一次握手:主机A发送位码为syn=1,随机产生seq number=x的数据包(以便验证服务端是否收到,收到会返回加一数)到服务器(B),客户端进入SYN_SEND状态,等待服务器(B)的确认;主机B由SYN=1知道,A要求创建联机;
      • 第二次握手:主机B收到请求后要确认联机信息,向A发送ack number(主机A的seq+1),syn=1,ack=1,随机产生seq=y的包,此时服务器(B)进入SYN_RECV状态;
      • 第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则链接创建成功。客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手
    • 问题一:为何链接创建须要三次握手,而不是两次握手?

      • 答:防止失效的链接请求报文段被服务端接收,从而产生错误,也就是说要确认客户端还活着。若创建链接只需两次握手,客户端并无太大的变化,仍然须要得到服务端的应答后才进入ESTABLISHED状态,而服务端在收到链接请求后就进入ESTABLISHED状态。此时若是网络拥塞,客户端发送的链接请求迟迟到不了服务端,客户端便超时重发请求,若是服务端正确接收并确认应答,双方便开始通讯,通讯结束后释放链接。此时,若是那个失效的链接请求抵达了服务端,因为只有两次握手,服务端收到请求就会进入ESTABLISHED状态,等待发送数据或主动发送数据。但此时的客户端早已进入CLOSED状态,服务端将会一直等待下去,这样浪费服务端链接资源。

       

      img

    • 四次挥手:

      • 第一次挥手:主机1(可使客户端,也能够是服务器端),设置Sequence NumberAcknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
      • 第二次挥手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment NumberSequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我也没有数据要发送了,能够进行关闭链接了;
      • 第三次挥手:主机2向主机1发送FIN报文段,请求关闭链接,同时主机2进入CLOSE_WAIT状态;
      • 第四次挥手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,而后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段之后,就关闭链接;此时,主机1等待2MSL后依然没有收到回复,则证实Server端已正常关闭,那好,主机1也能够关闭链接了

    img

    • 问题一:.为何TIME_WAIT状态须要通过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

      • 答:虽然按道理,四个报文都发送完毕,咱们能够直接进入CLOSE状态了,可是咱们必须假象网络是不可靠的,有能够最后一个ACK丢失。因此TIME_WAIT状态就是用来重发可能丢失的ACK报文
    • 问题二:lient发送完最后一个ack以后,进入time_wait状态,可是他怎么知道server有没有收到这个ack呢?莫非sever也要等待一段时间,若是收到了这个ack就close,若是没有收到就再发一个fin给client?这么说server最后也有一个time_wait哦?求解答

      • 答:由于网络缘由,主动关闭的一方发送的这个ACK包极可能延迟,从而触发被动链接一方重传FIN包。极端状况下,这一去一回,就是两倍的MSL时长。若是主动关闭的一方跳过TIME_WAIT直接进入CLOSED,或者在TIME_WAIT停留的时长不足两倍的MSL,那么当被动

        关闭的一方早先发出的延迟包到达后,就可能出现相似下面的问题:1.旧的TCP链接已经不存在了,系统此时只能返回RST包2.新的TCP链接被创建起来了,延迟包可能干扰新的链接,这就是为何time_wait须要等待2MSL时长的缘由。

    • 问题三:为何要四次挥手?

      • 答:确保数据可以完整传输。

        当被动方收到主动方的FIN报文通知时,它仅仅表示主动方没有数据再发送给被动方了。但未必被动方全部的数据都完整的发送给了主动方,因此被动方不会立刻关闭SOCKET,它可能还须要发送一些数据给主动方后,再发送FIN报文给主动方,告诉主动方赞成关闭链接,因此这里的ACK报文和FIN报文多数状况下都是分开发送的

  •  

 
 
 
xxxxxxxxxx
 
 
 
 
总结:
TCP协议:
   一、 面向链接\可靠\慢\对传递的数据的长短没有要求
   二、 两台机器之间要想传递信息必须先创建链接
   三、 以后在有了链接的基础上,进行信息的传递
   四、可靠 : 数据不会丢失 不会重复被接收
   五、慢 : 每一次发送的数据还要等待结果
   六、 三次握手和四次挥手
UDP协议:
    一、无链接\不可靠\快\不能传输过长的数据0
    二、机器之间传递信息不须要创建链接 直接发就行
    三、不可靠 : 数据有可能丢失
    
五层协议:
一、应用层  python   send(b'hello,world')
   # socket(虚拟,对下面的进行了整合,直接使用)
二、传输层      端口 tcp/udp协议     四层路由器  四层交换机
三、网络层      ip地址相关 ip协议      路由器    三层交换机
四、数据链路层   mac地址相关 arp协议   网卡      二层交换机
五、物理层       网线
 

2、套接字的使用

  • 基于TCP协议的socket使用:

     
     
     
    x
     
     
     
     
    #基础服务端 
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
    sk.listen()               #监听连接
    conn,addr = sk.accept()   #接受客户端连接
    ret = conn.recv(1024)     #接收客户端信息
    print(ret)               #打印客户端信息,须要时要解码decode
    conn.send(b'hi')         #向客户端发送信息,编码必要时
    conn.close()             #关闭客户端套接字,不是closed
    sk.close()               #关闭服务器套接字(可选)
    #基础客服端
    import socket
    sk = socket.socket()           # 建立客户套接字
    sk.connect(('127.0.0.1',8898))    # 尝试链接服务器,0,0,0,1是全网段,全部来防本机的均可以
    sk.send(b'hello!')
    ret = sk.recv(1024)         # 对话(发送/接收)
    print(ret)
    sk.close()                   # 关闭客户套接字
    #注意,在服务端启动时,若是遇到,Address alreadly in use,则须要在bind前加sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 
    #TCP的聊天版本服务端:可多链接,可是必须一对一回复
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',9001))
    sk.listen()
    while True:
        conn,addr = sk.accept()   # 等待用户来链接我
        while  True:
            msg = input('>>>')
            conn.send(msg.encode('utf-8'))
            if msg.upper() == 'Q':
                break
            content = conn.recv(1024).decode('utf-8')  # 等待 客户端给我φ消息
            if content.upper() == 'Q': break
            print(content)
        conn.close()
    sk.close()
    #TCP客户端:
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1',9001))
    while True:
        ret = sk.recv(1024).decode('utf-8')
        if ret.upper() == 'Q':break
        print(ret)
        msg = input('>>>')
        sk.send(msg.encode('utf-8'))
        if msg.upper() == 'Q':
            break
    sk.close()
     
  • 基于UDP协议的socket的使用:

     
     
     
    xxxxxxxxxx
     
     
     
     
    #基础服务端
    import socket
    udp_sk= socket.socket(type=socket.SOCK_DGRAM)  #建立一个服务器的套接字
    udp_sk.bind(('127.0.0.1',9000))        #绑定服务器套接字
    msg,addr = udp_sk.recvfrom(1024)    # 若是有from则须要addr接收地址
    print(msg)
    udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)
    udp_sk.close() 
    #基础客服端
    import socket
    ip_port=('127.0.0.1',9000)
    udp_sk=socket.socket(type=socket.SOCK_DGRAM)
    udp_sk.sendto(b'hello',ip_port)
    back_msg,addr=udp_sk.recvfrom(1024)
    print(back_msg.decode('utf-8'),addr)
    #进阶服务端
    import socket
    sk = socket.socket(type=socket.SOCK_DGRAM)
    sk.bind(('127.0.0.1',9001))
    while True:
        msg,client_addr = sk.recvfrom(1024)
        print(msg.decode('utf-8'))
        content = input('>>>')
        sk.sendto(content.encode('utf-8'),client_addr)
    sk.close()
    #进阶客户端
    import socket
    sk = socket.socket(type=socket.SOCK_DGRAM)
    server_addr = ('127.0.0.1',9001)
    while True:
        content = input('>>>')
        if content.upper() == 'Q':break
        sk.sendto(content.encode('utf-8'),server_addr)
        msg = sk.recv(1024).decode('utf-8')
        if msg.upper() == 'Q':break
        print(msg)
    sk.close()
    #高级QQ服务端
    import socket
    ip_port=('127.0.0.1',8081)
    udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    udp_server_sock.bind(ip_port)
    while True:
        qq_msg,addr=udp_server_sock.recvfrom(1024)
        print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
        back_msg=input('回复消息: ').strip()
        udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
        
    #高级QQ服务端
    import socket
    BUFSIZE=1024
    udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    qq_name_dic={
        '金老板':('127.0.0.1',8081),
        '哪吒':('127.0.0.1',8081),
        'egg':('127.0.0.1',8081),
        'yuan':('127.0.0.1',8081),
    }
    while True:
        qq_name=input('请选择聊天对象: ').strip()
        while True:
            msg=input('请输入消息,回车发送,输入q结束和他的聊天: ').strip()
            if msg == 'q':break
            if not msg or not qq_name or qq_name not in qq_name_dic:continue
            udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])
            back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)
            print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))
    udp_client_socket.close()
     

     

3、socket的相关方法:

 
 
 
xxxxxxxxxx
 
 
 
 
服务端套接字函数
s.bind()    绑定(主机,端口号)到套接字
s.listen()  开始TCP监听
s.accept()  被动接受TCP客户的链接,(阻塞式)等待链接的到来
客户端套接字函数
s.connect()     主动初始化TCP服务器链接
s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv()            接收TCP数据
s.send()            发送TCP数据
s.sendall()         发送TCP数据,发送一个字符串套接字,知道全部都发完,递归版的send
s.recvfrom()        接收UDP数据
s.sendto()          发送UDP数据
s.getpeername()     链接到当前套接字的远端的地址
s.getsockname()     当前套接字的地址
s.getsockopt()      返回指定套接字的参数
s.setsockopt()      设置指定套接字的参数
s.close()           关闭套接字
面向锁的套接字方法
s.setblocking()     设置套接字的阻塞与非阻塞模式
s.settimeout()      设置阻塞套接字操做的超时时间
s.gettimeout()      获得阻塞套接字操做的超时时间
面向文件的套接字的函数
s.fileno()          返回套接字的文件描述符
s.makefile()        建立一个与该套接字相关的文件
相关文章
相关标签/搜索