Socket

2020年3月2日Socket通信
1.通信原理
1.1.TCP服务器端:
1.第一步是创建socket对象。调用socket构造函数
2.第二步是将socket绑定到指定地址
3.第三步是使用socket套接字的listen方法接收连接请求。
4.第四步是服务器套接字通过socket的accept方法等待客户请求一个连接。
connection, address = socket.accept()
5.第五步是处理阶段,服务器和客户端通过send和recv方法通信(传输 数据)。
6.传输结束,服务器调用socket的close方法关闭连接
3 接收完毕可以关闭套接字,close。
ss.socket(Socket.AF_INET,Socket.SOCK_STRAM)  #创建服务器套接字
代码实现
ss.socket(Socket.AF_INET,Socket.SOCK_STRAM)  #创建服务器套接字
ss.bind() #把本地地址绑到套接字上
ss.listen() #监听连接
inf_loop: #服务器无限循环
cs=ss.accept() #接受客户端的连接
comm._loop: #通信循环
cs.recv()/cs.send() #对话
cs.close() #关闭客户套接字
ss.close() #关闭服务器套接字

1.2.TCP客户端
1 创建套接字,然后连接远端地址,socket ,connect。
2 建立连接之后开始发送数据。Send(data),当然可以从缓冲区读取服务器发来的数据。Recv(BUFF)
3 完毕后,关闭套接字。Close
cs=socket(Socket.AF_INET,Socket.SOCK_DGRAM)
代码实现
#创建客户套接字
cs.connect() #尝试连接服务器
comm._loop: #通信循环
cs.send()/cs.recv() #对话
cs.close() #关闭套接字

2.客户端服务端基础例子
2.1.服务端
import sys

import socket
#开启ip和端口
ip_port = ('127.0.0.1',9999)
#生成一个句柄
sk = socket.socket()
#绑定ip端口
sk.bind(ip_port)
#最多连接数
sk.listen(5)
print ('进入监听状态...')
#等待链接,阻塞,直到渠道链接 conn打开一个新的对象 专门给当前链接的客户端 addr是ip地址
conn,addr = sk.accept()
#获取客户端请求数据
print(addr)  #打印结果('127.0.0.1', 26568)
client_data = conn.recv(1024)
#打印对方的数据
print (client_data.decode("utf-8"))
#向对方发送数据
conn.send('服务端回复内容'.encode("utf-8"))
#关闭链接
conn.close()
2.2.客户端
import socket
#链接服务端ip和端口
ip_port = ('127.0.0.1',9999)
#生成一个句柄
sk = socket.socket()
#请求连接服务端
sk.connect(ip_port)
#发送数据
sk.send('客户端发送数据'.encode("utf-8"))
#接受数据
server_reply = sk.recv(1024)
#打印接受的数据
print("get data:",server_reply.decode("utf-8"))
#关闭连接
sk.close()

3.客户端服务端的例子
Server代码
import socket, time, socketserver, struct, os, _thread

host = '127.0.0.1'
port = 8998
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 定义socket类型
s.bind((host, port))  # 绑定需要监听的Ip和端口号,tuple格式
s.listen(1)


def conn_thread(connection, address):
    while True:
        try:
            connection.settimeout(600)
            fileinfo_size = struct.calcsize('12sl')#12s表示12个字符,l表示一个长整型数
            buf = connection.recv(fileinfo_size)
            if buf:  # 如果不加这个if,第一个文件传输完成后会自动走到下一句,需要拿到文件大小信息才可以继续执行
                filename, filesize = struct.unpack('12sl', buf)
                filename_f = filename.decode("utf-8").strip('\00')  # C语言中’\0’是一个ASCII码为0的字符,在python中表示占一个位置得空字符
                filenewname = os.path.join('e:\\', os.path.basename(filename_f))
                print(u'文件名称:%s , 文件大小: %s' % (filenewname, filesize))
                recvd_size = 0  # 定义接收了的文件大小
                file = open(filenewname, 'wb')
                print(u"开始传输文件内容")
                while not recvd_size == filesize:#只要小于文件大小,就会收
                    if filesize - recvd_size > 1024:#文字大小一剩余要收的大小
                        rdata = connection.recv(1024)
                        recvd_size += len(rdata)
                    else:#每次收到数字会向 这个变量去写
                        rdata = connection.recv(filesize - recvd_size)
                        recvd_size = filesize
                    file.write(rdata)
                file.close()
                print('receive done')
                # connection.close()
        except socket.timeout:
            connection.close()

while True:
    print(u"开始进入监听状态")
    connection, address = s.accept()
    print('Connected by ', address)
    # thread = threading.Thread(target=conn_thread,args=(connection,address)) #使用threading也可以
    # thread.start()
    _thread.start_new_thread(conn_thread, (connection, address))
s.close()





客户端代码
# -*- coding: UTF-8 -*-
import socket, os, struct

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 8998))
while True:
    filepath = input('请输入要传输的文件绝对路径:\r\n')
    print(type(filepath))
    print(len(filepath.encode("utf-8")))
    if os.path.isfile(filepath):#如果文件存在
        #fileinfo_size = struct.calcsize('20sl')  # 定义打包规则
        # 定义文件头信息,包含文件名和文件大小
        fhead = struct.pack('12sl', filepath.encode("utf-8"), os.stat(filepath).st_size)
        print(os.stat(filepath).st_size)
        s.send(fhead)#收到的文件绝对路径及文件大小
        print (u'文件路径: ', filepath)
        # with open(filepath,'rb') as fo: 这样发送文件有问题,发送完成后还会发一些东西过去
        fo = open(filepath, 'rb')
        while True:
            filedata = fo.read(1024)#每次每一次1024的字节
            if not filedata:
                break
            s.send(filedata)
        fo.close()
        print (u'传输成功')
        # s.close()





以下为老师课堂笔记开始--------------------------------------
4.Socket(传输层的释义)

socket:
7层协议中的传输层:
Tcp:稳定传输,确保传输不会丢包,有重传机制,效率比较低
    三次握手建立连接、四次挥手断开连接
    Established
    time_wait
    端口会有10种左右的状态
 
Udp:不稳定传输,会丢包,默认协议是没有重传机制的,开发者
可以自己设计重传机制,效率很高,没有握手的过程

网上传输的是数据包:字符串类型(bytes类型)
python:int str float 这些数据类型无法直接在网络上传输

struct包干的事儿。


将两个整形数据,转换成了一个bytes的字符串。转换成包
在网络上进行传输。实现了python的数据类型可以在网络上传输了



5.数据压缩成包,解包的过程
5.1.数据压缩成包
import struct    
a = 20    
b = 400    
s = struct.pack("ii", a, b) #转换后的str虽然是字符串类型,但相当于其他语言中的字节流(字节数组),可以在网络上传输    
print ('length:', len(s))    
print (s)    
print (repr(s))   
      
s = struct.pack("2i", a, b) #转换后的str虽然是字符串类型,但相当于其他语言中的字节流(字节数组),可以在网络上传输    
print ('length:', len(s))    
print (s)    
print (repr(s))

执行结果:

E:\工作\测试开发\0-学习书本+上课课件\第十七章:Socket>python socket.py
length: 8
b'\x14\x00\x00\x00\x90\x01\x00\x00'
b'\x14\x00\x00\x00\x90\x01\x00\x00'
length: 8
b'\x14\x00\x00\x00\x90\x01\x00\x00'
b'\x14\x00\x00\x00\x90\x01\x00\x00'

E:\工作\测试开发\0-学习书本+上课课件\第十七章:Socket>



5.2.数据解包
import struct
s = struct.pack("ii", 20, 400)  
a1, a2 = struct.unpack("ii", s)  
print ('a1:', a1)  
print ('a2:', a2 )


6.即时走即时关闭UDP

6.1.服务器

from socket import *
from time import ctime

HOST = ''
PORT = 1200
BUFSIZ = 128
ADDR = (HOST, PORT)

# 创建一个服务器端UDP套接字
udpServer = socket(AF_INET, SOCK_DGRAM)
# 绑定服务器套接字
udpServer.bind(ADDR)
print('已经进入监听状态...')
# 接收来自客户端的数据
data, addr = udpServer.recvfrom(BUFSIZ)
print(u"得到客户端数据:",data.decode("utf-8"))
# 向客户端发送数据
udpServer.sendto(b'%s %s[%s]' % ("服务器发送消息:".encode("utf-8"),ctime().encode("utf-8"),data),addr)
print('向客户端发送数据:', data)
udpServer.close()




6.2.客户端


#encoding=utf-8
from socket import *

HOST = 'localhost'
PORT = 1200
BUFSIZ = 128
ADDR = (HOST, PORT)

# 创建客户端UDP套接字
udpClient = socket(AF_INET, SOCK_DGRAM)
data = input('>')
# 向服务器端发送数据
udpClient.sendto(data.encode("utf-8"), ADDR)
# 接收来自服务器端的数据
data, ADDR = udpClient.recvfrom(BUFSIZ)
print(data.decode("utf-8"))
udpClient.close()

7.对客户端传入数据做简单处理

7.1.服务器端代码
from socket import *
from time import ctime

HOST ''
PORT = 1200
BUFSIZ = 128
ADDR = (HOST, PORT)

# 创建一个服务器端UDP套接字
udpServer = socket(AF_INET, SOCK_DGRAM)
# 绑定服务器套接字
udpServer.bind(ADDR)
print('已经进入监听状态...')
# 接收来自客户端的数据
data, addr = udpServer.recvfrom(BUFSIZ)
print(u"得到客户端数据:",data.decode("utf-8"))
# 向客户端发送数据
udpServer.sendto(b'%s %s[%s]' % ("服务器发送消息:".encode("utf-8"),ctime().encode("utf-8"),data),addr)
print('向客户端发送数据:', data)
udpServer.close()


7.2.客户端代码
#encoding=utf-8
if __name__ == '__main__':
    import socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('localhost', 8009))
    import time
    time.sleep(2)
    sock.send('2'.encode("utf-8"))
    print(sock.recv(1023).decode("utf-8"))
    sock.send('1'.encode("utf-8"))
    print(sock.recv(1024).decode("utf-8"))
    sock.send('close'.encode("utf-8"))
    print(sock.recv(1024).decode("utf-8"))
    print("Done!")
    sock.close()



8.客户端与服务端传输时注意事项
客户端传文件的操作:
rb:把文件内容(不管是二进制文件,还是文本文件)都使用二进制读出,
类型就是bytes,可以直接传输。

r:把文件内容(文本文件)使用的文本读出,类型就是str,所以无法直接传输
  你还必须encode为bytes类型才可以传输。

服务器端:
wb:收到的bytes类型直接往里写即可。
w:你收到的bytes类型,需要decode后,才能写入open要指定文件的编码。


9.进程好还是线程好
进程在CPU运行时不需要切换进程上下文
服务端
多线程一般是IO的请求,读数据上的一个文件,操作服务器上的数据库。
多线程节省操作系统资源



10.实现多进程机制可运行版本
from socketserver import TCPServer, ForkingMixIn, StreamRequestHandler
import time

class Server(ForkingMixIn, TCPServer):  # 自定义Server类
    pass

class MyHandler(StreamRequestHandler):

    def handle(self):  # 重写父类的handle函数
        timeout_telnet = float(6.0)
        self.connection.settimeout(timeout_telnet)
        try:
            addr = self.request.getpeername()
            print(u'得到得请求是从客户端:', addr)  # 打印客户端地址
            while 1:
                data = self.rfile.readline().strip()  # 客户端发送的信息必须带有回车,否则会一直等待客户端继续发送数据
                print(data)
                if data==b"":
                    break
                time.sleep(1)  # 休眠5秒钟
                if data:
                    self.wfile.write(u'这是从服务端进程中发出得消息'.encode("utf-8"))  # 给客户端发送信息
        except:
            print('超时')
host ''
port = 18101
server = Server((host, port), MyHandler)

11.服务端启动服务

# encoding=utf-8

from socketserver import TCPServer, ThreadingMixIn, StreamRequestHandler
import time


class Server(ThreadingMixIn, TCPServer):  # 自定义Server类
    pass


class MyHandler(StreamRequestHandler):

    def handle(self):  # 重载handle函数
        addr = self.request.getpeername()
        print(u'得到得请求是从客户端:', addr)  # 打印客户端地址
        data = self.rfile.readline().strip()  # 客户端发送的信息必须带有回车,否则会一直等待客户端继续发送数据
        print(data)
        time.sleep(1)  # 休眠5秒钟
        if data:
            self.wfile.write(u'这是从服务端线程中发出得消息'.encode("utf-8"))  # 给客户端发送信息


host ''
port = 8001
server = Server((host, port), MyHandler)

server.serve_forever()  # 开始侦听并处理连接


12.线程池
# encoding=utf-8

from socketserver import TCPServer, ThreadingMixIn, StreamRequestHandler
import time


class Server(ThreadingMixIn, TCPServer):  # 自定义Server类
    pass


class MyHandler(StreamRequestHandler):

    def handle(self):  # 重载handle函数
        addr = self.request.getpeername()
        print(u'得到得请求是从客户端:', addr)  # 打印客户端地址
        data = self.rfile.readline().strip()  # 客户端发送的信息必须带有回车,否则会一直等待客户端继续发送数据
        print(data)
        time.sleep(1)  # 休眠5秒钟
        if data:
            self.wfile.write(u'这是从服务端线程中发出得消息'.encode("utf-8"))  # 给客户端发送信息


host = ''
port = 8001
server = Server((host, port), MyHandler)

server.serve_forever()  # 开始侦听并处理连接



if __name__ == '__main__':
    import socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('127.0.0.1', 8001))
    import time
    time.sleep(2)
    sock.send('ls -al /home/wxh'.encode("utf-8")+"\n".encode("utf-8"))
    print (sock.recv(1024).decode("utf-8"))
    sock.close()
13.用tcpserver传server

host='127.0.0.1'
port=12302
ADDR=(host,port)
import time

class MyRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        print(':', self.client_address)
        while True:
            fileinfo_size=struct.calcsize('128sl') #定义文件信息。128s表示文件名为128bytes长,l表示一个int或log文件类型,在此为文件大小
            self.buf = self.request.recv(fileinfo_size)
            if self.buf: #如果不加这个if,第一个文件传输完成后会自动走到下一句
                self.filename,self.filesize =struct.unpack('128sl',self.buf) #根据128sl解包文件信息,与client端的打包规则相同
                print(u'文件内容大小: ',self.filesize,u'文件名字大小: ',len(self.filename)) #文件名长度为128,大于文件名实际长度
                self.filenewname = os.path.join('d:\\downloads',('new_%s_' % (time.time())+ self.filename.decode("utf-8")).strip('\00')) #使用strip()删除打包时附加的多余空字符
                print(self.filenewname,type(self.filenewname))
                recvd_size = 0 #定义接收了的文件大小
                file = open(self.filenewname,'wb')
                print(u'开始接收...')
                while not recvd_size == self.filesize:
                    if self.filesize - recvd_size > 1024:
                        rdata = self.request.recv(1024)
                        recvd_size += len(rdata)
                    else:
                        rdata = self.request.recv(self.filesize - recvd_size)
                        recvd_size = self.filesize
                    file.write(rdata)
                file.close()
                print(u'接收完毕')
        #self.request.close()

tcpServ = socketserver.ThreadingTCPServer(ADDR, MyRequestHandler)
print(u'正在监听状态...' )
tcpServ.serve_forever()



14.用tcpserver传文件
14.1.服务端
#-*- coding: UTF-8 -*-
import socket,time,socketserver,struct,os
host='127.0.0.1'
port=12302
ADDR=(host,port)
import time

class MyRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        print(':', self.client_address)
        while True:
            fileinfo_size=struct.calcsize('128sl') #定义文件信息。128s表示文件名为128bytes长,l表示一个int或log文件类型,在此为文件大小
            self.buf = self.request.recv(fileinfo_size)
            if self.buf: #如果不加这个if,第一个文件传输完成后会自动走到下一句
                self.filename,self.filesize =struct.unpack('128sl',self.buf) #根据128sl解包文件信息,与client端的打包规则相同
                print(u'文件内容大小: ',self.filesize,u'文件名字大小: ',len(self.filename)) #文件名长度为128,大于文件名实际长度
                self.filenewname = os.path.join('E:\\zsq',('new_%s_' % (time.time())+ self.filename.decode("utf-8")).strip('\00')) #使用strip()删除打包时附加的多余空字符
                print(self.filenewname,type(self.filenewname))
                recvd_size = 0 #定义接收了的文件大小
                file = open(self.filenewname,'wb')
                print(u'开始接收...')
                while not recvd_size == self.filesize:
                    if self.filesize - recvd_size > 1024:
                        rdata = self.request.recv(1024)
                        recvd_size += len(rdata)
                    else:
                        rdata = self.request.recv(self.filesize - recvd_size)
                        recvd_size = self.filesize
                    file.write(rdata)
                file.close()
                print(u'接收完毕')
        #self.request.close()

tcpServ = socketserver.ThreadingTCPServer(ADDR, MyRequestHandler)
print(u'正在监听状态...' )
tcpServ.serve_forever()

14.2.客户端

import socket, os, struct

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 12302))

while True:
    filepath = input(u'文件绝对路径:\r\n')
    if os.path.isfile(filepath):
        fileinfo_size = struct.calcsize('128sl')  # 定义打包规则
        # 定义文件头信息,包含文件名和文件大小
        fhead = struct.pack('128sl', os.path.basename(filepath).encode("utf-8"), os.stat(filepath).st_size)
        s.send(fhead)
        print (u'客户端传输文件绝对路径: ', filepath)
        # with open(filepath,'rb') as fo: 这样发送文件有问题,发送完成后还会发一些东西过去
        fo = open(filepath, 'rb')
        while True:
            filedata = fo.read(1024)
            if not filedata:
                break
            s.send(filedata)
        fo.close()
        print (u'传输完成...')
        # s.close()



15.Select的用法(select是一个单线程)
s.bind(('127.0.0.1', 8888))
s.listen(5)
r_list = [s, ]
num = 0
while True:
    print(u"开始进入监听状态...")
    rl, wl, error = select.select(r_list, [], [], 10)#所有的过程都是单线程
要操作的对象都会放在rl里面的
    # 第一次执行循环体:客户端建立的连接的时候,rl和r_list分别是[s,]和[s,]
    #                  执行连接之后,r_list变为了[s,conn],建立连接会走if逻辑
    # 第二次执行循环体:有需要读取的时候,rl和r_list分别是[conn,]和[s,conn],执行else逻辑
    # 。。。。。如果客户端没有发送消息rl是[]
    ##第n次执行循环体:rl和r_list分别是[conn,]和[s,conn],执行else逻辑
    #简单来说rl会在建立连接后,添加socket对象,但是以后就不会在添加socket对象了,
    #因为建立连接的事件只会被select监听到一次。
    #然后select就一直监听已经建立的连接对象是否有数据发来了。当有异常的时候,会把链接对象从rl中删除掉。
    num += 1
    print(u'执行次数%s'% num)
    print("rl's length is %s" % len(rl))
    print("r_list length %s" % len(r_list))
    print([i for i in rl])
    for fd in rl:
        if fd == s:#客户端连接的时候会被触发一次
            conn, addr = fd.accept()#建立连接
            r_list.append(conn)#加入到监听列表里面
            msg = conn.recv(200)#收取从客户端来的数据
            conn.sendall(('first----%s' % msg.upper()).encode("utf-8"))#给客户端回复数据
        else:#客户端建立连接后发来了新的消息,会执行else分支
            try:
                msg = fd.recv(200)
                fd.sendall(msg.upper())#把收到的数据转换为大写后,发回给客户端
            except (ConnectionAbortedError, ConnectionResetError):
                r_list.remove(fd)
s.close()

15.1.Select的解释

fd_r_list, fd_w_list, fd_e_list = select.select(rlist, wlist, xlist, [timeout])

参数: 可接受四个参数(前三个必须)
rlist: 等待准备阅读
wlist: 等待准备写作(一般不使用)
xlist: 等待“例外情况”
timeout: 超时时间,表示多少秒监听一次,如果为None或者为空则阻塞直到至少有一个文件描述符已经准备好了。

s.bind(('127.0.0.1', 8888))
s.listen(5)
r_list = [s, ]
num = 0
while True:
    print(u"开始进入监听状态...")
    rl, wl, error = select.select(r_list, [], [], 10)
    # 第一次执行循环体:客户端建立的连接的时候,rl和r_list分别是[s,]和[s,]
    #                  执行连接之后,r_list变为了[s,conn],建立连接会走if逻辑
    # 第二次执行循环体:有需要读取的时候,rl和r_list分别是[conn,]和[s,conn],执行else逻辑
    # 。。。。。如果客户端没有发送消息rl是[]
    ##第n次执行循环体:rl和r_list分别是[conn,]和[s,conn],执行else逻辑
    #简单来说rl会在建立连接后,添加socket对象,但是以后就不会在添加socket对象了,
    #因为建立连接的事件只会被select监听到一次。
    #然后select就一直监听已经建立的连接对象是否有数据发来了。当有异常的时候,会把链接对象从rl中删除掉。
    num += 1
    print(u'执行次数%s'% num)
    print("rl's length is %s" % len(rl))
    print("r_list length %s" % len(r_list))
    print([i for i in rl])
    for fd in rl:
        if fd == s:#客户端连接的时候会被触发一次
            conn, addr = fd.accept()#建立连接
            r_list.append(conn)#加入到监听列表里面
            msg = conn.recv(200)#收取从客户端来的数据
            conn.sendall(('first----%s' % msg.upper()).encode("utf-8"))#给客户端回复数据
        else:#客户端建立连接后发来了新的消息,会执行else分支
            try:
                msg = fd.recv(200)
                fd.sendall(msg.upper())#把收到的数据转换为大写后,发回给客户端
            except (ConnectionAbortedError, ConnectionResetError):
                r_list.remove(fd)
s.close()


15.2.服务端的情况
r_list:select需要监听的文件对象。
开始的时候我初始化这个列表:r_list = [s, ] 
把socket放进去了。

1)开始执行:rl, wl, error = select.select(r_list, [], [], 10)
等待10秒,看看有没有人来建立连接,如果没有
2)代码继续执行从num += 1开始执行,然后执行到if,if和else并没有被触发
因为rl里面是空,什么都没有。
继续下一次循环,从1开始执行,如此反复。


如果有客户端进行连接了呢?开始代码向下执行:
rl从[]---->[s],
代码继续执行从num += 1开始执行,然后执行到if
if 的条件被触发了,客户端和server的连接被建立了。
    if fd == s:#客户端连接的时候会被触发一次
            conn, addr = fd.accept()#建立连接
            r_list.append(conn)#加入到监听列表里面
            msg = conn.recv(200)#收取从客户端来的数据
            conn.sendall(('first----%s' % msg.upper()).encode("utf-8"))#给客户端回复数据
结束当前循环,继续从1)开始。这个时候除了监控s对象有没有新建连接
请求外,conn也被监控看看客户端是否发来了数据。
如果没有新建链接也没有conn被发来数据,rl=[] -->if和else都不会被执行
继续下一次循环,从1)开始执行,如此反复。



r_list包含了conn对象(被监听),如果客户端发来数据,
rl, wl, error = select.select(r_list, [], [], 10)
rl=[] 变成了[conn],会触发else:操作,从连接中读取发来的数据,并
转换为大写之后返回给客户端

try:
                msg = fd.recv(200)
                fd.sendall(msg.upper())#把收到的数据转换为大写后,发回给客户端
            except (ConnectionAbortedError, ConnectionResetError):
                r_list.remove(fd)
继续下一次循环,从1)开始执行,如此反复。

15.3.客户端的情况 

rl:客户端既没有连接,有没有发送数据的情况下,rl一直是[]
只有在客户端有动作的时候,
它才从[]--->[s]或者[conn1]或者[conn1,conn2...]
1)新建连接的请求,s(socket对象)
2)已经建立连接后发送数据的连接对象conn对象(可能是一个也可能是多个。)

客户端的代码

import socket
flag = 1
s = socket.socket()
s.connect(('127.0.0.1', 8888))
while flag:
    input_msg = input('input>>>')
    if input_msg == '0':
        break
    s.sendall(input_msg.encode())
    msg = s.recv(1024)
    print(msg.decode())
s.close()

16.Twisster
16.1.定义工厂方法
1)定义一个工厂类EchoFactory,类里面需要实现一个协议的方法buildProtocol
2)这个方法里面必须返回一个继承自Protocol类实例
3)定义一下Protocol类Echo---》具体管理建立连接、断开连接、收到数据后的
动作等
4)FACTORY = EchoFactory()#生成工厂
5)reactor.listenTCP(1200, FACTORY) #基于工厂类实例进行监听
# 开始监听事件
print(u"开始进入监听状态...")
6)运行服务 reactor.run()
16.2.服务端

# coding: utf-8
from twisted.internet.protocol import Protocol
from twisted.internet.protocol import Factory
from twisted.internet import reactor


class Echo(Protocol):#协议类:怎么管理连接和收到数据时候的处理机制的。
    '''协议类实现用户的服务协议,例如 http,ftp,ssh 等'''

    def __init__(self, factory):
        self.factory = factory

    def connectionMade(self):#当建立连接的时候你要干什么
        '''连接建立时被回调的方法'''
        #基于工厂的类变量+1
        self.factory.numProtocols = self.factory.numProtocols + 1 #
        #给客户端发送数据的
        self.transport.write("目前有 %d 个开放式连接\n".encode("utf-8") % (self.factory.numProtocols,))

    def connectionLost(self, reason):
        '''连接关闭时被回调的方法,把工厂的类变量-1'''
        self.factory.numProtocols = self.factory.numProtocols - 1

    def dataReceived(self, data):
        '''接收数据的函数,当有数据到达时被回调'''
        print(data.decode("utf-8"))
        self.transport.write(data) #收到数据的时候,原封不动给客户端返回

class EchoFactory(Factory):
    '''协议工厂类,当客户端建立连接的时候,创建协议对象,协议对象与客户端连接一一对应'''
    numProtocols = 0#用于统计多少客户端和服务端建立连接了。

    # protocol = Echo
    def buildProtocol(self, addr):
        return Echo(self)#返回了一个协议类的实例


if __name__ == '__main__':
    # 创建监听端口
    FACTORY = EchoFactory()
    reactor.listenTCP(1200, FACTORY)
    # 开始监听事件
    print(u"开始进入监听状态...")
reactor.run()


16.3.客户端
if __name__ == '__main__':
    import socket  
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
    sock.connect(('localhost', 1200))  
    import time  
    time.sleep(2)  
    sock.send('ls -al /home/wxh'.encode("utf-8")+"\n".encode("utf-8"))  
    print("收到内容:",sock.recv(1024).decode("utf-8") ) 
    sock.send('ipconfig'.encode("utf-8")+"\n".encode("utf-8"))  
    print("收到内容:",sock.recv(1024).decode("utf-8"))  
    sock.close()


17.聊天的案例
单线程处理并发
17.1.客户端
# socket client end
from socket import *
import time
s = socket(AF_INET, SOCK_STREAM)
remote_host = gethostname()
print ('remote_host:', remote_host)
port = 1200
s.connect((remote_host, port))  # 发起连接
print (u"连接从", s.getsockname())  ##返回本地IP和端口
print (u"连接到", s.getpeername())  ##返回服务端IP和端口

print (u'从服务器返回消息:')
print (s.recv(1200).decode("utf-8").strip())

username = input("请输入你要使用的英文用户名:\n")
s.send(('%s\r\n' %username.strip()).encode("utf-8"))  # 发送一行字符串(以\r\n 结束)到服务器端
print (u'从服务器返回消息:')
print (s.recv(1200).decode("utf-8").strip())
print("*"*50)
print("""查看当前登录用户列表的命令:list
查看别人给你发送的消息命令要求:getmessage 
给别人发送消息的数据格式:username:要发送的消息  
""")
print("*"*50)


while 1:
    send_message=input("请输入发送的信息:\n")    
    if send_message=="getmessage" :
        s.send(('%s:%s\r\n' %(username,send_message)).encode("utf-8"))
        print (u'从服务器返回消息:')
        print (s.recv(1200).decode("utf-8").strip())
    elif send_message=="list":
        s.send(('%s\r\n' %send_message).encode("utf-8"))
        print (u'从服务器返回消息:')
        print (s.recv(1200).decode("utf-8").strip())
    elif send_message=="bye":
        s.send(('%s\r\n' %send_message).encode("utf-8"))
        print (u'从服务器返回消息:')
        print (s.recv(1200).decode("utf-8").strip())
        s.close()
        break
    else:
        s.send(('%s\r\n' %send_message.strip()).encode("utf-8"))
        time.sleep(1)
        Continue


17.2.服务端
# encoding=utf-8
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver  # 事件处理器
from twisted.internet import reactor

class Chat(LineReceiver):
    message_dict={} #聊天的人发送的消息都会存这个字典里面,
                    #key:收这些消息的客户端name
    def __init__(self, users):
        self.users = users  #工程类中定义的字典赋值给了self.users
        self.name = None   #默认聊天人的名字是None
        self.state = "GETNAME" #聊天人的状态是GETNAME,
                               #要求聊天人的客户端必须先起一个名字

    def connectionMade(self):  # 连接开始,且开始做处理。
                               #每一个客户端的连接都是一个Chat的实例
                               #自动生成的实例,连接建立就自动生成了。
        if self.name is None:  #如果name是None,则给客户端发消息,你叫什么名字
            self.sendLine(u"你叫什么名字?".encode("utf-8"))

    def connectionLost(self, reason):#连接断开的断开,会从2个字典删除此用户信息。
        if self.name in self.users:
            del self.users[self.name]
            try:
                del Chat.message_dict[self.name]
            except:
                print("删除用户的聊天记录失败")


    def lineReceived(self, line):  # 我收到客户端给我发送的消息
                                   #line参数是客户端发给服务器的消息,自动存到line
        if self.state == "GETNAME" and "getmessage" not in line.decode("utf-8"):  # 根据状态开始选择不同得内容处理
            print("line:",line)
            self.handle_GETNAME(line.decode("utf-8"))#调用起名字的方法
        else:#否则调用聊天的方法
            self.handle_CHAT(line.decode("utf-8"))

    def handle_GETNAME(self, name):
        if name in self.users:
            self.sendLine(u"谢谢,请做另外得选择.\n".encode("utf-8") )  # 每个用户只能有一个聊天通信存在
            return
        self.sendLine(("欢迎, %s!\n"  %name).encode("utf-8"))
        self.name = name
        self.users[name] = self#连接对象是Chat的实例,也就是self,存到字典的value中
        self.state = "CHAT"


    def handle_CHAT(self, message):  # 对正在聊天得用户进行发送消息

            if ":" in message and "getmessage" not in message:#给指定用户发送一条消息
                username = message.split(":")[0]
                if username not in Chat.message_dict:
                    Chat.message_dict[username] = []

                Chat.message_dict[username].append(message)
                print(message, "---->", "增加了用户发送的消息:%s" %message , "\n")
                return
            elif "getmessage" in message:#输入getmessage的时候获取别人给你留言列表
                username = message.split(":")[0]
                print("*"*2,username,Chat.message_dict)
                if (username  not in Chat.message_dict) or  Chat.message_dict[username] == []:
                    self.users[username].sendLine("没有别人给你发送的数据".encode("utf-8"))
                    print(message,"---->","没有别人给你发送的数据","\n")
                    return
                message_indict=Chat.message_dict[username].pop(0)
                print(message_indict)
                send_message = message_indict.split(":")[1]
                username = message_indict.split(":")[0]
                if username in self.users:
                    print(username,self.users[username],self.users[username].name)
                    self.users[username].sendLine(("%s:%s" % (self.name, send_message)).encode("utf-8"))
                return
            elif message.strip() =="list":#输入list的时候触发,返回谁在线
                print("list response")
                self.sendLine((str([username for username in self.users]) + "\n").encode("utf-8"))
                print(message, "---->", (str([username for username in self.users]) + "\n"),"\n")
                return
            elif message.strip() =="bye":#输入bye的时候触发。有bug,需要添加断开连接
                print("list response")
                self.sendLine(("Bye from server!\n").encode("utf-8"))
                print(message, "---->", (str([username for username in self.users]) + "\n"),"\n")
                return
            else:#发送一条提示信息
                send_message= ("请指定用户名,按照格式‘用户名:消息’来进行发送。\n或者输入list查看当前登录用户\n输入getmessage\n")
                #print (type(send_message))
                self.sendLine(send_message.encode("utf-8"))
                print(message, "---->",send_message,"\n")
                return



class ChatFactory(Factory):#定义了工厂类
    def __init__(self):
        self.users = {}  #将所有与服务器端连接的对象存放到此字典中,所有的实例均可以使用此字典获取所有的连接对象
        #聊天人的名字:name:客户端和服务端的连接对象


    def buildProtocol(self, addr):#必须实现的方法,返回协议protocol类Chat的实例
        return Chat(self.users)


if __name__ == '__main__':#twisted启动服务
    reactor.listenTCP(1200, ChatFactory())
    print ("开始进入监听状态...")
    reactor.run()




   def handle_CHAT(self, message):  # 对正在聊天得用户进行发送消息
            #客户端输入:wulaoshi:test
            if ":" in message and "getmessage" not in message:
                username = message.split(":")[0]
                if username not in Chat.message_dict:
                    Chat.message_dict[username] = []

                Chat.message_dict[username].append(message)
                print(message, "---->", "增加了用户发送的消息:%s" %message , "\n")
                return
             #wulaoshi客户端:getmessage:wulaoshi之后,取消息
            elif "getmessage" in message:
                username = message.split(":")[0]
                print("*"*2,username,Chat.message_dict)
                if (username  not in Chat.message_dict) or  Chat.message_dict[username] == []:
                    self.users[username].sendLine("没有别人给你发送的数据".encode("utf-8"))
                    print(message,"---->","没有别人给你发送的数据","\n")
                    return
                message_indict=Chat.message_dict[username].pop(0)
                print(message_indict)
                send_message = message_indict.split(":")[1]
                username = message_indict.split(":")[0]
                if username in self.users:
                    print(username,self.users[username],self.users[username].name)
                    self.users[username].sendLine(("%s:%s" % (self.name, send_message)).encode("utf-8"))
                return

            #查看哪些用户处于登录状态
            elif message.strip() =="list":
                print("list response")
                self.sendLine((str([username for username in self.users]) + "\n").encode("utf-8"))
                print(message, "---->", (str([username for username in self.users]) + "\n"),"\n")
                return
            #客户端输入bye,打印一句话,再见,打印一下登录者的状态
            #没有实现退出的逻辑
            elif message.strip() =="bye":
                print("list response")
                self.sendLine(("Bye from server!\n").encode("utf-8"))
                print(message, "---->", (str([username for username in self.users]) + "\n"),"\n")
                return
            #如果你发的所有消息都没有被拦截住,我打印两行星号和一些提示语。
            else:
                send_message= ("请指定用户名,按照格式‘用户名:消息’来进行发送。\n或者输入list查看当前登录用户\n输入getmessage\n")
                #print (type(send_message))
                self.sendLine(send_message.encode("utf-8"))
                print(message, "---->",send_message,"\n")
                return

 

posted @ 2020-03-02 01:24  进阶的淑琴  阅读(235)  评论(0)    收藏  举报