date: 2016-08-02 22:37
來源: swoole - 學(xué)習(xí)Swoole需要掌握哪些基礎(chǔ)知識(shí): http://wiki.swoole.com/wiki/page/487.html - 評論君
推薦書籍: <aix unix 系統(tǒng)管理/維護(hù)與高可用架構(gòu)> <構(gòu)建高可用linux服務(wù)器> <設(shè)計(jì)原本> <領(lǐng)域特定語言> <代碼之殤>1
1-4 tcp/ip 協(xié)議簇 與 各種重要網(wǎng)絡(luò)協(xié)議
tpc/ip 協(xié)議簇
數(shù)據(jù)鏈路層: ARP + RARP -> ip / 機(jī)器物理地址 相互轉(zhuǎn)換
網(wǎng)絡(luò)層: 數(shù)據(jù)包的選路和轉(zhuǎn)發(fā)(逐跳通信); ip -> 根據(jù)數(shù)據(jù)包的目的ip地址選擇如何投遞; icmp -> 檢測網(wǎng)絡(luò)連接p
傳輸層: 端到端(end to end)通信; tcp + udp + sctp
應(yīng)用層: 處理應(yīng)用程序邏輯; ping telnet ospf dns
應(yīng)用層 -> send/write -> 傳輸層: tcp 報(bào)文/ udp 數(shù)據(jù)包(datagram) -> 網(wǎng)絡(luò)層: ip 數(shù)據(jù)報(bào) -> 數(shù)據(jù)鏈路層: 幀(frame, 幀的最大傳輸單元 max transmit unit, MTU)
分用: 數(shù)據(jù)鏈路層 -> 應(yīng)用層 的過程中, 每層交界都面對多個(gè)不同的協(xié)議, 根據(jù)數(shù)據(jù)報(bào)頭部的字段需要分發(fā)給哪個(gè)協(xié)議來繼續(xù)執(zhí)行
MTU: 可以使用 ifconfig/netstat 查看
ARP: ip地址/物理地址 相互映射; 維護(hù)一個(gè)高速緩存, 包含 經(jīng)常訪問/最近訪問
DNS: 域名/ip 相互映射, 協(xié)議中包含查詢類型, 比如 cname
# arp
arp -d 192.168.1.2 # 清除緩存
tcpdump -i eth0 -ent '(dst 192.168.1.2 and src 192.168.1.3)or(dst 192.168.1.3 and src 192.168.1.2)' # 在 1.3 上面抓包
telnet 192.168.1.2 echo # 用 1.3 來連接 1.2, 這樣 tcpdump 中就可以抓到 arp 包了
# dns
/etc/resolv.conf # dns 服務(wù)器的ip
host -t A baidu.com # 查詢 baidu.com 的 ip
tcpdump -i eth0 -nt -s 500 port domain # 使用 port domain 過濾, 只使用域名服務(wù)的包
ip 協(xié)議
為上層提供 無狀態(tài)/無連接/不可靠 服務(wù)
無狀態(tài): 數(shù)據(jù)報(bào) 相互獨(dú)立/沒有上下文關(guān)系 -> 無法 處理亂序/重復(fù); 簡單/高效
無連接: ip通信雙方都不長久的維持雙方的任何信息, 上層協(xié)議需要指明ip地址
不可靠: 不能保證ip數(shù)據(jù)報(bào)準(zhǔn)確到達(dá)
服務(wù)類型: 最小延時(shí)(ssh/telnet) 最大吞吐量(ftp) 最高可靠性 最小費(fèi)用
ip分片: ip數(shù)據(jù)報(bào) > MTU
http://qiniu.daydaygo.top/high-performance-linux-server-programming/ip-module.png
查看路由表: netstat/route
ip 轉(zhuǎn)發(fā): 主機(jī)一般只能 發(fā)送/接收 數(shù)據(jù)報(bào), 配置在 /proc/sys/net/ipv4/ip_forward
重定向: icmp 重定向報(bào)文; 主機(jī)重定向
ipv6: ipv4 地址不夠用; 多播和流 / 自動(dòng)配置, 便于管理 / 網(wǎng)絡(luò)安全功能
tcpdump -ntx -i lo # 抓取本地回路上的數(shù)據(jù)包, -x 輸出數(shù)據(jù)包的二進(jìn)制碼
telnet 127.0.0.1
tcpdump -ntv -i eth0 icmp # 只抓取 icmp 報(bào)文
ping baidu.com -s 1473 # -s 指定發(fā)送數(shù)據(jù)字節(jié)大小
route add -host 192.168.1.2 dev eth0 # 發(fā)送到 1.2 的數(shù)據(jù)包直接結(jié)果 eth0 傳送
route del -net 192.168.1.0 netmask 255.255.255.0 # 無法訪問同一個(gè)局域網(wǎng)的其他機(jī)器
route del default # 刪除默認(rèn)路由, 結(jié)果就是無法訪問外網(wǎng)了
route add defalut gw 192.168.1.2 dev eth0 # 重設(shè)默認(rèn)路由, 但是將網(wǎng)關(guān)設(shè)為某臺(tái)主機(jī), 而非路由
route -Cn # 查看路由緩沖
tcp 協(xié)議
tcp vs udp: 面向連接 字節(jié)流 可靠傳輸
tcp: 建立連接(一對一, 無法廣播和多播) -> 分配必要內(nèi)核資源以管理連接狀態(tài)和連接上的數(shù)據(jù)傳輸 -> 全雙工 -> 都必須斷開連接釋放系統(tǒng)資源
MSS: max segment size, 最大報(bào)文長度, 通常設(shè)置為 MTU-40
半關(guān)閉狀態(tài): 發(fā)送 FIN 并得到確認(rèn), 就由 連接狀態(tài) 進(jìn)入 半關(guān)閉狀態(tài), 此時(shí)還可以接受對方的數(shù)據(jù), 直到對方發(fā)送 FIN, 連接關(guān)閉
斷線重連: 次數(shù)由 /proc/sys/net/ipv4/tcp_syn_retries
配置, 由1s開始, 每次重試時(shí)間 x2
復(fù)位報(bào)文段(RST): 訪問不存在的端口; 異常中止連接; 處理半打開連接
按照數(shù)據(jù)長度: 交互數(shù)據(jù)(ssh/telnet, nagle算法, 通信雙方任何時(shí)刻都最多只能發(fā)送一個(gè)未被確認(rèn)的tcp報(bào)文段) 塊狀數(shù)據(jù)(ftp)
超時(shí)重傳: 超時(shí)時(shí)間 + 重傳次數(shù)
擁塞控制: 慢啟動(dòng)(slow start) 擁塞避免(congestion avoidance) 快速重傳(fast retransmit) 快速恢復(fù)(fast recovery); 算法 -> reno vegas cubic
http://qiniu.daydaygo.top/high-performance-linux-server-programming/tcp-state-transition.png
udp: 非常適合做廣播和多播
http://qiniu.daydaygo.top/high-performance-linux-server-programming/tcp vs udp.png
tcpdump -i eth0 -nt '(dst 192.168.1.2 and src 192.168.1.3)or(dst 192.168.1.3 and src 192.168.1.2)' # 查看 tcp 連接的 建立/關(guān)閉
telnet 192.168.1.2 80
nc -p 12345 192.168.1.2 80 # 測試 tcp TIME_WAIT 狀態(tài)
ctrl-c # 中斷連接
nc -p 12345 192.168.1.2 80 # 重新建立, 顯示連接失敗
netstat -nat # 查看連接狀態(tài), 此時(shí)就處于 TIME_WAIT 狀態(tài)
iperf -s # 1.2, iperf 是一個(gè)衡量網(wǎng)絡(luò)狀況的工具, -s 表示作為服務(wù)器運(yùn)行, 默認(rèn)監(jiān)聽5001端口, 并丟棄該端口接受的所有數(shù)據(jù)
telnet 192.168.1.2 5001 # 1.3 連接 iperf
tcpdump -n -i eth0 port 5001
tcp/ip 通信案例: 訪問 Internet 上的 web 服務(wù)器
正向代理: 客戶端配置, 通過正向代理訪問其他網(wǎng)絡(luò)
反向代理: 服務(wù)器配置, 接收用戶請求, 分發(fā)給其他服務(wù)器處理
tcpdump 抓一次 wget: 代理服務(wù)器 -> dns 服務(wù)器(dns); 代理服務(wù)器 -> 查詢路由器MAC地址(arp); wegt -> 代理服務(wù)器(http); 代理服務(wù)器 -> web 服務(wù)器(http)
http 請求(request): 請求行(請求方法 + 資源地址) + 請求頭部(header) + 空行(<CR><LF>
, 標(biāo)識(shí)頭部結(jié)束)
http 應(yīng)答(response): 狀態(tài)行 + 應(yīng)答頭部
http 無狀態(tài) -> cookie -> 標(biāo)識(shí) 不同客戶端
/etc/init.d/ # 服務(wù)器程序存放地址
service start|stop|restart xxx # 服務(wù)管理
/etc/hosts # dns 查詢 Internet 上的域名, 本地名稱查詢使用 hosts 文件
/etc/host.conf # 自定義系統(tǒng)解析主機(jī)名
order hosts,bind # 先本地, 后dns
multi on # 允許匹配到多個(gè)ip
高性能服務(wù)器編程
C語言函數(shù)常見套路: 成功返回 0, 失敗返回 -1 并設(shè)置 errno; 使用 bit 里標(biāo)識(shí)狀態(tài)(節(jié)約空間, 位運(yùn)算也更快), 然后使用 掩碼 來改變值(位運(yùn)算, 比如 改變狀態(tài)/ip地址轉(zhuǎn)換); 使用正負(fù)來處理有限狀態(tài), 避免使用更多參數(shù)
linux 網(wǎng)絡(luò)編程基礎(chǔ) api
字節(jié)序: cpu累加器 一般能加載超過一個(gè)字節(jié), 所以字節(jié)的順序, 會(huì)影響加載的整數(shù)的值
大端序(big endian): 高位存儲(chǔ)在高地址; 網(wǎng)絡(luò); java虛擬機(jī)
小端序(little endian): 高位存儲(chǔ)在低地址; 現(xiàn)代 pc 大部分采取
協(xié)議族(protocol family) + 地址族(address family): unix(本地協(xié)議族) inet inet6
ip 地址轉(zhuǎn)換函數(shù): char <-> int
socket 選項(xiàng)
inet_aton() inet_ntoa() # ipv4
inet_pton() inet_ntop() # ipv4 + ipv6
int socket(int domain, int type, int protocol) # 創(chuàng)建socket; domain->協(xié)議族 type->流/數(shù)據(jù)報(bào) protocol->0(默認(rèn)); 返回 socket 文件描述法
int bind() # 命名socket, 綁定到地址族中的具體socket地址
int listen(int socketfd, int backlog) # 監(jiān)聽socket, 不能馬上接聽客戶連接, 要?jiǎng)?chuàng)建一個(gè)監(jiān)聽隊(duì)列來存放待處理客戶連接
int accept() # 從監(jiān)聽隊(duì)列接受一個(gè)連接
int connect() # 客戶端主動(dòng)與服務(wù)器建立連接
int close(int fd) # 將 fd 引用計(jì)數(shù)減一
int shutdown(int socketfd, int howto) # 關(guān)閉 socket 的行為: r/w
ssize_t recv(int socketfd, void *buf, size_t len, int flags) # 讀取
ssize_t send(int socketfd, const void *buf, size_t len, int flags) # 發(fā)送
recvfrom() sendto() # udp
recvmsg() sendmsg() # 通用: tcp + udp
socketmark(int socketfd) # tcp 帶外數(shù)據(jù)接收方法
getsockname() / getpeername() # 獲取一個(gè)socket的 本/遠(yuǎn) 端socket地址
getsocketopt() / setsocketopt() # 獲取/設(shè)置 socket 文件描述符
gethostbyname() / gethostbyaddr()
getservbyname() / getservbyport()
getaddrinfo() # gethostbyname() + getservbyname()
getnameinfo() # gethostbyaddr() + getservbyaddr()
高級 io 函數(shù)
fcntl(file control) 函數(shù): 提供了對 fd 的各種控制操作; 通常用來將一個(gè) fd 設(shè)置為 非阻塞
int pipe(int fd[2]) # 創(chuàng)建一個(gè)管道, 以實(shí)現(xiàn)進(jìn)程間通信; 單向, read/write 阻塞; 建立的管道也有數(shù)據(jù)大小
int dup(int file_descriptor) # stdin -> 文件 / stdout -> 網(wǎng)絡(luò)連接(如: cgi編程)
int dup2(int file_descriptor, int file_descriptor_two)
ssize_t readv(int fd, const struct iovec* vector, int count) # 分散寫
ssize_t writev(int fd, const struct iovec* vector, int count) # 集中讀
sendfile() # 在2個(gè)文件描述符之間直接傳遞數(shù)據(jù)(完全在內(nèi)核中), 避免內(nèi)核緩沖區(qū)和用戶緩沖區(qū)之間的數(shù)據(jù)拷貝 -> 零拷貝
mmap() # 申請一段內(nèi)存 -> 進(jìn)程間通信的共享內(nèi)存 / 直接將文件映射到其中
munmap() # 釋放由 mmap() 創(chuàng)建的內(nèi)存
splice() # 在2個(gè) fd 之間移動(dòng)數(shù)據(jù), 也是 零拷貝
tee() # 2個(gè) 管道fd 之間復(fù)制數(shù)據(jù), 也是 零拷貝
int fcntl(int fd, int cmd, ...) # fcntl 函數(shù)
linux 服務(wù)器程序規(guī)范
一般以后臺(tái)進(jìn)程形式運(yùn)行(也稱為守護(hù)進(jìn)程 daemon): 沒有控制終端, 因而不會(huì)意外接受用戶輸入; 父進(jìn)程通常為 init 進(jìn)程(pid=1)
通常有一套日志系統(tǒng) -> 文件 / udp服務(wù)器 / /var/log
下?lián)碛凶约喝罩灸夸?br>
一般以某個(gè)非root用戶運(yùn)行: mysqld -> mysql; httpd -> apche; syslogd -> syslog
通常是可配置的 -> /etc
通常會(huì)在啟動(dòng)時(shí)生成一個(gè) pid 文件并存入 /var/run
目錄中, 比如 syslogd -> /var/run/syslogd.pid
通常需要考慮 系統(tǒng)資源和限制, 以預(yù)測自身能承受多大負(fù)荷, 比如 fd總數(shù)/內(nèi)存
linux 系統(tǒng)日志: rsyslogd
用戶信息: 大部分服務(wù)器程序以 root 啟動(dòng), 但不以 root 運(yùn)行; uid euid gid egid
euid: 使得運(yùn)行用戶擁有改程序的有效用戶的權(quán)限, 方便資源訪問; 比如 su 程序被設(shè)置了 set-user-id
標(biāo)記
進(jìn)程間關(guān)系: 進(jìn)程組(pgid, 每個(gè)進(jìn)程都隸屬一個(gè)進(jìn)程組); 會(huì)話(session, 一些有關(guān)聯(lián)的會(huì)話形成一個(gè)會(huì)話)
系統(tǒng)資源: 物理設(shè)備(cpu, 內(nèi)存) 系統(tǒng)策略限制(cpu時(shí)間) 具體實(shí)現(xiàn)限制(文件名長度限制)
void syslog(int priority, const char* message, ...) # 和 rsyslosd 通信
priority:
LOG_EMEGE 0 系統(tǒng)不可用
LOG_ALERT 1 報(bào)警, 需要立即采取行動(dòng)
LOG_CRIT 2 非常嚴(yán)重的情況
LOG_ERR 3 錯(cuò)誤
LOG_WARNING 4 警告
LOG_NOTICE 5 通知
LOG_INFO 6 信息
LOG_DEBUG 7 調(diào)試
void openlog(const char* ident, int logopt, int facility) # 改變 syslog 默認(rèn)的輸出方式, 進(jìn)一步結(jié)構(gòu)化日志內(nèi)容
logopt:
LOG_PID 0x01 在日志消息中包含程序 pid
LOG_CONS 0x02 如果無法記錄到日志文件, 則打印到終端
LOG_ODELAY 0x04 延遲打開日志功能, 直到第一次調(diào)用 syslog
LOG_NDELAY 0x08 不延遲打開日志功能
int setlogmask(int maskpri) # 簡單設(shè)置日志掩碼, 使日志級別大于日志掩碼的日志信息被系統(tǒng)忽略
int closelog()
getuid() / setuid() # 用戶身份相關(guān)函數(shù)
getpgid() / setpgid() # 進(jìn)程組
setsid() / getsid() # 會(huì)話, 使用調(diào)用進(jìn)程 pid 作為 sid
ps -o pid,ppid,pgid,sid,comm | less # 使用 ps 查看
setrlimit() / getrilimit() # 系統(tǒng)資源
getcwd() / chdir() / chroot() # 獲取工作目錄 / 改變進(jìn)程工作目錄 / 改變進(jìn)程根目錄
int daemon(int nochdir, int noclose) # 服務(wù)器程序后臺(tái)化
高性能服務(wù)器程序框架
3個(gè)主要模塊: io 處理單元(4種io處理模式+2種高效事件處理模式); 邏輯單元(2種高效并發(fā)模式+有限狀態(tài)機(jī)); 存儲(chǔ)單元(可選, 和網(wǎng)絡(luò)編程無關(guān))
c/s模型: client-server, server為中心
http://qiniu.daydaygo.top/high-performance-linux-server-programming/tcp-workflow.png
p2p(peer to peer, 點(diǎn)對點(diǎn))模型: 優(yōu)點(diǎn) -> 每臺(tái)機(jī)器消耗服務(wù)的同時(shí)也給別人提供服務(wù); 缺點(diǎn) -> 用戶之間傳輸?shù)恼埱筮^多時(shí), 網(wǎng)絡(luò)負(fù)載將加重 / 主機(jī)之間很難互相發(fā)現(xiàn), 需要帶有一個(gè)發(fā)現(xiàn)服務(wù)器; 其實(shí)每個(gè)點(diǎn)既是 服務(wù)器 也是 客戶端, 也是采用 c/s 模型實(shí)現(xiàn)
io處理單元: 服務(wù)器管理客戶連接
邏輯單元: 通常一個(gè) 進(jìn)程/線程, 分析并處理客戶數(shù)據(jù), 然后將結(jié)果傳遞給 io 處理單元
網(wǎng)絡(luò)存儲(chǔ)單元: DB / cache / file
請求隊(duì)列: 各單元通信的抽象
http://qiniu.daydaygo.top/high-performance-linux-server-programming/server-basic-framework.png
4種io模型
socket 基本api中可能被阻塞的系統(tǒng)調(diào)用: accept send recv connect
非阻塞io通常要和其他其他io通知機(jī)制一起使用, 比如 io復(fù)用 / sigio信號(hào)
io復(fù)用(最常使用): 應(yīng)用程序通過 io復(fù)用函數(shù) 向內(nèi)核注冊一組事件, 內(nèi)核通過 io復(fù)用函數(shù) 把其中的就緒事件通知給應(yīng)用程序
linux常用 io復(fù)用函數(shù): select poll epoll_wait; 本身是阻塞的, 具有同時(shí)監(jiān)聽多個(gè)io事件的能力
sigio 信號(hào): 為一個(gè) fd 指定 宿主進(jìn)程 -> fd 上有事件發(fā)生 -> sigio 信號(hào)處理函數(shù)被觸發(fā) -> 被指定的宿主程序捕獲到 sigio信號(hào)
理論上 阻塞io / io復(fù)用 / 信號(hào)驅(qū)動(dòng)io 都是 同步io模型: 先 io(就緒)事件, 后 io讀寫
異步io(aio.h
): 用戶直接對io執(zhí)行讀寫操作 -> 內(nèi)核完成io操作 -> 通知應(yīng)用程序 io完成事件
同步 vs 異步: 內(nèi)核向應(yīng)用程序通知的時(shí)間(就緒事件 vs 完成時(shí)間) 由誰來完成io讀寫(應(yīng)用程序 vs 內(nèi)核)
2種高效事件處理模式
reactor模式(同步io): 主線程(io處理單元) 只負(fù)責(zé)監(jiān)聽 fd 上的事件, 有就通知 工作線程(邏輯單元), 不做其他實(shí)質(zhì)性工作; 工作線程完成 讀寫數(shù)據(jù)/接受連接/處理連接 等
http://qiniu.daydaygo.top/high-performance-linux-server-programming/reactor.png
proactor模式(異步io): 所有io操作交給主線程和內(nèi)核, 工作線程只負(fù)責(zé)業(yè)務(wù)邏輯; 更符合服務(wù)器編程框架
http://qiniu.daydaygo.top/high-performance-linux-server-programming/proactor.png
http://qiniu.daydaygo.top/high-performance-linux-server-programming/monitor-proactor.png
2種高效并發(fā)模式
并發(fā)編程: 如果是計(jì)算密集型, 并發(fā)編程沒有優(yōu)勢, 反而由于任務(wù)切換使效率降低; io密集型, io操作速度 遠(yuǎn)小于 cpu計(jì)算速度
半同步/半異步模式: 同步 -> 程序完全按照代碼順序執(zhí)行, 異步 -> 程序執(zhí)行由系統(tǒng)事件(中斷/信號(hào))來驅(qū)動(dòng); 同步 -> 邏輯單元, 異步 -> io單元
半同步/半反應(yīng)堆(half-sync/half-reactive)模式: 主線程(異步, 監(jiān)聽/連接 socket) -> 請求隊(duì)列 -> 工作線程(獲取連接socket); 缺點(diǎn) -> 共享請求隊(duì)列, 需要加鎖 / 每個(gè)工作線程同一時(shí)間只能處理一個(gè)客戶請求, 增加工作進(jìn)程會(huì)增加工作線程切換開銷
高效 半同步/半異步模式: 主線程 只監(jiān)聽socket -> 派發(fā)新請求給 工作進(jìn)程 -> 工作進(jìn)程 連接socket/處理io/維持自己的事件循環(huán)
領(lǐng)導(dǎo)者/追隨者模式: 多個(gè)工作現(xiàn)成輪流獲得事件源集合, 輪流監(jiān)聽/分發(fā)并處理事件; 1個(gè)領(lǐng)導(dǎo)者 + 多個(gè)追隨者(線程池, 休眠) -> 領(lǐng)導(dǎo)者監(jiān)聽到io事件 -> 自己處理io事件/線程池中選出新的領(lǐng)導(dǎo)者
http://qiniu.daydaygo.top/high-performance-linux-server-programming/leader-follower.png
有限狀態(tài)機(jī) finite state machine
提供服務(wù)器性能的其他建議
池(pool): 服務(wù)器硬件資源相對 充裕 -> 空間換時(shí)間; 一組資源集合, 服務(wù)器啟動(dòng)之初就完全創(chuàng)建并初始化, 這樣就成為了 靜態(tài)資源, 服務(wù)器正式運(yùn)行時(shí), 需要相關(guān)資源可以直接從池中獲取, 無須動(dòng)態(tài)分配, 使用完后可以直接把資源放回池中, 無須執(zhí)行系統(tǒng)調(diào)用來釋放資源; 分配 足夠多 + 動(dòng)態(tài)分配; 內(nèi)存池 / 進(jìn)程池 / 線程池 / 連接池
內(nèi)存池: socket 接收緩存/發(fā)送緩存
進(jìn)程池/線程池 -> 并發(fā)編程, 無須調(diào)用 fork/pthread_create
連接池: 服務(wù)器/服務(wù)器機(jī)群的內(nèi)部永久連接, 比如 db連接池
數(shù)據(jù)復(fù)制: 避免不必要的數(shù)據(jù)復(fù)制
內(nèi)存緩沖區(qū) -> 用戶程序緩沖區(qū): 內(nèi)核 直接處理, 比如 ftp服務(wù)器中使用 零拷貝 函數(shù) sendfile()
用戶代碼內(nèi) -> 比如2個(gè)進(jìn)程間要傳遞大量數(shù)據(jù), 應(yīng)該考慮 共享內(nèi)存, 而不是 管道/消息隊(duì)列
上線文切換(context switch): 進(jìn)程切換/線程切換 導(dǎo)致的 系統(tǒng)開銷
共享資源加鎖保護(hù): 鎖 -> 不處理任何業(yè)務(wù)邏輯, 而且需要訪問內(nèi)核資源 -> 如果有更好的解決方案, 就應(yīng)該避免使用鎖 / 如果必須使用鎖, 應(yīng)盡量減少鎖的粒度
io復(fù)用
io復(fù)用: 程序同時(shí)監(jiān)聽多個(gè) fd; 本身是阻塞的
使用 io 復(fù)用的場景:
- client需要同時(shí)監(jiān)聽多個(gè) socket
- client需要同時(shí)處理用戶輸入和網(wǎng)絡(luò)連接
- tcp server需要同時(shí)處理 socket 監(jiān)聽/連接 -> io復(fù)用最多的場合
- server 需要同時(shí)處理 tcp/udp, 比如 回射 server
- server 需要 監(jiān)聽多個(gè)端口/處理多個(gè)服務(wù), 比如 xinetd server
select 系統(tǒng)調(diào)用用途: 在一段指定時(shí)間內(nèi), 監(jiān)聽用戶感興趣的 fd 上的 read/write/exception 事件; fd 就緒條件: 可讀 -> balabala; 可寫 -> balabala; 處理帶外數(shù)據(jù)
poll 系統(tǒng)調(diào)用: 和 select 類似, 在一段時(shí)間內(nèi)輪詢一定數(shù)量的 fd, 以測試其中是否有就緒者
epoll 系統(tǒng)調(diào)用
使用一組函數(shù)來完成
把用戶關(guān)心的 fd 上的事件放在內(nèi)核的一個(gè)事件表中
LT(level trigger, 電平觸發(fā)): 默認(rèn), 相當(dāng)于一個(gè)效率較高的 poll; epoll_wait
檢測到事件時(shí)就通知應(yīng)用程序, 應(yīng)用程序可以不立即處理(因?yàn)闀?huì)重復(fù)通知)
ET(edge trigger, 邊沿觸發(fā)): epoll的高效工作模式; epoll_wait
檢測到事件時(shí)就通知應(yīng)用程序, 應(yīng)用程序必須立即處理(因?yàn)楹笮虿粫?huì)再通知), 減低了同一個(gè) epoll 事件被重復(fù)觸發(fā)的次數(shù)
EPOLLONESHOT 事件: 一個(gè) socket 連接在任一時(shí)刻都只被一個(gè)線程處理; 保證了連接完整性, 避免很多可能的競態(tài)條件
int epoll_create(int size) # 創(chuàng)建額外的 fd, 用來標(biāo)識(shí)內(nèi)核中的事件表
int epoll_ctl() # 操作內(nèi)核事件表
int epoll_wait() # 在一段超時(shí)時(shí)間內(nèi)等待一組 fd 上的事件 -> 只傳遞就緒事件, 不處理用戶注冊的事件
http://qiniu.daydaygo.top/high-performance-linux-server-programming/select-poll-epoll.png
信號(hào)
用戶/系統(tǒng)/進(jìn)程 發(fā)送給目標(biāo)進(jìn)程的信息, 以通知目標(biāo)進(jìn)程 某個(gè)狀態(tài)的改變/系統(tǒng)異常
被掛起的信號(hào): 設(shè)置進(jìn)程信號(hào)掩碼 -> 屏蔽信號(hào)(程序不用處理所有的信號(hào))
統(tǒng)一事件源: 信號(hào)事件/io事件一樣被處理; 信號(hào)事件 -> 管道 -> 監(jiān)聽管道讀端fd 上的可讀事件 -> io事件; 如 libevent 庫
linux信號(hào)可由如下條件產(chǎn)生:
- 對于前臺(tái)進(jìn)程, 可以通過輸入特殊的終端字符來發(fā)送信號(hào), 如 Ctrl-C 通常會(huì)發(fā)送一個(gè)中斷信號(hào)
- 系統(tǒng)異常, 如 浮點(diǎn)異常/非法內(nèi)存段訪問
- 系統(tǒng)狀態(tài)變化, 如 alarm定時(shí)器到期 -> SIGALRM 信號(hào)
- 運(yùn)行kill命令/調(diào)用kill函數(shù)
int kill(pid_t pid, int sig) # 發(fā)送信號(hào); pid -> pid/本進(jìn)程組/除init進(jìn)程外/其他進(jìn)程組; sig -> 都大于0
int signal() # 為一個(gè)信號(hào)設(shè)置處理函數(shù)
int sigaction() # 更健壯的接口
int sigpending() # 獲得當(dāng)前進(jìn)程被掛起的信號(hào)
網(wǎng)絡(luò)編程相關(guān)信號(hào):
- SIGHUP: 掛起進(jìn)程的控制終端; 沒有控制終端的網(wǎng)絡(luò)后臺(tái)程序 -> 強(qiáng)制服務(wù)器重讀配置文件
- SIGPIPE: 向 讀端關(guān)閉的 管道/socket 寫數(shù)據(jù) -> 默認(rèn)關(guān)閉進(jìn)程 -> 不希望錯(cuò)誤的寫操作而導(dǎo)致程序退出 -> 代碼中捕獲并處理該信號(hào)/至少忽略
- SIGURG: 內(nèi)核通知應(yīng)用程序帶外數(shù)據(jù)到達(dá) -> io復(fù)用/SIGURG信號(hào)
定時(shí)器
需要處理的第三類事件 - 定時(shí)事件: 如 定時(shí)檢測一個(gè)客戶連接的活動(dòng)狀態(tài); 有效組織, 預(yù)期觸發(fā) + 不影響主要邏輯
定時(shí)事件 -> 封裝成 定時(shí)器 -> 使用某種容器類數(shù)據(jù)結(jié)構(gòu)(2種高效管理定時(shí)器的容器: 時(shí)間輪/時(shí)間堆) -> 將所有定時(shí)器串聯(lián)起來 -> 實(shí)現(xiàn)對定時(shí)事件的統(tǒng)一管理
linux 3種定時(shí)方法: socket選項(xiàng) SO_RECVTIEMO/SO_SENDTIEMO
; SIGALRM 信號(hào); io復(fù)用超時(shí)參數(shù)
定時(shí)器至少包含2個(gè)成員: 超時(shí)時(shí)間(絕對/相對) + 任務(wù)回調(diào)函數(shù)
定時(shí)tick: 使用固定頻率心搏函數(shù)tick -> 依次檢測到期定時(shí)器 -> 執(zhí)行定時(shí)器上的回調(diào)函數(shù)(時(shí)間輪)
最短時(shí)間tick: 每次使用最小定時(shí)器超時(shí)值作為tick -> tick調(diào)用 -> 最小定時(shí)器被調(diào)用 -> 更新剩余定時(shí)器(時(shí)間堆)
簡單時(shí)間輪: 每個(gè)槽(slot)用有相同的槽間隔si(slot interval, 其實(shí)就是心搏時(shí)間tick); 每個(gè)槽放在一個(gè) 定時(shí)器鏈表, 新的定時(shí)器分配通過定時(shí)時(shí)間hash到不同的槽
復(fù)雜時(shí)間輪 -> 類似 水表, 有不同精度的輪子
時(shí)間堆: 最小堆實(shí)現(xiàn)
高性能io框架庫 - Libevent
linux服務(wù)器必須處理的3類事件: io事件/信號(hào)/定時(shí)事件
處理事件需要考慮的問題: 統(tǒng)一事件源 -> io復(fù)用; 可移植性; 對并發(fā)編程的支持, 避免競態(tài)條件
句柄(handle): 統(tǒng)一事件源 -> 綁定句柄 -> 內(nèi)核檢測到就緒事件 -> 通過句柄通知應(yīng)用程序
事件循環(huán): 無法預(yù)知客戶 連接請求/暫停信號(hào) -> 循環(huán)等待并處理
事件多路分發(fā)器(EventDemultiplexer): 封裝各種 io復(fù)用系統(tǒng) 為統(tǒng)一的接口
具體事件處理器: 框架提供一個(gè)接口, 應(yīng)用程序來自己擴(kuò)展
Libevent 源碼分析:
- 跨平臺(tái)
- 統(tǒng)一事件源
- 線程安全, 使用 libevent_pthreads
- 基于 Reactor 模式實(shí)現(xiàn)
- 編寫產(chǎn)品級函數(shù)庫需要考慮哪些細(xì)節(jié)
- 提高C語言功底: 大量函數(shù)指針 + 多態(tài)機(jī)制 + 一些基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)的高效實(shí)現(xiàn)
多進(jìn)程編程
多進(jìn)程編程內(nèi)容:
- fork系統(tǒng)調(diào)用 -> 復(fù)制進(jìn)程映像; exec系統(tǒng)調(diào)用 -> 替換進(jìn)程映像
- 僵尸進(jìn)程以及如何避免
- 進(jìn)程間通信(inter-process communication, IPC)最簡單的方式: 管道
- 3種System V IPC: 信號(hào)量/消息隊(duì)列/共享內(nèi)存
- 進(jìn)程間傳遞fd的通用做法: 通過unix本地域socket傳遞特殊的輔助數(shù)據(jù)
fork 系統(tǒng)調(diào)用: 創(chuàng)建進(jìn)程; 每次調(diào)用返回2次 -> 父進(jìn)程返回子進(jìn)程pid, 子進(jìn)程返回0; 寫時(shí)復(fù)制(copy on write)
exec 系統(tǒng)調(diào)用: 子進(jìn)程中執(zhí)行其他程序; 原程序已經(jīng)被exec參數(shù)指定的程序完全替換(代碼+數(shù)據(jù)) -> 原程序在exec調(diào)用之后的代碼都不會(huì)執(zhí)行
僵尸態(tài) 1: 父進(jìn)程一般需要跟蹤子進(jìn)程退出狀態(tài), 所以子進(jìn)程退出時(shí)內(nèi)核不會(huì)立即釋放該進(jìn)程進(jìn)程表表項(xiàng) -> 子進(jìn)程退出之后, 父進(jìn)程讀取其退出狀態(tài)之前
僵尸態(tài) 2: 父進(jìn)程異常 結(jié)束/異常終止, 子進(jìn)程繼續(xù)運(yùn)行, os將其ppid設(shè)置為1(init進(jìn)程) -> 父進(jìn)程退出之后, 子進(jìn)程退出之前
訪問共享資源的代碼 -> 關(guān)鍵代碼段/臨界區(qū)
進(jìn)程同步問題 -> 同一個(gè)時(shí)刻只有一個(gè)進(jìn)程可以擁有對資源的獨(dú)占式訪問 -> 確保任一時(shí)刻只有一個(gè)進(jìn)程能進(jìn)入關(guān)鍵代碼段
信號(hào)量(semaphore): 特殊的變量, 只能取自然數(shù)值并只支持2種操作 P/V 操作
共享內(nèi)存: 最高效IPC機(jī)制; 需要輔助手段同步進(jìn)程對共享內(nèi)存的訪問; 通常和其他IPC方式一起使用;
api1: sys/shm.h
-> shmget/shmat/shmdt/shmctl
api2: mmap() + 打開同一個(gè)文件 -> 無關(guān)進(jìn)程之間共享內(nèi)存
實(shí)例: 聊天室服務(wù)器
管道/命名管道: 必須 FIFO 方式接收數(shù)據(jù)
消息隊(duì)列: 在2個(gè)進(jìn)程之間傳遞二進(jìn)制數(shù)據(jù)塊的一種簡單有效方式; 每個(gè)數(shù)據(jù)塊包含特定type -> 接收方有選擇的接收數(shù)據(jù)
api: sys/msg.h
-> msgget() / msgsnd / msgrcv / msgctl
傳遞fd: 接收進(jìn)程創(chuàng)建一個(gè)新的fd -> 發(fā)送進(jìn)程和新進(jìn)程的 fd 都執(zhí)行內(nèi)核中形同的文件表項(xiàng)
pid_t fork(void)
# 退出進(jìn)程, 避免僵尸進(jìn)程
wait() # 將進(jìn)程阻塞, 直到某個(gè)子進(jìn)程結(jié)束運(yùn)行
waitpid() # 只等待pid參數(shù)指定的子進(jìn)程
# 管道
pipe() # 創(chuàng)建管道
socketpair() # 創(chuàng)建全雙工管道
# 信號(hào)量 P/V操作
P(sv): sv>0 -> --sv; sv==0 -> 掛起
V(sv): 其他進(jìn)程等待 sv -> 喚醒; 沒有, ++sv
# 信號(hào)量系統(tǒng)調(diào)用
int semget() # 創(chuàng)建新的信號(hào)量集
int semop() # 改變信號(hào)量, 即 P/V 操作
int semctl() # 允許調(diào)用者對信號(hào)量進(jìn)行直接控制
# 共享內(nèi)存系統(tǒng)調(diào)用
int shmget() # 創(chuàng)建/獲取 共享內(nèi)存
void shmat() # 關(guān)聯(lián) 共享內(nèi)存 到進(jìn)程的地址空間
int shmdt() # 從進(jìn)程地址空間 分離 共享內(nèi)存
int shmctl() # 控制 共享內(nèi)存 某些屬性
# ipc 相關(guān)命令
ipcs # 列出os中共享資源
ipcrm # 刪除遺留在os中的共享資源
多線程編程
linux線程庫 -> NPTL(native POSIX thread library)
POSIX 線程(簡稱 pthread)標(biāo)準(zhǔn):
- 創(chuàng)建/結(jié)束 線程
- 讀取/設(shè)置 線程屬性
- 同步方式: POSIX信號(hào)量 / 互斥鎖 / 條件變量
根據(jù)運(yùn)行環(huán)境和調(diào)度者: 內(nèi)核線程 -> 運(yùn)行在內(nèi)核空間, 由內(nèi)核來調(diào)度; 用戶線程 -> 運(yùn)行在用戶空間, 由線程庫來調(diào)度; 內(nèi)核線程 作為 用戶線程 運(yùn)行的容器
完全在用戶空間實(shí)現(xiàn)的線程; 無須內(nèi)核支持; 對應(yīng)一個(gè)內(nèi)核線程; 優(yōu)點(diǎn) -> 創(chuàng)建/調(diào)度 快, 不占用額外系統(tǒng)資源; 缺點(diǎn) -> 一個(gè)進(jìn)程的多個(gè)線程無法運(yùn)行在不同 cpu 上
完全由內(nèi)核調(diào)度: 1:1 映射用戶空間線程和內(nèi)核線程
雙層調(diào)度: 混合上面2種
pthread api: pthread.h
POSIX 信號(hào)量: 和IPC中的信號(hào)量定義一樣; api -> semaphore.h
互斥鎖: 同步線程對共享數(shù)據(jù)的訪問; 用來保護(hù)關(guān)鍵代碼塊(加鎖/解鎖), 類似二進(jìn)制信號(hào)量; api -> pthread.h -> pthread_mutex_*
互斥鎖死鎖: 對一個(gè)已經(jīng)加鎖的普通鎖再次加鎖 -> 如設(shè)計(jì)不夠仔細(xì)的遞歸函數(shù); 2個(gè)線程按照不同的順序唉申請2個(gè)互斥鎖
條件變量: 線程之間同步共享數(shù)據(jù)的值; api -> pthread.h -> pthread_cond_*
可重入函數(shù): 能被多個(gè)線程同時(shí)調(diào)用且不發(fā)生靜態(tài)條件 -> 線程安全(thread safe); linux庫函數(shù)只有一小部分不可重入; 不可重入函數(shù)的重入版本 -> 函數(shù)末尾加 _r
# pthread
pthread_read() # 創(chuàng)建
pthread_exit() # 最好調(diào)用此函數(shù), 以確保安全/干凈地退出
pthread_join() # 同一個(gè)進(jìn)程的所有進(jìn)程都可以調(diào)用此函數(shù)來回收其他線程
pthread_cancel() # 異常終止一個(gè)線程
pthread_atfork() # 確保fork調(diào)用后父進(jìn)程和子進(jìn)程都擁有一個(gè)清楚的鎖狀態(tài)
pthread_sigmask() # 每個(gè)線程可以獨(dú)立的設(shè)置信號(hào)掩碼
進(jìn)程池和線程池
動(dòng)態(tài)創(chuàng)建缺點(diǎn): 耗時(shí), 響應(yīng)慢; 動(dòng)態(tài)創(chuàng)建 子進(jìn)程/子線程 為一個(gè)客戶服務(wù)會(huì)產(chǎn)生大量細(xì)微 進(jìn)程/線程, 進(jìn)程/線程 切換將消耗大量cpu; 動(dòng)態(tài)創(chuàng)建 子進(jìn)程 是當(dāng)前進(jìn)程的完整映像, 必須警慎管理分配的 fd 等系統(tǒng)資源
進(jìn)程池: 建立主進(jìn)程 -> 主進(jìn)程建立主進(jìn)程 -> 新任務(wù) -> 分配任務(wù) -> 通知機(jī)制(主進(jìn)程傳遞信息給子進(jìn)程)
分配任務(wù)方式: 主動(dòng)選擇 -> 隨機(jī)算法/輪流選取(round robin) -> 均衡分配; 工作隊(duì)列
通知機(jī)制: 預(yù)先建立管道; 父子線程之間只需要把這些數(shù)據(jù)定義為全局即可
處理多用戶: 并發(fā)模式選擇; 常連接(一個(gè)客戶多次請求可以復(fù)用一個(gè)tcp連接); 同一個(gè)客戶是否由同一個(gè)進(jìn)程來處理
高性能服務(wù)器優(yōu)化與監(jiān)控
服務(wù)器 調(diào)制/調(diào)試/測試
系統(tǒng)配置調(diào)制
fd: 幾乎所有的系統(tǒng)調(diào)用都是和 fd 打交道, 而系統(tǒng)的分配的 fd數(shù)量 是有限制的, 所以我們總是要關(guān)閉那些不在使用的 fd, 以釋放占用的資源, 如 守護(hù)進(jìn)程關(guān)閉 stdio/stderr
最大 fd 限制: 用戶級 + 系統(tǒng)級
內(nèi)核模塊
ulimit -n # 查看用戶級 fd 限制
ulimit -SHn mak-file-number # (臨時(shí))修改用戶級 fd 限制為 max-file-number
/etc/security/limits.conf # (永久)
sysctl -w fs.file-max=max-file-number # (臨時(shí))修改系統(tǒng)級 fd 限制為 max-file-number
/etc/sysctl.conf # (永久)
sysctl -a # 查看下面這些內(nèi)核參數(shù)
/proc/sys # 幾乎所有的 內(nèi)核模塊+驅(qū)動(dòng)程序 都在此文件系統(tǒng)下提供了某些配置文件以供用戶調(diào)整模塊的屬性和行為
fs/ # 文件系統(tǒng)相關(guān)
file-max # 系統(tǒng)級 fd 限制(臨時(shí)修改)
innode-max # 應(yīng)當(dāng)設(shè)置為 file-max 的 3-4 倍
epoll/max_user_wathes # 一個(gè)用戶能忘epoll內(nèi)核事件表注冊的事件總量
net/ # 網(wǎng)絡(luò)模塊
core/
somaxconn # listen監(jiān)聽隊(duì)列: ESTABLISH最大數(shù)量
ipv4/
tcp_max_syn_backlog # listen監(jiān)聽隊(duì)列: ESTABLISH+SYN_RCVD 最大數(shù)量
tcp_wmem # 一個(gè)socket的tcp寫緩沖區(qū) min/default/max
tcp_rmem # 一個(gè)socket的tcp讀緩沖區(qū) min/default/max -> 接收通告窗口
tcp_syncookies # 是否打開tcp同步標(biāo)簽(tcp_syncookies)
ipv6/
gdb調(diào)試
# 調(diào)試子進(jìn)程
ps -ef|grep cgisrv
gdb
(gdb) gdb attach 4183 # 附加子進(jìn)程
(gdb) b processpool.h:264 # 設(shè)置子進(jìn)程中的斷點(diǎn)
(gdb) set follow-fork-mode parent/child # 程序執(zhí)行 fork 系統(tǒng)調(diào)用后調(diào)試 父/子 進(jìn)程
# 調(diào)試多線程程序
info threads # 顯示當(dāng)前可調(diào)試的所有線程, 線程會(huì)帶上id
thread id # 調(diào)試指定id的線程
set scheduler-locking off/on/step # 是否只讓當(dāng)前調(diào)試進(jìn)程運(yùn)行
壓力測試
思路: 使用 epool 實(shí)現(xiàn)io復(fù)用來模仿壓力
系統(tǒng)監(jiān)控工具
tcpdump: tcp 抓包工具
lsof(list open file): 查看打開的 fd
nc(netcat): 快速構(gòu)建網(wǎng)絡(luò)連接
strace: 測試服務(wù)器性能的重要工具
netstat: 網(wǎng)絡(luò)信息統(tǒng)計(jì)
vmstat(virtual memory statistic): 實(shí)時(shí)輸出系統(tǒng)各種資源的使用情況
ifstat(interface statistic): 簡單的網(wǎng)絡(luò)流量監(jiān)測工具
mpstat(multi-processor statistic): 實(shí)時(shí)監(jiān)測多處理器系統(tǒng)上的每個(gè) cpu 使用情況
# 使用表達(dá)式進(jìn)一步過濾數(shù)據(jù)
tcpdump net 1.2.3.0/24 # type: host net port portrange
tcpdump dst port 13679 # dir(方向): src dst
tcpdump icmp # proto(協(xié)議): icmp tcp udp ip
tcpdump ip host a and not b # 支持 邏輯運(yùn)算
# lsof
lsof -i@192.168.1.108:22 # 連接到 ssh 的 socket fd
lsof -c websrv # 查看 websrv 程序打開了哪些 fd