day 08

 

day08

socket 网络编程初级

先看看这个图片

这个图片是socket 的简单演示
socket连接演示

什么是socket
    socket 起源于linux,Linux中的进程间的通信就是靠socket 来实现的.
    在Linux中,一切皆文件,也就是说我所有的东西都可以通过文件来实现,
    网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。
    Socket也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。

如果还是想研究看这个连接What Is a Socket?

socket 如何通信(网络中的)
    首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!
    在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。
    其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。
    这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中需要互相通信的进程,
    就可以利用这个标志在他们之间进行交互。请看下面这个TCP/IP协议结构图

结构图

    从这个图来看,当我们写有关网络程序的时候,我们可以通过socket来做,这样一些TCP/IP协议
    之类我们可以暂且不用管他.
    只需关心socket就好
socket的家族:
    socket.AF_UNIX unix本机进程间通信
    socket.AF_INET IPV4 
    socket.AF_INET6  IPV6
Socket Types
    socket.SOCK_STREAM  #for tcp
    socket.SOCK_DGRAM   #for udp

下面两个不常用:

    socket.SOCK_RAW   #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;
    其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
    socket.SOCK_RDM  #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。
    SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
开始一个简单的socket
        #!/usr/bin/env python
        #coding:utf-8
        '''这个是服务端'''
        from socket import *   #注意导入包的方式
        phone = socket(AF_INET,SOCK_STREAM)
        phone.bind(('127.0.0.1',8080))
        phone.listen(5)  #制定backlog 的值
        print('starting....')  #这样就开始接客了..
        conn,addr=phone.accept()
        #打印一下,这两个东西是什么
        print('连接号码',conn)
        print('client addr',addr)
        print('ready to read msg' )
        client_msg=conn.recv(1024) #收消息  注意此时的1024
        print('client msg: %s' %client_msg)
        conn.send((str(client_msg)+'我是土豆,我是土豆').encode('utf-8')) #发消息
        conn.close()  #关闭这个连接
        phone.close() #关闭这个服务


        #!/usr/bin/env python
        #coding:utf-8
        '''这个是客户端'''
        from socket import *   #注意导入包的方式
        phone = socket(AF_INET,SOCK_STREAM)
        phone.connect(('127.0.0.1',8080))
        phone.send('hello'.encode('utf-8'))
        back_msg=phone.recv(1024).decode('utf-8')
        print('返回的消息: '+back_msg)
        phone.close()
  • 上面程序很简单,所以暴露出的问题也很多:

    1. 当我重复开启端口的时候,突然报了这个问题.说是端口被占用,但是我的程序明明是终止了啊..
      有可能出现这种情况
      这种情况就是你的tcp链接还在,可以修改内核参数啊(linux)或者添加下面一句话
      phone.setsockopt(SQL_SOCKET,SO_REUSEADDR,1)

    2. 当我们发送一个空消息的时候,server 端就会不停的等待,一直等待. 解决方法,那就判断如果发过来的是空的,那就终端这个链接
      try:#针对windows平台下客户端断开链接
      client_msg=conn.recv(1024) #收消息
      if not client_msg:break #针对linux系统平台下客户端断开链接
      print(‘client msg: %s’ %client_msg)
      conn.send(client_msg.upper()) #发消息
      except Exception:
      break

  • 当我们用上面的程序进行拓展的时候,比如远程执行个命令之类的时候可能会出现 粘包问题

    代码如下:

        from socket import  *
        import   subprocess
        '''这个是服务端'''
    
        phone = socket(AF_INET,SOCK_STREAM)
        phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
        phone.bind(('127.0.0.1',8080))
        phone.listen(5)
    
        while  True:
        print('开始接客......')
        conn,addr=phone.accept()
        print('cliet addr', addr)
        while True:
            try:
                cmd = conn.recv(1024)
                print(cmd.decode('utf-8'))
                if not   cmd:break
                print('开始执行命令')
                res = subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                err = res.stderr.read()
                if err:
                    send_res =err
                else:
                    send_res = res.stdout.read()
                conn.send(send_res)
            except TypeError:   #这个的异常处理不用管它
                break
        conn.close()
        phone.close()
    
        #!/usr/bin/env python   
        #coding:utf-8
        '''这个是客户端'''
        from socket import  *
        import   subprocess
    
        phone = socket(AF_INET,SOCK_STREAM)
        phone.connect(('127.0.0.1',8080))
        while  True:
            msg = input('cmd: ').strip()
            if not msg:continue
            phone.send(msg.encode('utf-8'))
            back_msg = phone.recv(1024)
            print(back_msg.decode('gbk'))  #这个的gbk 编码是因为我在win的平台下,默认是gbk编码
    
        phone.close()
    
        在做测试的时候就输入两遍dir 命令,在输入ipconfig  就会出现粘包现象
    
  • 粘包问题
    可以看这篇文章粘包问题 说的很细致

    主要是怎么去解决这个问题.()如果你是udp 的协议,那你就不用管了)
    如果是tcp的协议:
        1.  自定义时间间隔,也就是说隔一段时间在发
        2.  自定义收取的大小
    但是问题很明显,处理的过程都是很死的.而且效率很差
    
    其实这个问题的根源在于:
        接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕如何让发送端在发送数据前,
        把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据
    
  • 解决粘包问题

        可以为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,
        对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
        在py中,有一个模块 struct 该模块可以把一个类型,如数字,转成固定长度的bytes .
    
        看代码:
    
        #!/usr/bin/env python   
        #coding:utf-8
        '''解决粘包的问题'''
        from socket import  *
        import  json
        import  subprocess
        import  struct
    
        phone = socket(AF_INET,SOCK_STREAM)
        phone.bind(('127.0.0.1',8080))
        phone.listen(5)
    
        while True:
        print('开始接听.....')
        conn,addr = phone.accept()
    
        while True:
            cmd = conn.recv(1024)
            if not cmd:break
            res = subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            err = res.stderr.read()
            if err :
                cmd_res = err
            else:
                cmd_res = res.stdout.read()
            #先制作报头,里面有着各种各样的说明
            head_dic={'filename':None,'hash':None,'total_size':len(cmd_res)}
            #序列化一下
            head_json = json.dumps(head_dic)
            head_bytes = head_json.encode('utf-8')
    
            #先发送抱头的长度
            conn.send(struct.pack('i',len(head_bytes)))
    
            #再发送包头的bytes
            conn.send(head_bytes)
    
            #最后发送真实的数据
            conn.send(cmd_res)
        conn.close()
        phone.close()
    
        #!/usr/bin/env python   
        #coding:utf-8
        from   socket  import  *
        import  json
        import  struct
        client = socket(AF_INET,SOCK_STREAM)
        client.connect(('127.0.0.1',8080))
    
        while  True:
            msg = input('cmd: ').strip()
            if not  msg:continue
            client.send(msg.encode('utf-8'))
    
            #收包头的长度了
            head_struct=client.recv(4)
            head_len=struct.unpack('i',head_struct)[0]
    
            #再接收抱头的bytes
            head_bytes = client.recv(head_len)
            head_json=head_bytes.decode('utf-8')
            head_dic= json.loads(head_json)
    
            #最后根据报头取真实的信息和数据
            print(head_dic)
            total_size=head_dic['total_size']
            recv_size = 0
            data = b''
            while  recv_size <  total_size:
                recv_data = client.recv(1024)
                data += recv_data
                recv_size += len(recv_data)
            print(data.decode('gbk'))
        client.close()
    
    这样就可以完美的解决粘包问题
    
posted @ 2017-06-24 10:44  隔壁家的二狗  阅读(188)  评论(0编辑  收藏  举报