Python 网络编程操作TCP/UDP 初探(一)

通信模式介绍

服务器与客户端之间通信模式:

我觉得这个举例是很恰当的。将服务器->客服总线,客户端->客户,新的客户端->客服代表。

客服总线比如说400-xxxxxx这类的电话,一直处于等待状态,当有新的客户来电之后,总线接线员接到电话后,将客户的电话切换给客服代表进行处理。这样空出主线,以便总线接线员可以继续等待新的客户电话。而此时之前接入的客户及对应的客服代表,能够进行他们自己独立的谈话。当有新的客户B进来之后,总线接线员会创建一个新的客服代表进行处理,总线接线员继续进行等待。

接下来我们直接开始撸

1、TCP连接

TCP服务器端

from socket import *

from time import ctime

HOST = ''    # 对bind方法的标识,标识可以使用任何可用的地址
PORT = 21567  # 随机设置的端口号
BUFSIZE = 1024  # 缓冲区大小1KB
ADDR = (HOST, PORT)

tcpSerSock = socket(AF_INET, SOCK_STREAM)  # 分配TCP服务器套接字
tcpSerSock.bind(ADDR)  # 将套接字绑定到服务器地址
tcpSerSock.listen(5)  # 开启TCP监听器

while True:
    print 'waiting fo connection...'   # 无限循环中,等待客户端的连接
    tcpCliSock, addr = tcpSerSock.accept()
    print '...connected from:', addr

    while True:
        data = tcpCliSock.recv(BUFSIZE)  # 接收客户端数据
        if not data:  # 如果接收的消息是空白数据,这以为着客户端已经退出,跳出循环,关闭当前的客户端连接,继续等待另一个客户端连接
            break
        tcpCliSock.send('[%s] %s' % (ctime(), data))  # 接受到客户端消息不为空,则将其格式化并返回相同的数据,加上当前的时间戳前缀。
    tcpCliSock.close()
tcpSerSock.close()  # 永远不会执行,只是提醒大家,可以用这种方式,关闭服务器套接字,退出服务

TCP客户端

# coding:utf-8
from socket import *

HOST = 'localhost'  # 本地连接通信,如果要其余机器连接,可改成服务器端IP
PORT = 21567        # 随机设置的端口号
BUFSIZE = 1024      # 缓冲区大小1KB
ADDR = (HOST, PORT)     

tcpCliSock = socket(AF_INET, SOCK_STREAM)  # 分配TCP客户端套接字,主动调用并连接到服务器
tcpCliSock.connect(ADDR)

while True:
    data = raw_input('> ')      # 用户输入发送数据
    if not data:
        break
    tcpCliSock.send(data)       # 发送数据
    data = tcpCliSock.recv(BUFSIZE)  # 接收服务器端返回数据
    if not data:
        break
    print data                      # 打印服务器端加了时间戳之后的返回数据,显示
tcpCliSock.close()

如果需要IPv6地址:需要将HOST改为 HOST = '::1' ,同时请求套接字的AF_INET6家族。

接着为了看他们的工作,先运行服务器端程序,然后启动客户端程序:

客户端显示如下:

这是来自客户端的请求 [Fri Sep 30 16:26:40 2016]这是来自客户端的请求! _

服务器端显示如下:

waiting fo connection... ...connected from: ('127.0.0.1', 57551) waiting fo connection...

通过以上的例子,给我们展示了数据如何从客户端到达服务器,并最后返回到客户端。这里服务器就是作为了一个“时间服务器”,获取服务器端的时间。

2、UDP连接

UDP连接与TCP连接通信的一个显著差异就是它不是面向连接的,无连接,无需监听传入的连接。这类服务器仅仅接受消息并有可能回复数据。只有创建套接字并将其绑定到地址中,然后无限循环接受客户端消息,处理,返回消息。

UDP服务器端

# coding:utf-8
from socket import *
from time import ctime

HOST = ''  # 对bind方法的标识,标识可以使用任何可用的地址
PORT = 21567  # 随机设置的端口号
BUFSIZE = 1024  # 缓冲区大小1KB
ADDR = (HOST, PORT)

udpSerSock = socket(AF_INET, SOCK_DGRAM)  # 分配TCP服务器套接字
udpSerSock.bind(ADDR)  # 将套接字绑定到服务器地址

while True:
    print 'waiting for message...'
    data, addr = udpSerSock.recvfrom(BUFSIZE)  # 接收客户端数据
    udpSerSock.sendto('[%s] %s' % (ctime(), data), addr)  # 格式化客户端数据,并返回给客户端
    print '... received from and returned tto:', addr
udpSerSock.close()  # 永远不会执行,只是提醒大家,可以用这种方式,关闭服务器套接字,退出服务

UDP客户端:

# coding:utf-8

from socket import *

HOST = 'localhost'  # 对bind方法的标识,标识可以使用任何可用的地址,使用本地连接localhost
PORT = 21567  # 随机设置的端口号
BUFSIZE = 1024  # 缓冲区大小1KB
ADDR = (HOST, PORT)

udpSockCli = socket(AF_INET, SOCK_DGRAM)  # 分配udp客户端套接字,主动调用并连接到服务器

while True:
    data = raw_input('> ')  # 输入数据
    if not data:  # 如果输入数据为空,默认退出连接
        break
    udpSockCli.sendto(data, ADDR)  # 客户端发送输入的数据到服务器
    data, addr = udpSockCli.recvfrom(BUFSIZE)  # 客户端接收从服务器返回的数据
    if not data:  # 如果从服务器接收到的数据为空,默认断开连接
        break
    print data
udpSockCli.close()  # 永远不会执行,只是提醒大家,可以用这种方式,关闭服务器套接字,退出服务

客户端显示:

测试连接服务器 [Wed Oct 5 09:13:14 2016] 测试连接服务器

服务器端显示:

waiting for message... ... received from and returned tto: ('127.0.0.1',65264) waiting for message

在使用TCP连接的时候,我们必须先跑服务器端的程序,然后再起客户端的程序。但是在用UDP连接的时候,就不必管这样的启动顺序,可以先启动客户端程序,然后再启动服务器端程序进行通信。

3.SocketServer模块

服务器端:

# coding:utf-8

from SocketServer import (TCPServer as TCP, StreamRequestHandler as SRH)
from time import ctime

HOST = ''
PORT = 21567
ADDR = (HOST, PORT)

class MyRequestHandle(SRH):  # 创建基于StreamRequestHandler的子类.
    def handle(self):  # 重写Handle方法
        print '...connected from:', self.client_address
        ''' StreamRequestHandler类将输入和输出套接字看作类似文件的对象.因此
            通过write发送字符串给到客户端.readline获取客户端消息.因为采用的是
            类似文件的处理方式,所以需要额外的回车和换行符,在客户端进行处理.

        '''
        self.wfile.write('[%s] %s' %(ctime(), self.rfile.readline()))

tcpSSSer = TCP(ADDR, MyRequestHandle)  # 建立连接服务
print 'waiting for connection ...'
tcpSSSer.serve_forever()  # 无限循环等待,服务客户端请求

客户端:

# coding:utf-8

from socket import *

HOST = 'localhost'
PORT = 21567
BUFSIZE = 1024
ADDR = (HOST, PORT)

while True:
    tcpSSCliSock = socket(AF_INET, SOCK_STREAM)
    tcpSSCliSock.connect(ADDR)
    data = raw_input('> ')
    if not data:
        break
    '''
        因为采用的是类似文件的处理方式,所以需要额外的回车和换行符,
        在客户端进行处理.
    '''
    tcpSSCliSock.send('%s\r\n' % data)
    data = tcpSSCliSock.recv(BUFSIZE)
    if not data:
        break
    print data.strip()
    tcpSSCliSock.close()

SocketServer请求处理程序的默认行为是接受连接,获取请求,然后关闭连接。由于这个原因,我们不能在应用程序整个执行过程中都保持连接,因此每次向服务器发送消息时,都需要创建一个新的套接字。

4.Twisted框架

服务器端

# coding:utf-8

from twisted.internet import protocol, reactor
from time import ctime

PORT = 21567   # 定义连接端口
class TsSerProtocol(protocol.Protocol):  # 定义基于protocol的子类
    def connectionMade(self):  # 重写connectionMade方法,客户端连接服务器默认调用该方法.
        clnt = self.clnt = self.transport.getPeer().host  # 获取主机信息
        print '...connected from:', clnt
    def dataReceived(self, data):  # 重写dataReceived方法,客户端通过网络发送数据时默认调用该方法.
        self.transport.write('[%s] %s ' % (ctime(), data))  # 时间戳+发送的数据作为返回数据.

factory = protocol.Factory()  # 协议工厂,每个连接接入,制造一个协议实例
factory.protocol = TsSerProtocol
print 'waiting for connection...'
'''
    异步编程:这样的异步模式称为Reactor模式
    1.监听事件
    2.事件发生执行对应的回调函数
    3.回调完成(可能产生新的事件添加进监听队列)
    4.回到1,监听事件
'''
reactor.listenTCP(PORT, factory)  # reactor 安装TCP监听器,检查服务请求,接收到请求后就创建一个TsSerProtocol实例来处理客户端事物
reactor.run()  # 运行事件管理器

reactor是事件管理器,用于注册、注销事件,运行事件循环,当事件发生时调用回调函数处理。关于reactor有下面几个结论:

    1、Twisted的reactor只有通过调用reactor.run()来启动。
    2、Twisted的reactor只有通过调用reactor.run()来启动。
    3、一旦启动,就会一直运行下去。reactor就会在程序的控制下(或者具体在一个启动它的线程的控制下)。
    4、reactor循环并不会消耗任何CPU的资源。
    5、并不需要显式的创建reactor,只需要引入就OK了。

最后一条需要解释清楚。在Twisted中,reactor是Singleton(也就是单例模式),即在一个程序中只能有一个reactor,并且只要你引入它就相应地创建一个。上面引入的方式这是twisted默认使用的方法。

客户端

# coding:utf-8

from twisted.internet import protocol, reactor
import random
from time import *

HOST = 'localhost'  # 定义本地127.0.0.1连接
PORT = 21567        # 定义默认端口

class TsCliProtocol(protocol.Protocol):  # 定义基于protocol的子类
    def sendData(self):  # 添加默认的发送数据方法.
        # data =  raw_input('> ')
        data = '123123' + str(random.randint(0, 1000))  # 为观察异步通信,而选择一直传输随机字符串
        sleep(3)
        if data:
            print '...sending %s ...' % data
            self.transport.write(data)  # 发送数据给到服务器
        else:
            self.transport.loseConnection()  # 断开客户端与服务器连接,关闭套接字,调用工厂函数clientConnectionLost(),停止reactor事物监听器

    def connectionMade(self):  # 建立连接
        self.sendData()

    def dataReceived(self, data):  # 接收到返回数据回调函数
        print data
        self.sendData()

class TsCliFactory(protocol.ClientFactory):  # 客户端工厂类,继承于protocol.ClientFactory
    protocol = TsCliProtocol
    clientConnectionLost = clientConnectionFailed = lambda self, connector, reason: reactor.stop()

reactor.connectTCP(HOST, PORT, TsCliFactory())  # 创建服务器TCP连接
reactor.run()  # 运行事件管理器

运行上面的程序,一个服务器,两个客户端,我们可以看下如下情景:

服务器端:

客户端:

通过上面的几个尝试,我们不难发现单线程的通信是很容易实现的,但是在实际工作中使用呢?

可能存在以下一些问题,同时我们将在下一篇中进行探索:

1.异步通信,也是在不断的轮询排队处理中,如果采用服务器端多线程处理呢?

2.多线程与异步操作的异同

3.如果A - 服务器 -B该如何实现?


评论(0 ) 点赞(43)


暂未登录,请登录之后发表评论。 QQ