对 Python Socket 编程的初探

铁扇公主1 发布于 2017/05/23 20:35
阅读 146
收藏 1

阿里云2020上云采购季,爆款产品,低至0.6折,点击抢购!>>>

对于python网络编程来说,免不了要用到socket模块。下面主要分享一下个人对python socket的一些理解。

 

socket编程步骤

 

  1. 服务端创建一个socket,绑定地址和端口,然后监听端口上传入的连接,一旦有连接进来,就通过accept函数接收传入的连接。

  2. 客户端也是创建一个socket。绑定远程地址和端口,然后建立连接,发送数据。

 

服务端socket

 

下面通过一段实例代码来详细说明

 

服务端 socker_server.py

 

import socket

import sys

 

HOST = "127.0.0.1"              

PORT = 10000              

s = None

for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC,

                              socket.SOCK_STREAM, 0, socket.AI_PASSIVE):

    af, socktype, proto, canonname, sa = res

    try:

        s = socket.socket(af, socktype, proto)

    except socket.error as msg:

        s = None

        continue

    try:

        s.bind(sa)

        s.listen(5)

    except socket.error as msg:

        s.close()

        s = None

        continue

    break

if s is None:

    print 'could not open socket'

    sys.exit(1)

conn, addr = s.accept()

print 'Connected by', addr

while 1:

    data = conn.recv(1024)

    if not data: break

    conn.send(data)

conn.close()

 

首先我们通过socket.getaddrinnfo函数将host/port转换成一个包含5元组的序列。这个5元组包含我们创建一个socket连接所需要的所有必要参数。返回的5元组分别是 (family, sockettype, proto, canonname, sockaddr)

 

family 地址簇,用与socket()函数的第一个参数。主要有以下几个

 

  1. socket.AF_UNIX 用与单一机器下的进程通信

  2. socket.AF_INET 用与服务器之间相互通信,通常都用这个。

  3. socket.AF_INET6 支持IPv6

 

sockettype socket类型,用与socket()函数的第二个参数,常用的有

 

  1. socket.SOCK_STREAM 默认,用于TCP协议

  2. socket.SOCK_DGRAM 用于UDP协议

 

proto 协议,用于socket()函数的第三个参数。 getaddrinnfo函数会根据地址格式和socket类型,返回合适的协议

 

canonname 一个规范化的host name。

 

sockaddr 描述了一个socket address .是一个二元组,主要用于bind()和connect()函数

 

接下来创建一个socket对象,传入getaddrinnfo函数返回的af,sockettype,proto。

 

s = socket.socket(af, socktype, proto)

 

然后绑定socket address

 

s.bind(sa)

 

开启监听模式

 

s.listen(5)

 

listen函数会监听连接到socket上的连接,参数表示在拒绝连接之前系统可以挂起的最大连接队列数量为5。这些连接还没有被accept处理。数量不能无限大,通常指定5。

 

一旦我们监听到了连接,就会调用accept函数接收连接

 

conn, addr = s.accept()

 

accept函数返回一个二元组,conn是一个新的socket对象,用来接收和发送数据。addr表示另一端的socket地址。

 

接下来我们就可以用conn对象发送和接收数据了

 

data = conn.recv(1024) # 接收数据, 这里指定一次最多接收的字符数量为1024

conn.send(data) # 发送数据

 

这里我们接收到一个连接socket就会停止运行,所以如果要循环连接的话,将accept函数放入到一个死循环里。

 

客户端socket

 

客户端socket编程相对比较简单,通过connect和服务端建立连接之后,就可以相互通信了。socket_client.py如下

 

for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM):

    af, socktype, proto, canonname, sa = res

    try:

        s = socket.socket(af, socktype, proto)

    except socket.error as msg:

        s = None

        continue

    try:

        s.connect(sa)

    except socket.error as msg:

        s.close()

        s = None

        continue

    break

if s is None:

    print 'could not open socket'

    sys.exit(1)

s.sendall('Hello, world')

data = s.recv(1024)

s.close()

print 'Received', repr(data)

 

备注: 对于getaddrinfo函数,可以参考 这个函数的作用是把协议相关性安全隐藏在这个底层库函数内部。应用程序只要处理由getaddrinfo函数返回的对象即可。

 

以上主要是针对TCP流数据的socket编程。对于UDP协议的数据,处理略有不同。譬如发送接收UDP数据包处理函数为:

 

socket.sendto(string, flags, address)

socket.recvfrom(bufsize[, flags]) #返回(string, address),string是返回的数据,address是发送方的socket地址

 

 

 

SocketServer模块

 

python中网络编程除了socket模块还提供了SocketServer模块,这一模块主要是对socket模块进行了封装,将socket的对象的创建,绑定,连接,接收,发送,关闭都封装在里面,大大简化了网络服务的编程。

 

此模块提供了以下2个主要的网络服务类,用于创建相应的套接字流

 

  1. TCPServer 创建TCP协议的套接字流

  2. UDPServer 创建UDP协议的套接字流

 

我们有了套接字流对象,还需要一个请求处理类。SocketServer模块提供了请求处理类有BaseRequestHandler,以及它的派生类StreamRequestHandler和DatagramRequestHandler。所以只要继承这3个类中的一个,然后重写handle函数,此函数将用来处理接收到的请求。下面看一个服务端的代码示例

 

import SocketServer

 

class MyTCPHandler(SocketServer.StreamRequestHandler):

   """创建请求处理类,重写handle方法。此外也可以重写setup()和finish()来做一些请求处理前和处理后的一些工作"""

    def handle(self):

        # self.request is the TCP socket connected to the client

        self.data = self.request.recv(1024).strip()

        print "{} wrote:".format(self.client_address[0])

        print self.data

        # just send back the same data, but upper-cased

        self.request.sendall(self.data.upper())

 

if __name__ == "__main__":

    HOST, PORT = "localhost", 10000

 

    server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)

 

    # Activate the server; this will keep running until you

    # interrupt the program with Ctrl-C

    # server.shutdown()

    server.serve_forever()  # 一直循环接收请求

    # server.handle_request() # 只处理一次请求就退出

 

看着是不是代码简单了很多,而且SocketServer模块内部使用了多路复用IO技术,可以实现更好的连接性能。看serve_forever函数的源代码用到了select模块。通过传入socket对象调用select.select()来监听socket对象的文件描述符,一旦发现socket对象就绪,就通知应用程序进行相应的读写操作。源代码如下:

 

def serve_forever(self, poll_interval=0.5):

        """Handle one request at a time until shutdown.

 

        Polls for shutdown every poll_interval seconds. Ignores

        self.timeout. If you need to do periodic tasks, do them in

        another thread.

        """

        self.__is_shut_down.clear()

        try:

            while not self.__shutdown_request:

                # XXX: Consider using another file descriptor or

                # connecting to the socket to wake this up instead of

                # polling. Polling reduces our responsiveness to a

                # shutdown request and wastes cpu at all other times.

                r, w, e = _eintr_retry(select.select, [self], [], [],

                                       poll_interval)

                if self in r:

                    self._handle_request_noblock()

        finally:

            self.__shutdown_request = False

            self.__is_shut_down.set()

 

即使使用了select技术,TCPServer,UDPServer处理请求仍然是同步的,意味着一个请求处理完,才能处理下一个请求。但SocketServer模块提供了另外2个类用来支持异步的模式。

 

  1. ForkingMixIn 利用多进程实现异步

  2. ThreadingMixIn 利用多线程实现异步

 

看名字就知道用mixin模式来实现异步。而mixin模式可以通过多继承来实现,所以通过对网络服务类进行多继承的方式就可以实现异步模式

 

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):

    pass

 

针对ThreadindMixIn,实现异步的原理也就是在内部对每个请求创建一个线程来处理。看源码

 

def process_request(self, request, client_address):

        """Start a new thread to process the request."""

        t = threading.Thread(target = self.process_request_thread,

                             args = (request, client_address))

        t.daemon = self.daemon_threads

        t.start()

 

下面提供一个异步模式的示例

 

import socket

import threading

import SocketServer

 

class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):

 

    def handle(self):

        data = self.request.recv(1024)

        cur_thread = threading.current_thread()

        response = "{}: {}".format(cur_thread.name, data)

        self.request.sendall(response)

 

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):

    pass

 

def client(ip, port, message):

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    sock.connect((ip, port))

    try:

        sock.sendall(message)

        response = sock.recv(1024)

        print "Received: {}".format(response)

    finally:

        sock.close()

 

if __name__ == "__main__":

    # Port 0 means to select an arbitrary unused port

    HOST, PORT = "localhost", 0

 

    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)

    ip, port = server.server_address

 

    # Start a thread with the server -- that thread will then start one

    # more thread for each request

    server_thread = threading.Thread(target=server.serve_forever)

    # Exit the server thread when the main thread terminates

    server_thread.daemon = True

    server_thread.start()

    print "Server loop running in thread:", server_thread.name

 

    client(ip, port, "Hello World 1")

    client(ip, port, "Hello World 2")

    client(ip, port, "Hello World 3")

 

    server.shutdown()

    server.server_close()

学好python你需要一个良好的环境,一个优质的开发交流群,群里都是那种相互帮助的人才是可以的,我有建立一个python学习交流群,在群里我们相互帮助,相互关心,相互分享内容,这样出问题帮助你的人就比较多,群号是301,还有056,最后是069,这样就可以找到大神聚合的群,如果你只愿意别人帮助你,不愿意分享或者帮助别人,那就请不要加了,你把你会的告诉别人这是一种分享。如果你看了觉得还可以的麻烦给我点个赞谢谢
  
 

加载中
0
开源中国首席罗纳尔多
开源中国首席罗纳尔多

怎么都把教程贴发在提问区上啊

返回顶部
顶部