• Python socket


    Python的网络编程主要支持两种网络协议:TCP和UDP。这两种协议都通过叫Socket的编程抽象进行处理。Socket起源于Unix,是类似于文件的存在,可以像文件一样进行I/O、打开、关闭等操作,最主要的是它可以实现网络上不同主机的进程间通信,所以基本上Socket是任何一种网络通讯中最基础的内容。

    Python中建立一个套接字很简单:

    import socket
    s = socket.socket(family, type)
    

    地址族

    family为地址族,该族指定要使用的网络协议,主要使用的有:

    • AF_INET:IPv4协议(TCP,UDP)
    • AF_INET6:IPv6协议(TCP,UDP)
    • AF_UNIX:UNIX域协议,用于同一台机器的进程间通讯

    套接字类型

    type为套接字类型,指定给定的协议组中使用的通信类型:

    • SOCK_STREAM:用于TCP
    • SOCK_DGRAM:用于UDP

    TCP和UDP都是基于Client/Server的编程模型,所以Socket编程也分为客户端和服务器端,以TCP为例:

    TCP客户端编程

    要获取远程主机的ip地址,可以使用socket标准库提供的gethostbyname()方法:

    >>> import socket
    >>> socket.gethostbyname('www.baidu.com')
    '115.239.211.112'
    

    socket套接字实例s可用于客户端的方法有以下几个:

    • s.connect(addr):连接服务器端套接字。addr格式取决于地址族,对于IPv4来说,是一个包含ip地址与端口的元组,(host, port)。连接失败会报socket.error错误。
    • s.sendall(string):尝试发送所有数据,成功则返回None,失败则报异常。
    • s.recv(bufsize):接收数据,bufsize指定接收的最大数据量。
    • s.close():关闭套接字

    OK,现在可以用socket向远程主机发送一个HTTP GET请求了:

    # -*- coding: utf-8 -*-
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #建立套接字
    host = 'www.baidu.com'
    port = 80
    ip = socket.gethostbyname(host)  #获取ip
    s.connect((ip, port))  #建立连接
    message = 'GET / HTTP/1.1
    
    '
    s.sendall(message)  #发送GET请求
    r = s.recv(4096)    #接收数据
    print r
    s.close()    #关闭套接字
    

    返回:

    HTTP/1.1 302 Moved Temporarily
    Date: Wed, 10 Jan 2018 18:56:45 GMT
    Content-Type: text/html
    Content-Length: 225
    Connection: Keep-Alive
    Location: http://www.baidu.com/search/error.html
    Server: BWS/1.1
    X-UA-Compatible: IE=Edge,chrome=1
    BDPAGETYPE: 3
    Set-Cookie: BDSVRTM=0; path=/
    

    下面我们可以实现自己的服务器。

    TCP服务器端编程

    Socket实例与服务器端编程有关的方法有以下几个:

    • s.bind(addr):addr也是(host, port)形式的元组,将套接字绑定到特定的地址和端口上。空字符串表示任意地址,'broadcast'可以用做发送广播信息。
    • s.listen(backlog):开始监听连接,backlog为最大挂起连接次数。
    • s.accept:返回元组(conn,addr),conn为新的套接字,可以用来发送和接收数据。addr是客户端的套接字地址。
    • s.recv()、s.sendall()和s.close()与客户端同。

    现在写一个将客户端发送来的信息发送回去的服务器:

    # -*- coding: utf-8 -*-
    import socket
    import sys
     
    HOST = ''   
    PORT = 8088
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((HOST, PORT))
    s.listen(5)
    print '开始监听'
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])
    data = conn.recv(1024)
    conn.sendall(data)
    conn.close()
    s.close()
    

    运行:

    >>> 
    开始监听
    

    服务器开始监听连接了。修改一下刚才写的客户端程序:

    # -*- coding: utf-8 -*-
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    host = 'localhost'
    port = 8088
    s.connect((host, port))  #建立连接
    message = 'GET / HTTP/1.1
    
    '
    s.sendall(message)  #发送GET请求
    r = s.recv(4096)    #接收数据
    print r
    s.close()    #关闭套接字
    

    运行,连接本地的服务器,服务器端输出:

    >>> 
    开始监听
    Connected with 127.0.0.1:60933
    

    连接成功。客户端输出:

    >>> 
    GET / HTTP/1.1
    

    发送的消息被返回了。

    这就是一个最简单的服务器了。上述服务器只能处理一次连接,这显然不是我们想看到的,保持一直运行:

    # -*- coding: utf-8 -*-
    import socket
    import sys
     
    HOST = ''   
    PORT = 8088
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((HOST, PORT))
    s.listen(5)
    print '开始监听'
    while True:
        conn, addr = s.accept()
        print 'Connected with ' + addr[0] + ':' + str(addr[1])
        data = conn.recv(1024)
        conn.sendall(data)
        conn.close()
    s.close()
    

    现在就可以使用客户端无限连接了:

    >>> 
    开始监听
    Connected with 127.0.0.1:61240
    Connected with 127.0.0.1:61242
    Connected with 127.0.0.1:61245
    Connected with 127.0.0.1:61250
    

    服务器端多线程处理连接

    现在服务器端虽然可以处理无限多个连接,但只能一个一个的处理,后面的客户端连接只能等待前面的连接完成才能发送数据。要同时处理多个连接,可以使用多线程。服务器端接收到新的连接后,开启一个线程处理新连接,主线程去建立下一个连接。

    服务器端:

    # -*- coding: utf-8 -*-
    import socket
    import threading
    
    HOST = ''   
    PORT = 8088
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((HOST, PORT))
    s.listen(5)
    print '开始监听'
    
    def runThread(conn):
        data = conn.recv(1024)
        print data
        conn.sendall(data)
        conn.close()
    
    while True:
        conn, addr = s.accept()
        print 'Connected with ' + addr[0] + ':' + str(addr[1])
        t = threading.Thread(target=runThread, args=(conn,))
        t.daemon = True
        t.start()
    

    客户端启动多个连接:

    # -*- coding: utf-8 -*-
    import socket
    import time
    import threading
    
    def run():
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        host = 'localhost'
        port = 8088
        s.connect((host, port)) 
        message = 'GET / HTTP/1.1
    
    '
        s.sendall(message) 
        print s.recv(4096)    
        s.close()
    
                    
    if __name__ == '__main__':
        for i in xrange(4):
            t = threading.Thread(target=run)
            t.start()
    

    运行:

    开始监听
    Connected with 127.0.0.1:61772
    GET / HTTP/1.1
    
    Connected with 127.0.0.1:61773
    GET / HTTP/1.1
    
    Connected with 127.0.0.1:61774
    GET / HTTP/1.1
    
    Connected with 127.0.0.1:61775
    GET / HTTP/1.1
    

    UDP编程

    UDP与TCP的不同之处在于UDP是不用建立连接的。

    在此需要使用s.recvfrom()与s.sendto()方法,前者与s.recv()相同,但返回(data, addr)的元组,addr为数据发送端的套接字地址,后者发送数据时需要加入要发送的远程地址。

    服务器:

    # -*- coding: utf-8 -*-
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.bind(('', 10000))
    while True:
        data, addr = s.recvfrom(1024)
        print '接收到%s的连接'%str(addr)
        s.sendto(data, addr)
    

    客户端:

    # -*- coding: utf-8 -*-
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.sendto('Hello World', ('localhost', 10000))
    r, addr = s.recvfrom(1024)
    print r
    s.close()

    运行:

    >>> 
    接收到('127.0.0.1', 64112)的连接
    >>> 
    Hello World
    

      

      

  • 相关阅读:
    考察数据结构(An Extensive Examination of Data Structures)
    考察数据结构——第一部分:数据结构简介[译]
    老生常谈
    使用DataSet的ReadXml和WriteXml方法
    要掌握Sql Server,我还差得远啊!
    新浪短信Web Service
    还是水晶报表
    两种报表组件的功能分析
    无限级分类(非递归算法/存储过程版/GUID主键)完整数据库示例_(4)显示记录
    c#方法中调用参数的值传递方式和引用传递方式,以及ref与out的区别
  • 原文地址:https://www.cnblogs.com/linxiyue/p/8265006.html
Copyright © 2020-2023  润新知