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