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

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

我以为这个举例是很恰当的。将服务器->客服总线,客户端->客户,新的客户端->客服表明。编程

客服总线好比说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客户端:dom

# 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家族。异步

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

客户端显示以下:tcp

服务器端显示以下:

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()  # 永远不会执行,只是提醒你们,能够用这种方式,关闭服务器套接字,退出服务

客户端显示:

服务器端显示:

在使用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有下面几个结论:

  • Twisted的reactor只有经过调用reactor.run()来启动。
  • reactor循环是在其开始的进程中运行,也就是运行在主进程中。
  • 一旦启动,就会一直运行下去。reactor就会在程序的控制下(或者具体在一个启动它的线程的控制下)。
  • reactor循环并不会消耗任何CPU的资源。
  • 并不须要显式的建立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该如何实现?

相关文章
相关标签/搜索