zoukankan      html  css  js  c++  java
  • day 28

    什么是socket

    socket 是应用层与TCP/IP协议族通讯的中间软件抽象层,是一组接口,本质上就是封装了,一大堆网络协议的模块。调用就是满足协议要求的。

    socket在OSI模型中的位置

    为什么需要socket

    我们使用socket的原因就是为了使用它里面已经封装好的各种网络协议,使用socket后就不用自己就不用去编写代码来实现三次握手,四次挥手,以及里面一大堆的标准协议,都不用再去刻意的去注意了。只要遵循socket的规定去编程,一定就是遵循tcp/udp标准的。

    socket的发展

    套接字起源于20世纪70年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的BSD Unix。因此也叫伯克利套字,BSD套接字。一开始,套接字被设计用在同一台应用程序之间的通讯。套接字分为两种:基于文件型和基于网络型。

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

    AF-UNIX

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

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

    AF-INET

    在python中大部分通讯都是网络通讯,所以大部分时候使用 AF-INET。

    python中的socket

    在所有的编程语言中socket和网络协议相关概念都是一样的,仅仅是在不同的编程语言中所用到

    的函数名称不一样而已。

    # 1.导入socket模块
    import socket
    # 2.创建socket对象 函数定义如下
    socket.socket(socket_family,socket_type,protocal=0)
        #socket_family 可以是 AF_UNIX 或 AF_INET。
        #socket_type 可以是 SOCK_STREAM表示TCP协议 或 SOCK_DGRAM表示UDP协议。
        #protocol 一般不填,默认值为 0。
        
    # 2.1获取TCP 套接字
    tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 或者 后面的参数都有默认值,可以不写,默认创建的是TCP协议socket
    tcpSock = socket.socket()
    
    # 2.2获取udp/ip套接字
    udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    #由于 socket 模块中有太多的属性。可以使用'from module import *'语句。使用 'from socket import *',把 socket 模块里的所有属性都导入当前命名空间里了,这样能大幅减短代码。
    #例如:tcpSock = socket(AF_INET, SOCK_STREAM)

    不管是服务器还是客户端使用的都是socket对象。

    基于TCP的socket

    socket 是已经被封装好的接口模块,我们只要根据通讯的流程来编写代码即可。

    TCP通讯流程

    在图中可以看出,划分为两块区域,服务器和客户端。

    首先我们要跟着流程创建一个通讯设备,第二部获取ip地址和端口号,监听设备,然后等待

    客户端的连接请求发送过来,到请求发送过来之前不会向下执行,accept 起阻塞的作用,当有客户端给服务器发起请求之后,才会进行对信息进行读取,回应数据,并与客户端进行数据数据之间交互。当客户端与服务器完成四次挥手之后正常结束进程。

    tcp 的通讯流程与接打电话的过程十分相似,当只有接收到电话请求时才会进行通话。

    拥有手机 == socket()
    装手机卡 == 获得自己的ip和端口号 bind()
    打开手机允许别人给我打电话,规定一次接一个电话  == listen()

    电话来了接听电话 == accept()
    开始对话 == send()发出信息,recv()接收信息

    TCP服务端

    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('接到来自%s的电话' %addr[0])
    
    msg=conn.recv(BUFSIZE)             #听消息,听话
    print(msg,type(msg))
    
    conn.send(msg.upper())          #发消息,说话
    
    conn.close()                    #挂电话
    
    s.close()                       #手机关机
    
    服务端

    TCP客户端

    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()                                       #挂电话

    在tcp中必须先启动服务器后再启动客户端,否则客户端由于无法连接到服务器,会直接报错。

    在上边的代码中只是实现了,单次信息的交互,在实际生活中,信息的交互并不是一次性的,所以要给信息交互的代码加上循环。

    改进版服务器端

    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)             #听消息,听话
            print(msg,type(msg))
            conn.send(msg.upper())          #发消息,说话
        conn.close()                    #挂电话
    s.close()                       #手机关机

    改进版客户端

    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()                                       #挂电话

    常见错误

    端口占用

    在调试过程中,可能会遇见以下错误:

    问题发生原因:

    1.可能是由于你已经启动了服务器程序,却又再次启动了服务器程序,同一个端口不能被多个进程使用导致!

    2.三次握手或四次挥手时,发生了异常导致对方程序已经结束而服务器任然处于time_wait状态导致!

    3.在高并发的场景下,由于链接的客户端太多,也会产生大量处于time_wait状态的链接

    解决的方案:

    第1种原因,很简单关闭之前运行的服务器即可

    第2,3中原因导致的问题,有两种解决方案:

    2.强行关闭链接

    (发生错误演示,运行上面的改进版的服务器与客户端,链接成功后直接停止客户端程序)

    当客服端与服务器链接成功后,如果一方没有执行close,而是直接强行终止程序(或是遇到异常被迫终止),都会导致另一方发送问题,

    在windows下,接收数据的一方在recv函数处将抛出异常

    #### 2.强行关闭链接
    
    (发生错误演示,运行上面的改进版的服务器与客户端,链接成功后直接停止客户端程序)
    
    当客服端与服务器链接成功后,如果一方没有执行close,而是直接强行终止程序(或是遇到异常被迫终止),都会导致另一方发送问题,
    
    在windows下,接收数据的一方在recv函数处将抛出异常
    
    ```python
    Traceback (most recent call last):
      File "C:/Users/jerry/PycharmProjects/untitled/TCP/server.py", line 9, in <module>
        conn.recv(1024)
    ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。
    ```
    
    linux下,不会抛出异常会导致接收数据的一方,recv方法不断的收到空消息,造成死循环
    
    要使应用程序能够在不同平台正常工作,那需要分别处理这两个问题
    
    解决方案如下:
    
    ```python
    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()           
        while True:                         
            try:
                msg=conn.recv(BUFSIZE)             
                #linux不会抛出异常,会接收到空消息,这里加以判断
                if not msg:
                    conn.close()
                    break
                print(msg,type(msg))
                conn.send(msg.upper())        
           except ConnectionResetError:
                #只要异常发生则意味着对方以及关闭了,服务器也相应的关闭该链接
                conn.close()
                break
        conn.close()              
    s.close()                       
    ```
    
    至此TCP通讯模板程序就完成了,可以不断的接收新的链接,不断的收发消息,并且不会因为客户端强制关闭而异常退出!

    linux下,不会抛出异常会导致接收数据的一方,recv方法不断的收到空消息,造成死循环

    要使应用程序能够在不同平台正常工作,那需要分别处理这两个问题

    解决方案如下:

    #### 2.强行关闭链接
    
    (发生错误演示,运行上面的改进版的服务器与客户端,链接成功后直接停止客户端程序)
    
    当客服端与服务器链接成功后,如果一方没有执行close,而是直接强行终止程序(或是遇到异常被迫终止),都会导致另一方发送问题,
    
    在windows下,接收数据的一方在recv函数处将抛出异常
    
    ```python
    Traceback (most recent call last):
      File "C:/Users/jerry/PycharmProjects/untitled/TCP/server.py", line 9, in <module>
        conn.recv(1024)
    ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。
    ```
    
    linux下,不会抛出异常会导致接收数据的一方,recv方法不断的收到空消息,造成死循环
    
    要使应用程序能够在不同平台正常工作,那需要分别处理这两个问题
    
    解决方案如下:
    
    ```python
    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()           
        while True:                         
            try:
                msg=conn.recv(BUFSIZE)             
                #linux不会抛出异常,会接收到空消息,这里加以判断
                if not msg:
                    conn.close()
                    break
                print(msg,type(msg))
                conn.send(msg.upper())        
           except ConnectionResetError:
                #只要异常发生则意味着对方以及关闭了,服务器也相应的关闭该链接
                conn.close()
                break
        conn.close()              
    s.close()                       
    ```
    
    至此TCP通讯模板程序就完成了,可以不断的接收新的链接,不断的收发消息,并且不会因为客户端强制关闭而异常退出!

    至此TCP通讯模板程序就完成了,可以不断的接收新的链接,不断的收发消息,并且不会因为客户端强制关闭而异常退出!

  • 相关阅读:
    Debug相关的一些小技巧
    <Information Storage and Management> 读书笔记 之二
    <<Information Storage and Management>>读书笔记 之三
    LINQ to SQL语句(2)之Select/Distinct【转】
    Asp.Net MVC实践 探索UrlRouting并分析UrlHelper (基于ASP.NET MVC Preview 3) 【转】
    MVC学习之分页 【转】
    在 ASP.NET MVC 项目中使用 WebForm 【转】
    Asp.net Mvc Codeplex Preview 5 第三篇 实现Action参数传递繁杂类型 【转】
    jQuery入门[1]-构造函数 【转】
    LINQ to SQL语句(1)之Where【转】
  • 原文地址:https://www.cnblogs.com/1624413646hxy/p/10939796.html
Copyright © 2011-2022 走看看