python socket和socketserver

Python提供了两个基本的socket模块。一个是socket,它提供了标准的BSD Socket API;另一个是socketServer,它提供了服务器中心类,可以简化网络服务器的开发。

        下面先简要介绍socket模块包含的类及其使用。

        1.开始了解socket模块前,先熟悉下Python的网络编程模块主要支持的两种Intent协议:TCP和UDP。TCP协议是一种面向连接的可靠协议,用于建立机器之间的双向通信流。UDP协议是一种较低级别的、以数据包为基础的协议(无连接传输模式)。与TCP不同,UDP信息不可靠。

         他们的区别如下图所示:左图为TCP连接协议,右图为UDP连接协议

                     

  2.socket模块的部分类方法介绍

类方法

说明

socket.socket(family, type[,proto])

创建并返回一个新的 socket对象

socket.getfqdn(name)

将使用点号分隔的 IP地址字符串转换成一个完整的域名

socket.gethostbyname(hostname)

将主机名解析为一个使用点号分隔的 IP地址字符串

socket.gethostbyname_ex(name)

它返回一个包含三个元素的元组,从左到右分别是给定地址的主要的主机名、同一IP地址的可选的主机名的一个列表、关于同一主机的同一接口的其它IP地址的一个列表(列表可能都是空的)。

socket.gethostbyaddr(address)

作用与gethostbyname_ex相同,只是你提供给它的参数是一个IP地址字符串

Socket.getservbyname(service,protocol)

它要求一个服务名(如'telnet'或'ftp')和一个协议(如'tcp'或'udp'),返回服务所使用的端口号

socket.fromfd(fd, family, type)

从现有的文件描述符创建一个 socket对象

 

 3.socket对象的部分方法介绍

实例方法

说明

sock.bind( (adrs, port) )

将 socket绑定到一个地址和端口上

sock.accept()

返回一个客户机 socket(带有客户机端的地址信息)

sock.listen(backlog)

将 socket设置成监听模式,能够监听 backlog 外来的连接请求

sock.connect( (adrs, port) )

将 socket连接到定义的主机和端口上

sock.recv( buflen[, flags] )

从 socket中接收数据,最多 buflen 个字符

sock.recvfrom( buflen[, flags] )

从 socket中接收数据,最多 buflen 个字符,同时返回数据来源的远程主机和端口号

sock.send( data[, flags] )

通过 socket发送数据

sock.sendto( data[, flags], addr )

通过 socket发送数据

sock.close()

关闭 socket

sock.getsockopt( lvl, optname )

获得指定 socket 选项的值

sock.setsockopt( lvl, optname, val )

设置指定 socket选项的值

 

4.编写socket测试程序

(a)编写server的步骤

第一步是创建socket对象。调用socket构造函数。如:

socket = socket.socket( family, type )

family参数代表地址家族,可为AF_INET或AF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用于同一台机器上的进程间通信。

type参数代表套接字类型,可为SOCK_STREAM(流套接字)和SOCK_DGRAM(数据报套接字)。

第二步是将socket绑定到指定地址。这是通过socket对象的bind方法来实现的:

socket.bind( address ) 

由AF_INET所创建的套接字,address地址必须是一个双元素元组,格式是(host,port)。host代表主机,port代表端口号。如果端口号正在使用、主机名不正确或端口已被保留,bind方法将引发socket.error异常。

第三步是使用socket套接字的listen方法接收连接请求。

socket.listen( backlog )

backlog指定最多允许多少个客户连接到服务器。它的值至少为1。收到连接请求后,这些请求需要排队,如果队列满,就拒绝请求。

第四步是服务器套接字通过socket的accept方法等待客户请求一个连接。

connection, address = socket.accept()

调 用accept方法时,socket会时入“waiting”状态。客户请求连接时,方法建立连接并返回服务器。accept方法返回一个含有两个元素的元组(connection,address)。第一个元素connection是新的socket对象,服务器必须通过它与客户通信;第二个元素 address是客户的Internet地址。

第 五步是处理阶段,服务器和客户端通过send和recv方法通信(传输 数据)。服务器调用send,并采用字符串形式向客户发送信息。send方法返回已发送的字符个数。服务器使用recv方法从客户接收信息。调用recv 时,服务器必须指定一个整数,它对应于可通过本次方法调用来接收的最大数据量。recv方法在接收数据时会进入“blocked”状态,最后返回一个字符 串,用它表示收到的数据。如果发送的数据量超过了recv所允许的,数据会被截短。多余的数据将缓冲于接收端。以后调用recv时,多余的数据会从缓冲区 删除(以及自上次调用recv以来,客户可能发送的其它任何数据)。 

传输结束,服务器调用socket的close方法关闭连接。

(b)编写client的步骤

首先创建一个socket以连接服务器:socket =socket.socket( family, type ) 

使用socket的connect方法连接服务器。对于AF_INET家族,连接格式如下:

socket.connect( (host,port) )

host代表服务器主机名或IP,port代表服务器进程所绑定的端口号。如连接成功,客户就可通过套接字与服务器通信,如果连接失败,会引发socket.error异常。

处理阶段,客户和服务器将通过send方法和recv方法通信。 

Server:

import socket
port=8081
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#从指定的端口,从任何发送者,接收UDP数据
s.bind(('',port))
print('正在等待接入...')
while True:
    #接收一个数据
    data,addr=s.recvfrom(1024)
    print('Received:',data,'from',addr)

Client:

import socket
port=8081
host='localhost'
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.sendto(b'hello,this is a test info !',(host,port))

很简单 。下面是TCP方式:

Server:

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

HOST=''
PORT=12345
BUFSIZ=1024
ADDR=(HOST, PORT)
sock=socket(AF_INET, SOCK_STREAM)

sock.bind(ADDR)

sock.listen(5)
while True:
    print('waiting for connection')
    tcpClientSock, addr=sock.accept()
    print('connect from ', addr)
    while True:
        try:
            data=tcpClientSock.recv(BUFSIZ)
        except:
            print(e)
            tcpClientSock.close()
            break
        if not data:
            break
        s='Hi,you send me :[%s] %s' %(ctime(), data.decode('utf8'))
        tcpClientSock.send(s.encode('utf8'))
        print([ctime()], ':', data.decode('utf8'))
tcpClientSock.close()
sock.close()

Client:

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

class TcpClient:
    HOST='127.0.0.1'
    PORT=12345
    BUFSIZ=1024
    ADDR=(HOST, PORT)
    def __init__(self):
        self.client=socket(AF_INET, SOCK_STREAM)
        self.client.connect(self.ADDR)

        while True:
            data=input('>')
            if not data:
                break
            self.client.send(data.encode('utf8'))
            data=self.client.recv(self.BUFSIZ)
            if not data:
                break
            print(data.decode('utf8'))
            
if __name__ == '__main__':
    client=TcpClient()

上面的TCP方式有个问题,不能退出,好吧,我们改造一下,使这个程序可以发送quit命令以退出:

Server:

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

HOST=''
PORT=1122  #设置侦听端口
BUFSIZ=1024
ADDR=(HOST, PORT)
sock=socket(AF_INET, SOCK_STREAM)

sock.bind(ADDR)

sock.listen(5)
#设置退出条件
STOP_CHAT=False
while not STOP_CHAT:
    print('等待接入,侦听端口:%d' % (PORT))
    tcpClientSock, addr=sock.accept()
    print('接受连接,客户端地址:',addr)
    while True:
        try:
            data=tcpClientSock.recv(BUFSIZ)
        except:
            #print(e)
            tcpClientSock.close()
            break
        if not data:
            break
        #python3使用bytes,所以要进行编码
        #s='%s发送给我的信息是:[%s] %s' %(addr[0],ctime(), data.decode('utf8'))
        #对日期进行一下格式化
        ISOTIMEFORMAT='%Y-%m-%d %X'
        stime=time.strftime(ISOTIMEFORMAT, localtime())
        s='%s发送给我的信息是:%s' %(addr[0],data.decode('utf8'))
        tcpClientSock.send(s.encode('utf8'))
        print([stime], ':', data.decode('utf8'))
        #如果输入quit(忽略大小写),则程序退出
        STOP_CHAT=(data.decode('utf8').upper()=="QUIT")
        if STOP_CHAT:
            break
tcpClientSock.close()
sock.close()

Client:

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

class TcpClient:
    #测试,连接本机
    HOST='127.0.0.1'
    #设置侦听端口
    PORT=1122 
    BUFSIZ=1024
    ADDR=(HOST, PORT)
    def __init__(self):
        self.client=socket(AF_INET, SOCK_STREAM)
        self.client.connect(self.ADDR)

        while True:
            data=input('>')
            if not data:
                break
            #python3传递的是bytes,所以要编码
            self.client.send(data.encode('utf8'))
            print('发送信息到%s:%s' %(self.HOST,data))
            if data.upper()=="QUIT":
                break            
            data=self.client.recv(self.BUFSIZ)
            if not data:
                break
            print('从%s收到信息:%s' %(self.HOST,data.decode('utf8')))
            
            
if __name__ == '__main__':
    client=TcpClient()

下面先简要介绍socketserver模块包含的类及其使用

SocketServer是标准库中一个高级别的模块。用于简化网络客户与服务器的实现。模块中,已经实现了一些可供使用的类。

我们将再次实现之前的那个基本TCP的例子。你会注意到新实现与之前有很多相似之处,但你也要注意到,现在很多繁杂的事情已经被封装好了,你不用再去关心那个样板代码了。例子给出的是一个最简单的同步服务器。

为了要隐藏实现的细节。我们现在写程序时会使用类,这是与之前代码的另一个不同。用面向对象的方法可以帮助我们更好的组织数据与逻辑功能。你也会注意到,我们的程序现在是“事件驱动”了。这就意味着,只有在事件出现的时候,程序才有“反应”。

在之前的服务循环中,我们阻塞等待请求,有请求来的时候就处理请求,然后再回去继续等待。现在的服务循环中,就不用在服务器里写代码了,改成定义一个处理器,服务器在收到进来的请求的时候,可以调用你的处理函数。

1. 创建一个SocketServerTCP服务器:

在代码中,先导入我们的服务器类,然后像之前一样定义主机常量。主机常量后就是我们的请求处理器类,然后是启动代码。在下面的代码片断中可以看到更多细节。

#!/usr/bin/env python
 
from SocketServer import (TCPServer as TCP,
                          StreamRequestHandler as SRH)
from time import ctime
 
HOST = ''
PORT = 12346
ADDR = (HOST, PORT)
 
class MyRequestHandler(SRH):
    def handle(self):
        print '...connected from:', self.client_address
        self.wfile.write('[%s] %s' % (ctime(), self.rfile.readline()))
 
tcpServ = TCP(ADDR, MyRequestHandler)
print 'waiting for connection...'
tcpServ.serve_forever()

我们从SocketServer的StreamRequestHandler类中派生出一个子类,并重写handle()函数。在BaseRequest类中,这个函数什么也不做。在有客户消息进来的时候,handle()函数就会被调用。StreamRequestHandler 类支持像操作文件对象那样操作输入输出套接字。我们可以用readline()函数得到客户消息,用write()函数把字符串发给客户。

为了保持一致性,我们要在客户与服务器两端的代码里都加上回车与换行。实际上,你在代码中看不到这个,因为,我们重用了客户传过来的回车与换行。

2. 创建SocketServerTCP客户端

#!/usr/bin/env python
 
from socket import *
 
HOST = 'localhost'
PORT = 12346
BUFSIZE = 1024
ADDR = (HOST, PORT)
 
while True:
    tcpCliSock = socket(AF_INET, SOCK_STREAM)
    tcpCliSock.connect(ADDR)
    data = raw_input('>')
    if not data:
        break
    tcpCliSock.send('%s\r\n' % data)
    data = tcpCliSock.recv(BUFSIZE)
    if not data:
        break
    print data.strip()
    tcpCliSock.close()

SocketServer的请求处理器的默认行为是接受连接,得到请求,然后就关闭连接。这使得我们不能在程序的运行时,一直保持连接状态,要每次发送数据到服务器的时候都要创建一个新的套接字。这种行为使得TCP 服务器的行为有些像UDP服务器。不过,这种行为也可以通过重写请求处理器中相应的函数来改变。

现在,我们的客户端有点完全不一样了(我们得每次都创建一个连接)。其它的小区别在服务器代码的逐行解释中已经看到了:我们使用的处理器类像文件一样操作套接字,所以我们每次都要发送行结束字符(回车与换行)。服务器只是保留并重用我们发送的行结束字符。当我们从服务器得到数据的时候,我们使用strip()函数去掉它们,然后使用print语句提供的回车。

    3.   使用SocketServer处理多连接

    上面的例子一次只能连接一个客户机并出力它的请求,如果要处理多连接问题,那么有三种主要的方法能实现这个目的:分叉(forking)、线程(threading)以及异步I/O(asynchronous I/O)。通过对SocketServer服务器使用混入类(mix-in class),派生进程和线程很容易处理。即使要自己实现它们,这些方法也很容易使用。它们确实有缺点:分叉占据资源,并且如果有太多的客户端时分叉不能很好分叉(尽管如此,对于合理数量的客户端,分叉在现代的UNIX或者Linux系统中是很高效的,如果有一个多CPU系统,那系统效率会更高);线程处理能导致同步问题。使用SocketServer框架创建分叉或者线程服务器非常简单: 

分叉SocketServer服务器:

#!/usr/bin/env python
 
from SocketServer import (TCPServer as TCP,
                          StreamRequestHandler as SRH,
                          ForkingMixIn as FMI)
from time import ctime
 
HOST = ''
PORT = 12346
ADDR = (HOST, PORT)
 
class Server(FMI, TCP):
    pass
 
class MyRequestHandler(SRH):
    def handle(self):
        print '...connected from:', self.client_address
        self.wfile.write('[%s] %s' % (ctime(), self.rfile.readline()))
 
tcpServ = Server(ADDR, MyRequestHandler)
print 'waiting for connection...'
tcpServ.serve_forever()

多线程SocketServer服务器:

#!/usr/bin/env python
 
from SocketServer import (TCPServer as TCP,
                          StreamRequestHandler as SRH,
                          ThreadingMixIn as TMI)
from time import ctime
 
HOST = ''
PORT = 12346
ADDR = (HOST, PORT)
 
class Server(TMI, TCP):
    pass
 
class MyRequestHandler(SRH):
    def handle(self):
        print '...connected from:', self.client_address
        self.wfile.write('[%s] %s' % (ctime(), self.rfile.readline()))
 
tcpServ = Server(ADDR, MyRequestHandler)
print 'waiting for connection...'
tcpServ.serve_forever()

 







posted @ 2016-07-09 07:46  百衲本  阅读(3001)  评论(0编辑  收藏  举报
cnblogs_post_body { color: black; font: 0.875em/1.5em "微软雅黑" , "PTSans" , "Arial" ,sans-serif; font-size: 15px; } cnblogs_post_body h1 { text-align:center; background: #333366; border-radius: 6px 6px 6px 6px; box-shadow: 0 0 0 1px #5F5A4B, 1px 1px 6px 1px rgba(10, 10, 0, 0.5); color: #FFFFFF; font-family: "微软雅黑" , "宋体" , "黑体" ,Arial; font-size: 23px; font-weight: bold; height: 25px; line-height: 25px; margin: 18px 0 !important; padding: 8px 0 5px 5px; text-shadow: 2px 2px 3px #222222; } cnblogs_post_body h2 { text-align:center; background: #006699; border-radius: 6px 6px 6px 6px; box-shadow: 0 0 0 1px #5F5A4B, 1px 1px 6px 1px rgba(10, 10, 0, 0.5); color: #FFFFFF; font-family: "微软雅黑" , "宋体" , "黑体" ,Arial; font-size: 20px; font-weight: bold; height: 25px; line-height: 25px; margin: 18px 0 !important; padding: 8px 0 5px 5px; text-shadow: 2px 2px 3px #222222; } cnblogs_post_body h3 { background: #2B6695; border-radius: 6px 6px 6px 6px; box-shadow: 0 0 0 1px #5F5A4B, 1px 1px 6px 1px rgba(10, 10, 0, 0.5); color: #FFFFFF; font-family: "微软雅黑" , "宋体" , "黑体" ,Arial; font-size: 18px; font-weight: bold; height: 25px; line-height: 25px; margin: 18px 0 !important; padding: 8px 0 5px 5px; text-shadow: 2px 2px 3px #222222; } 回到顶部 博客侧边栏 回到顶部 页首代码 回到顶部 页脚代码