-
Linux多線程同步機(jī)制 - 信號量
信號量函數(shù)定義如下:
include <sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...);
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);事實上,為了獲得我們特定操作所需要的#define定義凡纳,我們需要在包含sys/sem.h文件之前通常需要包含sys/types.h與sys/ipc.h文件宪赶。而在某些情況下魄健,這并不是必須的丛楚。
因為我們會依次了解每一個函數(shù)舰涌,記住礁扮,這些函數(shù)的設(shè)計是用于操作信號量值數(shù)組的呀伙,從而會使用其操作向比單個信號量所需要的操作更為復(fù)雜补履。
注意,key的作用類似于一個文件名剿另,因為他表示程序也許會使用或是合作所用的資源箫锤。相類似的,由semget所返回的并且為其他的共享內(nèi)存函數(shù)所用的標(biāo) 識符與由fopen函數(shù)所返回 的FILE *十分相似驰弄,因為他被進(jìn)程用來訪問共享文件麻汰。而且與文件類似,不同的進(jìn)程會有不同的信號量標(biāo)識符戚篙,盡管他們指向相同的信號量五鲫。key與標(biāo)識符的用法對于在 這里所討論的所有IPC程序都是通用的,盡管每一個程序會使用獨立的key與標(biāo)識符岔擂。
semget
semget函數(shù)創(chuàng)建一個新的信號量或是獲得一個已存在的信號量鍵值位喂。
int semget(key_t key, int num_sems, int sem_flags);
第一個參數(shù)key是一個用來允許不相關(guān)的進(jìn)程訪問相同信號量的整數(shù)值。所有的信號量是為不同的程序通過提供一個key來間接訪問的乱灵,對于每一個信號量系統(tǒng) 生成一個信號量標(biāo)識符塑崖。信號量鍵值只可以由semget獲得,所有其他的信號量函數(shù)所用的信號量標(biāo)識符都是由semget所返回的痛倚。
還有一個特殊的信號量key值规婆,IPC_PRIVATE(通常為0),其作用是創(chuàng)建一個只有創(chuàng)建進(jìn)程可以訪問的信號量蝉稳。這通常并沒有有用的目的抒蚜,而幸運的是,因為在某些Linux系統(tǒng)上耘戚,手冊頁將IPC_PRIVATE并沒有阻止其他的進(jìn)程訪問信號量作為一個bug列出嗡髓。
num_sems參數(shù)是所需要的信號量數(shù)目。這個值通呈战颍總是1饿这。
sem_flags參數(shù)是一個標(biāo)記集合浊伙,與open函數(shù)的標(biāo)記十分類似。低九位是信號的權(quán)限长捧,其作用與文件權(quán)限類似嚣鄙。另外,這些標(biāo)記可以與 IPC_CREAT進(jìn)行或操作來創(chuàng)建新的信號量串结。設(shè)置IPC_CREAT標(biāo)記并且指定一個已經(jīng)存在的信號量鍵值并不是一個錯誤拗慨。如果不需 要,IPC_CREAT標(biāo)記只是被簡單的忽略奉芦。我們可以使用IPC_CREAT與IPC_EXCL的組合來保證我們可以獲得一個新的,唯一的信號量剧蹂。如果 這個信號量已經(jīng)存在声功,則會返回一個錯誤。
如果成功宠叼,semget函數(shù)會返回一個正數(shù)先巴;這是用于其他信號量函數(shù)的標(biāo)識符。如果失敗冒冬,則會返回-1伸蚯。
semop
函數(shù)semop用來改變信號量的值:
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);
第一個參數(shù),sem_id简烤,是由semget函數(shù)所返回的信號量標(biāo)識符剂邮。第二個參數(shù),sem_ops横侦,是一個指向結(jié)構(gòu)數(shù)組的指針挥萌,其中的每一個結(jié)構(gòu)至少包含下列成員:
struct sembuf {
? short sem_num;
? short sem_op;
? short sem_flg;
}第一個成員,sem_num枉侧,是信號量數(shù)目引瀑,通常為0,除非我們正在使用一個信號量數(shù)組榨馁。sem_op成員是信號量的變化量值憨栽。(我們可以以任何量改變信 號量值,而不只是1)通常情況下中使用兩個值翼虫,-1是我們的P操作屑柔,用來等待一個信號量變得可用,而+1是我們的V操作蛙讥,用來通知一個信號量可用锯蛀。
最后一個成員,sem_flg次慢,通常設(shè)置為SEM_UNDO旁涤。這會使得操作系統(tǒng)跟蹤當(dāng)前進(jìn)程對信號量所做的改變翔曲,而且如果進(jìn)程終止而沒有釋放這個信號量, 如果信號量為這個進(jìn)程所占有劈愚,這個標(biāo)記可以使得操作系統(tǒng)自動釋放這個信號量瞳遍。將sem_flg設(shè)置為SEM_UNDO是一個好習(xí)慣,除非我們需要不同的行 為菌羽。如果我們確實變我們需要一個不同的值而不是SEM_UNDO掠械,一致性是十分重要的,否則我們就會變得十分迷惑注祖,當(dāng)我們的進(jìn)程退出時猾蒂,內(nèi)核是否會嘗試清 理我們的信號量。
semop的所用動作會同時作用是晨,從而避免多個信號量的使用所引起的競爭條件肚菠。我們可以在手冊頁中了解關(guān)于semop處理更為詳細(xì)的信息。
semctl
semctl函數(shù)允許信號量信息的直接控制:
int semctl(int sem_id, int sem_num, int command, ...);
第一個參數(shù)罩缴,sem_id蚊逢,是由semget所獲得的信號量標(biāo)識符。sem_num參數(shù)是信號量數(shù)目箫章。當(dāng)我們使用信號量數(shù)組時會用到這個參數(shù)烙荷。通常,如果 這是第一個且是唯一的一個信號量檬寂,這個值為0终抽。command參數(shù)是要執(zhí)行的動作,而如果提供了額外的參數(shù)焰薄,則是union semun拿诸,根據(jù)X/OPEN規(guī)范,這個參數(shù)至少包括下列參數(shù):
union semun {
? int val;
? struct semid_ds *buf;
? unsigned short *array;
}許多版本的Linux在頭文件(通常為sem.h)中定義了semun聯(lián)合塞茅,盡管X/Open確認(rèn)說我們必須定義我們自己的聯(lián)合亩码。如果我們發(fā)現(xiàn)我們確實需 要定義我們自己的聯(lián)合,我們可以查看semctl手冊頁了解定義野瘦。如果有這樣的情況描沟,建議使用手冊頁中提供的定義,盡管這個定義與上面的有區(qū)別鞭光。
有多個不同的command值可以用于semctl吏廉。在這里我們描述兩個會經(jīng)常用到的值。要了解semctl功能的詳細(xì)信息惰许,我們應(yīng)該查看手冊頁席覆。
這兩個通常的command值為:
SETVAL:用于初始化信號量為一個已知的值。所需要的值作為聯(lián)合semun的val成員來傳遞汹买。在信號量第一次使用之前需要設(shè)置信號量佩伤。
IPC_RMID:當(dāng)信號量不再需要時用于刪除一個信號量標(biāo)識聊倔。semctl函數(shù)依據(jù)command參數(shù)會返回不同的值。對于SETVAL與IPC_RMID生巡,如果成功則會返回0耙蔑,否則會返回-1。
-
線程操作
踩坑:由于pthread 庫不是 Linux 系統(tǒng)默認(rèn)的庫孤荣,連接時需要使用靜態(tài)庫 libpthread.a甸陌,所以在使用pthread_create()創(chuàng)建線程,以及調(diào)用 pthread_atfork()函數(shù)建立fork處理程序時盐股,在編譯中要加 -lpthread參數(shù)钱豁。
例如:
gcc thread.c -o thread -lpthread
?
頭文件:pthread.h
- pthread_create()
功能:創(chuàng)建線程
int pthread_create(pthread_t *restrict tidp, //返回線程的ID const pthread_attr_t *restrict attr, //線程屬性,默認(rèn)為NULL void *(*start_rtn)(void), //線程函數(shù)入口地址 void *restrict arg); //參數(shù)
- pthread_join()
功能:以阻塞的方式等待thread指定的線程結(jié)束疯汁。當(dāng)函數(shù)返回時寥院,被等待線程的資源被收回。如果進(jìn)程已經(jīng)結(jié)束涛目,那么該函數(shù)會立即返回。并且thread指定的線程必須是joinable的凛澎。
int pthread_join(pthread_t thread, void **retval);
參數(shù):
thread: 線程標(biāo)識符霹肝,即線程ID,標(biāo)識唯一線程塑煎。
retval: 用戶定義的指針沫换,用來存儲被等待線程的返回值。
返回值 : 0代表成功最铁,失敗返回的則是錯誤號讯赏。 -
flask-mail
-
安裝 Flask-Mail
pip install Flask-Mail
Flask-Mail 使用標(biāo)準(zhǔn)的 Flask 配置 API 進(jìn)行配置。
- MAIL_SERVER : 默認(rèn)為 ‘localhost’
- MAIL_PORT : 默認(rèn)為 25
- MAIL_USE_TLS : 默認(rèn)為 False
- MAIL_USE_SSL : 默認(rèn)為 False
- MAIL_DEBUG : 默認(rèn)為 app.debug
- MAIL_USERNAME : 默認(rèn)為 None
- MAIL_PASSWORD : 默認(rèn)為 None
- MAIL_DEFAULT_SENDER : 默認(rèn)為 None
- MAIL_MAX_EMAILS : 默認(rèn)為 None
- MAIL_SUPPRESS_SEND : 默認(rèn)為 app.testing
- MAIL_ASCII_ATTACHMENTS : 默認(rèn)為 False
-
-
Mail
實例進(jìn)行管理
```
from flask import Flask
from flask_mail import Mail
app = Flask(__name__)
mail = Mail(app)
```
或者你也可以在應(yīng)用程序配置的時候設(shè)置你的 Mail 實例冷尉,通過使用 init_app 方法:
```
mail = Mail()
app = Flask(__name__)
mail.init_app(app)
```
-
發(fā)送郵件
為了能夠發(fā)送郵件漱挎,首先需要創(chuàng)建一個
Message
實例:from flask_mail import Message @app.route("/") def index(): msg = Message("Hello", sender="from@example.com", recipients=["to@example.com"])
你能夠設(shè)置一個或者多個收件人:
msg.recipients = ["you@example.com"] msg.add_recipient("somebodyelse@example.com")
如果你設(shè)置了
MAIL_DEFAULT_SENDER
,就不必再次填寫發(fā)件人雀哨,默認(rèn)情況下將會使用配置項的發(fā)件人:msg = Message("Hello", recipients=["to@example.com"])
如果
sender
是一個二元組磕谅,它將會被分成姓名和郵件地址:msg = Message("Hello", sender=("Me", "me@example.com")) assert msg.sender == "Me <me@example.com>"
郵件內(nèi)容可以包含主體以及/或者 HTML:
msg.body = "testing" msg.html = "<b>testing</b>"
最后,發(fā)送郵件的時候請使用 Flask 應(yīng)用設(shè)置的
Mail
實例:mail.send(msg)
-
大量郵件
通常在一個 Web 應(yīng)用中每一個請求會同時發(fā)送一封或者兩封郵件雾棺。在某些特定的場景下膊夹,有可能會發(fā)送數(shù)十或者數(shù)百封郵件,不過這種發(fā)送工作會給交離線任務(wù)或者腳本執(zhí)行捌浩。
在這種情況下發(fā)送郵件的代碼會有些不同:
with mail.connect() as conn: for user in users: message = '...' subject = "hello, %s" % user.name msg = Message(recipients=[user.email], body=message, subject=subject) conn.send(msg)
與電子郵件服務(wù)器的連接會一直保持活動狀態(tài)直到所有的郵件都已經(jīng)發(fā)送完成后才會關(guān)閉(斷開)放刨。
有些郵件服務(wù)器會限制一次連接中的發(fā)送郵件的上限。你可以設(shè)置重連前的發(fā)送郵件的最大數(shù)尸饺,通過配置 MAIL_MAX_EMAILS 进统。
-
附件
在郵件中添加附件同樣非常簡單:
with app.open_resource("image.png") as fp: msg.attach("image.png", "image/png", fp.read())
具體細(xì)節(jié)請參看 API 助币。
如果
MAIL_ASCII_ATTACHMENTS
設(shè)置成 True 的話,文件名將會轉(zhuǎn)換成 ASCII 的麻昼。 當(dāng)文件名是以 UTF-8 編碼的時候奠支,使用郵件轉(zhuǎn)發(fā)的時候會修改郵件內(nèi)容并且混淆 Content-Disposition 描述,這個時候MAIL_ASCII_ATTACHMENTS
配置項是十分有用的抚芦。轉(zhuǎn)換成 ASCII 的基本方式就是對 non-ASCII 字符的去除倍谜。任何一個 unicode 字符能夠被 NFKD 分解成一個或者多個 ASCII 字符。
-
SMTP發(fā)送郵件
SMTP是發(fā)送郵件的協(xié)議叉抡,Python內(nèi)置對SMTP的支持尔崔,可以發(fā)送純文本郵件、HTML郵件以及帶附件的郵件褥民。
Python對SMTP支持有
smtplib
和email
兩個模塊季春,email
負(fù)責(zé)構(gòu)造郵件,smtplib
負(fù)責(zé)發(fā)送郵件消返。首先载弄,我們來構(gòu)造一個最簡單的純文本郵件:
from email.mime.text import MIMEText msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
注意到構(gòu)造
MIMEText
對象時,第一個參數(shù)就是郵件正文撵颊,第二個參數(shù)是MIME的subtype宇攻,傳入'plain'
,最終的MIME就是'text/plain'
倡勇,最后一定要用utf-8
編碼保證多語言兼容性逞刷。然后,通過SMTP發(fā)出去:
# 輸入Email地址和口令: from_addr = raw_input('From: ') password = raw_input('Password: ') # 輸入SMTP服務(wù)器地址: smtp_server = raw_input('SMTP server: ') # 輸入收件人地址: to_addr = raw_input('To: ') import smtplib server = smtplib.SMTP(smtp_server, 25) # SMTP協(xié)議默認(rèn)端口是25 server.set_debuglevel(1) server.login(from_addr, password) server.sendmail(from_addr, [to_addr], msg.as_string()) server.quit()
我們用
set_debuglevel(1)
就可以打印出和SMTP服務(wù)器交互的所有信息妻熊。SMTP協(xié)議就是簡單的文本命令和響應(yīng)夸浅。login()
方法用來登錄SMTP服務(wù)器,sendmail()
方法就是發(fā)郵件扔役,由于可以一次發(fā)給多個人帆喇,所以傳入一個list
,郵件正文是一個str
亿胸,as_string()
把MIMEText
對象變成str
番枚。如果一切順利,就可以在收件人信箱中收到我們剛發(fā)送的Email:
仔細(xì)觀察损敷,發(fā)現(xiàn)如下問題:
- 郵件沒有主題肄扎;
- 收件人的名字沒有顯示為友好的名字无牵,比如
Mr Green <green@example.com>
篡诽; - 明明收到了郵件罕模,卻提示不在收件人中。
這是因為郵件主題、如何顯示發(fā)件人洋丐、收件人等信息并不是通過SMTP協(xié)議發(fā)給MTA呈昔,而是包含在發(fā)給MTA的文本中的,所以友绝,我們必須把
From
堤尾、To
和Subject
添加到MIMEText
中,才是一封完整的郵件:# -*- coding: utf-8 -*- from email import encoders from email.header import Header from email.mime.text import MIMEText from email.utils import parseaddr, formataddr import smtplib def _format_addr(s): name, addr = parseaddr(s) return formataddr(( \ Header(name, 'utf-8').encode(), \ addr.encode('utf-8') if isinstance(addr, unicode) else addr)) from_addr = raw_input('From: ') password = raw_input('Password: ') to_addr = raw_input('To: ') smtp_server = raw_input('SMTP server: ') msg = MIMEText('hello, send by Python...', 'plain', 'utf-8') msg['From'] = _format_addr(u'Python愛好者 <%s>' % from_addr) msg['To'] = _format_addr(u'管理員 <%s>' % to_addr) msg['Subject'] = Header(u'來自SMTP的問候……', 'utf-8').encode() server = smtplib.SMTP(smtp_server, 25) server.set_debuglevel(1) server.login(from_addr, password) server.sendmail(from_addr, [to_addr], msg.as_string()) server.quit()
我們編寫了一個函數(shù)
_format_addr()
來格式化一個郵件地址迁客。注意不能簡單地傳入name <addr@example.com>
郭宝,因為如果包含中文,需要通過Header
對象進(jìn)行編碼掷漱。msg['To']
接收的是字符串而不是list粘室,如果有多個郵件地址,用,
分隔即可卜范。再發(fā)送一遍郵件衔统,就可以在收件人郵箱中看到正確的標(biāo)題、發(fā)件人和收件人:
你看到的收件人的名字很可能不是我們傳入的
管理員
海雪,因為很多郵件服務(wù)商在顯示郵件時锦爵,會把收件人名字自動替換為用戶注冊的名字,但是其他收件人名字的顯示不受影響奥裸。如果我們查看Email的原始內(nèi)容棉浸,可以看到如下經(jīng)過編碼的郵件頭:
From: =?utf-8?b?UHl0aG9u54ix5aW96ICF?= <xxxxxx@163.com> To: =?utf-8?b?566h55CG5ZGY?= <xxxxxx@qq.com> Subject: =?utf-8?b?5p2l6IeqU01UUOeahOmXruWAmeKApuKApg==?=
這就是經(jīng)過
Header
對象編碼的文本,包含utf-8編碼信息和Base64編碼的文本刺彩。如果我們自己來手動構(gòu)造這樣的編碼文本,顯然比較復(fù)雜枝恋。發(fā)送HTML郵件
如果我們要發(fā)送HTML郵件创倔,而不是普通的純文本文件怎么辦?方法很簡單焚碌,在構(gòu)造
MIMEText
對象時畦攘,把HTML字符串傳進(jìn)去,再把第二個參數(shù)由plain
變?yōu)?code>html就可以了:msg = MIMEText('<html><body><h1>Hello</h1>' + '<p>send by <a + '</body></html>', 'html', 'utf-8')
再發(fā)送一遍郵件十电,你將看到以HTML顯示的郵件:
發(fā)送附件
如果Email中要加上附件怎么辦知押?帶附件的郵件可以看做包含若干部分的郵件:文本和各個附件本身,所以鹃骂,可以構(gòu)造一個
MIMEMultipart
對象代表郵件本身台盯,然后往里面加上一個MIMEText
作為郵件正文,再繼續(xù)往里面加上表示附件的MIMEBase
對象即可:# 郵件對象: msg = MIMEMultipart() msg['From'] = _format_addr(u'Python愛好者 <%s>' % from_addr) msg['To'] = _format_addr(u'管理員 <%s>' % to_addr) msg['Subject'] = Header(u'來自SMTP的問候……', 'utf-8').encode() # 郵件正文是MIMEText: msg.attach(MIMEText('send with file...', 'plain', 'utf-8')) # 添加附件就是加上一個MIMEBase畏线,從本地讀取一個圖片: with open('/Users/michael/Downloads/test.png', 'rb') as f: # 設(shè)置附件的MIME和文件名静盅,這里是png類型: mime = MIMEBase('image', 'png', filename='test.png') # 加上必要的頭信息: mime.add_header('Content-Disposition', 'attachment', filename='test.png') mime.add_header('Content-ID', '<0>') mime.add_header('X-Attachment-Id', '0') # 把附件的內(nèi)容讀進(jìn)來: mime.set_payload(f.read()) # 用Base64編碼: encoders.encode_base64(mime) # 添加到MIMEMultipart: msg.attach(mime)
然后,按正常發(fā)送流程把
msg
(注意類型已變?yōu)?code>MIMEMultipart)發(fā)送出去寝殴,就可以收到如下帶附件的郵件:發(fā)送圖片
如果要把一個圖片嵌入到郵件正文中怎么做蒿叠?直接在HTML郵件中鏈接圖片地址行不行明垢?答案是,大部分郵件服務(wù)商都會自動屏蔽帶有外鏈的圖片市咽,因為不知道這些鏈接是否指向惡意網(wǎng)站痊银。
要把圖片嵌入到郵件正文中,我們只需按照發(fā)送附件的方式施绎,先把郵件作為附件添加進(jìn)去溯革,然后,在HTML中通過引用
src="cid:0"
就可以把附件作為圖片嵌入了粘姜。如果有多個圖片鬓照,給它們依次編號,然后引用不同的cid:x
即可孤紧。把上面代碼加入
MIMEMultipart
的MIMEText
從plain
改為html
豺裆,然后在適當(dāng)?shù)奈恢靡脠D片:msg.attach(MIMEText('<html><body><h1>Hello</h1>' + '<p><img src="cid:0"></p>' + '</body></html>', 'html', 'utf-8'))
再次發(fā)送,就可以看到圖片直接嵌入到郵件正文的效果:
同時支持HTML和Plain格式
如果我們發(fā)送HTML郵件号显,收件人通過瀏覽器或者Outlook之類的軟件是可以正常瀏覽郵件內(nèi)容的臭猜,但是,如果收件人使用的設(shè)備太古老押蚤,查看不了HTML郵件怎么辦蔑歌?
辦法是在發(fā)送HTML的同時再附加一個純文本,如果收件人無法查看HTML格式的郵件揽碘,就可以自動降級查看純文本郵件次屠。
利用
MIMEMultipart
就可以組合一個HTML和Plain,要注意指定subtype是alternative
:msg = MIMEMultipart('alternative') msg['From'] = ... msg['To'] = ... msg['Subject'] = ... msg.attach(MIMEText('hello', 'plain', 'utf-8')) msg.attach(MIMEText('<html><body><h1>Hello</h1></body></html>', 'html', 'utf-8')) # 正常發(fā)送msg對象...
加密SMTP
使用標(biāo)準(zhǔn)的25端口連接SMTP服務(wù)器時雳刺,使用的是明文傳輸劫灶,發(fā)送郵件的整個過程可能會被竊聽。要更安全地發(fā)送郵件掖桦,可以加密SMTP會話本昏,實際上就是先創(chuàng)建SSL安全連接,然后再使用SMTP協(xié)議發(fā)送郵件枪汪。
某些郵件服務(wù)商涌穆,例如Gmail,提供的SMTP服務(wù)必須要加密傳輸雀久。我們來看看如何通過Gmail提供的安全SMTP發(fā)送郵件宿稀。
必須知道,Gmail的SMTP端口是587赖捌,因此原叮,修改代碼如下:
smtp_server = 'smtp.gmail.com' smtp_port = 587 server = smtplib.SMTP(smtp_server, smtp_port) server.starttls() # 剩下的代碼和前面的一模一樣: server.set_debuglevel(1) ...
只需要在創(chuàng)建
SMTP
對象后,立刻調(diào)用starttls()
方法,就創(chuàng)建了安全連接奋隶。后面的代碼和前面的發(fā)送郵件代碼完全一樣擂送。如果因為網(wǎng)絡(luò)問題無法連接Gmail的SMTP服務(wù)器,請相信我們的代碼是沒有問題的唯欣,你需要對你的網(wǎng)絡(luò)設(shè)置做必要的調(diào)整嘹吨。
-
jwt
JWT是Auth0提出的通過對JSON進(jìn)行加密簽名來實現(xiàn)授權(quán)驗證的方案,編碼之后的JWT看起來是這樣的一串字符:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
由
.
分為三段境氢,通過解碼可以得到:- 頭部(Header)
// 包括類別(typ)蟀拷、加密算法(alg); { "alg": "HS256", "typ": "JWT" }
jwt的頭部包含兩部分信息:
- 聲明類型萍聊,這里是jwt
- 聲明加密的算法 通常直接使用 HMAC SHA256
然后將頭部進(jìn)行base64加密(該加密是可以對稱解密的)问芬,構(gòu)成了第一部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
- 載荷(payload)
載荷就是存放有效信息的地方寿桨。這些有效信息包含三個部分:
- 標(biāo)準(zhǔn)中注冊聲明
- 公共的聲名
- 私有的聲明
公共的聲明 :
公共的聲明可以添加任何的信息此衅,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需要的必要信息.但不建議添加敏感信息,因為該部分在客戶端可解密亭螟。私有的聲明 :
私有聲明是提供者和消費者所共同定義的聲明挡鞍,一般不建議存放敏感信息,因為base64是對稱解密的预烙,意味著該部分信息可以歸類為明文信息墨微。下面是一個例子:
// 包括需要傳遞的用戶信息; { "iss": "Online JWT Builder", "iat": 1416797419, "exp": 1448333419, "aud": "www.gusibi.com", "sub": "uid", "nickname": "goodspeed", "username": "goodspeed", "scopes": [ "admin", "user" ] }
- iss: 該JWT的簽發(fā)者扁掸,是否使用是可選的翘县;
- sub: 該JWT所面向的用戶,是否使用是可選的谴分;
- aud: 接收該JWT的一方锈麸,是否使用是可選的;
- exp(expires): 什么時候過期狸剃,這里是一個Unix時間戳,是否使用是可選的狗热;
- iat(issued at): 在什么時候簽發(fā)的(UNIX時間)钞馁,是否使用是可選的;
其他還有:
- nbf (Not Before):如果當(dāng)前時間在nbf里的時間之前匿刮,則Token不被接受僧凰;一般都會留一些余地,比如幾分鐘熟丸;训措,是否使用是可選的;
- jti: jwt的唯一身份標(biāo)識,主要用來作為一次性token绩鸣,從而回避重放攻擊怀大。
將上面的JSON對象進(jìn)行
base64編碼
可以得到下面的字符串。這個字符串我們將它稱作JWT的Payload(載荷)呀闻。eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0MTY3OTc0MTksImV4cCI6MTQ0ODMzMzQxOSwiYXVkIjoid3d3Lmd1c2liaS5jb20iLCJzdWIiOiIwMTIzNDU2Nzg5Iiwibmlja25hbWUiOiJnb29kc3BlZWQiLCJ1c2VybmFtZSI6Imdvb2RzcGVlZCIsInNjb3BlcyI6WyJhZG1pbiIsInVzZXIiXX0
信息會暴露
:由于這里用的是可逆的base64 編碼化借,所以第二部分的數(shù)據(jù)實際上是明文的。我們應(yīng)該避免在這里存放不能公開的隱私信息捡多。- 簽名(signature)
// 根據(jù)alg算法與私有秘鑰進(jìn)行加密得到的簽名字串蓖康; // 這一段是最重要的敏感信息,只能在服務(wù)端解密垒手; HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), SECREATE_KEY )
jwt的第三部分是一個簽證信息蒜焊,這個簽證信息由三部分組成:
- header (base64后的)
- payload (base64后的)
- secret
將上面的兩個編碼后的字符串都用句號.連接在一起(頭部在前),就形成了:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9
最后科贬,我們將上面拼接完的字符串用HS256算法進(jìn)行加密泳梆。在加密的時候,我們還需要提供一個密鑰(secret)唆迁。如果我們用
secret
作為密鑰的話鸭丛,那么就可以得到我們加密后的內(nèi)容:pq5IDv-yaktw6XEa5GEv07SzS9ehe6AcVSdTj0Ini4o
將這三部分用.連接成一個完整的字符串,構(gòu)成了最終的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0MTY3OTc0MTksImV4cCI6MTQ0ODMzMzQxOSwiYXVkIjoid3d3Lmd1c2liaS5jb20iLCJzdWIiOiIwMTIzNDU2Nzg5Iiwibmlja25hbWUiOiJnb29kc3BlZWQiLCJ1c2VybmFtZSI6Imdvb2RzcGVlZCIsInNjb3BlcyI6WyJhZG1pbiIsInVzZXIiXX0.pq5IDv-yaktw6XEa5GEv07SzS9ehe6AcVSdTj0Ini4o
簽名的目的
:簽名實際上是對頭部以及載荷內(nèi)容進(jìn)行簽名。所以唐责,如果有人對頭部以及載荷的內(nèi)容解碼之后進(jìn)行修改鳞溉,再進(jìn)行編碼的話,那么新的頭部和載荷的簽名和之前的簽名就將是不一樣的鼠哥。而且熟菲,如果不知道服務(wù)器加密的時候用的密鑰的話,得出來的簽名也一定會是不一樣的朴恳。
這樣就能保證token不會被篡改抄罕。token 生成好之后,接下來就可以用token來和服務(wù)器進(jìn)行通訊了于颖。
下圖是client 使用 JWT 與server 交互過程:
[圖片上傳失敗...(image-4fb30f-1525878931041)]
這里在第三步我們得到 JWT 之后呆贿,需要將JWT存放在 client,之后的每次需要認(rèn)證的請求都要把JWT發(fā)送過來森渐。(請求時可以放到 header 的 Authorization )
- JWT 使用場景
JWT的主要優(yōu)勢在于使用無狀態(tài)做入、可擴(kuò)展的方式處理應(yīng)用中的用戶會話。服務(wù)端可以通過內(nèi)嵌的聲明信息同衣,很容易地獲取用戶的會話信息竟块,而不需要去訪問用戶或會話的數(shù)據(jù)庫。在一個分布式的面向服務(wù)的框架中耐齐,這一點非常有用浪秘。
但是蒋情,如果系統(tǒng)中需要使用黑名單實現(xiàn)長期有效的token刷新機(jī)制,這種無狀態(tài)的優(yōu)勢就不明顯了耸携。
優(yōu)點:
- 快速開發(fā)
- 不需要cookie
- JSON在移動端的廣泛應(yīng)用
- 不依賴于社交登錄
- 相對簡單的概念理解
缺點:
- Token有長度限制
- Token不能撤銷
- 需要token有失效時間限制(exp)
-
pop3收取郵件
python的poplib也針對這些命令分別提供了對應(yīng)的方法,上面在第二列里已經(jīng)標(biāo)出來棵癣。
?
收取郵件的過程一般是:
- 連接pop3服務(wù)器 (poplib.POP3.init 或者poplib.POP3_SSL.init)
- 發(fā)送用戶名和密碼進(jìn)行驗證 (poplib.POP3.user poplib.POP3.pass_)
- 獲取郵箱中信件信息 (poplib.POP3.stat)
- 收取郵件 (poplib.POP3.retr)
- 刪除郵件 (poplib.POP3.dele)
- 退出 (poplib.POP3.quit)
官方文檔:
-
POP3.set_debuglevel
(level)Set the instance’s debugging level. This controls the amount of debugging output printed. The default,
0
, produces no debugging output. A value of1
produces a moderate amount of debugging output, generally a single line per request. A value of2
or higher produces the maximum amount of debugging output, logging each line sent and received on the control connection.
-
POP3.getwelcome
()Returns the greeting string sent by the POP3 server.
-
POP3.user
(username)Send user command, response should indicate that a password is required.
-
POP3.pass_
(password)Send password, response includes message count and mailbox size. Note: the mailbox on the server is locked until
quit()
is called.
-
POP3.apop
(user, secret)Use the more secure APOP authentication to log into the POP3 server.
-
POP3.rpop
(user)Use RPOP authentication (similar to UNIX r-commands) to log into POP3 server.
-
POP3.stat
()Get mailbox status. The result is a tuple of 2 integers:
(message count, mailbox size)
.
-
POP3.list
([which])Request message list, result is in the form
(response, ['mesg_num octets', ...], octets)
. If which is set, it is the message to list.
-
POP3.retr
(which)Retrieve whole message number which, and set its seen flag. Result is in form
(response, ['line', ...], octets)
.
-
POP3.dele
(which)Flag message number which for deletion. On most servers deletions are not actually performed until QUIT (the major exception is Eudora QPOP, which deliberately violates the RFCs by doing pending deletes on any disconnect).
-
POP3.rset
()Remove any deletion marks for the mailbox.
-
POP3.noop
()Do nothing. Might be used as a keep-alive.
-
POP3.quit
()Signoff: commit changes, unlock mailbox, drop connection.
-
POP3.top
(which, howmuch)Retrieves the message header plus howmuch lines of the message after the header of message number which. Result is in form
(response,['line', ...], octets)
.The POP3 TOP command this method uses, unlike the RETR command, doesn’t set the message’s seen flag; unfortunately, TOP is poorly specified in the RFCs and is frequently broken in off-brand servers. Test this method by hand against the POP3 servers you will use before trusting it.