Socket通讯原理描述:
什么是套接字:(微软的描述)
套接字是一个位于网络中特定节点的服务所具有的标识符。套接字包含一个节点地址和一个端口号,用来标识这一服务。
套接字是为特定网络协议(例如TCP/IP,ICMP/IP,UDP/IP等)套件对上的网络应用程序提供者提供当前可移植标准的对象。它们允许程序接受并进行连接,如发送和接受数据。为了建立通信通道,网络通信的每个端点拥有一个套接字对象极为重要。和大多数语言一样,Python 支持面向连接和无连接,实现接口功能与步骤也大致相同。
面向连接即需要先连接然后通讯, 面向连接主要协议就是传输控制协议(tcp),要创建tcp套接字时需要指定套接字类型为 SOCK_STRAM,表达了他作为流套接字的特点。
无连接,顾名思义无需建立连接就可以进行通讯,这时数据到达顺序、可靠性就无法保证了。实现这种连接的协议就是用户数据包协议(udp)。创建UDP时需要指定套接字类型为 SOCK_DGRAM。
TCP服务器端:
1. 第一步是创建socket对象。调用socket构造函数。如:
socket = socket.socket( family, type )
family参数代表地址家族,可为AF_INET或AF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用于同一台机器上的进程间通信。
type参数代表套接字类型,可为SOCK_STREAM(流套接字)和SOCK_DGRAM(数据报套接字)。
2. 第二步是将socket绑定到指定地址。这是通过socket对象的bind方法来实现的:
socket.bind( address )
由AF_INET所创建的套接字,address地址必须是一个双元素元组,格式是(host,port)。host代表主机,port代表端口号。如果端口号正在使用、主机名不正确或端口已被保留,bind方法将引发socket.error异常。
3. 第三步是使用socket套接字的listen方法接收连接请求。
socket.listen( backlog )
backlog指定最多允许多少个客户连接到服务器。它的值至少为1。收到连接请求后,这些请求需要排队,如果队列满,就拒绝请求。
4. 第四步是服务器套接字通过socket的accept方法等待客户请求一个连接。
connection, address = socket.accept()
调 用accept方法时,socket会时入“waiting”状态。客户请求连接时,方法建立连接并返回服务器。accept方法返回一个含有两个元素的 元组(connection,address)。第一个元素connection是新的socket对象,服务器必须通过它与客户通信;第二个元素 address是客户的Internet地址。
5. 第五步是处理阶段,服务器和客户端通过send和recv方法通信(传输 数据)。服务器调用send,并采用字符串形式向客户发送信息。send方法返回已发送的字符个数。服务器使用recv方法从客户接收信息。调用recv 时,服务器必须指定一个整数,它对应于可通过本次方法调用来接收的最大数据量。recv方法在接收数据时会进入“blocked”状态,最后返回一个字符 串,用它表示收到的数据。如果发送的数据量超过了recv所允许的,数据会被截短。多余的数据将缓冲于接收端。以后调用recv时,多余的数据会从缓冲区 删除(以及自上次调用recv以来,客户可能发送的其它任何数据)。
6. 传输结束,服务器调用socket的close方法关闭连接
伪代码大致如下:
1 创建套接字,绑定套接字到当地地址,然后开始监听连接。就是socket,bind,listen。
2 进入循环,不断接受客户端的连接请求,然后接收传来的数据,当然也可以发送给对方数据。就是accept一个连接,然后recv数据。
3 接收完毕可以关闭套接字,close。
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() #关闭服务器套接字
TCP客户端:
1. 第一步是创建一个socket以连接服务器:socket = socket.socket( family, type )
2. 第二步是使用socket的connect方法连接服务器。对于AF_INET家族,连接格式如下:
socket.connect( (host,port) )
host代表服务器主机名或IP,port代表服务器进程所绑定的端口号。如连接成功,客户就可通过套接字与服务器通信,如果连接失败,会引发socket.error异常。
3. 第三步是处理阶段,客户和服务器将通过send方法和recv方法通信。
4. 传输结束,客户通过调用socket的close方法关闭连接。
伪代码如下:
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() #关闭套接字
简单的server 端和客户端
即时走即时关闭的TCP服务端和客户端
服务端
#encoding=utf-8
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()
客户端
#coding:utf-8
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()
即时走即时关闭的UDP服务端和客户端
Udp Server 端:
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()
Udp 的客户端:
#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()
简单模拟和服务器通信的客户端
不加异常处理:
服务端
#encoding=utf-8
import socket #socket模块
HOST='127.0.0.1'
PORT=8085
s= socket.socket(socket.AF_INET,socket.SOCK_STREAM) #定义socket类型,网络通信,TCP
s.bind((HOST,PORT)) #套接字绑定的IP与端口
s.listen(1) #开始TCP监听,listen中的参数表示可以多少客户端来进行连接
while 1:
print(u"开始连接")
conn,addr=s.accept() #接受TCP连接,并返回新的套接字与IP地址
print('Connected by',addr) #输出客户端的IP地址
while 1:
data=conn.recv(1024) #把接收的数据实例化
if data.decode("utf-8").strip() == "bye":
break
print(data.decode("utf-8"))
conn.sendall(u'从服务得到结果:'.encode("utf-8")+data.upper())
conn.close() #关闭连接
客户端:
# encoding=utf-8
import sys
import socket
HOST = '127.0.0.1'
PORT = 8085
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 定义socket类型,网络通信,TCP
s.connect((HOST, PORT)) # 要连接的IP与端口
while 1:
cmd = input(u"向服务器发送命令:") # 与人交互,输入命令
s.sendall(cmd.encode("utf-8")) # 把命令发送给对端
if cmd == "bye":
break
data = s.recv(1024) # 把接收的数据定义为变量
print(data.decode("utf-8")) # 输出变量
s.close() # 关闭连接
加异常处理版本:
#encoding=utf-8
import socket #socket模块
HOST='127.0.0.1'
PORT=8085
s= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#定义socket类型,网络通信,TCP
s.bind((HOST,PORT)) #套接字绑定的IP与端口
s.listen(5)
#开始TCP监听,listen中的参数表示可以多少客户端来进行连接
while 1:
print("开始接收数据")
conn,addr=s.accept() #接受TCP连接,并返回新的套接字与IP地址
print('Connected by',addr) #输出客户端的IP地址
while 1:
try:
data=conn.recv(1024) #把接收的数据实例化
print(data)
conn.sendall('服务返回结果'.encode("utf-8")+data.upper())
except Exception:
conn.close() #关闭连接
break
客户端:
#encoding=utf-8
import socket
HOST='127.0.0.1'
PORT=8085
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#定义socket类型,网络通信,TCP
s.connect((HOST,PORT)) #要连接的IP与端口
times=3
while times>0:
cmd=input("向服务器发送命令:") #与人交互,输入命令
s.sendall(cmd.encode("utf-8")) #把命令发送给对端
data=s.recv(1024) #把接收的数据定义为变量,如果1024改为很小的值,则报错字符串未正常结束
print(data.decode("utf-8")) #输出变量
times-=1
s.close() #关闭连接
Send和sendall区别:
sendall:尝试发送string的所有数据, 成功则返回None, 失败则抛出异常。
send:send()的返回值式发送的字节数量, 这个数量值可能小于要发送的string的字节数,也就是说可能无法发送string中所有的数据。如果有错误,则会抛出异常。
以下两端代码是等价的:
sock.sendall("Hello world\n")
buffer = "Hello world\n" while buffer: bytes = sock.send(buffer) buffer = buffer[bytes:]
对客户端传入数据做简单处理
服务端:
#encoding=utf-8
import time
if __name__ == '__main__':
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 8009))
sock.listen(5)
while True:
print(u"服务开始监听状态")
time.sleep(0.1)
connection,address = sock.accept()
while 1:
try:
connection.settimeout(5)
buf = connection.recv(1024).decode("utf-8")
print("got message from client:",buf )
if buf == '1':
print("1" )
connection.send('您做的操作是:选择1'.encode("utf-8"))
elif buf == '2':
connection.send('您做的操作是:选择2'.encode("utf-8"))
elif buf == "close":
connection.send('您做的操作是:选择close'.encode("utf-8"))
connection.close()
break
except socket.timeout:
print('连接超时')
connection.close()
break
except Exception as e:
print(e )
connection.close()
break
客户端:
#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()
实现一个调用系统命令的功能(此例子为window)
服务器端:
import os
if __name__ == '__main__':
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 8001))
sock.listen(5)
while True:
connection,address = sock.accept()
try:
connection.settimeout(5)
command = connection.recv(1024)
print(command)
result=os.popen(command.decode("utf-8"))
connection.send(command)
connection.send(result.read().encode("utf-8"))
except socket.timeout:
print('time out')
connection.close()
客户端:
if __name__ == '__main__':
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 8001))
import time
time.sleep(2)
sock.send('ipconfig'.encode("utf-8"))
print("command:",sock.recv(10).decode("utf-8"))
print ("command result:",sock.recv(1024).decode("utf-8"))
sock.close()
给服务端发送任务,并把执行结果返回给客户端的例子:
import os
import requests
def get(url):
return requests.get(url)
if __name__ == '__main__':
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 8001))
sock.listen(5)
while True:
connection,address = sock.accept()
try:
connection.settimeout(5)
content = connection.recv(1024)
print(content.decode("utf-8"))
command,param = content.split()
command= command.decode("utf-8")
result=eval(command+"('"+param.decode("utf-8")+"')")
connection.send(result.text[:1024].encode("utf-8"))
except socket.timeout:
print('time out')
connection.close()
客户端:
if __name__ == '__main__':
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 8001))
import time
time.sleep(2)
sock.send('get http://www.sohu.com'.encode("utf-8"))
print ("command result:",sock.recv(1024).decode("utf-8"))
sock.close()
使用Socket 传送一个文件
服务端定义一个传输内容得规则,然后客户端按照此内容进行传输,服务端按照此内容进行解析。
服务器端
# -*- coding: UTF-8 -*-
import socket, time, socketserver, struct, os, _thread
host = '127.0.0.1'
port = 12307
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', 12307))
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)
if not filedata:
break
s.send(filedata)
fo.close()
print (u'传输成功')
# s.close()
#12sl表示文件名是12个字符l表示一个数字,12个字符表示的是文件名,1个数字表示的是文件长度。如果长度不匹配,则可能会报错。本例子中我使用的文件是:d:\\97gg.txt
socket.send(string[, flags]) 发送TCP数据,返回发送的字节大小。这个字节长度可能少于实际要发送的数据的长度。换句话说,这个函数执行一次,并不一定能发送完给定的数据,可能需要重复多次才能发送完成。
socket.sendall(string[, flags])发送完整的TCP数据,成功返回None,失败抛出异常
Socket实现多连接:
# -*- coding: utf-8 -*-
import socket
HOST = '127.0.0.1' # 本地localhost
PORT = 50008
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(5)
while True:
print (u"开始进入监听状态")
conn,addr = s.accept()#接收连接
print ("开始接收连接从: ",addr)
while True:
try:
data = conn.recv(1024)
if not data:
break
print ("收到",data)
msg = u"这是一个多进程服务测试"
conn.sendall(msg.encode("utf-8"))
except socket.error:
break
conn.close()
客户端:
#encoding=utf-8
import socket
HOST='127.0.0.1'
PORT=50008
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#定义socket类型,网络通信,TCP
s.connect((HOST,PORT)) #要连接的IP与端口
times=3
while times>0:
cmd=input("向服务器发送命令:") #与人交互,输入命令
s.sendall(cmd.encode("utf-8")) #把命令发送给对端
data=s.recv(1024) #把接收的数据定义为变量
print (data.decode("utf-8")) #输出变量
times-=1
s.close() #关闭连接
上面例子算狭义上实现多用户访问服务,但是都是在同步执行也就是一个用户连接关闭下个用户才可以开始执行向服务发送请求执行内容。
其实现的核心是什么呢?
是因为服务器接收连接部分写在死循环内可以一直保持接收新用户端发起的请求的状态。
SockerServer
socket编程在模块创建时无法进行多进程得处理,需要大量请求时,请求就会阻塞在队列中,甚至发生请求丢弃,如果需要大量socket就需要许多得socket绑定端口。写很多重复性得代码。
SocketServer简化了网络服务器的编写。在进行socket创建时,使用SocketServer会大大减少创建的步骤,并且SocketServer使用了select它有4个类:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer。这4个类是同步进行处理的,另外通过ForkingMixIn和ThreadingMixIn类来支持异步。
ForkingMixIn和ThreadingMixIn两个混合类,它们都提供Server类中process_request方法的新实现,前者在处理每次用户连接的时候都会开启新的进程,而后者会开启新的线程。想要让Server类实现并发处理,只用利用多重继承即可,或者直接使用已经混合好的
步骤
1、 创建一个请求处理的类,是BaseRequestHandler的子类并重写其handle方法
2、 实例化一个服务器类,传入服务器的地址和请求处理的程序类
3、 调用handle_request()一般是调用其他事件循环或者使用select或serve_forever
集成ThreadingMixIn类时需要处理异常关闭。daemon_threads指示服务器是否要等待线程终止,要是线程互相独立,必须要设置为True,默认是False。
BaseServer
↑
TCPServer ThreadingMixIn/ ForkingMixIn
↑ ↑
ThreadingTCPServer/ForkingTCPServer
BaseServer
↑
UDPServer ThreadingMixIn/ForkingMixIn
↑ ↑
ThreadingUDPServer /ForkingUDPServer
SocketServer模块的Fork方式(进程模式)linux 下执行
Server 端代码
# encoding=utf-8
from socketserver import TCPServer, ForkingMixIn, StreamRequestHandler
import time
class Server(ForkingMixIn, 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()
注释:
多个连接同时到达服务器端的时候,每个连接主进程都生成一个子进程专门用来处理此连接,而主进程则依旧保持在侦听状态。因主进程和子进程是同时进行的,所以不会阻塞新的连接。但由于生成进程消耗的资源比较大,这种处理方式在有很多连接的时候会带来性能问题。
SocketServer模块的Fork方式(线程模式)linux 下执行
服务器端代码:
# 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()
线程是一种轻量级的进程,比Fork消耗的资源更少,而且主线程和子线程之间具有相同的地址空间,处理效率高。但大量的使用线程会带来线程之间的数据同步问题,处理不好可能使服务程序失去响应。上述与Fork方式中代码基本相同,仅仅是采用的ThreadingMixIn类不同。
SocketServer模块的Threading线程池方式
服务端
# encoding=utf-8
import socketserver
import threading
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
while True:
self.data = self.request.recv(1024).strip()
cur_thread = threading.current_thread()
print(cur_thread)
if not self.data:
print(u"客户端:%s 退出!" % self.client_address[0])
break
print(u"%s 内容:%s" % (self.client_address[0], self.data.decode("utf-8")))
self.request.sendall(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "", 8001
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
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()
使用socketserver 传送一个文件
服务端:
#-*- 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('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()
客户端:
# -*- coding: UTF-8 -*-
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()
Select (单线程实现多线程效果核心):
文件描述符:
内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。
select 模块支持 C 中常用的 IO 复用,如:
select:在 windows,Unix 和 Linux 下均可使用,但在 windows 下,select 只能用于处理 socket
poll:在 windows 下不可用
epoll:只有Linux 2.5.44 以上版本支持
kqueue:只有 BSD 系统支持
kevent:只有 BSD 系统支持
I/O多路复用
监听多个描述符的状态,如果描述符状态改变,则会被内核修改标志位,从而被进程获取进而进行读写操作
I/O多路复用是在单线程模式下实现多线程的效果,实现一个多I/O并发的效果。
进程指定内核监听哪些文件描述符(最多监听1024个fd)的哪些事件,当没有文件描述符事件发生时,进程被阻塞;当一个或者多个文件描述符事件发生时,进程被唤醒。
当我们调用select()时:
1、上下文切换转换为内核态(当一个任务(进程)执行系统调用而执行内核代码时,称进程处于内核内核态,此时处理器处于特权级最高的(0级)内核代码中执行)
2、将fd从用户空间复制到内核空间
3、内核遍历所有fd,查看其对应事件是否发生
4、如果没发生,将进程阻塞,当设备驱动产生中断或者timeout时间后,将进程唤醒,再次进行遍历
5、返回遍历后的fd
6、将fd从内核空间复制到用户空间
fd:file descriptor 文件描述符
fd_r_list, fd_w_list, fd_e_list = select.select(rlist, wlist, xlist, [timeout])
参数:可接受四个参数(前三个必须)
rlist: 等待准备阅读
wlist: 等待准备写作(一般不使用)
xlist: 等待“例外情况”
timeout: 超时时间,表示多少秒监听一次,如果为None或者为空则阻塞直到至少有一个文件描述符已经准备好了。
在python中,select函数是一个对底层操作系统的直接访问的接口。它用来监控sockets、files和pipes,等待IO完成(Waiting for I/O completion)。当有可读、可写或是异常事件产生时,select可以很容易的监控到。
select.select(rlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。第四个是一个可选参数,表示超时秒数。其返回3个tuple,每个tuple都是一个准备好的对象列表,它和前边的参数是一样的顺序。
Server端:
#encoding=utf-8
import socket
import select
s = socket.socket()
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:
try:
msg = fd.recv(200)
fd.sendall(msg.upper())
except (ConnectionAbortedError, ConnectionResetError):
r_list.remove(fd)
s.close()
客户端:
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()
Twisted 框架
Twisted是用Python实现的基于事件驱动的网络引擎框架。Twisted诞生于2000年初,在当时的网络游戏开发者看来,无论他们使用哪种语言,手中都鲜有可兼顾扩展性及跨平台的网络库。Twisted的作者试图在当时现有的环境下开发游戏,这一步走的非常艰难,他们迫切地需要一个可扩展性高、基于事件驱动、跨平台的网络开发框架,为此他们决定自己实现一个,并从那些之前的游戏和网络应用程序的开发者中学习,汲取他们的经验教训。
Twisted支持许多常见的传输及应用层协议,包括TCP、UDP、SSL/TLS、HTTP、IMAP、SSH、IRC以及FTP。就像python一样,Twisted也具有“内置电池”(batteries-included)的特点。Twisted对于其支持的所有协议都带有客户端和服务器实现,同时附带有基于命令行的工具,使得配置和部署产品级的Twisted应用变得非常方便。
当我们面对如下的环境时,事件驱动模型通常是一个好的选择:
1.程序中有许多任务;
2.任务之间高度独立(因此它们不需要互相通信,或者等待彼此);
3.在等待事件到来时,某些任务会阻塞。
Twisted中的客户端和服务器是用Python开发的,采用了一致性的接口。这使得开发新的客户端和服务器变得很容易实现,可以在客户端和服务器之间共享代码,在协议之间共享应用逻辑,以及对某个实现的代码做测试。
安装
安装pywin32
py -3 -m pip install twisted
Pywin32作用:Python是没有自带访问windows系统API的库的,安装方法
py -3 -m pip install Pywin32
Twisted组成:
Reactor
在单线程环境中调度多个事件源产生的事件到它们各自的事件处理例程中去。
Transports
是用来收发数据,服务器端与客户端的数据收发与处理都是基于这个模块
Transports代表网络中两个通信结点之间的连接。Transports负责描述连接的细节,比如连接是面向流式的还是面向数据报的,流控以及可靠性。TCP、UDP和Unix套接字可作为transports的例子。它们被设计为“满足最小功能单元,同时具有最大程度的可复用性”,而且从协议实现中分离出来,这让许多协议可以采用相同类型的传输。Transports实现了ITransports接口,它包含如下的方法:
将transports从协议中分离出来也使得对这两个层次的测试变得更加简单。可以通过简单地写入一个字符串来模拟传输,用这种方式来检查。
Protocols
ProtocolFactory: 是工厂模式的体现,在这里面生成协议
Protocol对象实现协议内容,即通信的内容协议
Protocols描述了如何以异步的方式处理网络中的事件。TCP、UDP应用层例子,Protocols实现了IProtocol接口,它包含如下的方法:
Python3.6下的安装
方法1:
py -3 -m pip install twisted
但是每次都是下载失败
方法2:手动下载包进行安装
https://pypi.org/project/zope.interface/#files
安装zope.interface,下载32位:zope.interface-4.5.0-cp36-cp36m-win32.whl
或者64位:zope.interface-4.5.0-cp36-cp36m-win_amd64.whl
安装方法:py -3 -m pip install zope.interface-4.5.0-cp36-cp36m-win_amd64.whl
py -3 -m pip install zope.interface-4.5.0-cp36-cp36m-win32.whl
https://pypi.org/project/Twisted/
下载Twisted-18.7.0.tar.bz2 ,解压缩后,进入解压目录执行 py -3 setup.py install
执行服务器端代码,会报如下错误:
File "c:\python3.6\lib\site-packages\twisted\python\lockfile.py", line 52, in
_open = file
NameError: name 'file' is not defined
找到lockfile.py的文件,将_open = file修改为_open = open即可以解决
方法3:
py -3 -m pip install C:\Users\lenovo\Downloads\Twisted-18.9.0-cp36-cp36m-win_amd64.whl
twisted 官网的例子
服务端(保存成)
# 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):
'''连接建立时被回调的方法'''
self.factory.numProtocols = self.factory.numProtocols + 1
self.transport.write("目前有 %d 个开放式连接\n".encode("utf-8") % (self.factory.numProtocols,))
def connectionLost(self, reason):
'''连接关闭时被回调的方法'''
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()
协议工厂继承自twisted.internet.protocol.Factory,需实现buildProtocol方法,协议工厂负责实例化协议类,不应该保存于连接相关的状态信息,因为协议工厂类仅创建一个。协议类继承自twisted.internet.protocol.Protocol,需实现dataReceived等方法,在协议类中实现应用协议,每一个客户端连接都会创建一个新的协议类对象。transport就是连接对象,通过它进行网络写数据。
客户端:
# coding: utf-8
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()
twisted 服务端客户端应用
服务器端:
# coding=utf-8
from twisted.internet.protocol import Protocol
from twisted.internet.protocol import Factory
from twisted.internet.endpoints import TCP4ServerEndpoint
from twisted.internet import reactor
clients = []
class Spreader(Protocol):
def __init__(self, factory):
self.factory = factory
def connectionMade(self):
self.factory.numProtocols = self.factory.numProtocols + 1
self.transport.write(
"欢迎来到Spread Site, 你是第%s个客户端用户!\n".encode("utf-8") % (str(self.factory.numProtocols).encode("utf-8"))
)
print(u"新连接: %d" % (self.factory.numProtocols))
clients.append(self)
def connectionLost(self, reason):
self.factory.numProtocols = self.factory.numProtocols - 1
clients.remove(self)
print(u"断开连接: %d" % (self.factory.numProtocols))
def dataReceived(self, data):
print(u"接收数据:",data.decode("utf-8"))
self.transport.write(data)
class SpreadFactory(Factory):
def __init__(self):
self.numProtocols = 0
def buildProtocol(self, addr):
return Spreader(self)
if __name__=="__main__":
endpoint = TCP4ServerEndpoint(reactor, 8007)
endpoint.listen(SpreadFactory())
print(u"开始进入监听状态...")
reactor.run()
客户端:
# coding=utf-8
from twisted.internet.protocol import Protocol, ClientFactory
from twisted.internet import reactor
import threading
import time
import sys
import datetime
class Echo(Protocol):
def __init__(self):
self.connected = False
def connectionMade(self):
self.connected = True
def connectionLost(self, reason):
self.connected = False
def dataReceived(self, data):
print(u"接收到服务器端传过来得消息:", data.decode("utf-8"))
class EchoClientFactory(ClientFactory):
def __init__(self):
self.protocol = None
def startedConnecting(self, connector):
print(u"开始进入连接...")
def buildProtocol(self, addr):
print(u"已经连接...")
self.protocol = Echo()
return self.protocol
def clientConnectionLost(self, connector, reason):
print(u"断开连接. 原因: ", reason)
def clientConnectionFailed(self, connector, reason):
print(u"连接失败,原因: ", reason)
bStop = False
def routine(factory):
while not bStop:
if factory.protocol and factory.protocol.connected:
factory.protocol.transport.write("你好,我是 %s %s".encode("utf-8") % (
sys.argv[0].encode("utf-8"), str(datetime.datetime.now()).encode("utf-8")
))
print(sys.argv[0], datetime.datetime.now())
time.sleep(5)
if __name__=="__main__":
host = '127.0.0.1'
port = 8007
factory = EchoClientFactory()
reactor.connectTCP(host, port, factory)#连接服务端
threading.Thread(target=routine, args=(factory,)).start() #给服务端发消息
reactor.run()
bStop = True #结束发送消息
使用守护进程来运行服务器端程序
守护进程是一个在后台运行并且不受任何终端控制的进程。Unix操作系统有很多典型的守护进程(其数目根据需要或20—50不等),它们在后台运行,执行不同的管理任务。
用户使守护进程独立于所有终端是因为,在守护进程从一个终端启动的情况下,这同一个终端可能被其他的用户使用。例如,用户从一个终端启动守护进程后退出,然后另外一个人也登录到这个终端。用户不希望后者在使用该终端的过程中,接收到守护进程的任何错误信息。同样,由终端键人的任何信号(例如中断信号)也不应该影响先前在该终端启动的任何守护进程的运行。虽然让服务器后台运行很容易(只要shell命令行以&结尾即可),但用户还应该做些工作,让程序本身能够自动进入后台,且不依赖于任何终端。
守护进程没有控制终端,因此当某些情况发生时,不管是一般的报告性信息,还是需由管理员处理的紧急信息,都需要以某种方式输出。Syslog 函数就是输出这些信息的标准方法,它把信息发送给 syslogd 守护进程。
Linux服务器在启动时需要启动很多系统服务,它们向本地和网络用户提供了Linux的系统功能接口,直接面向应用程序和用户。提供这些服务的程序是由运行在后台的守护进程(daemons)来执行的。守护进程是生存期长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。
使用守护进程的方式运行服务,需要提供一个tac配置文件(这就是一个python文件,只是扩展名不同),并且在这个文件中需要创建一个应用程序对象,对象名必须是application。
首先创建一个echo.tac文件:
# coding=utf-8
from twisted.application import service, internet
import sys
sys.path.append("c:\\downloads") # echoServ.py保存在哪个目录,就把这个路径设定到哪里
from echoServ import EchoFactory
# 创建应用程序对象
application = service.Application(u'Echo 服务程序'.encode("gbk"))
# 创建 service 对象
myServices = internet.TCPServer(8000, EchoFactory())
# 设置 application 为 service 的父元素
myServices.setServiceParent(application)
然后用守护进程方式运行服务,运行命令:#twistd -y echo.tac。
若想要停止服务:#kill -15 (pid)。
服务端的代码:echoServ.py
# 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):
'''连接建立时被回调的方法'''
self.factory.numProtocols = self.factory.numProtocols + 1
self.transport.write("目前有 %d 个开放式连接\n".encode("utf-8") % (self.factory.numProtocols,))
def connectionLost(self, reason):
'''连接关闭时被回调的方法'''
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()
客户端程序:
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('ipconfig\n'.encode("utf-8"))
print (sock.recv(1024).decode("utf-8"))
sock.close()
聊天的服务器版本
每次客户端发请求,服务器就会执行一次新的处理,每个请求之间都是独立,同时请求之间不会相互阻塞。
服务器端:
# coding=utf-8
from twisted.internet import protocol, reactor
class Echo(protocol.Protocol):#处理接收请求内容
def dataReceived(self, data):
# As soon as any data is received, write it back
self.transport.write(u"我是服务器".encode("utf-8"))
print (data.decode("utf-8"))
class EchoFactory(protocol.Factory):#创建协议
def buildProtocol(self, addr):
return Echo()
reactor.listenTCP(8000, EchoFactory())
print (u"开始进入监听...")
reactor.run()
客户端:
# coding=utf-8
from twisted.internet import protocol, reactor
class Echo(protocol.Protocol):#处理接收请求内容
def dataReceived(self, data):
# As soon as any data is received, write it back
self.transport.write(u"我是服务器".encode("utf-8"))
print (data.decode("utf-8"))
class EchoFactory(protocol.Factory):#创建协议
def buildProtocol(self, addr):
return Echo()
reactor.listenTCP(8000, EchoFactory())
print (u"开始进入监听...")
reactor.run()
写一个聊天的服务器:
服务器端:
# encoding=utf-8
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver # 事件处理器
from twisted.internet import reactor
class Chat(LineReceiver):
def __init__(self, users):
self.users = users
self.name = None
self.state = "GETNAME"
def connectionMade(self): # 连接开始,且开始做处理
self.sendLine(u"你叫什么名字?\n".encode("utf-8"))
def connectionLost(self, reason):
if self.name in self.users:
del self.users[self.name]
def lineReceived(self, line): # 对返回内容开始做处理
if self.state == "GETNAME": # 根据状态开始选择不同得内容处理
self.handle_GETNAME(line)
else:
self.handle_CHAT(line)
def handle_GETNAME(self, name):
if name in self.users:
self.sendLine(u"谢谢,请做另外得选择.\n".encode("utf-8") ) # 每个用户只能有一个聊天通信存在
return
self.sendLine(u"欢迎, %s!\n".encode("utf-8") % (name,))
self.name = name
self.users[name] = self
self.state = "CHAT"
def handle_CHAT(self, message): # 对正在聊天得用户进行发送消息
message = "<%s> %s\n" % (self.name, message)
for name, protocol in self.users.items():
if protocol == self:
self.sendLine((message + "\n").encode("utf-8"))
class ChatFactory(Factory):
def __init__(self):
self.users = {} # 用户名映射到聊天实例
def buildProtocol(self, addr):
return Chat(self.users)
if __name__ == '__main__':
reactor.listenTCP(1200, ChatFactory())
print ("开始进入监听状态...")
reactor.run()
客户端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和端口
s.send('test1\r\n'.encode("utf-8")) # 发送一行字符串(以\r\n 结束)到服务器端
s.send('hi\r\n'.encode("utf-8"))
for i in range(100):
s.send('hello\r\n'.encode("utf-8"))
time.sleep(1)
print (u'从服务器返回消息:')
print (s.recv(1200).decode("utf-8"))
s.close()
客户端2:
import time
if __name__ == '__main__':
import socket
import time
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 1200)) # 建立连接
time.sleep(2)
sock.send('test2\r\n'.encode("utf-8"))
print(sock.recv(1020).decode("utf-8"))
for i in range(100):
sock.send('hi\r\n'.encode("utf-8"))
print(sock.recv(1020).decode("utf-8"))
time.sleep(1)
sock.send('good\r\n'.encode("utf-8"))
print(sock.recv(120).decode("utf-8"))
sock.send('gloryroad\r\n'.encode("utf-8"))
print(sock.recv(120).decode("utf-8"))
sock.close()
支持多个客户端互相通讯消息的例子:
服务器端:
# 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={}
def __init__(self, users):
self.users = users
self.name = None
self.state = "GETNAME"
def connectionMade(self): # 连接开始,且开始做处理
if self.name is None:
self.sendLine(u"你叫什么名字?".encode("utf-8"))
def connectionLost(self, reason):
if self.name in self.users:
del self.users[self.name]
try:
del Chat.message_dict[self.name]
except:
print("删除用户的聊天记录失败")
def lineReceived(self, 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
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:
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
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
class ChatFactory(Factory):
def __init__(self):
self.users = {} # # 将所有与服务器端连接的对象存放到此字典中,所有的实例均可以使用此字典获取所有的连接对象
def buildProtocol(self, addr):
return Chat(self.users)
if __name__ == '__main__':
reactor.listenTCP(1200, ChatFactory())
print ("开始进入监听状态...")
reactor.run()
客户端:
# 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
可以同时开始两个客户端互相发送消息,如下:
E:\>py -3 a.py
remote_host: wuxiaohua-PC
连接从 ('192.168.56.1', 17718)
连接到 ('192.168.56.1', 1200)
从服务器返回消息:
你叫什么名字?
请输入你要使用的英文用户名:
test1
从服务器返回消息:
欢迎, test1!
**************************************************
查看当前登录用户列表的命令:list
查看别人给你发送的消息命令要求:getmessage
给别人发送消息的数据格式:username:要发送的消息
**************************************************
请输入发送的信息:
list
从服务器返回消息:
['test1']
请输入发送的信息:
getmessage
从服务器返回消息:
没有别人给你发送的数据
请输入发送的信息:
test1:hi
请输入发送的信息:
getmessage
从服务器返回消息:
test1:from test2 info
Twisted框架中对文件的操作(向服务器日志写日志)
服务端:
工厂有startFactory和stopFactory两种方式来执行相关应用的创建与销毁,下述代码从客户端接收到的信息都会被写入文件中。前提先创建文件
#!/usr/bin/env python
# coding: utf-8
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor
class LoggingProtocol(LineReceiver):
def lineReceived(self, line):
self.factory.fp.write(line.decode("utf-8")+"\n")
self.factory.fp.flush() # 直接写入
self.sendLine("写入成功!".encode("utf-8"))
class LogfileFactory(Factory):
protocol = LoggingProtocol
def __init__(self, fileName):
self.file = fileName
def startFactory(self):
self.fp = open(self.file, 'a',encoding="utf-8")
def stopFactory(self):
self.fp.close() # 文件句柄关闭
print ("已关闭")
if __name__ == '__main__':
# 创建监听端口
FACTORY = LogfileFactory("e:\\ax.txt")
reactor.listenTCP(8007, FACTORY)
# 开始监听事件
print ("开始写入文件...")
reactor.run()
客户端:
if __name__ == '__main__':
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 8007)) #创建连接
import time
time.sleep(2)
sock.send('gloryroad1\r\n'.encode("utf-8")) #触发 startFactory然后开始执行LoggingProtocol
print (sock.recv(1020).decode("utf-8"))
sock.send('hi\r\n'.encode("utf-8"))
print (sock.recv(1020).decode("utf-8"))
sock.send('good\r\n'.encode("utf-8"))
print (sock.recv(120).decode("utf-8"))
sock.send('gloryroad2\r\n'.encode("utf-8"))
print (sock.recv(120).decode("utf-8"))
sock.close()#触发stopFactory,关闭文件
基于广播的聊天程序
server_socket.setsockopt(level,optname,value)
level定义哪个选项将被使用,一般都是使用SOL_SOCKET,意思是正在使用得socker选项。
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
SO_LINGER选项用来设置当调用closesocket时是否马上关闭socket;
SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使用。server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项。TCP,先调用close()的一方会进入TIME_WAIT状态,所以不设置此参数,需要系统等待一段事件才可以使用。
服务器端:Linux可以执行
import socket, select
import traceback
# Function to broadcast chat messages to all connected clients
def broadcast_data(sock, message):
# Do not send the message to master socket and the client who has send us the message
for socket in CONNECTION_LIST:
if socket != server_socket and socket != sock: # 不是服务器端的socket,也不是当前发送广播消息的socket连接并且是有效
try:
socket.send(message.encode("utf-8"))
except:
# broken socket connection may be, chat client pressed ctrl+c for example
socket.close()
CONNECTION_LIST.remove(socket)
if __name__ == "__main__":
# List to keep track of socket descriptors
CONNECTION_LIST = []
RECV_BUFFER = 4096 # Advisable to keep it as an exponent of 2
PORT = 6001
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# this has no effect, why ?
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 套接字关闭之后端口号可以直接被重用。
server_socket.bind(("0.0.0.0", PORT))
server_socket.listen(10)
# Add server socket to the list of readable connections
CONNECTION_LIST.append(server_socket) # 连接实例对象添加到CONNECTION_LIST
print ("Chat server started on port " + str(PORT)) # 打印正在连接得端口号
while 1:
# Get the list sockets which are ready to be read through select
read_sockets, write_sockets, error_sockets = select.select(CONNECTION_LIST, [], [])
for sock in read_sockets:
# 新建连接的分支,server_socket对象只会负责建立新的连接
if sock == server_socket: # 判断是否是当前的连接,如果是就开始建立连接,每次建立新连接,都会执行一次此分支。
# Handle the case in which there is a new connection recieved through server_socket
sockfd, addr = server_socket.accept()
CONNECTION_LIST.append(sockfd)
print ("Client (%s, %s) connected" % addr)
print("broadcast0 now!")
broadcast_data(sockfd, "[%s:%s] entered room\n" % addr)
# Some incoming message from a client,当客户端发送数据的时候,会执行else分支
else:
# Data recieved from client, process it
try:
# window系统中TCP连接可能会突然关闭导致出现连接复位得异常
data = sock.recv(RECV_BUFFER)
print("got data:", data)
if data:
print("broadcast2 now!")
broadcast_data(sock, "\r" + '<' + str(sock.getpeername()) + '> ' + data.decode("utf-8"))
except:
print(traceback.print_exc())
print("broadcast1 now!")
broadcast_data(sock, "Client (%s, %s) is offline" % addr) # 连接已退出后做处理
print ("Client (%s, %s) is offline" % addr)
sock.close()
CONNECTION_LIST.remove(sock)
continue
server_socket.close()
客户端:
import socket, select, string, sys
def prompt():
sys.stdout.write('
sys.stdout.flush()
# main function
if __name__ == "__main__":
if (len(sys.argv) < 3): # 传入内容是否准确
print ('Usage : python telnet.py hostname port')
sys.exit()
host = sys.argv[1]
port = int(sys.argv[2])
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(6)
# connect to remote host
try:
s.connect((host, port)) # 创建连接
except:
print ('Unable to connect') # 连接失败操作这块
sys.exit()
print ('Connected to remote host. Start sending messages')
prompt() # 给出格式开始接收信息
while 1:
rlist = [sys.stdin, s]
# s.connect((host, port))
# 获取列表可读套接字
read_list, write_list, error_list = select.select(rlist, [], [])
for sock in read_list:
# 远程服务端传入消息
if sock == s: # 如果是正在建立通信,然后开始接收服务端返回信息
data = sock.recv(4096)
if not data:
pass
else:
# print data
sys.stdout.write(data.decode("utf-8")) # 打印空内容
prompt()
# 用户输入消息发送给服务端
else:
msg = sys.stdin.readline()
s.send(msg.encode("utf-8"))
prompt()
执行:
需要多开几个客户端,才能看到广告效果,执行如下:
python client.py 127.0.0.1 6001
Unix下是sock,而windows下是winsock,中间语法有很多是不同的,所以windows运行client程序有问题