zoukankan      html  css  js  c++  java
  • socket

    客户端/服务器架构

    C/S架构与socket的关系:

    我们学习socket就是为了完成C/S架构的开发

    osi七层:

    应用层:

    功能:提供用户接口

    软件:QQ/微信、浏览器等各种能上网的应用程序

    协议:HTTP、HTTPS、OICQ、Telnet、SSH等

        提供用户接口,特指能够发起网络通信的应用程序。实际上,会话层,表示层、应用层也可以统称为应用层

    问题:在现代软件开发中,如果我们编码还要根据不同编码方式进行代码实现,那么就不断进行重复劳动了。我们可以将表示层再进行包装吗?

    解决方案:增加一层"应用层"。

    表示层:

    功能:担当数据的显示

        使用何种编码方式。比如要传输的数据使用ASCII编码,视频还是二进制文件,是否要加密和压缩。发送端和接收端程序必须使用相同的编码方式,才能正确显示,否则就产生乱码。

    问题:在现代软件开发中,如果我们编码还要根据不同编码方式进行代码实现,那么就不断进行重复劳动了。我们可以将表示层再进行包装吗?

    解决方案:增加一层"应用层"。

    会话层

    功能:担当会话管理。

    在两台电脑间,两个不同的应用程序间的:建立会话,区别于其他应用程序间的会话(如QQ的信息不会发送到浏览器中,使用端口号进行区分),保持会话,删除会话的过程。

    问题:我们两台电脑间的通信,不仅仅是文字的交互。而是有多种数据格式的。那么会话层提供不了这个功能。

    解决方案:增加一层"表示层"

     

    传输层

    功能:担当了可靠的端对端链接。

    协议:TCP、UDP

        提供了可靠或不可靠传输,能够纠正或失败重传,传输层通过端口号区分上层服务,并通过滑动窗口技术实现可靠传输、流量控制、拥塞控制等。传输层负责的是计算机之间的链接。

    问题:尽管传输层提供了可靠的链接,那么当有一个发送方对多个接收方时,我们如何确定数据传输给哪一个接收方呢?又如何与其建立链接、保持链接、删除链接呢?

    解决方案:增加一层"会话层"

    网络层

    功能:提供了三层寻址,三层数据转发功能

    设备:路由器

    协议:IP协议

    路由器主要是根据IP地址来进行不同PC间的通信的。虽然路由器工作再网络层,但它实际上是兼有数据链路层、物理层的。所以在同一路由器下,IP地址、MAC地址不能相同,否则会发生冲突。当然路由器也存在一个IP地址,用于跟别的路由器进行通信,这样就可以屏蔽不同局域网协议间不能通信的问题了。

    问题:仅仅通过路由器简单的发送数据可不行,如果因为网络的问题,导致数据丢失,数据传输不可控。这样就使得网络通信不可靠

    解决方案:在网络层基础上,增加"传输层"

     

    数据链路层

    功能:提供了二层寻址、二层数据转发功能。

    设备:网桥、交换机

    协议:PPP、Ethernet、ARP、RARP..

    数据链路层在不同的厂商有不同的实现,主要应用于没有路由器的情况下。多台电脑相互通信的情况,这种网络称为局域网。同一局域网中,MAC地址不能相同。

     

    问题:不同协议间肯定是不能进行通信的。那么我们该如何使两个局域网之间进行通信呢?

    解决方案:增加一层"网络层"

    物理层

    功能:提供物理规范,如线缆标准,接口标准

                                   互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层                                    

    具体通信原理可看:

    http://www.cnblogs.com/linhaifeng/articles/5937962.html

    使用 socket一定要先学习互联网协议

    C/S架构的软件(软件属于应用层)是基于网络进行通信的,网络的核心即一堆协议,协议即标准,你想开发一款基于网络通信的软件,就必须遵循这些标准。

     

    socket层

    socket是什么

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程

    套接字工作流程

    简单的socket实例:

    服务端

    __author__ = 'zyp'
    #-*- coding:utf-8 -*-
    import socket
    import os
    server = socket.socket()
    server.bind(('localhost',6969))
    server.listen(5)
    while True:
        print("我在等电话")
        conn,addr=server.accept()
        print("电话来了")
        while True:
            data = conn.recv(1024)
            print('recv:',data.decode())
            if not data :
                print("输入为空!")
                break
            res = os.popen(data.decode()).read()
    
            conn.send(res.encode())
    
    server.close()
    服务端

    客户端

    客户端

     socket()模块函数用法

     1 import socket
     2 socket.socket(socket_family,socket_type,protocal=0)
     3 socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。
     4 
     5 获取tcp/ip套接字
     6 tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     7 
     8 获取udp/ip套接字
     9 udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
     10 由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',
    我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。
    11 例如tcpSock = socket(AF_INET, SOCK_STREAM)
    服务端套接字函数
    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服务端

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

    tcp客户端

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

    如果在重启服务端时可能会遇到:地址被占用

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

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

    udp客户端

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

    udp实例

    # !usr/bin/env python
    # -*-coding:utf-8 -*-
    import socket
    server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    server.bind(("localhost",6666))
    while True:
        conn,addr = server.recvfrom(1024)
        print(conn.decode(),addr)
        server.sendto(conn.upper(),addr)
    upd_server
    # !usr/bin/env python
    # -*-coding:utf-8 -*-
    import socket
    client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    while True:
        msg = input(">>:").strip()
        if not msg:continue
        client.sendto(msg.encode("utf-8"),("localhost",6666))
        bak_msg,addr = client.recvfrom(1024)
        print(bak_msg.decode("utf-8"),addr)
    udp_client

    粘包现象

    基于tcp的socket,在运行时会发生粘包

    基于udp的socket,在运行时永远不会发生粘包

    注意注意注意:

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

    的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码

    且只能从管道里读一次结果

    注意:命令ls -l ; lllllll ; pwd 的结果是既有正确stdout结果,又有错误stderr结果

     socket收发消息的原理

    基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

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

    发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

    两种情况下会发生粘包。

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

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

    send(字节流)和recv(1024)及sendall

    recv里指定的1024意思是从缓存里一次拿出1024个字节的数据

    send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失

    解决粘包的方法

    1.在连续发或者收中间再增加发和收。不建议使用

    2.只收取相应文件大小

    3.高级方法 struct模块 

    为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据:http://www.cnblogs.com/linhaifeng/articles/6129246.html#_label12

    需要补充实例

    sockerserver

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

    socketserver模块中分两大类:server类(解决链接问题)和request类(解决通信问题)

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

    基于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即客户端地址

    1.建立一个请求处理类,并继承这个类要继承BaseRequestHandler

    2.重写父类的handle()   #这里处理与客户端的交互

    3.实例化TCPServer,并且传递server ip 和 你创建的请求处理类 给这个TCPServer

    4.   server.handle_request() #只处理一个请求

          server.serve_forever() #处理多个一个请求,永远执行

    server:
    
    import socketserver
    class MyTCPHandler(socketserver.BaseRequestHandler):
        def handle(self):
            while True:
                try:
                    self.data = self.request.recv(1024).strip()
                    print("{} wrote".format(self.client_address[0]))
                    print(self.data)
                    self.request.send(self.data.upper())
                except ConnectionResetError as e:
                    print("error",e)
                    break
    HOST,PORT = "localhost",9999
    selver = socketserver.TCPServer((HOST,PORT),MyTCPHandler)
    selver.serve_forever()
    
    client:
    
    import socket
    client = socket.socket()
    client.connect(("localhost",9999))
    while True:
        data = input(">>:").strip()
        if len(data) == 0:
            continue
        client.send(data.encode())
        res = client.recv(1024).decode()
        print("res",res)
    
    client.close()

    多并发

    selver = socketserver.THreadingTCPServer((HOST,PORT),MyTCPHandler)

  • 相关阅读:
    orleans 的一种模式
    在.net4的环境下使用Microsoft.AspNet.SignalR.Client 2.4.0
    微信卡券领用的附加测试
    SVN忽略本地文件不提交,同时不删除服务器上的文件
    SQL Server 2017安装错误:Polybase要求安装Oracle JRE 7更新51或更高版本的两种解决方法
    SQL Server遍历表(临时表)
    无法确定条件表达式的类型,因为“DateTime”和“<null>”之间没有隐式转换|Nullable类型问题与?:条件运算符
    C# 反射获取对象的内容
    c# 计算执行时间,性能,运行时间Stopwatch
    JS,JQuery循环数组,循环对象生成需要的数据
  • 原文地址:https://www.cnblogs.com/Aline2/p/8782668.html
Copyright © 2011-2022 走看看