zoukankan      html  css  js  c++  java
  • 第二十七天- 网络通信协议 TCP UDP 缓冲区

    1.网络通信协议

      osi七层模型:按照分工不同把互联网协议从逻辑上划分了层级

      socket层

    2.理解socket:

      Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。我们可理解成模块,直接拿来用。

    套接字socket历史:

    套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 

      基于文件类型的套接字家族: 

      套接字家族的名字:AF_UNIX

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

      基于网络类型的套接字家族:

      套接字家族的名字:AF_INET

      AF_INET6被用于ipv6,还有一些其他的地址家族,不过,基本没用,所有地址家族中,AF_INET是使用最广泛的一 个 ,python支持多种地址家族,不过我们主要用网络编程,所以主要还是AF_INET

    3.基于TCP和UDP两个协议下socket的通讯

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

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

    tcp协议下的socket:

      服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束

    注意:tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

    基本代码:

    server端

     1 import socket
     2 sk = socket.socket()
     3 sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
     4 sk.listen()          #监听链接
     5 conn,addr = sk.accept() #接受客户端链接
     6 ret = conn.recv(1024)  #接收客户端信息
     7 print(ret)       #打印客户端信息
     8 conn.send(b'hi')        #向客户端发送信息
     9 conn.close()       #关闭客户端套接字
    10 sk.close()        #关闭服务器套接字(可选)

    client端

    1 import socket
    2 sk = socket.socket()           # 创建客户套接字
    3 sk.connect(('127.0.0.1',8898))    # 尝试连接服务器
    4 sk.send(b'hello!')
    5 ret = sk.recv(1024)         # 对话(发送/接收)
    6 print(ret)
    7 sk.close()            # 关闭客户套接字

    相关bug:

    1.socket绑定IP和端口时可能出现下面的问题:不让重复使用端口

     1 #加入一条socket配置,重用ip和端口
     2 import socket
     3 from socket import SOL_SOCKET,SO_REUSEADDR
     4 sk = socket.socket()
     5 sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #在bind前加,允许地址重用
     6 sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
     7 sk.listen()          #监听链接
     8 conn,addr = sk.accept() #接受客户端链接
     9 ret = conn.recv(1024)   #接收客户端信息
    10 print(ret)              #打印客户端信息
    11 conn.send(b'hi')        #向客户端发送信息
    12 conn.close()       #关闭客户端套接字
    13 sk.close()        #关闭服务器套接字(可选)
    View 解决办法 Code

    若任然报错,出现 OSError: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试那么只能换端口了,因为你的电脑不支持端口重用。

    2.远程主机强迫关闭了一个先有连接

    这是由于强制断开造成的,解决很简单,谁依赖于谁,先关掉依赖者,再关闭被依赖者就好;还有一种是和多个连接造成,tcp协议下最好一对一,一对多可见下面代码。

     1 import socket
     2 
     3 server = socket.socket()
     4 ip_port = ('127.0.0.1',8081)
     5 server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  # 固定写法,允许地址重用,若还报错OSerror,系统原因,改端口
     6 
     7 server.bind(ip_port)
     8 server.listen()  # 监听 可跟参数 n 代表监听n+1次 如,listen(3),意思是我连接着一个,后面还有3排队,共4个.
     9 
    10 while 1:
    11     conn,addr = server.accept()  # 阻塞等待连接
    12     while 1:
    13         server_msg = input('>>>>> ')
    14         server_msg = server_msg.encode('utf-8')
    15         conn.send(server_msg)  # 发消息
    16         if server_msg == 'byebye':  # 多个客户端连接时,结束前一个后,跳出当前循环到上一个while,重新获得连接
    17             break
    18 
    19         from_client_msg = conn.recv(1024)  # 接消息
    20         from_client_msg = from_client_msg.decode('utf-8')
    21         if from_client_msg == 'byebye':
    22             break
    23         print('来自客户端的消息:',from_client_msg)
    24     conn.close()
    View 一对多_服务端 Code
     1 import socket
     2 
     3 client = socket.socket()
     4 server_ip = ('127.0.0.1',8081)
     5 client.connect(server_ip)
     6 
     7 while 1:
     8     from_server_msg = client.recv(1024)
     9     from_server_msg = from_server_msg.decode('utf-8')
    10     print('来自服务器>>>',from_server_msg)
    11     if from_server_msg == 'byebye':
    12         break
    13 
    14     client_msg = input('>>>>> ')
    15     client_msg = client_msg.encode('utf-8')
    16     client.send(client_msg)
    17     if client_msg == 'byebye':
    18         break
    19 
    20 client.close()
    View 一对多_客户端 Code
     1 # 再来一份即可
     2 import socket
     3 
     4 client = socket.socket()
     5 server_ip = ('127.0.0.1',8081)
     6 client.connect(server_ip)
     7 
     8 while 1:
     9     from_server_msg = client.recv(1024)
    10     from_server_msg = from_server_msg.decode('utf-8')
    11     print('来自服务器>>>',from_server_msg)
    12     if from_server_msg == 'byebye':
    13         break
    14 
    15     client_msg = input('>>>>> ')
    16     client_msg = client_msg.encode('utf-8')
    17     client.send(client_msg)
    18     if client_msg == 'byebye':
    19         break
    20 
    21 client.close()
    View 一对多_客户端01 Code

    总结:用socket进行通信,必须是一收一发对应好。

    udp协议下的socket

      服务器端先初始化Socket,然后与端口绑定(bind),recvform接收消息,这个消息有两项,消息内容和对方客户端的地址,然后回复消息时也要带着你收到的这个客户端的地址,发送回去,最后关闭连接,一次交互结束。

    注意:udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接,但在发消息时要跟上地址。

    基本 代码:

    server端

    1 import socket
    2 udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #创建一个服务器的套接字
    3 udp_sk.bind(('127.0.0.1',9000))        #绑定服务器套接字
    4 msg,addr = udp_sk.recvfrom(1024)
    5 print(msg)
    6 udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)
    7 udp_sk.close()                         # 关闭服务器套接字

    client端:

    1 import socket
    2 ip_port=('127.0.0.1',9000)
    3 udp_sk=socket.socket(type=socket.SOCK_DGRAM)
    4 udp_sk.sendto(b'hello',ip_port)
    5 back_msg,addr=udp_sk.recvfrom(1024)
    6 print(back_msg.decode('utf-8'),addr)

    4.练习代码:

    #  用户登录作业用tcp协议下的socket写:
    # 1. 服务端
    # - 等待客户端来发送数据:用户名、密码
    # - 本地文件中查看用户名密码是否合法。
    # - 合法:登录成功
    # - 否则:用户名或密码错误

    # 2. 客户端
    # - 用户输入:用户名、密码
    # - 发送到服务端进行校验。
     1 import socket
     2 import time
     3 
     4 server = socket.socket()
     5 ip_port = ('127.0.0.1',8083)
     6 server.bind(ip_port)
     7 server.listen()
     8 conn,addr = server.accept()  # 等待conn
     9 
    10 dic = {'张三':'123','赵四':'345','王八':'567'}
    11 
    12 client_msg = conn.recv(1024)
    13 client_msg = client_msg.decode('utf-8')  # 还原成字典
    14 client_msg = eval(client_msg)
    15 print(client_msg)
    16 time.sleep(5)
    17 
    18 for k in dic:
    19     if {k:dic[k]} == client_msg:
    20         conn.send('登录成功!'.encode('utf-8'))
    21     else:
    22         conn.send('用户名或密码错误!'.encode('utf-8'))
    View 服务端 Code
     1 import socket
     2 
     3 client = socket.socket()
     4 server_ip = ('127.0.0.1',8083)
     5 client.connect(server_ip)
     6 
     7 k = input('请输入账户:')
     8 v = input('请输入密码:')
     9 # 存入字典,发送给服务端
    10 msg = {k:v}
    11 client.send(str(msg).encode('utf-8'))
    12 
    13 from_server_msg = client.recv(1024)
    14 print(from_server_msg.decode('utf-8'))
    View 客户端 Code
    # udp协议下的socket聊天工具(类10086)
    # 1. 服务端
    # - 接收客户端发送的信息并作出回复。
    # - 检查是否有某些指定关键字并回复消息,如果发送过来的消息中还有sb字符串,那么将sb替换成alexsb,然后和你要输入的内容组合起来发送给客户端。
    # 2. 多个客户端
    # - 客户端向服务端发送信息
     1 import socket
     2 
     3 talk_server = socket.socket(type=socket.SOCK_DGRAM)
     4 ip_port = ('127.0.0.1',8086)
     5 talk_server.bind(ip_port)
     6 
     7 while 1:
     8     from_client_msg,addr = talk_server.recvfrom(1024)
     9     from_client_msg = from_client_msg.decode('utf-8')
    10     print('来自客户端>>>',from_client_msg)
    11     if from_client_msg == 'byebye':
    12         break
    13 
    14     msg = input('>>> ')
    15     if 'sb'in from_client_msg:
    16         msg2 = from_client_msg.replace('sb', 'alexsb')
    17         talk_server.sendto((msg+msg2).encode('utf-8'),addr)
    18     else:
    19         talk_server.sendto(msg.encode('utf-8'), addr)
    20 
    21 talk_server.close()
    View 服务端 Code
     1 # udp下复制多个以下代码即可实现多客户端 
     2 
     3 import socket
     4 
     5 talk_client = socket.socket(type=socket.SOCK_DGRAM)
     6 server_ip_port = ('127.0.0.1',8086)
     7 
     8 while 1:
     9     msg = input('>>>')
    10     if msg == 'byebye':
    11         break
    12     msg = msg.encode('utf-8')
    13     talk_client.sendto(msg,server_ip_port)
    14 
    15     from_server_msg,addr = talk_client.recvfrom(1024)
    16     print('来自服务端>>>',from_server_msg.decode('utf-8'))
    17 
    18 talk_client.close()
    View 客户端 Code

    5.缓冲区:

    # 缓冲区: socket对象 在接收和发送数据时都是先放到缓冲区,再到目标地址的,这样可避免网络延迟、数据丢包等.

    socket缓冲区解释:

    每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。

    write()/send() 并不立即向网络传数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。

    TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。

    read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。



      

  • 相关阅读:
    hdu 3342 Legal or Not 拓排序
    hdu 1596 find the safest road Dijkstra
    hdu 1874 畅通工程续 Dijkstra
    poj 2676 sudoku dfs
    poj 2251 BFS
    poj Prime Path BFS
    poj 3278 BFS
    poj 2387 Dijkstra 模板
    poj 3083 DFS 和BFS
    poj 1062 昂贵的聘礼 dijkstra
  • 原文地址:https://www.cnblogs.com/xi1419/p/10005240.html
Copyright © 2011-2022 走看看