zoukankan      html  css  js  c++  java
  • 网络编程

    详情参见:https://zhuanlan.zhihu.com/p/110296719

    一、客户端服务端架构

    """
    1.硬件C/S架构(打印机)
    
    2.软件C/S架构
    
    互联网中处处是C/S架构
    
    如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种)
    
    腾讯作为服务端为你提供视频,你得下个腾讯视频客户端才能看它的视频)
    
    C/S架构与socket的关系:
    
    我们学习socket就是为了完成C/S架构的开发
    """

    二、osi七层

    详情参见:http://www.cnblogs.com/linhaifeng/articles/5937962.html

    学习套接字编程的目的是为了开发一个C/S或者B/S架构的软件
    
      client ---------网络----------> server
    
      browser -----网络-----------> server
    
    互联网 = 物理连接介质 + 通信协议
    
    ois七层协议:

     arp协议(网络层)

    """
    地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。
    """
    
    """
    arp协议:ip解析成mac地址
    
    ip+port(端口) 表示全世界范围内独一无二的一个基于网络通信的软件
    """

    arp协议工作流程???

    """
    arp协议由来:计算机通信基本靠吼,即广播的方式,所有上层的包到最后都要封装上以太网头,然后通过以太网协议发送,在谈及以太网协议时候,我门了解到
    
    通信是基于mac的广播方式实现,计算机在发包时,获取自身的mac是容易的,如何获取目标主机的mac,就需要通过arp协议
    
    arp协议功能:广播的方式发送数据包,获取目标主机的mac地址
    """

     

    img

    三、socket层

    四、socket是什么

    """
    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
    在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
    """
    
    所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
    """
    也有人将socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,
    而port是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序 而程序的pid是同一台机器上不同进程或者线程的标识
    """

    五、套接字工作流程

    一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理。

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

    六、基于TCP的套接字

    tcp协议 不可靠传输

    """
    可靠传输,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。
    """

    udp协议 不可靠传输

    """
    不可靠传输,”报头”部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包。
    """

    tcp三次握手和四次挥手(详情图)

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

    三次握手

    """
    第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
    
    第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
    
    第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
    """

    四次挥手

    """
    1)客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),
    此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。 2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。
    TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。
    这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。 3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。 4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,
    此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。 5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。
    注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。 6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
    """

    常见面试题

    """
    【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
    
    答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。
       但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。
       只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
    
    
    
    【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
    
    答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。
        在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。
        所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。
        如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。
        所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,
        那么Client推断ACK已经被成功接收,则结束TCP连接。
    
    
    
    【问题3】为什么不能用两次握手进行连接?
    
    答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
        现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。
        按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。
        可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。
        在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
    
    
    
    【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
    答:TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。
    服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,
    以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
    """

    tcp服务端

    ss = socket() #创建服务器套接字
    ss.bind()      #把地址绑定到套接字
    ss.listen()      #监听链接
    inf_loop:      #服务器无限循环
        cs = ss.accept() #接受客户端链接
        comm_loop:         #通讯循环
            cs.recv()/cs.send() #对话(接收与发送)
        cs.close()    #关闭客户端套接字
    ss.close()        #关闭服务器套接字(可选)

    tcp客户端

    cs = socket()    # 创建客户套接字
    cs.connect()    # 尝试连接服务器
    comm_loop:        # 通讯循环
        cs.send()/cs.recv()    # 对话(发送/接收)
    cs.close()            # 关闭客户套接字

    low版的套接字通信

    服务端

    #_*_coding:utf-8_*_
    __author__ = 'Linhaifeng'
    import socket
    ip_port=('127.0.0.1',9000)  #电话卡
    BUFSIZE=1024                #收发消息的尺寸
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
    s.bind(ip_port) #手机插卡
    s.listen(5)     #手机待机
    
    
    conn,addr=s.accept()            #手机接电话
    # print(conn)
    # print(addr)
    print('接到来自%s的电话' %addr[0])
    
    msg=conn.recv(BUFSIZE)             #听消息,听话
    print(msg,type(msg))
    
    conn.send(msg.upper())          #发消息,说话
    
    conn.close()                    #挂电话
    
    s.close()                       #手机关机

    客户端

    #_*_coding:utf-8_*_
    __author__ = 'Linhaifeng'
    import socket
    ip_port=('127.0.0.1',9000)
    BUFSIZE=1024
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    s.connect_ex(ip_port)           #拨电话
    
    s.send('linhaifeng nb'.encode('utf-8'))         #发消息,说话(只能发送字节类型)
    
    feedback=s.recv(BUFSIZE)                           #收消息,听话
    print(feedback.decode('utf-8'))
    
    s.close()                                       #挂电话

    加上链接循环与通信循环

    服务端改进版

    #_*_coding:utf-8_*_
    __author__ = 'Linhaifeng'
    import socket
    ip_port=('127.0.0.1',8081)#电话卡
    BUFSIZE=1024
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
    s.bind(ip_port) #手机插卡
    s.listen(5)     #手机待机
    
    
    while True:                         #新增接收链接循环,可以不停的接电话
        conn,addr=s.accept()            #手机接电话
        # print(conn)
        # print(addr)
        print('接到来自%s的电话' %addr[0])
        while True:                         #新增通信循环,可以不断的通信,收发消息
            msg=conn.recv(BUFSIZE)             #听消息,听话
    
            # if len(msg) == 0:break        #如果不加,那么正在链接的客户端突然断开,recv便不再阻塞,死循环发生
    
            print(msg,type(msg))
    
            conn.send(msg.upper())          #发消息,说话
    
        conn.close()                    #挂电话
    
    s.close()                       #手机关机

    客户端改进版

    #_*_coding:utf-8_*_
    __author__ = 'Linhaifeng'
    import socket
    ip_port=('127.0.0.1',8081)
    BUFSIZE=1024
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    s.connect_ex(ip_port)           #拨电话
    
    while True:                             #新增通信循环,客户端可以不断发收消息
        msg=input('>>: ').strip()
        if len(msg) == 0:continue
        s.send(msg.encode('utf-8'))         #发消息,说话(只能发送字节类型)
    
        feedback=s.recv(BUFSIZE)                           #收消息,听话
        print(feedback.decode('utf-8'))
    
    s.close()                                       #挂电话

    问题

    有的同学在重启服务端时可能会遇到

     这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址(如果不懂,请深入研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发情况下会有大量的time_wait状态的优化方法)

    解决方案

    方案一

    #加入一条socket配置,重用ip和端口
    
    phone=socket(AF_INET,SOCK_STREAM)
    phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
    phone.bind(('127.0.0.1',8080))

    方案二

    发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,
    vi /etc/sysctl.conf
    
    编辑文件,加入以下内容:
    net.ipv4.tcp_syncookies = 1
    net.ipv4.tcp_tw_reuse = 1
    net.ipv4.tcp_tw_recycle = 1
    net.ipv4.tcp_fin_timeout = 30
    
    然后执行 /sbin/sysctl -p 让参数生效。
    
    net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
    
    net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
    
    net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
    
    net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间

    七、基于UDP的套接字通信

    udp是无链接的,先启动哪一端都不会报错

    udp服务端

    ss = socket()   #创建一个服务器的套接字
    ss.bind()       #绑定服务器套接字
    inf_loop:       #服务器无限循环
        cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
    ss.close()                         # 关闭服务器套接字

    udp客户端

    cs = socket()   # 创建客户套接字
    comm_loop:      # 通讯循环
        cs.sendto()/cs.recvfrom()   # 对话(发送/接收)
    cs.close()                      # 关闭客户套接字

    udp套接字简单示例

    udp服务端

    #_*_coding:utf-8_*_
    __author__ = 'Linhaifeng'
    import socket
    ip_port=('127.0.0.1',9000)
    BUFSIZE=1024
    udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    
    udp_server_client.bind(ip_port)
    
    while True:
        msg,addr=udp_server_client.recvfrom(BUFSIZE)
        print(msg,addr)
    
        udp_server_client.sendto(msg.upper(),addr)

    udp客户端

    #_*_coding:utf-8_*_
    __author__ = 'Linhaifeng'
    import socket
    ip_port=('127.0.0.1',9000)
    BUFSIZE=1024
    udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    
    while True:
        msg=input('>>: ').strip()
        if not msg:continue
    
        udp_server_client.sendto(msg.encode('utf-8'),ip_port)
    
        back_msg,addr=udp_server_client.recvfrom(BUFSIZE)
        print(back_msg.decode('utf-8'),addr)

    qq聊天

    时间服务器

    详情参见:https://zhuanlan.zhihu.com/p/110296719

    八、什么是粘包

    须知

    """
    只有TCP有粘包现象,UDP永远不会粘包
    """

    发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,
    也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。
    而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。
    怎样定义消息呢?
    可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。 例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束 所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。 此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。
    若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
    TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,
    更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。
    即面向流的通信是无消息保护边界的。
    UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,
    所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。
    即面向消息的通信是有消息保护边界的。

    tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,
    而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略
    udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y
    >x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠 tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

    两种情况下会发生粘包

    """
    发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
    
    接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
    """

    拆包发生的情况

    当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。

    补充问题一:为何tcp是可靠传输,udp是不可靠传输

    """
    基于tcp的数据传输请参考另一篇文章http://www.cnblogs.com/linhaifeng/articles/5937962.html,
    tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的 而udp发送数据,对端是不会返回确认信息的,因此不可靠
    """

    补充问题二:send(字节流)和recv(1024)及sendall

    """
    recv里指定的1024意思是从缓存里一次拿出1024个字节的数据
    
    send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失
    """

    九、解决粘包问题

    struct模块

    该模块可以把一个类型,如数字,转成固定长度的bytes

    import struct
    
    res = struct.pack('i',1245632)
    print(res)
    print(len(res))
    
    # b'xc0x01x13x00'
    # 4
    
    res = struct.unpack('i',res)
    print(res)
    # (1245632,)  元组

    解决粘包问题(终极版)

    服务端

    # 服务端应该满足两个特点
    # 1、一致对外提供服务
    # 2、并发的服务多个客户端
    
    import subprocess
    from socket import *
    import struct
    import json
    
    server = socket(AF_INET,SOCK_STREAM)  # #创建服务器套接字
    server.bind(('127.0.0.1',8081))  # #把地址绑定到套接字
    server.listen(5)  #  #监听链接
    
    # 服务端应该做的两件事
    # 第一件事:循环的从半连接池中取出连接请求与其建立双向链接,拿到链接对象
    while True:  #服务器无限循环
        conn,client_addr = server.accept()  # 接受客户端链接
    
        # 第二件事:拿到链接对象,与其进行通信循环
        while True:   #  #通讯循环
            try:
                cmd = conn.recv(1024)
                if not cmd:break
                obj = subprocess.Popen(cmd.decode('utf-8'),
                                 shell= True,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE
                                 )
                stdout_res = obj.stdout.read()  # 正确结果
                stderr_res = obj.stderr.read()  # 错误结果
                totoal_size = len(stdout_res)+len(stderr_res)
    
    
                # 1、制作头
                hander_dic = {
                    'filename':"a.txt",
                    'total_size':totoal_size,
                    'md5':"123456789"
                }
                json_str = json.dumps(hander_dic)
                json_str_bytes = json_str.encode('gbk')
    
    
                # 2、先把头的长度发过去
                x = struct.pack('i',len(json_str_bytes))
                conn.send(x)
    
                # 3、发头信息
                conn.send(json_str_bytes)
    
                # 4、再发真实的数据
                conn.send(stdout_res) # 命令的执行结果
                conn.send(stderr_res) # 命令的执行结果
    
            except Exception:
                break
        conn.close()

    客户端

    # import socket
    from socket import *
    import struct
    import json
    
    client = socket(AF_INET,SOCK_STREAM)
    client.connect(('127.0.0.1',8081))
    
    while True:
        msg = input('请输入命令:').strip()
        if not msg:
            continue
        client.send(msg.encode('utf-8'))
    
        # 收数据
        # 1、先收4个字节,从中提取接下来要收的头的长度
        x = client.recv(4)
        hander_len = struct.unpack('i',x)[0]
        # 2、接收头并解析
        json_str_bytes = client.recv(hander_len)
        json_str = json_str_bytes.decode('gbk')
        hander_dic = json.loads(json_str)
        print(hander_dic)
        total_size = hander_dic['total_size']
    
        # 3、接收真实的数据
        recv_size = 0
        while recv_size < total_size:
            recv_data = client.recv(1024)  # 本次接收,最大接收1024Bytes
            recv_size+=len(recv_data)
            print(recv_data.decode('gbk'),end='') # 强调:windows用gbk
        else:
            print()
    
    # 粘包问题出现的原因:
    # 1、tcp是流式协议,数据像水流一样黏在一起,没有任何边界区分
    # 2、收数据没有收干净,有残留,就会更下一次结果混淆在一起
    # 解决的核心法门就是:每次都收干净,不要有任何残留

    十、socketserver模块

    """
    基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环
    
    socketserver模块中分两大类:server类(解决链接问题)和request类(解决通信问题)
    """

    sockerserver基本使用(tcp)

    服务端

    import socketserver
    class MyRequestHandler(socketserver.BaseRequestHandler):
        def handle(self):
            # 如果是tcp协议,self.requese=>conn
            print(self.client_address)
    
            while True:
                try:
                    cmd = self.request.recv(1024)
                    if not cmd: break
                    self.request.send(cmd.upper())
                except Exception:
                    break
            self.request.close()
    
    # 服务端应该做的两件事
    # 第一件事:循环的从半连接池中取出连接请求与其建立双向链接,拿到链接对象
    s = socketserver.ThreadingTCPServer(('127.0.0.1',8888),MyRequestHandler)
    s.serve_forever()
    # 等同于:
    # while True:
    #     conn,client_addr = server.accept()
    #     启动一个线程(conn,client_addr)
    
    # 第二件事:拿到链接对象,与其进行通信循环--->handle

    客户端

    # import socket
    from socket import *
    
    client = socket(AF_INET,SOCK_STREAM)
    client.connect(('127.0.0.1',8888))
    
    while True:
        msg = input('请输入命令:').strip()
        if not msg:
            continue
        client.send(msg.encode('utf-8'))
    
    
        cmd_res = client.recv(1024)
    
        print(cmd_res.decode('gbk'))

    sockerserver基本使用(udp)

    服务端

    import socketserver
    
    class MyRequestHandle(socketserver.BaseRequestHandler):
        def handle(self):
            client_data = self.request[0]
            server = self.request[1]
            print('客户端发过来的数据%s' %client_data)
            client_address = self.client_address
            server.sendto(client_data.upper(),client_address)
    
    
    s = socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyRequestHandle)
    s.serve_forever()
    # 相当于:只负责循环的收
    # while True:
    #     data,client_addr = server.recvfrom(1024)
    #     启动一个线程处理后续的事情(data,client_addr)

    客户端

    import socket
    
    client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    
    while True:
        msg = input('>>>: ').strip()
        client.sendto(msg.encode('gbk'),('127.0.0.1',8080))
        res = client.recvfrom(1024)
        print(res)
    
    client.close()
  • 相关阅读:
    stm32 IO模式
    stm32的ADC
    bsp
    stm32的硬件调试设置
    RTC实时时钟
    快手2019笔试题 “回文子串" 解题思路
    C++内存修改器开源代码
    FC游戏修改教程(hack)小白文。
    GLFW+GLEW搭建opengl环境(备忘)
    8086 IO读写操作
  • 原文地址:https://www.cnblogs.com/ZhZhang12138/p/14868928.html
Copyright © 2011-2022 走看看