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

    一、客户端/服务端架构:

    1、硬件C/S架构(客户端/服务端)

    二、osi七层模型

    七层: 物理层 数据链路层 网络层 传输层 会话层 表示层 应用层

    引子:计算机系统由硬件,操作系统,应用软件三部分组成

    三、socket是什么

    socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,在设计模式中socket其实是一种门面模式,对于用户来说它就是一组简单的接口。

    有人将socket说成ip+port,ip是用来标识网络中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,

    而程序的pid是同一台机器上不同的进程或者是线程的标识。

    四、套接字的工作流程

    服务端:初始化socket、绑定地址端口(bind)、监听(listen)、调用accept阻塞、等待客户端链接。

    客户端:初始化socket、连接服务器(connect)

    import socket
    socket.socket(socket_famliy, socket_type, protocal=0)
    socket_famliy 可以是AF_UNIX或者是 AF_INET。socket_type可以是 SOCK_STREAM 或 SOCK_DGRAM。protocal 一般不填,默认值是0
    
    获取tcp/ip套接字
    tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    获取udp/ip套接字
    udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    服务端套接字函数
    s.bind()    绑定(主机,端口号)到套接字
    s.listen()  开始TCP监听
    s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来
    
    客户端套接字函数
    s.connect()     主动初始化TCP服务器连接
    s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
    
    公共用途的套接字函数
    s.recv()            接收TCP数据
    s.send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
    s.sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用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()        创建一个与该套接字相关的文件
    套接字函数

    五、基于TCP的套接字

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

    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()            # 关闭客户套接字
    

    加上链接循环和通信循环

    #_*_coding:utf-8_*_
    __author__ = 'mosson'
    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()                       #手机关机
    server
    #_*_coding:utf-8_*_
    __author__ = 'mosson'
    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() 
    client

    问题:

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

    这个是由于你的服务端仍然存在四次挥手的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))
    

    六、基于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() 
    

    七:粘包现象

    让我们给予tcp先制作一个远程执行命令的程序:(1:执行错误命令 2:执行ls 3:执行ifconfig)

    action:::

    res=subprocess.Popen(cmd.decode('utf-8'),
    shell=True,
    stderr=subprocess.PIPE,
    stdout=subprocess.PIPE)
    

    的结果的编码是以当前所在的系统为准

    八:什么事粘包

    Tip:只有tcp才有粘包现象,UDP永远不会粘包。

    发送端可以是一k一k的发送数据,而对应的接收端可以是两k两k的接收数据,当然也有可能一次提走3k或者是6k的数据。应用程序所看到的数据是一个整体,或者说说是一个流stream。

    tcp协议是面向流的数据,UDP协议是面向消息的。

    tcp套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的

    所谓的粘包,主要是因为接收方不知道消息之间的界限,不知道一次性需要提取多少数据,造成的。

    1、TCP(transport control protocal)是面向连接的面向流,提供高可靠性服务,socket是成对的。发送端为了将多个发往服务端的包,更有效发到对方,使用了优化,将多次间隔较小且数据量小的数据,合并成一个大的数据块,这样接收端就难于分辨,必须提供科学的拆包机制

    2、UDP(user datagram protocal)是无链接的面向消息的,提供高效率服务,不会使用合并算法,由于UDP支持一对多的模式,接收端的skbuff采用了链式结构

    3、tcp是基于数据流的,于是收发消息不能为空(为空需要特殊处理),UDP的手法消息可以为空

    两种情况下回发生粘包:

    发送端需要等缓冲区满,才发送出去,造成粘包(发送数据时间间隔短,数据量小,会合到一起,产生粘包)

    接收方不及时接收缓冲区的包,造成多个接受包(客户端发送了一段数据,服务端只接收了一小部分,服务端下次再接收的时候还是从缓冲区拿上次遗留的数据产生粘包)

    九:粘包的解决办法:

    为字节流加上固定长度的报头,报头中包含字节流长度,然后send到对端,对端在接收时,先从缓存中取出定长的报头,然后在根据报头取相应的数据。

    struct模块:

    >>> struct("i", 111111111111111111111)

    struct.error:"i" format requires -2147483648 <= number <= 2147483647 #这个是范围

    struct模块用法讲解:

    import json,struct
    #假设通过客户端上传1T:1073741824000的文件a.txt
    
    #为避免粘包,必须自定制报头
    header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T数据,文件路径和md5值
    
    #为了该报头能传送,需要序列化并且转为bytes
    head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并转成bytes,用于传输
    
    #为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
    head_len_bytes=struct.pack('i',len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度
    
    #客户端开始发送
    conn.send(head_len_bytes) #先发报头的长度,4个bytes
    conn.send(head_bytes) #再发报头的字节格式
    conn.sendall(文件内容) #然后发真实内容的字节格式
    
    #服务端开始接收
    head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式
    x=struct.unpack('i',head_len_bytes)[0] #提取报头的长度
    
    head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式
    header=json.loads(json.dumps(header)) #提取报头
    
    #最后根据报头的内容提取真实的数据,比如
    real_data_len=s.recv(header['file_size'])
    s.recv(real_data_len)
    struct用法
    import socket,struct,json
    import subprocess
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加
    
    phone.bind(('127.0.0.1',8080))
    
    phone.listen(5)
    
    while True:
        conn,addr=phone.accept()
        while True:
            cmd=conn.recv(1024)
            if not cmd:break
            print('cmd: %s' %cmd)
    
            res=subprocess.Popen(cmd.decode('utf-8'),
                                 shell=True,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)
            err=res.stderr.read()
            print(err)
            if err:
                back_msg=err
            else:
                back_msg=res.stdout.read()
    
    
            conn.send(struct.pack('i',len(back_msg))) #先发back_msg的长度
            conn.sendall(back_msg) #在发真实的内容
    
        conn.close()
    
    服务端(自定制报头)
    server自定制报头
    #_*_coding:utf-8_*_
    
    import socket,time,struct
    
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    res=s.connect_ex(('127.0.0.1',8080))
    
    while True:
        msg=input('>>: ').strip()
        if len(msg) == 0:continue
        if msg == 'quit':break
    
        s.send(msg.encode('utf-8'))
    
    
    
        l=s.recv(4)
        x=struct.unpack('i',l)[0]
        print(type(x),x)
        # print(struct.unpack('I',l))
        r_s=0
        data=b''
        while r_s < x:
            r_d=s.recv(1024)
            data+=r_d
            r_s+=len(r_d)
    
        # print(data.decode('utf-8'))
        print(data.decode('gbk')) #windows默认gbk编码
    
    客户端(自定制报头)
    client自定制报头

    FTP的上传下载:

    import socket
    import struct
    import json
    import subprocess
    import os
    
    class MYTCPServer:
        address_family = socket.AF_INET
    
        socket_type = socket.SOCK_STREAM
    
        allow_reuse_address = False
    
        max_packet_size = 8192
    
        coding='utf-8'
    
        request_queue_size = 5
    
        server_dir='file_upload'
    
        def __init__(self, server_address, bind_and_activate=True):
            """Constructor.  May be extended, do not override."""
            self.server_address=server_address
            self.socket = socket.socket(self.address_family,
                                        self.socket_type)
            if bind_and_activate:
                try:
                    self.server_bind()
                    self.server_activate()
                except:
                    self.server_close()
                    raise
    
        def server_bind(self):
            """Called by constructor to bind the socket.
            """
            if self.allow_reuse_address:
                self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind(self.server_address)
            self.server_address = self.socket.getsockname()
    
        def server_activate(self):
            """Called by constructor to activate the server.
            """
            self.socket.listen(self.request_queue_size)
    
        def server_close(self):
            """Called to clean-up the server.
            """
            self.socket.close()
    
        def get_request(self):
            """Get the request and client address from the socket.
            """
            return self.socket.accept()
    
        def close_request(self, request):
            """Called to clean up an individual request."""
            request.close()
    
        def run(self):
            while True:
                self.conn,self.client_addr=self.get_request()
                print('from client ',self.client_addr)
                while True:
                    try:
                        head_struct = self.conn.recv(4)
                        if not head_struct:break
    
                        head_len = struct.unpack('i', head_struct)[0]
                        head_json = self.conn.recv(head_len).decode(self.coding)
                        head_dic = json.loads(head_json)
    
                        print(head_dic)
                        #head_dic={'cmd':'put','filename':'a.txt','filesize':123123}
                        cmd=head_dic['cmd']
                        if hasattr(self,cmd):
                            func=getattr(self,cmd)
                            func(head_dic)
                    except Exception:
                        break
    
        def put(self,args):
            file_path=os.path.normpath(os.path.join(
                self.server_dir,
                args['filename']
            ))
    
            filesize=args['filesize']
            recv_size=0
            print('----->',file_path)
            with open(file_path,'wb') as f:
                while recv_size < filesize:
                    recv_data=self.conn.recv(self.max_packet_size)
                    f.write(recv_data)
                    recv_size+=len(recv_data)
                    print('recvsize:%s filesize:%s' %(recv_size,filesize))
    
    
    tcpserver1=MYTCPServer(('127.0.0.1',8080))
    
    tcpserver1.run()
    
    
    
    
    
    
    #下列代码与本题无关
    class MYUDPServer:
    
        """UDP server class."""
        address_family = socket.AF_INET
    
        socket_type = socket.SOCK_DGRAM
    
        allow_reuse_address = False
    
        max_packet_size = 8192
    
        coding='utf-8'
    
        def get_request(self):
            data, client_addr = self.socket.recvfrom(self.max_packet_size)
            return (data, self.socket), client_addr
    
        def server_activate(self):
            # No need to call listen() for UDP.
            pass
    
        def shutdown_request(self, request):
            # No need to shutdown anything.
            self.close_request(request)
    
        def close_request(self, request):
            # No need to close anything.
            pass
    server
    import socket
    import struct
    import json
    import os
    
    
    
    class MYTCPClient:
        address_family = socket.AF_INET
    
        socket_type = socket.SOCK_STREAM
    
        allow_reuse_address = False
    
        max_packet_size = 8192
    
        coding='utf-8'
    
        request_queue_size = 5
    
        def __init__(self, server_address, connect=True):
            self.server_address=server_address
            self.socket = socket.socket(self.address_family,
                                        self.socket_type)
            if connect:
                try:
                    self.client_connect()
                except:
                    self.client_close()
                    raise
    
        def client_connect(self):
            self.socket.connect(self.server_address)
    
        def client_close(self):
            self.socket.close()
    
        def run(self):
            while True:
                inp=input(">>: ").strip()
                if not inp:continue
                l=inp.split()
                cmd=l[0]
                if hasattr(self,cmd):
                    func=getattr(self,cmd)
                    func(l)
    
    
        def put(self,args):
            cmd=args[0]
            filename=args[1]
            if not os.path.isfile(filename):
                print('file:%s is not exists' %filename)
                return
            else:
                filesize=os.path.getsize(filename)
    
            head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize}
            print(head_dic)
            head_json=json.dumps(head_dic)
            head_json_bytes=bytes(head_json,encoding=self.coding)
    
            head_struct=struct.pack('i',len(head_json_bytes))
            self.socket.send(head_struct)
            self.socket.send(head_json_bytes)
            send_size=0
            with open(filename,'rb') as f:
                for line in f:
                    self.socket.send(line)
                    send_size+=len(line)
                    print(send_size)
                else:
                    print('upload successful')
    
    
    
    
    client=MYTCPClient(('127.0.0.1',8080))
    
    client.run()
    client

    十:认证客户端的链接的合法性

    如果你想在分布式系统中实现一个简单的客户端链接认证功能,又不想ssl那么复杂, 那么利用hmac+盐的方式来实现

    #_*_coding:utf-8_*_
    
    from socket import *
    import hmac,os
    
    secret_key=b'linhaifeng bang bang bang'
    def conn_auth(conn):
        '''
        认证客户端链接
        :param conn:
        :return:
        '''
        print('开始验证新链接的合法性')
        msg=os.urandom(32)
        conn.sendall(msg)
        h=hmac.new(secret_key,msg)
        digest=h.digest()
        respone=conn.recv(len(digest))
        return hmac.compare_digest(respone,digest)
    
    def data_handler(conn,bufsize=1024):
        if not conn_auth(conn):
            print('该链接不合法,关闭')
            conn.close()
            return
        print('链接合法,开始通信')
        while True:
            data=conn.recv(bufsize)
            if not data:break
            conn.sendall(data.upper())
    
    def server_handler(ip_port,bufsize,backlog=5):
        '''
        只处理链接
        :param ip_port:
        :return:
        '''
        tcp_socket_server=socket(AF_INET,SOCK_STREAM)
        tcp_socket_server.bind(ip_port)
        tcp_socket_server.listen(backlog)
        while True:
            conn,addr=tcp_socket_server.accept()
            print('新连接[%s:%s]' %(addr[0],addr[1]))
            data_handler(conn,bufsize)
    
    if __name__ == '__main__':
        ip_port=('127.0.0.1',9999)
        bufsize=1024
        server_handler(ip_port,bufsize)
    
    服务端
    server
    #_*_coding:utf-8_*_
    
    from socket import *
    import hmac,os
    
    secret_key=b'linhaifeng bang bang bang'
    def conn_auth(conn):
        '''
        验证客户端到服务器的链接
        :param conn:
        :return:
        '''
        msg=conn.recv(32)
        h=hmac.new(secret_key,msg)
        digest=h.digest()
        conn.sendall(digest)
    
    def client_handler(ip_port,bufsize=1024):
        tcp_socket_client=socket(AF_INET,SOCK_STREAM)
        tcp_socket_client.connect(ip_port)
    
        conn_auth(tcp_socket_client)
    
        while True:
            data=input('>>: ').strip()
            if not data:continue
            if data == 'quit':break
    
            tcp_socket_client.sendall(data.encode('utf-8'))
            respone=tcp_socket_client.recv(bufsize)
            print(respone.decode('utf-8'))
        tcp_socket_client.close()
    
    if __name__ == '__main__':
        ip_port=('127.0.0.1',9999)
        bufsize=1024
        client_handler(ip_port,bufsize)
    
    客户端(合法)
    client合法
    #_*_coding:utf-8_*_
    
    from socket import *
    
    def client_handler(ip_port,bufsize=1024):
        tcp_socket_client=socket(AF_INET,SOCK_STREAM)
        tcp_socket_client.connect(ip_port)
    
        while True:
            data=input('>>: ').strip()
            if not data:continue
            if data == 'quit':break
    
            tcp_socket_client.sendall(data.encode('utf-8'))
            respone=tcp_socket_client.recv(bufsize)
            print(respone.decode('utf-8'))
        tcp_socket_client.close()
    
    if __name__ == '__main__':
        ip_port=('127.0.0.1',9999)
        bufsize=1024
        client_handler(ip_port,bufsize)
    
    客户端(非法:不知道加密方式)
    client非法,不知道加密方式
    #_*_coding:utf-8_*_
    
    from socket import *
    import hmac,os
    
    secret_key=b'linhaifeng bang bang bang1111'
    def conn_auth(conn):
        '''
        验证客户端到服务器的链接
        :param conn:
        :return:
        '''
        msg=conn.recv(32)
        h=hmac.new(secret_key,msg)
        digest=h.digest()
        conn.sendall(digest)
    
    def client_handler(ip_port,bufsize=1024):
        tcp_socket_client=socket(AF_INET,SOCK_STREAM)
        tcp_socket_client.connect(ip_port)
    
        conn_auth(tcp_socket_client)
    
        while True:
            data=input('>>: ').strip()
            if not data:continue
            if data == 'quit':break
    
            tcp_socket_client.sendall(data.encode('utf-8'))
            respone=tcp_socket_client.recv(bufsize)
            print(respone.decode('utf-8'))
        tcp_socket_client.close()
    
    if __name__ == '__main__':
        ip_port=('127.0.0.1',9999)
        bufsize=1024
        client_handler(ip_port,bufsize)
    
    客户端(非法:不知道secret_key)
    client非法不知道secret_key

    十一:socketserver实现并发。

    基于tcp的套接字,关键就是两个循环,一个是链接循环(server),一个是通信循环(request)

    server类:

    request类:

    继承关系:

    以下述代码为例,分析socketserver源码:

    ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)
    ftpserver.serve_forever()

    查找属性的顺序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer

    1. 实例化得到ftpserver,先找类ThreadingTCPServer的__init__,在TCPServer中找到,进而执行server_bind,server_active
    2. 找ftpserver下的serve_forever,在BaseServer中找到,进而执行self._handle_request_noblock(),该方法同样是在BaseServer中
    3. 执行self._handle_request_noblock()进而执行request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然后执行self.process_request(request, client_address)
    4. 在ThreadingMixIn中找到process_request,开启多线程应对并发,进而执行process_request_thread,执行self.finish_request(request, client_address)
    5. 上述四部分完成了链接循环,本部分开始进入处理通讯部分,在BaseServer中找到finish_request,触发我们自己定义的类的实例化,去找__init__方法,而我们自己定义的类没有该方法,则去它的父类也就是BaseRequestHandler中找....

    源码分析总结:

    基于tcp的socketserver我们自己定义的类中的

    1.   self.server即套接字对象
    2.   self.request即一个链接
    3.   self.client_address即客户端地址

    基于udp的socketserver我们自己定义的类中的

    1.   self.request是一个元组(第一个元素是客户端发来的数据,第二部分是服务端的udp套接字对象),如(b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
    2.   self.client_address即客户端地址
  • 相关阅读:
    任务08——第一次半月学习总结
    任务5
    任务4
    任务3
    任务2
    mission 01
    HTML-CSS-JS Prettify报错Node.js was not found
    **1279
    UVa 10735
    UVa 1515
  • 原文地址:https://www.cnblogs.com/mosson/p/8423492.html
Copyright © 2011-2022 走看看