01P-復(fù)習(xí)-Linux網(wǎng)絡(luò)編程
02P-信號量生產(chǎn)者復(fù)習(xí)
03P-協(xié)議 協(xié)議: 一組規(guī)則。
04P-7層模型和4層模型及代表協(xié)議 分層模型結(jié)構(gòu):
OSI七層模型: 物、數(shù)、網(wǎng)、傳、會、表、應(yīng)TCP/IP 4層模型:網(wǎng)(鏈路層/網(wǎng)絡(luò)接口層)、網(wǎng)、傳、應(yīng)應(yīng)用層:http、ftp、nfs、ssh、telnet。。。傳輸層:TCP、UDP網(wǎng)絡(luò)層:IP、ICMP、IGMP鏈路層:以太網(wǎng)幀協(xié)議、ARP
05P-網(wǎng)絡(luò)傳輸數(shù)據(jù)封裝流程
網(wǎng)絡(luò)傳輸流程:
數(shù)據(jù)沒有封裝之前,是不能在網(wǎng)絡(luò)中傳遞。數(shù)據(jù)-》應(yīng)用層-》傳輸層-》網(wǎng)絡(luò)層-》鏈路層 --- 網(wǎng)絡(luò)環(huán)境
06P-以太網(wǎng)幀和ARP請求
以太網(wǎng)幀協(xié)議:
ARP協(xié)議:根據(jù) Ip 地址獲取 mac 地址。以太網(wǎng)幀協(xié)議:根據(jù)mac地址,完成數(shù)據(jù)包傳輸。
07P-IP協(xié)議 IP協(xié)議:
版本: IPv4、IPv6 -- 4位TTL: time to live 。 設(shè)置數(shù)據(jù)包在路由節(jié)點中的跳轉(zhuǎn)上限。每經(jīng)過一個路由節(jié)點,該值-1, 減為0的路由,有義務(wù)將該數(shù)據(jù)包丟棄源IP: 32位。--- 4字節(jié) 192.168.1.108 --- 點分十進(jìn)制 IP地址(string) --- 二進(jìn)制 目的IP:32位。--- 4字節(jié)
08P-端口號和UDP協(xié)議 UDP: 16位:源端口號。 2^16 = 65536
16位:目的端口號。
IP地址:可以在網(wǎng)絡(luò)環(huán)境中,唯一標(biāo)識一臺主機(jī)。
端口號:可以網(wǎng)絡(luò)的一臺主機(jī)上,唯一標(biāo)識一個進(jìn)程。
ip地址+端口號:可以在網(wǎng)絡(luò)環(huán)境中,唯一標(biāo)識一個進(jìn)程。
09P-TCP協(xié)議 TCP協(xié)議:
16位:源端口號。 2^16 = 65536 16位:目的端口號。32序號;32確認(rèn)序號。 6個標(biāo)志位。16位窗口大小。 2^16 = 65536
10P-BS和CS模型對比 c/s模型:
client-server
b/s模型:
browser-serverC/S B/S優(yōu)點: 緩存大量數(shù)據(jù)、協(xié)議選擇靈活 安全性、跨平臺、開發(fā)工作量較小速度快缺點: 安全性、跨平臺、開發(fā)工作量較大 不能緩存大量數(shù)據(jù)、嚴(yán)格遵守 http
11P-套接字 網(wǎng)絡(luò)套接字: socket
一個文件描述符指向一個套接字(該套接字內(nèi)部由內(nèi)核借助兩個緩沖區(qū)實現(xiàn)。)在通信過程中, 套接字一定是成對出現(xiàn)的。
12P-回顧
13P-網(wǎng)絡(luò)字節(jié)序 網(wǎng)絡(luò)字節(jié)序:
小端法:(pc本地存儲) 高位存高地址。地位存低地址。 int a = 0x12345678大端法:(網(wǎng)絡(luò)存儲) 高位存低地址。地位存高地址。htonl --> 本地--》網(wǎng)絡(luò) (IP) 192.168.1.11 --> string --> atoi --> int --> htonl --> 網(wǎng)絡(luò)字節(jié)序htons --> 本地--》網(wǎng)絡(luò) (port)ntohl --> 網(wǎng)絡(luò)--》 本地(IP)ntohs --> 網(wǎng)絡(luò)--》 本地(Port)
14P-IP地址轉(zhuǎn)換函數(shù) IP地址轉(zhuǎn)換函數(shù):
int inet_pton(int af, const char *src, void *dst); 本地字節(jié)序(string IP) ---> 網(wǎng)絡(luò)字節(jié)序af:AF_INET、AF_INET6src:傳入,IP地址(點分十進(jìn)制)dst:傳出,轉(zhuǎn)換后的 網(wǎng)絡(luò)字節(jié)序的 IP地址。 返回值:成功: 1異常: 0, 說明src指向的不是一個有效的ip地址。失敗:-1const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); 網(wǎng)絡(luò)字節(jié)序 ---> 本地字節(jié)序(string IP)af:AF_INET、AF_INET6src: 網(wǎng)絡(luò)字節(jié)序IP地址dst:本地字節(jié)序(string IP)size: dst 的大小。返回值: 成功:dst。 失敗:NULL
15P-sockaddr地址結(jié)構(gòu)
sockaddr地址結(jié)構(gòu): IP + port --> 在網(wǎng)絡(luò)環(huán)境中唯一標(biāo)識一個進(jìn)程。
struct sockaddr_in addr;addr.sin_family = AF_INET/AF_INET6 man 7 ipaddr.sin_port = htons(9527);int dst;inet_pton(AF_INET, "192.157.22.45", (void *)&dst);addr.sin_addr.s_addr = dst;【*】addr.sin_addr.s_addr = htonl(INADDR_ANY); 取出系統(tǒng)中有效的任意IP地址。二進(jìn)制類型。bind(fd, (struct sockaddr *)&addr, size);
16P-socket模型創(chuàng)建流程分析
17P-socket和bind socket函數(shù):
#include <sys/socket.h>int socket(int domain, int type, int protocol); 創(chuàng)建一個 套接字domain:AF_INET、AF_INET6、AF_UNIXtype:SOCK_STREAM、SOCK_DGRAMprotocol: 0 返回值:成功: 新套接字所對應(yīng)文件描述符失敗: -1 errno
#include <arpa/inet.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 給socket綁定一個 地址結(jié)構(gòu) (IP+port)sockfd: socket 函數(shù)返回值struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(8888);addr.sin_addr.s_addr = htonl(INADDR_ANY);addr: 傳入?yún)?shù)(struct sockaddr *)&addraddrlen: sizeof(addr) 地址結(jié)構(gòu)的大小。返回值:成功:0失敗:-1 errno
18P-listen和accept int listen(int sockfd, int backlog); 設(shè)置同時與服務(wù)器建立連接的上限數(shù)。(同時進(jìn)行3次握手的客戶端數(shù)量)
sockfd: socket 函數(shù)返回值backlog:上限數(shù)值。最大值 128.返回值:成功:0失敗:-1 errno
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 阻塞等待客戶端建立連接,成功的話,返回一個與客戶端成功連接的socket文件描述符。
sockfd: socket 函數(shù)返回值addr:傳出參數(shù)。成功與服務(wù)器建立連接的那個客戶端的地址結(jié)構(gòu)(IP+port)socklen_t clit_addr_len = sizeof(addr);addrlen:傳入傳出。 &clit_addr_len入:addr的大小。 出:客戶端addr實際大小。返回值:成功:能與客戶端進(jìn)行數(shù)據(jù)通信的 socket 對應(yīng)的文件描述。失敗: -1 , errno
19P-connect int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 使用現(xiàn)有的 socket 與服務(wù)器建立連接
sockfd: socket 函數(shù)返回值struct sockaddr_in srv_addr; // 服務(wù)器地址結(jié)構(gòu)srv_addr.sin_family = AF_INET;srv_addr.sin_port = 9527 跟服務(wù)器bind時設(shè)定的 port 完全一致。inet_pton(AF_INET, "服務(wù)器的IP地址",&srv_adrr.sin_addr.s_addr);addr:傳入?yún)?shù)。服務(wù)器的地址結(jié)構(gòu)addrlen:服務(wù)器的地址結(jié)構(gòu)的大小返回值:成功:0失敗:-1 errno如果不使用bind綁定客戶端地址結(jié)構(gòu), 采用"隱式綁定".
20P-CS模型的TCP通信分析 TCP通信流程分析:
server:1. socket() 創(chuàng)建socket2. bind() 綁定服務(wù)器地址結(jié)構(gòu)3. listen() 設(shè)置監(jiān)聽上限4. accept() 阻塞監(jiān)聽客戶端連接5. read(fd) 讀socket獲取客戶端數(shù)據(jù)6. 小--大寫 toupper()7. write(fd)8. close();client:1. socket() 創(chuàng)建socket2. connect(); 與服務(wù)器建立連接3. write() 寫數(shù)據(jù)到 socket4. read() 讀轉(zhuǎn)換后的數(shù)據(jù)。5. 顯示讀取結(jié)果6. close()
21P-server的實現(xiàn) 代碼如下:
#include <stdio.h> #include <ctype.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #define SERV_PORT 9527 void sys_err(const char *str) { perror(str);
exit(1);
} int main(int argc, char *argv[]) { int lfd = 0, cfd = 0;
int ret, i;
char buf[BUFSIZ], client_IP[1024];
struct sockaddr_in serv_addr, clit_addr; // 定義服務(wù)器地址結(jié)構(gòu) 和 客戶端地址結(jié)構(gòu)
socklen_t clit_addr_len; // 客戶端地址結(jié)構(gòu)大小
serv_addr.sin_family = AF_INET; // IPv4
serv_addr.sin_port = htons(SERV_PORT); // 轉(zhuǎn)為網(wǎng)絡(luò)字節(jié)序的 端口號
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 獲取本機(jī)任意有效IP
lfd = socket(AF_INET, SOCK_STREAM, 0); //創(chuàng)建一個 socket
if (lfd == -1) {
sys_err("socket error");
}
bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));//給服務(wù)器socket綁定地址結(jié)構(gòu)(IP+port)
listen(lfd, 128); // 設(shè)置監(jiān)聽上限
clit_addr_len = sizeof(clit_addr); // 獲取客戶端地址結(jié)構(gòu)大小
cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len); // 阻塞等待客戶端連接請求
if (cfd == -1)
sys_err("accept error");
printf("client ip:%s port:%d\n",
inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)),
ntohs(clit_addr.sin_port)); // 根據(jù)accept傳出參數(shù),獲取客戶端 ip 和 port
while (1) {
ret = read(cfd, buf, sizeof(buf)); // 讀客戶端數(shù)據(jù)
write(STDOUT_FILENO, buf, ret); // 寫到屏幕查看
for (i = 0; i < ret; i++) // 小寫 -- 大寫
buf[i] = toupper(buf[i]);
write(cfd, buf, ret); // 將大寫,寫回給客戶端。
}
close(lfd);
close(cfd);
return 0;
}
編譯測試,結(jié)果如下:
22P-獲取客戶端地址結(jié)構(gòu) cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len); accept函數(shù)中的clit_addr傳出的就是客戶端地址結(jié)構(gòu),IP+port
于是,在代碼中增加此段代碼,可獲取客戶端信息: printf(“client ip:%s port:%d\n”, inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)), ntohs(clit_addr.sin_port));
上一節(jié)代碼中已經(jīng)有這段代碼,這里就不再跑一遍了。
23P-client的實現(xiàn)
#include <stdio.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #define SERV_PORT 9527 void sys_err(const char *str) { perror(str);
exit(1);
} int main(int argc, char *argv[]) { int cfd;
int conter = 10;
char buf[BUFSIZ];
struct sockaddr_in serv_addr; //服務(wù)器地址結(jié)構(gòu)
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
//inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
cfd = socket(AF_INET, SOCK_STREAM, 0);
if (cfd == -1)
sys_err("socket error");
int ret = connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (ret != 0)
sys_err("connect err");
while (--conter) {
write(cfd, "hello\n", 6);
ret = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret);
sleep(1);
}
close(cfd);
return 0;
}
編譯運行,結(jié)果如下:
這里遇到過一個問題,如果之前運行server,用Ctrl+z終止進(jìn)程,ps aux列表里會有服務(wù)器進(jìn)程殘留,這個會影響當(dāng)前服務(wù)器。解決方法是kill掉這些服務(wù)器進(jìn)程。不然端口被占用,當(dāng)前運行的服務(wù)器進(jìn)程接收不到東西,沒有回顯。
24P-總結(jié)
協(xié)議: 一組規(guī)則。
分層模型結(jié)構(gòu):
OSI七層模型: 物、數(shù)、網(wǎng)、傳、會、表、應(yīng)TCP/IP 4層模型:網(wǎng)(鏈路層/網(wǎng)絡(luò)接口層)、網(wǎng)、傳、應(yīng)應(yīng)用層:http、ftp、nfs、ssh、telnet。。。傳輸層:TCP、UDP網(wǎng)絡(luò)層:IP、ICMP、IGMP鏈路層:以太網(wǎng)幀協(xié)議、ARP
c/s模型:
client-server
b/s模型:
browser-serverC/S B/S優(yōu)點: 緩存大量數(shù)據(jù)、協(xié)議選擇靈活 安全性、跨平臺、開發(fā)工作量較小速度快缺點: 安全性、跨平臺、開發(fā)工作量較大 不能緩存大量數(shù)據(jù)、嚴(yán)格遵守 http
網(wǎng)絡(luò)傳輸流程:
數(shù)據(jù)沒有封裝之前,是不能在網(wǎng)絡(luò)中傳遞。數(shù)據(jù)-》應(yīng)用層-》傳輸層-》網(wǎng)絡(luò)層-》鏈路層 --- 網(wǎng)絡(luò)環(huán)境
以太網(wǎng)幀協(xié)議:
ARP協(xié)議:根據(jù) Ip 地址獲取 mac 地址。以太網(wǎng)幀協(xié)議:根據(jù)mac地址,完成數(shù)據(jù)包傳輸。
IP協(xié)議:
版本: IPv4、IPv6 -- 4位TTL: time to live 。 設(shè)置數(shù)據(jù)包在路由節(jié)點中的跳轉(zhuǎn)上限。每經(jīng)過一個路由節(jié)點,該值-1, 減為0的路由,有義務(wù)將該數(shù)據(jù)包丟棄源IP: 32位。--- 4字節(jié) 192.168.1.108 --- 點分十進(jìn)制 IP地址(string) --- 二進(jìn)制 目的IP:32位。--- 4字節(jié)
IP地址:可以在網(wǎng)絡(luò)環(huán)境中,唯一標(biāo)識一臺主機(jī)。
端口號:可以網(wǎng)絡(luò)的一臺主機(jī)上,唯一標(biāo)識一個進(jìn)程。
ip地址+端口號:可以在網(wǎng)絡(luò)環(huán)境中,唯一標(biāo)識一個進(jìn)程。
UDP: 16位:源端口號。 2^16 = 65536
16位:目的端口號。
TCP協(xié)議:
16位:源端口號。 2^16 = 65536 16位:目的端口號。32序號;32確認(rèn)序號。 6個標(biāo)志位。16位窗口大小。 2^16 = 65536
網(wǎng)絡(luò)套接字: socket
一個文件描述符指向一個套接字(該套接字內(nèi)部由內(nèi)核借助兩個緩沖區(qū)實現(xiàn)。)在通信過程中, 套接字一定是成對出現(xiàn)的。
網(wǎng)絡(luò)字節(jié)序:
小端法:(pc本地存儲) 高位存高地址。地位存低地址。 int a = 0x12345678大端法:(網(wǎng)絡(luò)存儲) 高位存低地址。地位存高地址。htonl --> 本地--》網(wǎng)絡(luò) (IP) 192.168.1.11 --> string --> atoi --> int --> htonl --> 網(wǎng)絡(luò)字節(jié)序htons --> 本地--》網(wǎng)絡(luò) (port)ntohl --> 網(wǎng)絡(luò)--》 本地(IP)ntohs --> 網(wǎng)絡(luò)--》 本地(Port)
IP地址轉(zhuǎn)換函數(shù):
int inet_pton(int af, const char *src, void *dst); 本地字節(jié)序(string IP) ---> 網(wǎng)絡(luò)字節(jié)序af:AF_INET、AF_INET6src:傳入,IP地址(點分十進(jìn)制)dst:傳出,轉(zhuǎn)換后的 網(wǎng)絡(luò)字節(jié)序的 IP地址。 返回值:成功: 1異常: 0, 說明src指向的不是一個有效的ip地址。失敗:-1const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); 網(wǎng)絡(luò)字節(jié)序 ---> 本地字節(jié)序(string IP)af:AF_INET、AF_INET6src: 網(wǎng)絡(luò)字節(jié)序IP地址dst:本地字節(jié)序(string IP)size: dst 的大小。返回值: 成功:dst。 失敗:NULL
sockaddr地址結(jié)構(gòu): IP + port --> 在網(wǎng)絡(luò)環(huán)境中唯一標(biāo)識一個進(jìn)程。
struct sockaddr_in addr;addr.sin_family = AF_INET/AF_INET6 man 7 ipaddr.sin_port = htons(9527);int dst;inet_pton(AF_INET, "192.157.22.45", (void *)&dst);addr.sin_addr.s_addr = dst;【*】addr.sin_addr.s_addr = htonl(INADDR_ANY); 取出系統(tǒng)中有效的任意IP地址。二進(jìn)制類型。bind(fd, (struct sockaddr *)&addr, size);
socket函數(shù):
#include <sys/socket.h>int socket(int domain, int type, int protocol); 創(chuàng)建一個 套接字domain:AF_INET、AF_INET6、AF_UNIXtype:SOCK_STREAM、SOCK_DGRAMprotocol: 0 返回值:成功: 新套接字所對應(yīng)文件描述符失敗: -1 errno#include <arpa/inet.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 給socket綁定一個 地址結(jié)構(gòu) (IP+port)sockfd: socket 函數(shù)返回值struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(8888);addr.sin_addr.s_addr = htonl(INADDR_ANY);addr: 傳入?yún)?shù)(struct sockaddr *)&addraddrlen: sizeof(addr) 地址結(jié)構(gòu)的大小。返回值:成功:0失敗:-1 errnoint listen(int sockfd, int backlog); 設(shè)置同時與服務(wù)器建立連接的上限數(shù)。(同時進(jìn)行3次握手的客戶端數(shù)量)sockfd: socket 函數(shù)返回值backlog:上限數(shù)值。最大值 128.返回值:成功:0失敗:-1 errno int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 阻塞等待客戶端建立連接,成功的話,返回一個與客戶端成功連接的socket文件描述符。sockfd: socket 函數(shù)返回值addr:傳出參數(shù)。成功與服務(wù)器建立連接的那個客戶端的地址結(jié)構(gòu)(IP+port)socklen_t clit_addr_len = sizeof(addr);addrlen:傳入傳出。 &clit_addr_len入:addr的大小。 出:客戶端addr實際大小。返回值:成功:能與客戶端進(jìn)行數(shù)據(jù)通信的 socket 對應(yīng)的文件描述。失敗: -1 , errnoint connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 使用現(xiàn)有的 socket 與服務(wù)器建立連接sockfd: socket 函數(shù)返回值struct sockaddr_in srv_addr; // 服務(wù)器地址結(jié)構(gòu)srv_addr.sin_family = AF_INET;srv_addr.sin_port = 9527 跟服務(wù)器bind時設(shè)定的 port 完全一致。inet_pton(AF_INET, "服務(wù)器的IP地址",&srv_adrr.sin_addr.s_addr);addr:傳入?yún)?shù)。服務(wù)器的地址結(jié)構(gòu)addrlen:服務(wù)器的地址結(jié)構(gòu)的大小返回值:成功:0失敗:-1 errno如果不使用bind綁定客戶端地址結(jié)構(gòu), 采用"隱式綁定".
TCP通信流程分析:
server:1. socket() 創(chuàng)建socket2. bind() 綁定服務(wù)器地址結(jié)構(gòu)3. listen() 設(shè)置監(jiān)聽上限4. accept() 阻塞監(jiān)聽客戶端連接5. read(fd) 讀socket獲取客戶端數(shù)據(jù)6. 小--大寫 toupper()7. write(fd)8. close();client:1. socket() 創(chuàng)建socket2. connect(); 與服務(wù)器建立連接3. write() 寫數(shù)據(jù)到 socket4. read() 讀轉(zhuǎn)換后的數(shù)據(jù)。5. 顯示讀取結(jié)果6. close()
25P-復(fù)習(xí)
26P-三次握手建立連接
27P-數(shù)據(jù)通信
并不是一次發(fā)送,一次應(yīng)答。也可以批量應(yīng)答
28P-四次握手關(guān)閉連接
29P-半關(guān)閉補(bǔ)充說明 這里其實就是想說明,完成兩次揮手后,不是說兩端的連接斷開了,主動端關(guān)閉了寫緩沖區(qū),不能再向?qū)Χ税l(fā)送數(shù)據(jù),被動端關(guān)閉了讀緩沖區(qū),不能再從對端讀取數(shù)據(jù)。然而主動端還是能夠讀取對端發(fā)來的數(shù)據(jù)。
30P-滑動窗口和TCP數(shù)據(jù)包格式
滑動窗口:
發(fā)送給連接對端,本端的緩沖區(qū)大小(實時),保證數(shù)據(jù)不會丟失。
31P-通信時序與代碼對應(yīng)關(guān)系
32P-TCP通信時序總結(jié) 三次握手:
主動發(fā)起連接請求端,發(fā)送 SYN 標(biāo)志位,請求建立連接。 攜帶序號號、數(shù)據(jù)字節(jié)數(shù)(0)、滑動窗口大小。被動接受連接請求端,發(fā)送 ACK 標(biāo)志位,同時攜帶 SYN 請求標(biāo)志位。攜帶序號、確認(rèn)序號、數(shù)據(jù)字節(jié)數(shù)(0)、滑動窗口大小。主動發(fā)起連接請求端,發(fā)送 ACK 標(biāo)志位,應(yīng)答服務(wù)器連接請求。攜帶確認(rèn)序號。
四次揮手:
主動關(guān)閉連接請求端, 發(fā)送 FIN 標(biāo)志位。 被動關(guān)閉連接請求端, 應(yīng)答 ACK 標(biāo)志位。 ----- 半關(guān)閉完成。被動關(guān)閉連接請求端, 發(fā)送 FIN 標(biāo)志位。主動關(guān)閉連接請求端, 應(yīng)答 ACK 標(biāo)志位。 ----- 連接全部關(guān)閉
滑動窗口:
發(fā)送給連接對端,本端的緩沖區(qū)大小(實時),保證數(shù)據(jù)不會丟失。
33P-錯誤處理函數(shù)的封裝思路 wrap.h文件如下,就是包裹函數(shù)的聲明
#ifndef _WRAP_H #define _WRAP_H void perr_exit(const char *s); int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); int Bind(int fd, const struct sockaddr *sa, socklen_t salen); int Connect(int fd, const struct sockaddr *sa, socklen_t salen); int Listen(int fd, int backlog); int Socket(int family, int type, int protocol); ssize_t Read(int fd, void *ptr, size_t nbytes); ssize_t Write(int fd, const void *ptr, size_t nbytes); int Close(int fd); ssize_t Readn(int fd, void *vptr, size_t n); ssize_t Writen(int fd, const void *vptr, size_t n); ssize_t my_read(int fd, char *ptr); ssize_t Readline(int fd, void *vptr, size_t maxlen); #endif
wrap.c隨便取一部分,如下,就是包裹函數(shù)的代碼:
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <sys/socket.h> void perr_exit(const char *s) { perror(s);
exit(-1);
} int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) { int n;
again: if ((n = accept(fd, sa, salenptr)) < 0) {
if ((errno == ECONNABORTED) || (errno == EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
} int Bind(int fd, const struct sockaddr *sa, socklen_t salen) { int n;
if ((n = bind(fd, sa, salen)) < 0)
perr_exit("bind error");
return n;
}
這里原函數(shù)和包裹函數(shù)的函數(shù)名差異只有首字母大寫,這是因為man page對字母大小寫不敏感,同名的包裹函數(shù)一樣可以跳轉(zhuǎn)至man page
34P-錯誤處理函數(shù)封裝 就是重新包裹需要檢查返回值的函數(shù),讓代碼不那么肥胖。
35P-封裝思想總結(jié)和readn、readline封裝思想說明 錯誤處理函數(shù):
封裝目的: 在 server.c 編程過程中突出邏輯,將出錯處理與邏輯分開,可以直接跳轉(zhuǎn)man手冊。【wrap.c】 【wrap.h】存放網(wǎng)絡(luò)通信相關(guān)常用 自定義函數(shù) 存放 網(wǎng)絡(luò)通信相關(guān)常用 自定義函數(shù)原型(聲明)。命名方式:系統(tǒng)調(diào)用函數(shù)首字符大寫, 方便查看man手冊如:Listen()、Accept();函數(shù)功能:調(diào)用系統(tǒng)調(diào)用函數(shù),處理出錯場景。在 server.c 和 client.c 中調(diào)用 自定義函數(shù)聯(lián)合編譯 server.c 和 wrap.c 生成 serverclient.c 和 wrap.c 生成 client
readn: 讀 N 個字節(jié)
readline:
讀一行
36P-中午復(fù)習(xí) 三次握手:
主動發(fā)起連接請求端,發(fā)送 SYN 標(biāo)志位,請求建立連接。 攜帶序號號、數(shù)據(jù)字節(jié)數(shù)(0)、滑動窗口大小。被動接受連接請求端,發(fā)送 ACK 標(biāo)志位,同時攜帶 SYN 請求標(biāo)志位。攜帶序號、確認(rèn)序號、數(shù)據(jù)字節(jié)數(shù)(0)、滑動窗口大小。主動發(fā)起連接請求端,發(fā)送 ACK 標(biāo)志位,應(yīng)答服務(wù)器連接請求。攜帶確認(rèn)序號。
四次揮手:
主動關(guān)閉連接請求端, 發(fā)送 FIN 標(biāo)志位。 被動關(guān)閉連接請求端, 應(yīng)答 ACK 標(biāo)志位。 ----- 半關(guān)閉完成。被動關(guān)閉連接請求端, 發(fā)送 FIN 標(biāo)志位。主動關(guān)閉連接請求端, 應(yīng)答 ACK 標(biāo)志位。 ----- 連接全部關(guān)閉
滑動窗口:
發(fā)送給連接對端,本端的緩沖區(qū)大小(實時),保證數(shù)據(jù)不會丟失。
錯誤處理函數(shù):
封裝目的: 在 server.c 編程過程中突出邏輯,將出錯處理與邏輯分開,可以直接跳轉(zhuǎn)man手冊。【wrap.c】 【wrap.h】存放網(wǎng)絡(luò)通信相關(guān)常用 自定義函數(shù) 存放 網(wǎng)絡(luò)通信相關(guān)常用 自定義函數(shù)原型(聲明)。命名方式:系統(tǒng)調(diào)用函數(shù)首字符大寫, 方便查看man手冊如:Listen()、Accept();函數(shù)功能:調(diào)用系統(tǒng)調(diào)用函數(shù),處理出錯場景。在 server.c 和 client.c 中調(diào)用 自定義函數(shù)聯(lián)合編譯 server.c 和 wrap.c 生成 serverclient.c 和 wrap.c 生成 client
readn: 讀 N 個字節(jié)
readline:
讀一行
read 函數(shù)的返回值:
1. > 0 實際讀到的字節(jié)數(shù)2. = 0 已經(jīng)讀到結(jié)尾(對端已經(jīng)關(guān)閉)【 !重 !點 !】3. -1 應(yīng)進(jìn)一步判斷errno的值:errno = EAGAIN or EWOULDBLOCK: 設(shè)置了非阻塞方式 讀。 沒有數(shù)據(jù)到達(dá)。 errno = EINTR 慢速系統(tǒng)調(diào)用被 中斷。errno = “其他情況” 異常。
37P-多進(jìn)程并發(fā)服務(wù)器思路分析 1. Socket(); 創(chuàng)建 監(jiān)聽套接字 lfd 2. Bind() 綁定地址結(jié)構(gòu) Strcut scokaddr_in addr; 3. Listen(); 4. while (1) {
cfd = Accpet(); 接收客戶端連接請求。pid = fork();if (pid == 0){ 子進(jìn)程 read(cfd) --- 小-》大 --- write(cfd)close(lfd) 關(guān)閉用于建立連接的套接字 lfdread()小--大write()} else if (pid > 0) { close(cfd); 關(guān)閉用于與客戶端通信的套接字 cfd contiue;}}5. 子進(jìn)程:close(lfd)read()小--大write() 父進(jìn)程:close(cfd);注冊信號捕捉函數(shù): SIGCHLD在回調(diào)函數(shù)中, 完成子進(jìn)程回收while (waitpid());
38P-多線程并發(fā)服務(wù)器分析 多線程并發(fā)服務(wù)器: server.c
1. Socket(); 創(chuàng)建 監(jiān)聽套接字 lfd2. Bind() 綁定地址結(jié)構(gòu) Strcut scokaddr_in addr;3. Listen(); 4. while (1) { cfd = Accept(lfd, );pthread_create(&tid, NULL, tfn, (void *)cfd);pthread_detach(tid); // pthead_join(tid, void **); 新線程---專用于回收子線程。}5. 子線程:void *tfn(void *arg) {// close(lfd) 不能關(guān)閉。 主線程要使用lfdread(cfd)小--大write(cfd)pthread_exit((void *)10); }
39P-多進(jìn)程并發(fā)服務(wù)器實現(xiàn) 第一個版本的代碼如下:
#include <stdio.h> #include <ctype.h> #include <stdlib.h> #include <sys/wait.h> #include <string.h> #include <strings.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/socket.h> #include <arpa/inet.h> #include <pthread.h> #include “wrap.h” #define SRV_PORT 9999 int main(int argc, char *argv[]) { int lfd, cfd;
pid_t pid;
struct sockaddr_in srv_addr, clt_addr;
socklen_t clt_addr_len;
char buf[BUFSIZ];
int ret, i;
//memset(&srv_addr, 0, sizeof(srv_addr)); // 將地址結(jié)構(gòu)清零
bzero(&srv_addr, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = Socket(AF_INET, SOCK_STREAM, 0);
Bind(lfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
Listen(lfd, 128);
clt_addr_len = sizeof(clt_addr);
while (1) {
cfd = Accept(lfd, (struct sockaddr *)&clt_addr, &clt_addr_len);
pid = fork();
if (pid < 0) {
perr_exit("fork error");
} else if (pid == 0) {
close(lfd);
break;
} else {
close(cfd);
continue;
}
}
if (pid == 0) {
for (;;) {
ret = Read(cfd, buf, sizeof(buf));
if (ret == 0) {
close(cfd);
exit(1);
}
for (i = 0; i < ret; i++)
buf[i] = toupper(buf[i]);
write(cfd, buf, ret);
write(STDOUT_FILENO, buf, ret);
}
}
return 0;
}
編譯運行,結(jié)果如下:
這個代碼,有問題。我們Ctrl+C終止一個連接進(jìn)程,會發(fā)現(xiàn),有僵尸進(jìn)程。
如上圖所示,有個僵尸進(jìn)程。這是因為父進(jìn)程在阻塞等待,沒來得及去回收這個子進(jìn)程。
所以需要修改代碼,增加子進(jìn)程回收,用信號捕捉來實現(xiàn)。 修改部分如圖所示:
完整代碼如下:
#include <stdio.h> #include <ctype.h> #include <stdlib.h> #include <sys/wait.h> #include <string.h> #include <strings.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/socket.h> #include <arpa/inet.h> #include <pthread.h> #include “wrap.h” #define SRV_PORT 9999 void catch_child(int signum) { while ((waitpid(0, NULL, WNOHANG)) > 0);
return ;
} int main(int argc, char *argv[]) { int lfd, cfd;
pid_t pid;
struct sockaddr_in srv_addr, clt_addr;
socklen_t clt_addr_len;
char buf[BUFSIZ];
int ret, i;
//memset(&srv_addr, 0, sizeof(srv_addr)); // 將地址結(jié)構(gòu)清零
bzero(&srv_addr, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = Socket(AF_INET, SOCK_STREAM, 0);
Bind(lfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
Listen(lfd, 128);
clt_addr_len = sizeof(clt_addr);
while (1) {
cfd = Accept(lfd, (struct sockaddr *)&clt_addr, &clt_addr_len);
pid = fork();
if (pid < 0) {
perr_exit("fork error");
} else if (pid == 0) {
close(lfd);
break;
} else {
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
ret = sigaction(SIGCHLD, &act, NULL);
if (ret != 0) {
perr_exit("sigaction error");
}
close(cfd);
continue;
}
}
if (pid == 0) {
for (;;) {
ret = Read(cfd, buf, sizeof(buf));
if (ret == 0) {
close(cfd);
exit(1);
}
for (i = 0; i < ret; i++)
buf[i] = toupper(buf[i]);
write(cfd, buf, ret);
write(STDOUT_FILENO, buf, ret);
}
}
return 0;
}
這樣,當(dāng)子進(jìn)程退出時,父進(jìn)程收到信號,就會去回收子進(jìn)程了,不會出現(xiàn)僵尸進(jìn)程。
40P-多進(jìn)程服務(wù)器測試IP地址調(diào)整 使用橋接模式,讓自己主機(jī)和其他人主機(jī)處于同一個網(wǎng)段
41P-服務(wù)器程序上傳外網(wǎng)服務(wù)器并訪問 scp -r 命令,將本地文件拷貝至遠(yuǎn)程服務(wù)器上目標(biāo)位置 scp -r 源地址 目標(biāo)地址
42P-多線程服務(wù)器代碼review 代碼如下:
#include <stdio.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> #include <ctype.h> #include <unistd.h> #include <fcntl.h> #include “wrap.h” #define MAXLINE 8192 #define SERV_PORT 8000 struct s_info { //定義一個結(jié)構(gòu)體, 將地址結(jié)構(gòu)跟cfd捆綁 struct sockaddr_in cliaddr;
int connfd;
}; void *do_work(void *arg) { int n,i;
struct s_info *ts = (struct s_info*)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; //#define INET_ADDRSTRLEN 16 可用"[+d"查看
while (1) {
n = Read(ts->connfd, buf, MAXLINE); //讀客戶端
if (n == 0) {
printf("the client %d closed...\n", ts->connfd);
break; //跳出循環(huán),關(guān)閉cfd
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port)); //打印客戶端信息(IP/PORT)
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]); //小寫-->大寫
Write(STDOUT_FILENO, buf, n); //寫出至屏幕
Write(ts->connfd, buf, n); //回寫給客戶端
}
Close(ts->connfd);
return (void *)0;
} int main(void) { struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
pthread_t tid;
struct s_info ts[256]; //創(chuàng)建結(jié)構(gòu)體數(shù)組.
int i = 0;
listenfd = Socket(AF_INET, SOCK_STREAM, 0); //創(chuàng)建一個socket, 得到lfd
bzero(&servaddr, sizeof(servaddr)); //地址結(jié)構(gòu)清零
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //指定本地任意IP
servaddr.sin_port = htons(SERV_PORT); //指定端口號
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //綁定
Listen(listenfd, 128); //設(shè)置同一時刻鏈接服務(wù)器上限數(shù)
printf("Accepting client connect ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); //阻塞監(jiān)聽客戶端鏈接請求
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
pthread_detach(tid); //子線程分離,防止僵線程產(chǎn)生.
i++;
}
return 0;
}
編譯運行,結(jié)果如下:
43P-read返回值和總結(jié)
三次握手:
主動發(fā)起連接請求端,發(fā)送 SYN 標(biāo)志位,請求建立連接。 攜帶序號號、數(shù)據(jù)字節(jié)數(shù)(0)、滑動窗口大小。被動接受連接請求端,發(fā)送 ACK 標(biāo)志位,同時攜帶 SYN 請求標(biāo)志位。攜帶序號、確認(rèn)序號、數(shù)據(jù)字節(jié)數(shù)(0)、滑動窗口大小。主動發(fā)起連接請求端,發(fā)送 ACK 標(biāo)志位,應(yīng)答服務(wù)器連接請求。攜帶確認(rèn)序號。
四次揮手:
主動關(guān)閉連接請求端, 發(fā)送 FIN 標(biāo)志位。 被動關(guān)閉連接請求端, 應(yīng)答 ACK 標(biāo)志位。 ----- 半關(guān)閉完成。被動關(guān)閉連接請求端, 發(fā)送 FIN 標(biāo)志位。主動關(guān)閉連接請求端, 應(yīng)答 ACK 標(biāo)志位。 ----- 連接全部關(guān)閉
滑動窗口:
發(fā)送給連接對端,本端的緩沖區(qū)大小(實時),保證數(shù)據(jù)不會丟失。
錯誤處理函數(shù):
封裝目的: 在 server.c 編程過程中突出邏輯,將出錯處理與邏輯分開,可以直接跳轉(zhuǎn)man手冊。【wrap.c】 【wrap.h】存放網(wǎng)絡(luò)通信相關(guān)常用 自定義函數(shù) 存放 網(wǎng)絡(luò)通信相關(guān)常用 自定義函數(shù)原型(聲明)。命名方式:系統(tǒng)調(diào)用函數(shù)首字符大寫, 方便查看man手冊如:Listen()、Accept();函數(shù)功能:調(diào)用系統(tǒng)調(diào)用函數(shù),處理出錯場景。在 server.c 和 client.c 中調(diào)用 自定義函數(shù)聯(lián)合編譯 server.c 和 wrap.c 生成 serverclient.c 和 wrap.c 生成 client
readn: 讀 N 個字節(jié)
readline:
讀一行
read 函數(shù)的返回值:
1. > 0 實際讀到的字節(jié)數(shù)2. = 0 已經(jīng)讀到結(jié)尾(對端已經(jīng)關(guān)閉)【 !重 !點 !】3. -1 應(yīng)進(jìn)一步判斷errno的值:errno = EAGAIN or EWOULDBLOCK: 設(shè)置了非阻塞方式 讀。 沒有數(shù)據(jù)到達(dá)。 errno = EINTR 慢速系統(tǒng)調(diào)用被 中斷。errno = “其他情況” 異常。
多進(jìn)程并發(fā)服務(wù)器:server.c
1. Socket(); 創(chuàng)建 監(jiān)聽套接字 lfd
2. Bind() 綁定地址結(jié)構(gòu) Strcut scokaddr_in addr;
3. Listen();
4. while (1) {cfd = Accpet(); 接收客戶端連接請求。pid = fork();if (pid == 0){ 子進(jìn)程 read(cfd) --- 小-》大 --- write(cfd)close(lfd) 關(guān)閉用于建立連接的套接字 lfdread()小--大write()} else if (pid > 0) { close(cfd); 關(guān)閉用于與客戶端通信的套接字 cfd contiue;}}5. 子進(jìn)程:close(lfd)read()小--大write() 父進(jìn)程:close(cfd);注冊信號捕捉函數(shù): SIGCHLD在回調(diào)函數(shù)中, 完成子進(jìn)程回收while (waitpid());
多線程并發(fā)服務(wù)器: server.c
1. Socket(); 創(chuàng)建 監(jiān)聽套接字 lfd2. Bind() 綁定地址結(jié)構(gòu) Strcut scokaddr_in addr;3. Listen(); 4. while (1) { cfd = Accept(lfd, );pthread_create(&tid, NULL, tfn, (void *)cfd);pthread_detach(tid); // pthead_join(tid, void **); 新線程---專用于回收子線程。}5. 子線程:void *tfn(void *arg) {// close(lfd) 不能關(guān)閉。 主線程要使用lfdread(cfd)小--大write(cfd)pthread_exit((void *)10); }
44P-復(fù)習(xí)
45P-TCP狀態(tài)-主動發(fā)起連接 46P-TCP狀態(tài)-主動關(guān)閉連接 47P-TCP狀態(tài)-被動接收連接 48P-TCP狀態(tài)-被動關(guān)閉連接 49P-2MSL時長 50P-TCP狀態(tài)-其他狀態(tài)
netstat -apn | grep client 查看客戶端網(wǎng)絡(luò)連接狀態(tài) netstat -apn | grep port 查看端口的網(wǎng)絡(luò)連接狀態(tài)
TCP狀態(tài)時序圖:
結(jié)合三次握手、四次揮手 理解記憶。1. 主動發(fā)起連接請求端: CLOSE -- 發(fā)送SYN -- SEND_SYN -- 接收 ACK、SYN -- SEND_SYN -- 發(fā)送 ACK -- ESTABLISHED(數(shù)據(jù)通信態(tài))2. 主動關(guān)閉連接請求端: ESTABLISHED(數(shù)據(jù)通信態(tài)) -- 發(fā)送 FIN -- FIN_WAIT_1 -- 接收ACK -- FIN_WAIT_2(半關(guān)閉)-- 接收對端發(fā)送 FIN -- FIN_WAIT_2(半關(guān)閉)-- 回發(fā)ACK -- TIME_WAIT(只有主動關(guān)閉連接方,會經(jīng)歷該狀態(tài))-- 等 2MSL時長 -- CLOSE 3. 被動接收連接請求端: CLOSE -- LISTEN -- 接收 SYN -- LISTEN -- 發(fā)送 ACK、SYN -- SYN_RCVD -- 接收ACK -- ESTABLISHED(數(shù)據(jù)通信態(tài))4. 被動關(guān)閉連接請求端: ESTABLISHED(數(shù)據(jù)通信態(tài)) -- 接收 FIN -- ESTABLISHED(數(shù)據(jù)通信態(tài)) -- 發(fā)送ACK -- CLOSE_WAIT (說明對端【主動關(guān)閉連接端】處于半關(guān)閉狀態(tài)) -- 發(fā)送FIN -- LAST_ACK -- 接收ACK -- CLOSE重點記憶: ESTABLISHED、FIN_WAIT_2 <--> CLOSE_WAIT、TIME_WAIT(2MSL)netstat -apn | grep 端口號
2MSL時長:
一定出現(xiàn)在【主動關(guān)閉連接請求端】。 --- 對應(yīng) TIME_WAIT 狀態(tài)。保證,最后一個 ACK 能成功被對端接收。(等待期間,對端沒收到我發(fā)的ACK,對端會再次發(fā)送FIN請求。)
51P-端口復(fù)用函數(shù) 52P-半關(guān)閉及shutdown函數(shù) 端口復(fù)用:
int opt = 1; // 設(shè)置端口復(fù)用。setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));
半關(guān)閉:
通信雙方中,只有一端關(guān)閉通信。 --- FIN_WAIT_2close(cfd);shutdown(int fd, int how); how: SHUT_RD 關(guān)讀端SHUT_WR 關(guān)寫端SHUT_RDWR 關(guān)讀寫shutdown在關(guān)閉多個文件描述符應(yīng)用的文件時,采用全關(guān)閉方法。close,只關(guān)閉一個。
53P-多路IO轉(zhuǎn)接服務(wù)器設(shè)計思路
54P-select函數(shù)參數(shù)簡介 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
nfds:監(jiān)聽的所有文件描述符中,最大文件描述符+1readfds: 讀 文件描述符監(jiān)聽集合。 傳入、傳出參數(shù)writefds:寫 文件描述符監(jiān)聽集合。 傳入、傳出參數(shù) NULLexceptfds:異常 文件描述符監(jiān)聽集合 傳入、傳出參數(shù) NULLtimeout: > 0: 設(shè)置監(jiān)聽超時時長。NULL: 阻塞監(jiān)聽0: 非阻塞監(jiān)聽,輪詢返回值:> 0: 所有監(jiān)聽集合(3個)中, 滿足對應(yīng)事件的總數(shù)。0: 沒有滿足監(jiān)聽條件的文件描述符-1: errno
55P-中午復(fù)習(xí)
56P-select函數(shù)原型分析 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
nfds:監(jiān)聽的所有文件描述符中,最大文件描述符+1readfds: 讀 文件描述符監(jiān)聽集合。 傳入、傳出參數(shù)writefds:寫 文件描述符監(jiān)聽集合。 傳入、傳出參數(shù) NULLexceptfds:異常 文件描述符監(jiān)聽集合 傳入、傳出參數(shù) NULLtimeout: > 0: 設(shè)置監(jiān)聽超時時長。NULL: 阻塞監(jiān)聽0: 非阻塞監(jiān)聽,輪詢返回值:> 0: 所有監(jiān)聽集合(3個)中, 滿足對應(yīng)事件的總數(shù)。0: 沒有滿足監(jiān)聽條件的文件描述符-1: errno
57P-select相關(guān)函數(shù)參數(shù)分析 void FD_CLR(int fd, fd_set *set) 把某一個fd清除出去 int FD_ISSET(int fd, fd_set *set) 判定某個fd是否在位圖中 void FD_SET(int fd, fd_set *set) 把某一個fd添加到位圖 void FD_ZERO(fd_set *set) 位圖所有二進(jìn)制位置零
select多路IO轉(zhuǎn)接:
原理: 借助內(nèi)核, select 來監(jiān)聽, 客戶端連接、數(shù)據(jù)通信事件。void FD_ZERO(fd_set *set); --- 清空一個文件描述符集合。fd_set rset;FD_ZERO(&rset);void FD_SET(int fd, fd_set *set); --- 將待監(jiān)聽的文件描述符,添加到監(jiān)聽集合中FD_SET(3, &rset); FD_SET(5, &rset); FD_SET(6, &rset);void FD_CLR(int fd, fd_set *set); --- 將一個文件描述符從監(jiān)聽集合中 移除。FD_CLR(4, &rset);int FD_ISSET(int fd, fd_set *set); --- 判斷一個文件描述符是否在監(jiān)聽集合中。返回值: 在:1;不在:0;FD_ISSET(4, &rset);
58P-select實現(xiàn)多路IO轉(zhuǎn)接設(shè)計思路 思路分析:
int maxfd = 0;lfd = socket() ; 創(chuàng)建套接字maxfd = lfd;bind(); 綁定地址結(jié)構(gòu)listen(); 設(shè)置監(jiān)聽上限fd_set rset, allset; 創(chuàng)建r監(jiān)聽集合FD_ZERO(&allset); 將r監(jiān)聽集合清空FD_SET(lfd, &allset); 將 lfd 添加至讀集合中。while(1) {rset = allset; 保存監(jiān)聽集合ret = select(lfd+1, &rset, NULL, NULL, NULL); 監(jiān)聽文件描述符集合對應(yīng)事件。if(ret > 0) { 有監(jiān)聽的描述符滿足對應(yīng)事件if (FD_ISSET(lfd, &rset)) { // 1 在。 0不在。cfd = accept(); 建立連接,返回用于通信的文件描述符maxfd = cfd;FD_SET(cfd, &allset); 添加到監(jiān)聽通信描述符集合中。}for (i = lfd+1; i <= 最大文件描述符; i++){FD_ISSET(i, &rset) 有read、write事件read()小 -- 大write();} }
}
59P-select實現(xiàn)多路IO轉(zhuǎn)接-代碼review
代碼如下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <ctype.h> #include “wrap.h” #define SERV_PORT 6666 int main(int argc, char *argv[]) { int i, j, n, nready;
int maxfd = 0;
int listenfd, connfd;
char buf[BUFSIZ]; /* #define INET_ADDRSTRLEN 16 */
struct sockaddr_in clie_addr, serv_addr;
socklen_t clie_addr_len;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family= AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port= htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
Listen(listenfd, 128);
fd_set rset, allset; /* rset 讀事件文件描述符集合 allset用來暫存 */
maxfd = listenfd;
FD_ZERO(&allset);
FD_SET(listenfd, &allset); /* 構(gòu)造select監(jiān)控文件描述符集 */
while (1) {
rset = allset; /* 每次循環(huán)時都從新設(shè)置select監(jiān)控信號集 */
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (nready < 0)
perr_exit("select error");
if (FD_ISSET(listenfd, &rset)) { /* 說明有新的客戶端鏈接請求 */
clie_addr_len = sizeof(clie_addr);
connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len); /* Accept 不會阻塞 */
FD_SET(connfd, &allset); /* 向監(jiān)控文件描述符集合allset添加新的文件描述符connfd */
if (maxfd < connfd)
maxfd = connfd;
if (0 == --nready) /* 只有l(wèi)istenfd有事件, 后續(xù)的 for 不需執(zhí)行 */
continue;
}
for (i = listenfd+1; i <= maxfd; i++) { /* 檢測哪個clients 有數(shù)據(jù)就緒 */
if (FD_ISSET(i, &rset)) {
if ((n = Read(i, buf, sizeof(buf))) == 0) { /* 當(dāng)client關(guān)閉鏈接時,服務(wù)器端也關(guān)閉對應(yīng)鏈接 */
Close(i);
FD_CLR(i, &allset); /* 解除select對此文件描述符的監(jiān)控 */
} else if (n > 0) {
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Write(i, buf, n);
}
}
}
}
Close(listenfd);
return 0;
}
編譯運行,結(jié)果如下:
如圖,借助select也可以實現(xiàn)多線程
60P-select實現(xiàn)多路IO轉(zhuǎn)接-代碼實現(xiàn) 61P-select實現(xiàn)多路IO轉(zhuǎn)接-添加注釋
代碼太長了,直接看59話吧
62P-select優(yōu)缺點
select優(yōu)缺點:
缺點: 監(jiān)聽上限受文件描述符限制。 最大 1024.檢測滿足條件的fd, 自己添加業(yè)務(wù)邏輯提高小。 提高了編碼難度。優(yōu)點: 跨平臺。win、linux、macOS、Unix、類Unix、mips
select代碼里有個可以優(yōu)化的地方,用數(shù)組存下文件描述符,這樣就不需要每次掃描一大堆無關(guān)文件描述符了
63P-添加一個自定義數(shù)組提高效率 這里就是改進(jìn)之前代碼的問題,之前的代碼,如果最大fd是1023,每次確定有事件發(fā)生的fd時,就要掃描3-1023的所有文件描述符,這看起來很蠢。于是定義一個數(shù)組,把要監(jiān)聽的文件描述符存下來,每次掃描這個數(shù)組就行了。看起來科學(xué)得多。
如圖,加個client數(shù)組,存要監(jiān)聽的描述符。 代碼如下,挺長的
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <ctype.h> #include “wrap.h” #define SERV_PORT 6666 int main(int argc, char *argv[]) { int i, j, n, maxi;
int nready, client[FD_SETSIZE]; /* 自定義數(shù)組client, 防止遍歷1024個文件描述符 FD_SETSIZE默認(rèn)為1024 */
int maxfd, listenfd, connfd, sockfd;
char buf[BUFSIZ], str[INET_ADDRSTRLEN]; /* #define INET_ADDRSTRLEN 16 */
struct sockaddr_in clie_addr, serv_addr;
socklen_t clie_addr_len;
fd_set rset, allset; /* rset 讀事件文件描述符集合 allset用來暫存 */
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family= AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port= htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
Listen(listenfd, 128);
maxfd = listenfd; /* 起初 listenfd 即為最大文件描述符 */
maxi = -1; /* 將來用作client[]的下標(biāo), 初始值指向0個元素之前下標(biāo)位置 */
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* 用-1初始化client[] */
FD_ZERO(&allset);
FD_SET(listenfd, &allset); /* 構(gòu)造select監(jiān)控文件描述符集 */
while (1) {
rset = allset; /* 每次循環(huán)時都重新設(shè)置select監(jiān)控信號集 */
nready = select(maxfd+1, &rset, NULL, NULL, NULL); //2 1--lfd 1--connfd
if (nready < 0)
perr_exit("select error");
if (FD_ISSET(listenfd, &rset)) { /* 說明有新的客戶端鏈接請求 */
clie_addr_len = sizeof(clie_addr);
connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len); /* Accept 不會阻塞 */
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
ntohs(clie_addr.sin_port));
for (i = 0; i < FD_SETSIZE; i++)
if (client[i] < 0) { /* 找client[]中沒有使用的位置 */
client[i] = connfd; /* 保存accept返回的文件描述符到client[]里 */
break;
}
if (i == FD_SETSIZE) { /* 達(dá)到select能監(jiān)控的文件個數(shù)上限 1024 */
fputs("too many clients\n", stderr);
exit(1);
}
FD_SET(connfd, &allset); /* 向監(jiān)控文件描述符集合allset添加新的文件描述符connfd */
if (connfd > maxfd)
maxfd = connfd; /* select第一個參數(shù)需要 */
if (i > maxi)
maxi = i; /* 保證maxi存的總是client[]最后一個元素下標(biāo) */
if (--nready == 0)
continue;
}
for (i = 0; i <= maxi; i++) { /* 檢測哪個clients 有數(shù)據(jù)就緒 */
if ((sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ((n = Read(sockfd, buf, sizeof(buf))) == 0) { /* 當(dāng)client關(guān)閉鏈接時,服務(wù)器端也關(guān)閉對應(yīng)鏈接 */
Close(sockfd);
FD_CLR(sockfd, &allset); /* 解除select對此文件描述符的監(jiān)控 */
client[i] = -1;
} else if (n > 0) {
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Write(sockfd, buf, n);
Write(STDOUT_FILENO, buf, n);
}
if (--nready == 0)
break; /* 跳出for, 但還在while中 */
}
}
}
Close(listenfd);
return 0;
}
編譯運行和改進(jìn)前沒啥區(qū)別,這里就不貼圖了 64P-總結(jié)
TCP狀態(tài)時序圖:
結(jié)合三次握手、四次揮手 理解記憶。1. 主動發(fā)起連接請求端: CLOSE -- 發(fā)送SYN -- SEND_SYN -- 接收 ACK、SYN -- SEND_SYN -- 發(fā)送 ACK -- ESTABLISHED(數(shù)據(jù)通信態(tài))2. 主動關(guān)閉連接請求端: ESTABLISHED(數(shù)據(jù)通信態(tài)) -- 發(fā)送 FIN -- FIN_WAIT_1 -- 接收ACK -- FIN_WAIT_2(半關(guān)閉)-- 接收對端發(fā)送 FIN -- FIN_WAIT_2(半關(guān)閉)-- 回發(fā)ACK -- TIME_WAIT(只有主動關(guān)閉連接方,會經(jīng)歷該狀態(tài))-- 等 2MSL時長 -- CLOSE 3. 被動接收連接請求端: CLOSE -- LISTEN -- 接收 SYN -- LISTEN -- 發(fā)送 ACK、SYN -- SYN_RCVD -- 接收ACK -- ESTABLISHED(數(shù)據(jù)通信態(tài))4. 被動關(guān)閉連接請求端: ESTABLISHED(數(shù)據(jù)通信態(tài)) -- 接收 FIN -- ESTABLISHED(數(shù)據(jù)通信態(tài)) -- 發(fā)送ACK -- CLOSE_WAIT (說明對端【主動關(guān)閉連接端】處于半關(guān)閉狀態(tài)) -- 發(fā)送FIN -- LAST_ACK -- 接收ACK -- CLOSE重點記憶: ESTABLISHED、FIN_WAIT_2 <--> CLOSE_WAIT、TIME_WAIT(2MSL)netstat -apn | grep 端口號
2MSL時長:
一定出現(xiàn)在【主動關(guān)閉連接請求端】。 --- 對應(yīng) TIME_WAIT 狀態(tài)。保證,最后一個 ACK 能成功被對端接收。(等待期間,對端沒收到我發(fā)的ACK,對端會再次發(fā)送FIN請求。)
端口復(fù)用:
int opt = 1; // 設(shè)置端口復(fù)用。setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));
半關(guān)閉:
通信雙方中,只有一端關(guān)閉通信。 --- FIN_WAIT_2close(cfd);shutdown(int fd, int how); how: SHUT_RD 關(guān)讀端SHUT_WR 關(guān)寫端SHUT_RDWR 關(guān)讀寫shutdown在關(guān)閉多個文件描述符應(yīng)用的文件時,采用全關(guān)閉方法。close,只關(guān)閉一個。
select多路IO轉(zhuǎn)接:
原理: 借助內(nèi)核, select 來監(jiān)聽, 客戶端連接、數(shù)據(jù)通信事件。void FD_ZERO(fd_set *set); --- 清空一個文件描述符集合。fd_set rset;FD_ZERO(&rset);void FD_SET(int fd, fd_set *set); --- 將待監(jiān)聽的文件描述符,添加到監(jiān)聽集合中FD_SET(3, &rset); FD_SET(5, &rset); FD_SET(6, &rset);void FD_CLR(int fd, fd_set *set); --- 將一個文件描述符從監(jiān)聽集合中 移除。FD_CLR(4, &rset);int FD_ISSET(int fd, fd_set *set); --- 判斷一個文件描述符是否在監(jiān)聽集合中。返回值: 在:1;不在:0;FD_ISSET(4, &rset);int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);nfds:監(jiān)聽的所有文件描述符中,最大文件描述符+1readfds: 讀 文件描述符監(jiān)聽集合。 傳入、傳出參數(shù)writefds:寫 文件描述符監(jiān)聽集合。 傳入、傳出參數(shù) NULLexceptfds:異常 文件描述符監(jiān)聽集合 傳入、傳出參數(shù) NULLtimeout: > 0: 設(shè)置監(jiān)聽超時時長。NULL: 阻塞監(jiān)聽0: 非阻塞監(jiān)聽,輪詢返回值:> 0: 所有監(jiān)聽集合(3個)中, 滿足對應(yīng)事件的總數(shù)。0: 沒有滿足監(jiān)聽條件的文件描述符-1: errno
思路分析:
int maxfd = 0;lfd = socket() ; 創(chuàng)建套接字maxfd = lfd;bind(); 綁定地址結(jié)構(gòu)listen(); 設(shè)置監(jiān)聽上限fd_set rset, allset; 創(chuàng)建r監(jiān)聽集合FD_ZERO(&allset); 將r監(jiān)聽集合清空FD_SET(lfd, &allset); 將 lfd 添加至讀集合中。while(1) {rset = allset; 保存監(jiān)聽集合ret = select(lfd+1, &rset, NULL, NULL, NULL); 監(jiān)聽文件描述符集合對應(yīng)事件。if(ret > 0) { 有監(jiān)聽的描述符滿足對應(yīng)事件if (FD_ISSET(lfd, &rset)) { // 1 在。 0不在。cfd = accept(); 建立連接,返回用于通信的文件描述符maxfd = cfd;FD_SET(cfd, &allset); 添加到監(jiān)聽通信描述符集合中。}for (i = lfd+1; i <= 最大文件描述符; i++){FD_ISSET(i, &rset) 有read、write事件read()小 -- 大write();} }
}
select優(yōu)缺點:
缺點: 監(jiān)聽上限受文件描述符限制。 最大 1024.檢測滿足條件的fd, 自己添加業(yè)務(wù)邏輯提高小。 提高了編碼難度。優(yōu)點: 跨平臺。win、linux、macOS、Unix、類Unix、mips
65P-復(fù)習(xí)
66P-poll函數(shù)原型分析 poll是對select的改進(jìn),但是它是個半成品,相對select提升不大。最終版本是epoll,所以poll了解一下就完事兒,重點掌握epoll。
poll: int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:監(jiān)聽的文件描述符【數(shù)組】struct pollfd {int fd: 待監(jiān)聽的文件描述符short events: 待監(jiān)聽的文件描述符對應(yīng)的監(jiān)聽事件取值:POLLIN、POLLOUT、POLLERRshort revnets: 傳入時, 給0。如果滿足對應(yīng)事件的話, 返回 非0 --> POLLIN、POLLOUT、POLLERR}nfds: 監(jiān)聽數(shù)組的,實際有效監(jiān)聽個數(shù)。timeout: > 0: 超時時長。單位:毫秒。-1: 阻塞等待0: 不阻塞返回值:返回滿足對應(yīng)監(jiān)聽事件的文件描述符 總個數(shù)。優(yōu)點:自帶數(shù)組結(jié)構(gòu)。 可以將 監(jiān)聽事件集合 和 返回事件集合 分離。拓展 監(jiān)聽上限。 超出 1024限制。缺點:不能跨平臺。 Linux無法直接定位滿足監(jiān)聽事件的文件描述符, 編碼難度較大。
67P-poll函數(shù)使用注意事項示例
68P-poll函數(shù)實現(xiàn)服務(wù)器 這個東西用得少,基本都用epoll,從講義上掛個代碼過來,看看視頻里思路就完事兒
/* server.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <poll.h> #include <errno.h> #include “wrap.h” #define MAXLINE 80 #define SERV_PORT 6666 #define OPEN_MAX 1024 int main(int argc, char *argv[]) { int i, j, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 20);
client[0].fd = listenfd;
client[0].events = POLLRDNORM; /* listenfd監(jiān)聽普通讀事件 */
for (i = 1; i < OPEN_MAX; i++)
client[i].fd = -1; /* 用-1初始化client[]里剩下元素 */
maxi = 0; /* client[]數(shù)組有效元素中最大元素下標(biāo) */
for ( ; ; ) {
nready = poll(client, maxi+1, -1); /* 阻塞 */
if (client[0].revents & POLLRDNORM) { /* 有客戶端鏈接請求 */
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 1; i < OPEN_MAX; i++) {
if (client[i].fd < 0) {
client[i].fd = connfd; /* 找到client[]中空閑的位置,存放accept返回的connfd */
break;
}
}
if (i == OPEN_MAX)
perr_exit("too many clients");
client[i].events = POLLRDNORM; /* 設(shè)置剛剛返回的connfd,監(jiān)控讀事件 */
if (i > maxi)
maxi = i; /* 更新client[]中最大元素下標(biāo) */
if (--nready <= 0)
continue; /* 沒有更多就緒事件時,繼續(xù)回到poll阻塞 */
}
for (i = 1; i <= maxi; i++) { /* 檢測client[] */
if ((sockfd = client[i].fd) < 0)
continue;
if (client[i].revents & (POLLRDNORM | POLLERR)) {
if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
if (errno == ECONNRESET) { /* 當(dāng)收到 RST標(biāo)志時 */
/* connection reset by client */
printf("client[%d] aborted connection\n", i);
Close(sockfd);
client[i].fd = -1;
} else {
perr_exit("read error");
}
} else if (n == 0) {
/* connection closed by client */
printf("client[%d] closed connection\n", i);
Close(sockfd);
client[i].fd = -1;
} else {
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Writen(sockfd, buf, n);
}
if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
return 0;
}
69P-poll總結(jié) 優(yōu)點: 自帶數(shù)組結(jié)構(gòu)。 可以將 監(jiān)聽事件集合 和 返回事件集合 分離。
拓展 監(jiān)聽上限。 超出 1024限制。
缺點: 不能跨平臺。 Linux
無法直接定位滿足監(jiān)聽事件的文件描述符, 編碼難度較大。
70P-epoll函數(shù)實現(xiàn)的多路IO轉(zhuǎn)接
代碼如下:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <errno.h> #include <ctype.h> #include “wrap.h” #define MAXLINE 8192 #define SERV_PORT 8000 #define OPEN_MAX 5000 int main(int argc, char *argv[]) { int i, listenfd, connfd, sockfd;
int n, num = 0;
ssize_t nready, efd, res;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
struct epoll_event tep, ep[OPEN_MAX]; //tep: epoll_ctl參數(shù) ep[] : epoll_wait參數(shù)
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //端口復(fù)用
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
Listen(listenfd, 20);
efd = epoll_create(OPEN_MAX); //創(chuàng)建epoll模型, efd指向紅黑樹根節(jié)點
if (efd == -1)
perr_exit("epoll_create error");
tep.events = EPOLLIN;
tep.data.fd = listenfd; //指定lfd的監(jiān)聽時間為"讀"
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep); //將lfd及對應(yīng)的結(jié)構(gòu)體設(shè)置到樹上,efd可找到該樹
if (res == -1)
perr_exit("epoll_ctl error");
for ( ; ; ) {
/*epoll為server阻塞監(jiān)聽事件, ep為struct epoll_event類型數(shù)組, OPEN_MAX為數(shù)組容量, -1表永久阻塞*/
nready = epoll_wait(efd, ep, OPEN_MAX, -1);
if (nready == -1)
perr_exit("epoll_wait error");
for (i = 0; i < nready; i++) {
if (!(ep[i].events & EPOLLIN)) //如果不是"讀"事件, 繼續(xù)循環(huán)
continue;
if (ep[i].data.fd == listenfd) { //判斷滿足事件的fd是不是lfd
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); //接受鏈接
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
printf("cfd %d---client %d\n", connfd, ++num);
tep.events = EPOLLIN; tep.data.fd = connfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep); //加入紅黑樹
if (res == -1)
perr_exit("epoll_ctl error");
} else { //不是lfd,
sockfd = ep[i].data.fd;
n = Read(sockfd, buf, MAXLINE);
if (n == 0) { //讀到0,說明客戶端關(guān)閉鏈接
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); //將該文件描述符從紅黑樹摘除
if (res == -1)
perr_exit("epoll_ctl error");
Close(sockfd); //關(guān)閉與該客戶端的鏈接
printf("client[%d] closed connection\n", sockfd);
} else if (n < 0) { //出錯
perror("read n < 0 error: ");
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); //摘除節(jié)點
Close(sockfd);
} else { //實際讀到了字節(jié)數(shù)
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]); //轉(zhuǎn)大寫,寫回給客戶端
Write(STDOUT_FILENO, buf, n);
Writen(sockfd, buf, n);
}
}
}
}
Close(listenfd);
Close(efd);
return 0;
}
71P-突破1024文件描述符設(shè)置
突破 1024 文件描述符限制:
cat /proc/sys/fs/file-max --> 當(dāng)前計算機(jī)所能打開的最大文件個數(shù)。 受硬件影響。ulimit -a ——> 當(dāng)前用戶下的進(jìn)程,默認(rèn)打開文件描述符個數(shù)。 缺省為 1024修改:打開 sudo vi /etc/security/limits.conf, 寫入:* soft nofile 65536 --> 設(shè)置默認(rèn)值, 可以直接借助命令修改。 【注銷用戶,使其生效】* hard nofile 100000 --> 命令修改上限。
cat /proc/sys/fs/file-max 查看最大文件描述符上限
ulimit -a
sudo vi /etc/security/limits.conf 修改上限
修改之后,注銷用戶重新登錄,查看文件描述符上限:
如圖,已經(jīng)修改成功了。
如果使用ulimit -n 來修改,會受到之前設(shè)置的hard的限制:
用ulimit -n設(shè)置之后,往下調(diào)可以,往上調(diào)需要注銷用戶再登錄。
72P-epoll_create和epoll_ctl
epoll: int epoll_create(int size); 創(chuàng)建一棵監(jiān)聽紅黑樹
size:創(chuàng)建的紅黑樹的監(jiān)聽節(jié)點數(shù)量。(僅供內(nèi)核參考。)返回值:指向新創(chuàng)建的紅黑樹的根節(jié)點的 fd。 失敗: -1 errnoint epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 操作監(jiān)聽紅黑樹epfd:epoll_create 函數(shù)的返回值。 epfdop:對該監(jiān)聽紅黑數(shù)所做的操作。EPOLL_CTL_ADD 添加fd到 監(jiān)聽紅黑樹EPOLL_CTL_MOD 修改fd在 監(jiān)聽紅黑樹上的監(jiān)聽事件。EPOLL_CTL_DEL 將一個fd 從監(jiān)聽紅黑樹上摘下(取消監(jiān)聽)fd:待監(jiān)聽的fdevent: 本質(zhì) struct epoll_event 結(jié)構(gòu)體 地址成員 events:EPOLLIN / EPOLLOUT / EPOLLERR成員 data: 聯(lián)合體(共用體):int fd; 對應(yīng)監(jiān)聽事件的 fdvoid *ptr; uint32_t u32;uint64_t u64; 返回值:成功 0; 失敗: -1 errno
73P-epoll_wait函數(shù) int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 阻塞監(jiān)聽。
epfd:epoll_create 函數(shù)的返回值。 epfdevents:傳出參數(shù),【數(shù)組】, 滿足監(jiān)聽條件的 那些 fd 結(jié)構(gòu)體。maxevents:數(shù)組 元素的總個數(shù)。 1024struct epoll_event evnets[1024]timeout:-1: 阻塞0: 不阻塞>0: 超時時間 (毫秒)返回值:> 0: 滿足監(jiān)聽的 總個數(shù)。 可以用作循環(huán)上限。0: 沒有fd滿足監(jiān)聽事件-1:失敗。 errno
74P-中午復(fù)習(xí) epoll實現(xiàn)多路IO轉(zhuǎn)接思路:
lfd = socket(); 監(jiān)聽連接事件lfd bind(); listen();
int epfd = epoll_create(1024); epfd, 監(jiān)聽紅黑樹的樹根。
struct epoll_event tep, ep[1024]; tep, 用來設(shè)置單個fd屬性, ep 是 epoll_wait() 傳出的滿足監(jiān)聽事件的數(shù)組。
tep.events = EPOLLIN; 初始化 lfd的監(jiān)聽屬性。 tep.data.fd = lfd
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tep); 將 lfd 添加到監(jiān)聽紅黑樹上。
while (1) {
ret = epoll_wait(epfd, ep,1024, -1); 實施監(jiān)聽for (i = 0; i < ret; i++) {if (ep[i].data.fd == lfd) { // lfd 滿足讀事件,有新的客戶端發(fā)起連接請求cfd = Accept();tep.events = EPOLLIN; 初始化 cfd的監(jiān)聽屬性。tep.data.fd = cfd;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tep);} else { cfd 們 滿足讀事件, 有客戶端寫數(shù)據(jù)來。n = read(ep[i].data.fd, buf, sizeof(buf));if ( n == 0) {close(ep[i].data.fd);epoll_ctl(epfd, EPOLL_CTL_DEL, ep[i].data.fd , NULL); // 將關(guān)閉的cfd,從監(jiān)聽樹上摘下。} else if (n > 0) {小--大write(ep[i].data.fd, buf, n);}}
}
75P-ET和LT模式 epoll是Linux下多路復(fù)用IO接口select/poll的增強(qiáng)版本,它能顯著提高程序在大量并發(fā)連接中只有少量活躍的情況下的系統(tǒng)CPU利用率,因為它會復(fù)用文件描述符集合來傳遞結(jié)果而不用迫使開發(fā)者每次等待事件之前都必須重新準(zhǔn)備要被偵聽的文件描述符集合,另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內(nèi)核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。
EPOLL事件有兩種模型: Edge Triggered (ET) 邊緣觸發(fā)只有數(shù)據(jù)到來才觸發(fā),不管緩存區(qū)中是否還有數(shù)據(jù)。 Level Triggered (LT) 水平觸發(fā)只要有數(shù)據(jù)都會觸發(fā)。
視頻中epoll測試代碼如下,用一個子進(jìn)程來寫內(nèi)容,用ET和LT模式來讀取,結(jié)果很能說明問題:
#include <stdio.h> #include <stdlib.h> #include <sys/epoll.h> #include <errno.h> #include <unistd.h> #define MAXLINE 10 int main(int argc, char *argv[]) { int efd, i;
int pfd[2];
pid_t pid;
char buf[MAXLINE], ch = 'a';
pipe(pfd);
pid = fork();
if (pid == 0) { //子 寫
close(pfd[0]);
while (1) {
//aaaa\n
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//bbbb\n
for (; i < MAXLINE; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//aaaa\nbbbb\n
write(pfd[1], buf, sizeof(buf));
sleep(5);
}
close(pfd[1]);
} else if (pid > 0) { //父 讀
struct epoll_event event;
struct epoll_event resevent[10]; //epoll_wait就緒返回event
int res, len;
close(pfd[1]);
efd = epoll_create(10);
event.events = EPOLLIN | EPOLLET; // ET 邊沿觸發(fā)
// event.events = EPOLLIN; // LT 水平觸發(fā) (默認(rèn))
event.data.fd = pfd[0];
epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);
while (1) {
res = epoll_wait(efd, resevent, 10, -1);
printf("res %d\n", res);
if (resevent[0].data.fd == pfd[0]) {
len = read(pfd[0], buf, MAXLINE/2);
write(STDOUT_FILENO, buf, len);
}
}
close(pfd[0]);
close(efd);
} else {
perror("fork");
exit(-1);
}
return 0;
}
簡單理解就是,水平觸發(fā)就是有數(shù)據(jù)就觸發(fā),邊沿觸發(fā)是有新數(shù)據(jù)進(jìn)來才觸發(fā)。學(xué)電子的就比較清楚,觸發(fā)器就有這個分類。
76P-網(wǎng)絡(luò)中ET和LT模式 直接看代碼,server代碼如下:
#include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/epoll.h> #include <unistd.h> #define MAXLINE 10 #define SERV_PORT 9000 int main(void) { struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int efd;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
struct epoll_event event;
struct epoll_event resevent[10];
int res, len;
efd = epoll_create(10);
event.events = EPOLLIN | EPOLLET; /* ET 邊沿觸發(fā) */
//event.events = EPOLLIN; /* 默認(rèn) LT 水平觸發(fā) */
printf("Accepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
while (1) {
res = epoll_wait(efd, resevent, 10, -1);
printf("res %d\n", res);
if (resevent[0].data.fd == connfd) {
len = read(connfd, buf, MAXLINE/2); //readn(500)
write(STDOUT_FILENO, buf, len);
}
}
return 0;
}
client代碼如下:
#include <stdio.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #define MAXLINE 10 #define SERV_PORT 9000 int main(int argc, char *argv[]) { struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, i;
char ch = 'a';
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (1) {
//aaaa\n
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//bbbb\n
for (; i < MAXLINE; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//aaaa\nbbbb\n
write(sockfd, buf, sizeof(buf));
sleep(5);
}
close(sockfd);
return 0;
}
server邊沿觸發(fā),編譯運行,結(jié)果如下:
運行后,每過5秒鐘服務(wù)器才輸出一組字符,這是就是邊沿觸發(fā)的效果。
更改服務(wù)器為水平觸發(fā)模式,運行程序,如下:
運行后,每5秒輸出兩組字符串,這是因為只寫入了兩組,這個模式的服務(wù)器,緩沖區(qū)有多少讀多少。
ET模式:
邊沿觸發(fā):緩沖區(qū)剩余未讀盡的數(shù)據(jù)不會導(dǎo)致 epoll_wait 返回。 新的事件滿足,才會觸發(fā)。struct epoll_event event;event.events = EPOLLIN | EPOLLET;
LT模式:水平觸發(fā) -- 默認(rèn)采用模式。緩沖區(qū)剩余未讀盡的數(shù)據(jù)會導(dǎo)致 epoll_wait 返回。
77P-epoll的ET非阻塞模式 readn調(diào)用的阻塞,比如設(shè)定讀500個字符,但是只讀到498,完事兒阻塞了,等另剩下的2個字符,然而在server代碼里,一旦read變?yōu)閞eadn阻塞了,它就不會被喚醒了,因為epoll_wait因為readn的阻塞不會循環(huán)執(zhí)行,讀不到新數(shù)據(jù)。有點死鎖的意思,差倆字符所以阻塞,因為阻塞,讀不到新字符。
LT(level triggered):LT是缺省的工作方式,并且同時支持block和no-block socket。在這種做法中,內(nèi)核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進(jìn)行IO操作。如果你不作任何操作,內(nèi)核還是會繼續(xù)通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統(tǒng)的select/poll都是這種模型的代表。 ET(edge-triggered):ET是高速工作方式,只支持no-block socket。在這種模式下,當(dāng)描述符從未就緒變?yōu)榫途w時,內(nèi)核通過epoll告訴你。然后它會假設(shè)你知道文件描述符已經(jīng)就緒,并且不會再為那個文件描述符發(fā)送更多的就緒通知。請注意,如果一直不對這個fd作IO操作(從而導(dǎo)致它再次變成未就緒),內(nèi)核不會發(fā)送更多的通知(only once).
用fcntl設(shè)置阻塞
非阻塞epoll的服務(wù)器代碼如下:
#include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/epoll.h> #include <unistd.h> #include <fcntl.h> #define MAXLINE 10 #define SERV_PORT 8000 int main(void) { struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int efd, flag;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
///
struct epoll_event event;
struct epoll_event res_event[10];
int res, len;
efd = epoll_create(10);
event.events = EPOLLIN | EPOLLET; /* ET 邊沿觸發(fā),默認(rèn)是水平觸發(fā) */
//event.events = EPOLLIN;
printf("Accepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
flag = fcntl(connfd, F_GETFL); /* 修改connfd為非阻塞讀 */
flag |= O_NONBLOCK;
fcntl(connfd, F_SETFL, flag);
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); //將connfd加入監(jiān)聽紅黑樹
while (1) {
printf("epoll_wait begin\n");
res = epoll_wait(efd, res_event, 10, -1); //最多10個, 阻塞監(jiān)聽
printf("epoll_wait end res %d\n", res);
if (res_event[0].data.fd == connfd) {
while ((len = read(connfd, buf, MAXLINE/2)) >0 ) //非阻塞讀, 輪詢
write(STDOUT_FILENO, buf, len);
}
}
return 0;
}
其實就是多了這幾行:
結(jié)論: epoll 的 ET模式, 高效模式,但是只支持 非阻塞模式。 — 忙輪詢。
struct epoll_event event;event.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event); int flg = fcntl(cfd, F_GETFL); flg |= O_NONBLOCK;fcntl(cfd, F_SETFL, flg);優(yōu)點:高效。突破1024文件描述符。缺點:不能跨平臺。 Linux。
后面使用epoll就用這種非阻塞的
78P-epoll優(yōu)缺點總結(jié) 優(yōu)點:
高效。突破1024文件描述符。
缺點: 不能跨平臺。 Linux。
79P-補(bǔ)充對比ET和LT 這里重要的就一點,當(dāng)使用非阻塞讀時,讀取數(shù)據(jù)需要輪詢。 比如使用readn的時候,數(shù)據(jù)沒讀夠,因為非阻塞,跑了,想讀剩下的,就得輪詢。
80P-epoll反應(yīng)堆模型總述 epoll 反應(yīng)堆模型:
epoll ET模式 + 非阻塞、輪詢 + void *ptr。原來: socket、bind、listen -- epoll_create 創(chuàng)建監(jiān)聽 紅黑樹 -- 返回 epfd -- epoll_ctl() 向樹上添加一個監(jiān)聽fd -- while(1)---- epoll_wait 監(jiān)聽 -- 對應(yīng)監(jiān)聽fd有事件產(chǎn)生 -- 返回 監(jiān)聽滿足數(shù)組。 -- 判斷返回數(shù)組元素 -- lfd滿足 -- Accept -- cfd 滿足 -- read() --- 小->大 -- write回去。
反應(yīng)堆:不但要監(jiān)聽 cfd 的讀事件、還要監(jiān)聽cfd的寫事件。
socket、bind、listen -- epoll_create 創(chuàng)建監(jiān)聽 紅黑樹 -- 返回 epfd -- epoll_ctl() 向樹上添加一個監(jiān)聽fd -- while(1)---- epoll_wait 監(jiān)聽 -- 對應(yīng)監(jiān)聽fd有事件產(chǎn)生 -- 返回 監(jiān)聽滿足數(shù)組。 -- 判斷返回數(shù)組元素 -- lfd滿足 -- Accept -- cfd 滿足 -- read() --- 小->大 -- cfd從監(jiān)聽紅黑樹上摘下 -- EPOLLOUT -- 回調(diào)函數(shù) -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到紅黑上監(jiān)聽寫事件-- 等待 epoll_wait 返回 -- 說明 cfd 可寫 -- write回去 -- cfd從監(jiān)聽紅黑樹上摘下 -- EPOLLIN -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到紅黑上監(jiān)聽讀事件 -- epoll_wait 監(jiān)聽
反應(yīng)堆的理解:加入IO轉(zhuǎn)接之后,有了事件,server才去處理,這里反應(yīng)堆也是這樣,由于網(wǎng)絡(luò)環(huán)境復(fù)雜,服務(wù)器處理數(shù)據(jù)之后,可能并不能直接寫回去,比如遇到網(wǎng)絡(luò)繁忙或者對方緩沖區(qū)已經(jīng)滿了這種情況,就不能直接寫回給客戶端。反應(yīng)堆就是在處理數(shù)據(jù)之后,監(jiān)聽寫事件,能寫會客戶端了,才去做寫回操作。寫回之后,再改為監(jiān)聽讀事件。如此循環(huán)。
81P-epoll反應(yīng)堆main邏輯 直接上代碼,這就略微有點長了:
/* *epoll基于非阻塞I/O事件驅(qū)動 */ #include <stdio.h> #include <sys/socket.h> #include <sys/epoll.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <time.h> #define MAX_EVENTS 1024 //監(jiān)聽上限數(shù) #define BUFLEN 4096 #define SERV_PORT 8080 void recvdata(int fd, int events, void *arg); void senddata(int fd, int events, void *arg); /* 描述就緒文件描述符相關(guān)信息 */ struct myevent_s { int fd; //要監(jiān)聽的文件描述符
int events; //對應(yīng)的監(jiān)聽事件
void *arg; //泛型參數(shù)
void (*call_back)(int fd, int events, void *arg); //回調(diào)函數(shù)
int status; //是否在監(jiān)聽:1->在紅黑樹上(監(jiān)聽), 0->不在(不監(jiān)聽)
char buf[BUFLEN];
int len;
long last_active; //記錄每次加入紅黑樹 g_efd 的時間值
}; int g_efd; //全局變量, 保存epoll_create返回的文件描述符 struct myevent_s g_events[MAX_EVENTS+1]; //自定義結(jié)構(gòu)體類型數(shù)組. +1–>listen fd /將結(jié)構(gòu)體 myevent_s 成員變量 初始化/ void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg) { ev->fd = fd;
ev->call_back = call_back;
ev->events = 0;
ev->arg = arg;
ev->status = 0;
memset(ev->buf, 0, sizeof(ev->buf));
ev->len = 0;
ev->last_active = time(NULL); //調(diào)用eventset函數(shù)的時間
return;
} /* 向 epoll監(jiān)聽的紅黑樹 添加一個 文件描述符 */ //eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]); void eventadd(int efd, int events, struct myevent_s *ev) { struct epoll_event epv = {0, {0}};
int op;
epv.data.ptr = ev;
epv.events = ev->events = events; //EPOLLIN 或 EPOLLOUT
if (ev->status == 0) { //已經(jīng)在紅黑樹 g_efd 里
op = EPOLL_CTL_ADD; //將其加入紅黑樹 g_efd, 并將status置1
ev->status = 1;
}
if (epoll_ctl(efd, op, ev->fd, &epv) < 0) //實際添加/修改
printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
else
printf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events);
return ;
} /* 從epoll 監(jiān)聽的 紅黑樹中刪除一個 文件描述符*/ void eventdel(int efd, struct myevent_s *ev) { struct epoll_event epv = {0, {0}};
if (ev->status != 1) //不在紅黑樹上
return ;
//epv.data.ptr = ev;
epv.data.ptr = NULL;
ev->status = 0; //修改狀態(tài)
epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv); //從紅黑樹 efd 上將 ev->fd 摘除
return ;
} /* 當(dāng)有文件描述符就緒, epoll返回, 調(diào)用該函數(shù) 與客戶端建立鏈接 */ void acceptconn(int lfd, int events, void *arg) { struct sockaddr_in cin;
socklen_t len = sizeof(cin);
int cfd, i;
if ((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1) {
if (errno != EAGAIN && errno != EINTR) {
/* 暫時不做出錯處理 */
}
printf("%s: accept, %s\n", __func__, strerror(errno));
return ;
}
do {
for (i = 0; i < MAX_EVENTS; i++) //從全局?jǐn)?shù)組g_events中找一個空閑元素
if (g_events[i].status == 0) //類似于select中找值為-1的元素
break; //跳出 for
if (i == MAX_EVENTS) {
printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);
break; //跳出do while(0) 不執(zhí)行后續(xù)代碼
}
int flag = 0;
if ((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) { //將cfd也設(shè)置為非阻塞
printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
break;
}
/* 給cfd設(shè)置一個 myevent_s 結(jié)構(gòu)體, 回調(diào)函數(shù) 設(shè)置為 recvdata */
eventset(&g_events[i], cfd, recvdata, &g_events[i]);
eventadd(g_efd, EPOLLIN, &g_events[i]); //將cfd添加到紅黑樹g_efd中,監(jiān)聽讀事件
} while(0);
printf("new connect [%s:%d][time:%ld], pos[%d]\n",
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);
return ;
} void recvdata(int fd, int events, void *arg) { struct myevent_s *ev = (struct myevent_s *)arg;
int len;
len = recv(fd, ev->buf, sizeof(ev->buf), 0); //讀文件描述符, 數(shù)據(jù)存入myevent_s成員buf中
eventdel(g_efd, ev); //將該節(jié)點從紅黑樹上摘除
if (len > 0) {
ev->len = len;
ev->buf[len] = '\0'; //手動添加字符串結(jié)束標(biāo)記
printf("C[%d]:%s\n", fd, ev->buf);
eventset(ev, fd, senddata, ev); //設(shè)置該 fd 對應(yīng)的回調(diào)函數(shù)為 senddata
eventadd(g_efd, EPOLLOUT, ev); //將fd加入紅黑樹g_efd中,監(jiān)聽其寫事件
} else if (len == 0) {
close(ev->fd);
/* ev-g_events 地址相減得到偏移元素位置 */
printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
} else {
close(ev->fd);
printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
}
return;
} void senddata(int fd, int events, void *arg) { struct myevent_s *ev = (struct myevent_s *)arg;
int len;
len = send(fd, ev->buf, ev->len, 0); //直接將數(shù)據(jù) 回寫給客戶端。未作處理
eventdel(g_efd, ev); //從紅黑樹g_efd中移除
if (len > 0) {
printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
eventset(ev, fd, recvdata, ev); //將該fd的 回調(diào)函數(shù)改為 recvdata
eventadd(g_efd, EPOLLIN, ev); //從新添加到紅黑樹上, 設(shè)為監(jiān)聽讀事件
} else {
close(ev->fd); //關(guān)閉鏈接
printf("send[fd=%d] error %s\n", fd, strerror(errno));
}
return ;
} /*創(chuàng)建 socket, 初始化lfd */ void initlistensocket(int efd, short port) { struct sockaddr_in sin;
int lfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(lfd, F_SETFL, O_NONBLOCK); //將socket設(shè)為非阻塞
memset(&sin, 0, sizeof(sin)); //bzero(&sin, sizeof(sin))
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(port);
bind(lfd, (struct sockaddr *)&sin, sizeof(sin));
listen(lfd, 20);
/* void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg); */
eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);
/* void eventadd(int efd, int events, struct myevent_s *ev) */
eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);
return ;
} int main(int argc, char *argv[]) { unsigned short port = SERV_PORT;
if (argc == 2)
port = atoi(argv[1]); //使用用戶指定端口.如未指定,用默認(rèn)端口
g_efd = epoll_create(MAX_EVENTS+1); //創(chuàng)建紅黑樹,返回給全局 g_efd
if (g_efd <= 0)
printf("create efd in %s err %s\n", __func__, strerror(errno));
initlistensocket(g_efd, port); //初始化監(jiān)聽socket
struct epoll_event events[MAX_EVENTS+1]; //保存已經(jīng)滿足就緒事件的文件描述符數(shù)組
printf("server running:port[%d]\n", port);
int checkpos = 0, i;
while (1) {
/* 超時驗證,每次測試100個鏈接,不測試listenfd 當(dāng)客戶端60秒內(nèi)沒有和服務(wù)器通信,則關(guān)閉此客戶端鏈接 */
long now = time(NULL); //當(dāng)前時間
for (i = 0; i < 100; i++, checkpos++) { //一次循環(huán)檢測100個。 使用checkpos控制檢測對象
if (checkpos == MAX_EVENTS)
checkpos = 0;
if (g_events[checkpos].status != 1) //不在紅黑樹 g_efd 上
continue;
long duration = now - g_events[checkpos].last_active; //客戶端不活躍的世間
if (duration >= 60) {
close(g_events[checkpos].fd); //關(guān)閉與該客戶端鏈接
printf("[fd=%d] timeout\n", g_events[checkpos].fd);
eventdel(g_efd, &g_events[checkpos]); //將該客戶端 從紅黑樹 g_efd移除
}
}
/*監(jiān)聽紅黑樹g_efd, 將滿足的事件的文件描述符加至events數(shù)組中, 1秒沒有事件滿足, 返回 0*/
int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);
if (nfd < 0) {
printf("epoll_wait error, exit\n");
break;
}
for (i = 0; i < nfd; i++) {
/*使用自定義結(jié)構(gòu)體myevent_s類型指針, 接收 聯(lián)合體data的void *ptr成員*/
struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) { //讀就緒事件
ev->call_back(ev->fd, events[i].events, ev->arg);
//lfd EPOLLIN
}
if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) { //寫就緒事件
ev->call_back(ev->fd, events[i].events, ev->arg);
}
}
}
/* 退出前釋放所有資源 */
return 0;
}
main邏輯:創(chuàng)建套接字—》初始化連接—》超時驗證—》監(jiān)聽—》處理讀事件和寫事件
82P-epoll反應(yīng)堆-給lfd和cfd指定回調(diào)函數(shù) eventset函數(shù)指定了不同事件對應(yīng)的回調(diào)函數(shù),所以雖然讀寫事件都用的call_back來回調(diào),但實際上調(diào)用的是不同的函數(shù)。
83P-epoll反應(yīng)堆initlistensocket小總結(jié) eventset函數(shù):
設(shè)置回調(diào)函數(shù)。 lfd --》 acceptconn()cfd --> recvdata();cfd --> senddata();
eventadd函數(shù):
將一個fd, 添加到 監(jiān)聽紅黑樹。 設(shè)置監(jiān)聽 read事件,還是監(jiān)聽寫事件。
84P-epoll反應(yīng)堆wait被觸發(fā)后read和write回調(diào)及監(jiān)聽 網(wǎng)絡(luò)編程中: read — recv()
write --- send();
85P-epoll反應(yīng)堆-超時時間 用一個last_active存儲上次活躍時間,完事兒用當(dāng)前時間和上次活躍時間來計算不活躍時間長度,不活躍時間超過一定閾值,就踢掉這個客戶端。
86-總結(jié) 多路IO轉(zhuǎn)接:
select:
poll: int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:監(jiān)聽的文件描述符【數(shù)組】struct pollfd {int fd: 待監(jiān)聽的文件描述符short events: 待監(jiān)聽的文件描述符對應(yīng)的監(jiān)聽事件取值:POLLIN、POLLOUT、POLLERRshort revnets: 傳入時, 給0。如果滿足對應(yīng)事件的話, 返回 非0 --> POLLIN、POLLOUT、POLLERR}nfds: 監(jiān)聽數(shù)組的,實際有效監(jiān)聽個數(shù)。timeout: > 0: 超時時長。單位:毫秒。-1: 阻塞等待0: 不阻塞返回值:返回滿足對應(yīng)監(jiān)聽事件的文件描述符 總個數(shù)。優(yōu)點:自帶數(shù)組結(jié)構(gòu)。 可以將 監(jiān)聽事件集合 和 返回事件集合 分離。拓展 監(jiān)聽上限。 超出 1024限制。缺點:不能跨平臺。 Linux無法直接定位滿足監(jiān)聽事件的文件描述符, 編碼難度較大。
read 函數(shù)返回值:
> 0: 實際讀到的字節(jié)數(shù)=0: socket中,表示對端關(guān)閉。close()-1: 如果 errno == EINTR 被異常終端。 需要重啟。如果 errno == EAGIN 或 EWOULDBLOCK 以非阻塞方式讀數(shù)據(jù),但是沒有數(shù)據(jù)。 需要,再次讀。如果 errno == ECONNRESET 說明連接被 重置。 需要 close(),移除監(jiān)聽隊列。錯誤。
突破 1024 文件描述符限制:
cat /proc/sys/fs/file-max --> 當(dāng)前計算機(jī)所能打開的最大文件個數(shù)。 受硬件影響。ulimit -a ——> 當(dāng)前用戶下的進(jìn)程,默認(rèn)打開文件描述符個數(shù)。 缺省為 1024修改:打開 sudo vi /etc/security/limits.conf, 寫入:* soft nofile 65536 --> 設(shè)置默認(rèn)值, 可以直接借助命令修改。 【注銷用戶,使其生效】* hard nofile 100000 --> 命令修改上限。
epoll: int epoll_create(int size); 創(chuàng)建一棵監(jiān)聽紅黑樹
size:創(chuàng)建的紅黑樹的監(jiān)聽節(jié)點數(shù)量。(僅供內(nèi)核參考。)返回值:指向新創(chuàng)建的紅黑樹的根節(jié)點的 fd。 失敗: -1 errnoint epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 操作監(jiān)聽紅黑樹epfd:epoll_create 函數(shù)的返回值。 epfdop:對該監(jiān)聽紅黑數(shù)所做的操作。EPOLL_CTL_ADD 添加fd到 監(jiān)聽紅黑樹EPOLL_CTL_MOD 修改fd在 監(jiān)聽紅黑樹上的監(jiān)聽事件。EPOLL_CTL_DEL 將一個fd 從監(jiān)聽紅黑樹上摘下(取消監(jiān)聽)fd:待監(jiān)聽的fdevent: 本質(zhì) struct epoll_event 結(jié)構(gòu)體 地址成員 events:EPOLLIN / EPOLLOUT / EPOLLERR成員 data: 聯(lián)合體(共用體):int fd; 對應(yīng)監(jiān)聽事件的 fdvoid *ptr; uint32_t u32;uint64_t u64; 返回值:成功 0; 失敗: -1 errnoint epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 阻塞監(jiān)聽。epfd:epoll_create 函數(shù)的返回值。 epfdevents:傳出參數(shù),【數(shù)組】, 滿足監(jiān)聽條件的 哪些 fd 結(jié)構(gòu)體。maxevents:數(shù)組 元素的總個數(shù)。 1024struct epoll_event evnets[1024]timeout:-1: 阻塞0: 不阻塞>0: 超時時間 (毫秒)返回值:> 0: 滿足監(jiān)聽的 總個數(shù)。 可以用作循環(huán)上限。0: 沒有fd滿足監(jiān)聽事件-1:失敗。 errno
epoll實現(xiàn)多路IO轉(zhuǎn)接思路:
lfd = socket(); 監(jiān)聽連接事件lfd bind(); listen();
int epfd = epoll_create(1024); epfd, 監(jiān)聽紅黑樹的樹根。
struct epoll_event tep, ep[1024]; tep, 用來設(shè)置單個fd屬性, ep 是 epoll_wait() 傳出的滿足監(jiān)聽事件的數(shù)組。
tep.events = EPOLLIN; 初始化 lfd的監(jiān)聽屬性。 tep.data.fd = lfd
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tep); 將 lfd 添加到監(jiān)聽紅黑樹上。
while (1) {
ret = epoll_wait(epfd, ep,1024, -1); 實施監(jiān)聽for (i = 0; i < ret; i++) {if (ep[i].data.fd == lfd) { // lfd 滿足讀事件,有新的客戶端發(fā)起連接請求cfd = Accept();tep.events = EPOLLIN; 初始化 cfd的監(jiān)聽屬性。tep.data.fd = cfd;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tep);} else { cfd 們 滿足讀事件, 有客戶端寫數(shù)據(jù)來。n = read(ep[i].data.fd, buf, sizeof(buf));if ( n == 0) {close(ep[i].data.fd);epoll_ctl(epfd, EPOLL_CTL_DEL, ep[i].data.fd , NULL); // 將關(guān)閉的cfd,從監(jiān)聽樹上摘下。} else if (n > 0) {小--大write(ep[i].data.fd, buf, n);}}
}
}
epoll 事件模型:
ET模式:邊沿觸發(fā):緩沖區(qū)剩余未讀盡的數(shù)據(jù)不會導(dǎo)致 epoll_wait 返回。 新的事件滿足,才會觸發(fā)。struct epoll_event event;event.events = EPOLLIN | EPOLLET;
LT模式:水平觸發(fā) -- 默認(rèn)采用模式。緩沖區(qū)剩余未讀盡的數(shù)據(jù)會導(dǎo)致 epoll_wait 返回。結(jié)論:epoll 的 ET模式, 高效模式,但是只支持 非阻塞模式。 --- 忙輪詢。struct epoll_event event;event.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event); int flg = fcntl(cfd, F_GETFL); flg |= O_NONBLOCK;fcntl(cfd, F_SETFL, flg);優(yōu)點:高效。突破1024文件描述符。缺點:不能跨平臺。 Linux。
epoll 反應(yīng)堆模型:
epoll ET模式 + 非阻塞、輪詢 + void *ptr。原來: socket、bind、listen -- epoll_create 創(chuàng)建監(jiān)聽 紅黑樹 -- 返回 epfd -- epoll_ctl() 向樹上添加一個監(jiān)聽fd -- while(1)---- epoll_wait 監(jiān)聽 -- 對應(yīng)監(jiān)聽fd有事件產(chǎn)生 -- 返回 監(jiān)聽滿足數(shù)組。 -- 判斷返回數(shù)組元素 -- lfd滿足 -- Accept -- cfd 滿足 -- read() --- 小->大 -- write回去。反應(yīng)堆:不但要監(jiān)聽 cfd 的讀事件、還要監(jiān)聽cfd的寫事件。socket、bind、listen -- epoll_create 創(chuàng)建監(jiān)聽 紅黑樹 -- 返回 epfd -- epoll_ctl() 向樹上添加一個監(jiān)聽fd -- while(1)---- epoll_wait 監(jiān)聽 -- 對應(yīng)監(jiān)聽fd有事件產(chǎn)生 -- 返回 監(jiān)聽滿足數(shù)組。 -- 判斷返回數(shù)組元素 -- lfd滿足 -- Accept -- cfd 滿足 -- read() --- 小->大 -- cfd從監(jiān)聽紅黑樹上摘下 -- EPOLLOUT -- 回調(diào)函數(shù) -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到紅黑上監(jiān)聽寫事件-- 等待 epoll_wait 返回 -- 說明 cfd 可寫 -- write回去 -- cfd從監(jiān)聽紅黑樹上摘下 -- EPOLLIN -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到紅黑上監(jiān)聽讀事件 -- epoll_wait 監(jiān)聽eventset函數(shù):設(shè)置回調(diào)函數(shù)。 lfd --》 acceptconn()cfd --> recvdata();cfd --> senddata();
eventadd函數(shù):將一個fd, 添加到 監(jiān)聽紅黑樹。 設(shè)置監(jiān)聽 read事件,還是監(jiān)聽寫事件。網(wǎng)絡(luò)編程中: read --- recv()write --- send();
87P-復(fù)習(xí)
88P-補(bǔ)充說明epoll的man手冊 89P-epoll反應(yīng)堆再說明
90P-ctags使用
91P線程池模型原理分析
struct threadpool_t {
pthread_mutex_t lock; /* 用于鎖住本結(jié)構(gòu)體 */
pthread_mutex_t thread_counter; /* 記錄忙狀態(tài)線程個數(shù)de瑣 -- busy_thr_num */pthread_cond_t queue_not_full; /* 當(dāng)任務(wù)隊列滿時,添加任務(wù)的線程阻塞,等待此條件變量 */
pthread_cond_t queue_not_empty; /* 任務(wù)隊列里不為空時,通知等待任務(wù)的線程 */pthread_t *threads; /* 存放線程池中每個線程的tid。數(shù)組 */
pthread_t adjust_tid; /* 存管理線程tid */
threadpool_task_t *task_queue; /* 任務(wù)隊列(數(shù)組首地址) */int min_thr_num; /* 線程池最小線程數(shù) */
int max_thr_num; /* 線程池最大線程數(shù) */
int live_thr_num; /* 當(dāng)前存活線程個數(shù) */
int busy_thr_num; /* 忙狀態(tài)線程個數(shù) */
int wait_exit_thr_num; /* 要銷毀的線程個數(shù) */int queue_front; /* task_queue隊頭下標(biāo) */
int queue_rear; /* task_queue隊尾下標(biāo) */
int queue_size; /* task_queue隊中實際任務(wù)數(shù) */
int queue_max_size; /* task_queue隊列可容納任務(wù)數(shù)上限 */int shutdown; /* 標(biāo)志位,線程池使用狀態(tài),true或false */
};
typedef struct {
void *(*function)(void *); /* 函數(shù)指針,回調(diào)函數(shù) */
void arg; / 上面函數(shù)的參數(shù) */
92P-線程池描述結(jié)構(gòu)體 struct threadpool_t {
pthread_mutex_t lock; /* 用于鎖住本結(jié)構(gòu)體 */
pthread_mutex_t thread_counter; /* 記錄忙狀態(tài)線程個數(shù)de瑣 -- busy_thr_num */pthread_cond_t queue_not_full; /* 當(dāng)任務(wù)隊列滿時,添加任務(wù)的線程阻塞,等待此條件變量 */
pthread_cond_t queue_not_empty; /* 任務(wù)隊列里不為空時,通知等待任務(wù)的線程 */pthread_t *threads; /* 存放線程池中每個線程的tid。數(shù)組 */
pthread_t adjust_tid; /* 存管理線程tid */
threadpool_task_t *task_queue; /* 任務(wù)隊列(數(shù)組首地址) */int min_thr_num; /* 線程池最小線程數(shù) */
int max_thr_num; /* 線程池最大線程數(shù) */
int live_thr_num; /* 當(dāng)前存活線程個數(shù) */
int busy_thr_num; /* 忙狀態(tài)線程個數(shù) */
int wait_exit_thr_num; /* 要銷毀的線程個數(shù) */int queue_front; /* task_queue隊頭下標(biāo) */
int queue_rear; /* task_queue隊尾下標(biāo) */
int queue_size; /* task_queue隊中實際任務(wù)數(shù) */
int queue_max_size; /* task_queue隊列可容納任務(wù)數(shù)上限 */int shutdown; /* 標(biāo)志位,線程池使用狀態(tài),true或false */
};
93P-線程池main架構(gòu)
main();
創(chuàng)建線程池。向線程池中添加任務(wù)。 借助回調(diào)處理任務(wù)。銷毀線程池。
94P-線程池-pthreadpool_create 2. pthreadpool_create();
創(chuàng)建線程池結(jié)構(gòu)體 指針。初始化線程池結(jié)構(gòu)體 { N 個成員變量 }創(chuàng)建 N 個任務(wù)線程。創(chuàng)建 1 個管理者線程。失敗時,銷毀開辟的所有空間。(釋放)
95P-子線程回調(diào)函數(shù) 3. threadpool_thread()
進(jìn)入子線程回調(diào)函數(shù)。接收參數(shù) void *arg --》 pool 結(jié)構(gòu)體加鎖 --》lock --》 整個結(jié)構(gòu)體鎖判斷條件變量 --》 wait -------------------170
96P-管理者線程 4. adjust_thread()
循環(huán) 10 s 執(zhí)行一次。進(jìn)入管理者線程回調(diào)函數(shù)接收參數(shù) void *arg --》 pool 結(jié)構(gòu)體加鎖 --》lock --》 整個結(jié)構(gòu)體鎖獲取管理線程池要用的到 變量。 task_num, live_num, busy_num根據(jù)既定算法,使用上述3變量,判斷是否應(yīng)該 創(chuàng)建、銷毀線程池中 指定步長的線程。
97P-threadpool_add函數(shù) 5. threadpool_add ()
總功能:模擬產(chǎn)生任務(wù)。 num[20]設(shè)置回調(diào)函數(shù), 處理任務(wù)。 sleep(1) 代表處理完成。內(nèi)部實現(xiàn):加鎖初始化 任務(wù)隊列結(jié)構(gòu)體成員。 回調(diào)函數(shù) function, arg利用環(huán)形隊列機(jī)制,實現(xiàn)添加任務(wù)。 借助隊尾指針挪移 % 實現(xiàn)。喚醒阻塞在 條件變量上的線程。解鎖
98P-條件滿足,子線程wait被喚醒后處理任務(wù) 6. 從 3. 中的wait之后繼續(xù)執(zhí)行,處理任務(wù)。
加鎖獲取 任務(wù)處理回調(diào)函數(shù),及參數(shù)利用環(huán)形隊列機(jī)制,實現(xiàn)處理任務(wù)。 借助隊頭指針挪移 % 實現(xiàn)。喚醒阻塞在 條件變量 上的 server。解鎖加鎖 改忙線程數(shù)++解鎖執(zhí)行處理任務(wù)的線程加鎖 改忙線程數(shù)——解鎖
99P-線程池擴(kuò)容和銷毀 7. 創(chuàng)建 銷毀線程
管理者線程根據(jù) task_num, live_num, busy_num 根據(jù)既定算法,使用上述3變量,判斷是否應(yīng)該 創(chuàng)建、銷毀線程池中 指定步長的線程。如果滿足 創(chuàng)建條件pthread_create(); 回調(diào) 任務(wù)線程函數(shù)。 live_num++如果滿足 銷毀條件wait_exit_thr_num = 10; signal 給 阻塞在條件變量上的線程 發(fā)送 假條件滿足信號 跳轉(zhuǎn)至 --170 wait阻塞線程會被 假信號 喚醒。判斷: wait_exit_thr_num > 0 pthread_exit();
100P-TCP和UDP通信優(yōu)缺點 TCP通信和UDP通信各自的優(yōu)缺點:
TCP: 面向連接的,可靠數(shù)據(jù)包傳輸。對于不穩(wěn)定的網(wǎng)絡(luò)層,采取完全彌補(bǔ)的通信方式。 丟包重傳。優(yōu)點:穩(wěn)定。 數(shù)據(jù)流量穩(wěn)定、速度穩(wěn)定、順序缺點:傳輸速度慢。相率低。開銷大。使用場景:數(shù)據(jù)的完整型要求較高,不追求效率。大數(shù)據(jù)傳輸、文件傳輸。UDP: 無連接的,不可靠的數(shù)據(jù)報傳遞。對于不穩(wěn)定的網(wǎng)絡(luò)層,采取完全不彌補(bǔ)的通信方式。 默認(rèn)還原網(wǎng)絡(luò)狀況優(yōu)點:傳輸速度塊。相率高。開銷小。缺點:不穩(wěn)定。數(shù)據(jù)流量。速度。順序。使用場景:對時效性要求較高場合。穩(wěn)定性其次。游戲、視頻會議、視頻電話。 騰訊、華為、阿里 --- 應(yīng)用層數(shù)據(jù)校驗協(xié)議,彌補(bǔ)udp的不足。
101P-UDP通信server和client流程 UDP實現(xiàn)的 C/S 模型:
recv()/send() 只能用于 TCP 通信。 替代 read、writeaccpet(); ---- Connect(); ---被舍棄server:lfd = socket(AF_INET, STREAM, 0); SOCK_DGRAM --- 報式協(xié)議。bind();listen(); --- 可有可無while(1){read(cfd, buf, sizeof) --- 被替換 --- recvfrom() --- 涵蓋accept傳出地址結(jié)構(gòu)。
小-- 大
write();--- 被替換 --- sendto()---- connect}close();
client:
connfd = socket(AF_INET, SOCK_DGRAM, 0);sendto(‘服務(wù)器的地址結(jié)構(gòu)’, 地址結(jié)構(gòu)大小)recvfrom()寫到屏幕close();
102P-recvfrom和sendto函數(shù) ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
sockfd: 套接字buf:緩沖區(qū)地址len:緩沖區(qū)大小flags: 0src_addr:(struct sockaddr *)&addr 傳出。 對端地址結(jié)構(gòu)addrlen:傳入傳出。返回值: 成功接收數(shù)據(jù)字節(jié)數(shù)。 失敗:-1 errn。 0: 對端關(guān)閉。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd: 套接字buf:存儲數(shù)據(jù)的緩沖區(qū)len:數(shù)據(jù)長度flags: 0src_addr:(struct sockaddr *)&addr 傳入。 目標(biāo)地址結(jié)構(gòu)addrlen:地址結(jié)構(gòu)長度。返回值:成功寫出數(shù)據(jù)字節(jié)數(shù)。 失敗 -1, errno
103P-UDP實現(xiàn)的并發(fā)服務(wù)器和客戶端 直接上代碼,啃,啃就完事兒,這是服務(wù)器代碼
#include <string.h> #include <stdio.h> #include <unistd.h> #include <arpa/inet.h> #include <ctype.h> #define SERV_PORT 8000 int main(void) { struct sockaddr_in serv_addr, clie_addr;
socklen_t clie_addr_len;
int sockfd;
char buf[BUFSIZ];
char str[INET_ADDRSTRLEN];
int i, n;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(SERV_PORT);
bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
printf("Accepting connections ...\n");
while (1) {
clie_addr_len = sizeof(clie_addr);
n = recvfrom(sockfd, buf, BUFSIZ,0, (struct sockaddr *)&clie_addr, &clie_addr_len);
if (n == -1)
perror("recvfrom error");
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
ntohs(clie_addr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&clie_addr, sizeof(clie_addr));
if (n == -1)
perror("sendto error");
}
close(sockfd);
return 0;
}
下面是客戶端代碼:
#include <stdio.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <ctype.h> #define SERV_PORT 8000 int main(int argc, char *argv[]) { struct sockaddr_in servaddr;
int sockfd, n;
char buf[BUFSIZ];
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, BUFSIZ, stdin) != NULL) {
n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
if (n == -1)
perror("sendto error");
n = recvfrom(sockfd, buf, BUFSIZ, 0, NULL, 0); //NULL:不關(guān)心對端信息
if (n == -1)
perror("recvfrom error");
write(STDOUT_FILENO, buf, n);
}
close(sockfd);
return 0;
}
104P-借助TCP的CS模型,改寫UDP的CS模型 看懂前面的,問題就不大了。可以再看一下視頻復(fù)習(xí)復(fù)習(xí)
105P-本地套接字和網(wǎng)絡(luò)套接字比較
本地套接字:
IPC: pipe、fifo、mmap、信號、本地套(domain)--- CS模型對比網(wǎng)絡(luò)編程 TCP C/S模型, 注意以下幾點:1. int socket(int domain, int type, int protocol); 參數(shù) domain:AF_INET --> AF_UNIX/AF_LOCAL type: SOCK_STREAM/SOCK_DGRAM 都可以。
2. 地址結(jié)構(gòu): sockaddr_in --> sockaddr_unstruct sockaddr_in srv_addr; --> struct sockaddr_un srv_adrr;srv_addr.sin_family = AF_INET; --> srv_addr.sun_family = AF_UNIX;
· srv_addr.sin_port = htons(8888); strcpy(srv_addr.sun_path, “srv.socket”)
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY); len = offsetof(struct sockaddr_un, sun_path) + strlen("srv.socket");bind(fd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)); --> bind(fd, (struct sockaddr *)&srv_addr, len); 3. bind()函數(shù)調(diào)用成功,會創(chuàng)建一個 socket。因此為保證bind成功,通常我們在 bind之前, 可以使用 unlink("srv.socket");4. 客戶端不能依賴 “隱式綁定”。并且應(yīng)該在通信建立過程中,創(chuàng)建且初始化2個地址結(jié)構(gòu):1) client_addr --> bind()2) server_addr --> connect();
106P-本地套接字通信 服務(wù)器代碼:
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <strings.h> #include <string.h> #include <ctype.h> #include <arpa/inet.h> #include <sys/un.h> #include <stddef.h> #include “wrap.h” #define SERV_ADDR “serv.socket” int main(void) { int lfd, cfd, len, size, i;
struct sockaddr_un servaddr, cliaddr;
char buf[4096];
lfd = Socket(AF_UNIX, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_UNIX;
strcpy(servaddr.sun_path, SERV_ADDR);
len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path); /* servaddr total len */
unlink(SERV_ADDR); /* 確保bind之前serv.sock文件不存在,bind會創(chuàng)建該文件 */
Bind(lfd, (struct sockaddr *)&servaddr, len); /* 參3不能是sizeof(servaddr) */
Listen(lfd, 20);
printf("Accept ...\n");
while (1) {
len = sizeof(cliaddr); //AF_UNIX大小+108B
cfd = Accept(lfd, (struct sockaddr *)&cliaddr, (socklen_t *)&len);
len -= offsetof(struct sockaddr_un, sun_path); /* 得到文件名的長度 */
cliaddr.sun_path[len] = '\0'; /* 確保打印時,沒有亂碼出現(xiàn) */
printf("client bind filename %s\n", cliaddr.sun_path);
while ((size = read(cfd, buf, sizeof(buf))) > 0) {
for (i = 0; i < size; i++)
buf[i] = toupper(buf[i]);
write(cfd, buf, size);
}
close(cfd);
}
close(lfd);
return 0;
}
客戶端代碼:
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <string.h> #include <ctype.h> #include <arpa/inet.h> #include <sys/un.h> #include <stddef.h> #include “wrap.h” #define SERV_ADDR “serv.socket” #define CLIE_ADDR “clie.socket” int main(void) { int cfd, len;
struct sockaddr_un servaddr, cliaddr;
char buf[4096];
cfd = Socket(AF_UNIX, SOCK_STREAM, 0);
bzero(&cliaddr, sizeof(cliaddr));
cliaddr.sun_family = AF_UNIX;
strcpy(cliaddr.sun_path,CLIE_ADDR);
len = offsetof(struct sockaddr_un, sun_path) + strlen(cliaddr.sun_path); /* 計算客戶端地址結(jié)構(gòu)有效長度 */
unlink(CLIE_ADDR);
Bind(cfd, (struct sockaddr *)&cliaddr, len); /* 客戶端也需要bind, 不能依賴自動綁定*/
bzero(&servaddr, sizeof(servaddr)); /* 構(gòu)造server 地址 */
servaddr.sun_family = AF_UNIX;
strcpy(servaddr.sun_path, SERV_ADDR);
len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path); /* 計算服務(wù)器端地址結(jié)構(gòu)有效長度 */
Connect(cfd, (struct sockaddr *)&servaddr, len);
while (fgets(buf, sizeof(buf), stdin) != NULL) {
write(cfd, buf, strlen(buf));
len = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
}
close(cfd);
return 0;
}
107P-本地套接字和網(wǎng)絡(luò)套接字實現(xiàn)對比 由于布局原因,直接看課程筆記比較科學(xué)。 linux網(wǎng)絡(luò)編程資料\day5\1-教學(xué)資料\課堂筆記.txt
108P-總結(jié) 直接看課程筆記linux網(wǎng)絡(luò)編程資料\day5\1-教學(xué)資料\課堂筆記.txt
109P-復(fù)習(xí)
110P-libevent簡介 libevent庫
開源。精簡。跨平臺(Windows、Linux、maxos、unix)。專注于網(wǎng)絡(luò)通信。
111P-libevent庫的下載和安裝 源碼包安裝: 參考 README、readme
./configure 檢查安裝環(huán)境 生成 makefilemake 生成 .o 和 可執(zhí)行文件sudo make install 將必要的資源cp置系統(tǒng)指定目錄。進(jìn)入 sample 目錄,運行demo驗證庫安裝使用情況。編譯使用庫的 .c 時,需要加 -levent 選項。庫名 libevent.so --> /usr/local/lib 查看的到。
特性: 基于“事件”異步通信模型。— 回調(diào)。
這里遇到一個問題:
解決辦法: 解決這個問題的博客
完事兒運行測試,結(jié)果如下:
112P-libevent封裝的框架思想 libevent框架:
1. 創(chuàng)建 event_base (樂高底座)
2. 創(chuàng)建 事件evnet
3. 將事件 添加到 base上
4. 循環(huán)監(jiān)聽事件滿足
5. 釋放 event_base
創(chuàng)建 event_base (樂高底座)
struct event_base *event_base_new(void);struct event_base *base = event_base_new();
創(chuàng)建 事件evnet
常規(guī)事件 event --> event_new(); bufferevent --> bufferevent_socket_new();
將事件 添加到 base上
int event_add(struct event *ev, const struct timeval *tv)
循環(huán)監(jiān)聽事件滿足
int event_base_dispatch(struct event_base *base);event_base_dispatch(base);
釋放 event_base
event_base_free(base);
113P-結(jié)合helloworld初識libevent 特性: 基于“事件”異步通信模型。— 回調(diào)。
114P-框架相關(guān)的不常用函數(shù) 查看支持哪些多路IO: 代碼如下:
編譯運行,結(jié)果如下:
115P-創(chuàng)建事件對象 創(chuàng)建事件event:
struct event *ev;struct event *event_new(struct event_base *base,evutil_socket_t fd,short what,event_callback_fn cb; void *arg);base: event_base_new()返回值。fd: 綁定到 event 上的 文件描述符what:對應(yīng)的事件(r、w、e)EV_READ 一次 讀事件EV_WRTIE 一次 寫事件EV_PERSIST 持續(xù)觸發(fā)。 結(jié)合 event_base_dispatch 函數(shù)使用,生效。cb:一旦事件滿足監(jiān)聽條件,回調(diào)的函數(shù)。typedef void (*event_callback_fn)(evutil_socket_t fd, short, void *) arg: 回調(diào)的函數(shù)的參數(shù)。返回值:成功創(chuàng)建的 event
116P-事件event操作 添加事件到 event_base
int event_add(struct event *ev, const struct timeval *tv);ev: event_new() 的返回值。tv:NULL
銷毀事件
int event_free(struct event *ev);ev: event_new() 的返回值。
117P-使用fifo的讀寫 讀端的代碼如下:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <fcntl.h> #include <event2/event.h> // 對操作處理函數(shù) void read_cb(evutil_socket_t fd, short what, void *arg) { // 讀管道
char buf[1024] = {0};
int len = read(fd, buf, sizeof(buf));
printf("read event: %s \n", what & EV_READ ? "Yes" : "No");
printf("data len = %d, buf = %s\n", len, buf);
sleep(1);
} // 讀管道 int main(int argc, const char* argv[]) { unlink("myfifo");
//創(chuàng)建有名管道
mkfifo("myfifo", 0664);
// open file
//int fd = open("myfifo", O_RDONLY | O_NONBLOCK);
int fd = open("myfifo", O_RDONLY);
if(fd == -1)
{
perror("open error");
exit(1);
}
// 創(chuàng)建個event_base
struct event_base* base = NULL;
base = event_base_new();
// 創(chuàng)建事件
struct event* ev = NULL;
ev = event_new(base, fd, EV_READ | EV_PERSIST, read_cb, NULL);
// 添加事件
event_add(ev, NULL);
// 事件循環(huán)
event_base_dispatch(base); // while(1) { epoll();}
// 釋放資源
event_free(ev);
event_base_free(base);
close(fd);
return 0;
}
如代碼所示,這個也遵循libevent搭積木的過程
寫管道代碼如下:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <fcntl.h> #include <event2/event.h> // 對操作處理函數(shù) void write_cb(evutil_socket_t fd, short what, void *arg) { // write管道
char buf[1024] = {0};
static int num = 0;
sprintf(buf, "hello,world-%d\n", num++);
write(fd, buf, strlen(buf)+1);
sleep(1);
} // 寫管道 int main(int argc, const char* argv[]) { // open file
//int fd = open("myfifo", O_WRONLY | O_NONBLOCK);
int fd = open("myfifo", O_WRONLY);
if(fd == -1)
{
perror("open error");
exit(1);
}
// 寫管道
struct event_base* base = NULL;
base = event_base_new();
// 創(chuàng)建事件
struct event* ev = NULL;
// 檢測的寫緩沖區(qū)是否有空間寫
//ev = event_new(base, fd, EV_WRITE , write_cb, NULL);
ev = event_new(base, fd, EV_WRITE | EV_PERSIST, write_cb, NULL);
// 添加事件
event_add(ev, NULL);
// 事件循環(huán)
event_base_dispatch(base);
// 釋放資源
event_free(ev);
event_base_free(base);
close(fd);
return 0;
}
編譯運行,結(jié)果如下:
118P-使用fifo的讀寫編碼實現(xiàn) 這個基本上就是把前面代碼寫了一遍,復(fù)習(xí)一下,問題不大
119P-未決和非未決 未決和非未決:
非未決: 沒有資格被處理未決: 有資格被處理,但尚未被處理event_new --> event ---> 非未決 --> event_add --> 未決 --> dispatch() && 監(jiān)聽事件被觸發(fā) --> 激活態(tài) --> 執(zhí)行回調(diào)函數(shù) --> 處理態(tài) --> 非未決 event_add && EV_PERSIST --> 未決 --> event_del --> 非未決
120P-中午復(fù)習(xí)
121P-bufferevent特性
帶緩沖區(qū)的事件 bufferevent
#include <event2/bufferevent.h> read/write 兩個緩沖. 借助 隊列.
122P-bufferevent事件對象創(chuàng)建、銷毀 創(chuàng)建、銷毀bufferevent:
struct bufferevent *ev;struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, enum bufferevent_options options);base: event_basefd: 封裝到bufferevent內(nèi)的 fdoptions:BEV_OPT_CLOSE_ON_FREE返回: 成功創(chuàng)建的 bufferevent事件對象。void bufferevent_socket_free(struct bufferevent *ev);
123P-給bufferevent事件對象設(shè)置回調(diào) 給bufferevent設(shè)置回調(diào):
對比event: event_new( fd, callback ); event_add() -- 掛到 event_base 上。bufferevent_socket_new(fd) bufferevent_setcb( callback )void bufferevent_setcb(struct bufferevent * bufev,bufferevent_data_cb readcb,bufferevent_data_cb writecb,bufferevent_event_cb eventcb,void *cbarg );bufev: bufferevent_socket_new() 返回值readcb: 設(shè)置 bufferevent 讀緩沖,對應(yīng)回調(diào) read_cb{ bufferevent_read() 讀數(shù)據(jù) }writecb: 設(shè)置 bufferevent 寫緩沖,對應(yīng)回調(diào) write_cb { } -- 給調(diào)用者,發(fā)送寫成功通知。 可以 NULLeventcb: 設(shè)置 事件回調(diào)。 也可傳NULLtypedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);void event_cb(struct bufferevent *bev, short events, void *ctx){。。。。。}events: BEV_EVENT_CONNECTEDcbarg: 上述回調(diào)函數(shù)使用的 參數(shù)。read 回調(diào)函數(shù)類型:typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void*ctx);void read_cb(struct bufferevent *bev, void *cbarg ){.....bufferevent_read(); --- read();}bufferevent_read()函數(shù)的原型:size_t bufferevent_read(struct bufferevent *bev, void *buf, size_t bufsize);write 回調(diào)函數(shù)類型:int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
124P-緩沖區(qū)開啟和關(guān)閉 啟動、關(guān)閉 bufferevent的 緩沖區(qū):
void bufferevent_enable(struct bufferevent *bufev, short events); 啟動 events: EV_READ、EV_WRITE、EV_READ|EV_WRITE默認(rèn)、write 緩沖是 enable、read 緩沖是 disablebufferevent_enable(evev, EV_READ); -- 開啟讀緩沖。
125P-客戶端和服務(wù)器連接和監(jiān)聽 連接客戶端:
socket();connect();int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen);bev: bufferevent 事件對象(封裝了fd)address、len:等同于 connect() 參2/3
創(chuàng)建監(jiān)聽服務(wù)器:
------ socket();bind();listen();accept();struct evconnlistener * listnerstruct evconnlistener *evconnlistener_new_bind ( struct event_base *base,evconnlistener_cb cb, void *ptr, unsigned flags,int backlog,const struct sockaddr *sa,int socklen);base: event_basecb: 回調(diào)函數(shù)。 一旦被回調(diào),說明在其內(nèi)部應(yīng)該與客戶端完成, 數(shù)據(jù)讀寫操作,進(jìn)行通信。ptr: 回調(diào)函數(shù)的參數(shù)flags: LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLEbacklog: listen() 2參。 -1 表最大值sa:服務(wù)器自己的地址結(jié)構(gòu)體socklen:服務(wù)器自己的地址結(jié)構(gòu)體大小。返回值:成功創(chuàng)建的監(jiān)聽器。
釋放監(jiān)聽服務(wù)器:
void evconnlistener_free(struct evconnlistener *lev);
126P-libevent實現(xiàn)TCP服務(wù)器流程 服務(wù)器端 libevent 創(chuàng)建TCP連接:
創(chuàng)建event_base
創(chuàng)建bufferevent事件對象。bufferevent_socket_new();
使用bufferevent_setcb() 函數(shù)給 bufferevent的 read、write、event 設(shè)置回調(diào)函數(shù)。
當(dāng)監(jiān)聽的 事件滿足時,read_cb會被調(diào)用, 在其內(nèi)部 bufferevent_read();讀
使用 evconnlistener_new_bind 創(chuàng)建監(jiān)聽服務(wù)器, 設(shè)置其回調(diào)函數(shù),當(dāng)有客戶端成功連接時,這個回調(diào)函數(shù)會被調(diào)用。
封裝 listner_cb() 在函數(shù)內(nèi)部。完成與客戶端通信。
設(shè)置讀緩沖、寫緩沖的 使能狀態(tài) enable、disable
啟動循環(huán) event_base_dispath();
釋放連接。
127P-libevent實現(xiàn)TCP服務(wù)器源碼分析 服務(wù)器源碼如下:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <event2/event.h> #include <event2/listener.h> #include <event2/bufferevent.h> // 讀緩沖區(qū)回調(diào) void read_cb(struct bufferevent *bev, void *arg) { char buf[1024] = {0};
bufferevent_read(bev, buf, sizeof(buf));
printf("client say: %s\n", buf);
char *p = "我是服務(wù)器, 已經(jīng)成功收到你發(fā)送的數(shù)據(jù)!";
// 發(fā)數(shù)據(jù)給客戶端
bufferevent_write(bev, p, strlen(p)+1);
sleep(1);
} // 寫緩沖區(qū)回調(diào) void write_cb(struct bufferevent *bev, void *arg) { printf("I'm服務(wù)器, 成功寫數(shù)據(jù)給客戶端,寫緩沖區(qū)回調(diào)函數(shù)被回調(diào)...\n");
} // 事件 void event_cb(struct bufferevent *bev, short events, void *arg) { if (events & BEV_EVENT_EOF)
{
printf("connection closed\n");
}
else if(events & BEV_EVENT_ERROR)
{
printf("some other error\n");
}
bufferevent_free(bev);
printf("buffevent 資源已經(jīng)被釋放...\n");
} void cb_listener( struct evconnlistener *listener,
evutil_socket_t fd,
struct sockaddr *addr,
int len, void *ptr)
{ printf(“connect new client\n”); struct event_base* base = (struct event_base*)ptr; // 通信操作 // 添加新事件 struct bufferevent *bev; bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); // 給bufferevent緩沖區(qū)設(shè)置回調(diào) bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL); bufferevent_enable(bev, EV_READ); } int main(int argc, const char* argv[]) { // init server
struct sockaddr_in serv;
memset(&serv, 0, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(9876);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
struct event_base* base;
base = event_base_new();
// 創(chuàng)建套接字
// 綁定
// 接收連接請求
struct evconnlistener* listener;
listener = evconnlistener_new_bind(base, cb_listener, base,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
36, (struct sockaddr*)&serv, sizeof(serv));
event_base_dispatch(base);
evconnlistener_free(listener);
event_base_free(base);
return 0;
}
128P-服務(wù)器注意事項 bufev_server的代碼,錘起來:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/socket.h> #include <event2/event.h> #include <event2/bufferevent.h> #include <event2/listener.h> #include <pthread.h> void sys_err(const char *str) { perror(str);
exit(1);
} // 讀事件回調(diào) void read_cb(struct bufferevent *bev, void *arg) { char buf[1024] = {0};
// 借助讀緩沖,從客戶端拿數(shù)據(jù)
bufferevent_read(bev, buf, sizeof(buf));
printf("clinet write: %s\n", buf);
// 借助寫緩沖,寫數(shù)據(jù)回給客戶端
bufferevent_write(bev, "abcdefg", 7);
} // 寫事件回調(diào) void write_cb(struct bufferevent *bev, void *arg) { printf("-------fwq------has wrote\n");
} // 其他事件回調(diào) void event_cb(struct bufferevent *bev, short events, void *ctx) { } // 被回調(diào),說明有客戶端成功連接, cfd已經(jīng)傳入該參數(shù)內(nèi)部。 創(chuàng)建bufferevent事件對象 // 與客戶端完成讀寫操作。 void listener_cb(struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr)
{ struct event_base *base = (struct event_base *)ptr;
// 創(chuàng)建bufferevent 對象
struct bufferevent *bev = NULL;
bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE);
// 給bufferevent 對象 設(shè)置回調(diào) read、write、event
void bufferevent_setcb(struct bufferevent * bufev,
bufferevent_data_cb readcb,
bufferevent_data_cb writecb,
bufferevent_event_cb eventcb,
void *cbarg );
// 設(shè)置回調(diào)函數(shù)
bufferevent_setcb(bev, read_cb, write_cb, NULL, NULL);
// 啟動 read 緩沖區(qū)的 使能狀態(tài)
bufferevent_enable(bev, EV_READ);
return ;
} int main(int argc, char *argv[]) { // 定義服務(wù)器地址結(jié)構(gòu)
struct sockaddr_in srv_addr;
bzero(&srv_addr, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(8765);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 創(chuàng)建event_base
struct event_base *base = event_base_new();
/*
struct evconnlistener *evconnlistener_new_bind (
struct event_base *base,
evconnlistener_cb cb,
void *ptr,
unsigned flags,
int backlog,
const struct sockaddr *sa,
int socklen);
*/
// 創(chuàng)建服務(wù)器監(jiān)聽器:
struct evconnlistener *listener = NULL;
listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1,
(struct sockaddr *)&srv_addr, sizeof(srv_addr));
// 啟動監(jiān)聽循環(huán)
event_base_dispatch(base);
// 銷毀event_base
evconnlistener_free(listener);
event_base_free(base);
return 0;
}
129P-客戶端流程簡析和回顧
代碼走起:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <event2/bufferevent.h> #include <event2/event.h> #include <arpa/inet.h> void read_cb(struct bufferevent *bev, void *arg) { char buf[1024] = {0};
bufferevent_read(bev, buf, sizeof(buf));
printf("fwq say:%s\n", buf);
bufferevent_write(bev, buf, strlen(buf)+1);
sleep(1);
} void write_cb(struct bufferevent *bev, void *arg) { printf("----------我是客戶端的寫回調(diào)函數(shù),沒卵用\n");
} void event_cb(struct bufferevent *bev, short events, void *arg) { if (events & BEV_EVENT_EOF)
{
printf("connection closed\n");
}
else if(events & BEV_EVENT_ERROR)
{
printf("some other error\n");
}
else if(events & BEV_EVENT_CONNECTED)
{
printf("已經(jīng)連接服務(wù)器...\\(^o^)/...\n");
return;
}
// 釋放資源
bufferevent_free(bev);
} // 客戶端與用戶交互,從終端讀取數(shù)據(jù)寫給服務(wù)器 void read_terminal(evutil_socket_t fd, short what, void *arg) { // 讀數(shù)據(jù)
char buf[1024] = {0};
int len = read(fd, buf, sizeof(buf));
struct bufferevent* bev = (struct bufferevent*)arg;
// 發(fā)送數(shù)據(jù)
bufferevent_write(bev, buf, len+1);
} int main(int argc, const char* argv[]) { struct event_base* base = NULL;
base = event_base_new();
int fd = socket(AF_INET, SOCK_STREAM, 0);
// 通信的fd放到bufferevent中
struct bufferevent* bev = NULL;
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
// init server info
struct sockaddr_in serv;
memset(&serv, 0, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(9876);
inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
// 連接服務(wù)器
bufferevent_socket_connect(bev, (struct sockaddr*)&serv, sizeof(serv));
// 設(shè)置回調(diào)
bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
// 設(shè)置讀回調(diào)生效
// bufferevent_enable(bev, EV_READ);
// 創(chuàng)建事件
struct event* ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST,
read_terminal, bev);
// 添加事件
event_add(ev, NULL);
event_base_dispatch(base);
event_free(ev);
event_base_free(base);
return 0;
}
130P-總結(jié) linux網(wǎng)絡(luò)編程資料\day6\1-教學(xué)資料\課堂筆記.txt
131P-復(fù)習(xí)
132P-web大練習(xí)的概述 寫一個供用戶訪問主機(jī)文件的web服務(wù)器
133P-HTML文本和標(biāo)題
134P-HTML文本和標(biāo)題 和上一話重復(fù),僵硬,跳過
135P-錯誤頁面html 代碼比較簡單
136P-列表、圖片和超鏈接
137P-http協(xié)議請求、應(yīng)答協(xié)議基礎(chǔ)格式
138P-服務(wù)器框架復(fù)習(xí)和getline函數(shù) 代碼如下:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/epoll.h> #include <unistd.h> #include <fcntl.h> #define MAXSIZE 2048 int init_listen_fd(int port, int epfd) { // 創(chuàng)建監(jiān)聽的套接字 lfd
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1) {
perror("socket error");
exit(1);
}
// 創(chuàng)建服務(wù)器地址結(jié)構(gòu) IP+port
struct sockaddr_in srv_addr;
bzero(&srv_addr, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(port);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 端口復(fù)用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 給 lfd 綁定地址結(jié)構(gòu)
int ret = bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
if (ret == -1) {
perror("bind error");
exit(1);
}
// 設(shè)置監(jiān)聽上限
ret = listen(lfd, 128);
if (ret == -1) {
perror("listen error");
exit(1);
}
// lfd 添加到 epoll 樹上
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = lfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
if (ret == -1) {
perror("epoll_ctl add lfd error");
exit(1);
}
return lfd;
} void do_accept(int lfd, int epfd) { struct sockaddr_in clt_addr;
socklen_t clt_addr_len = sizeof(clt_addr);
int cfd = accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);
if (cfd == -1) {
perror("accept error");
exit(1);
}
// 打印客戶端IP+port
char client_ip[64] = {0};
printf("New Client IP: %s, Port: %d, cfd = %d\n",
inet_ntop(AF_INET, &clt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)),
ntohs(clt_addr.sin_port), cfd);
// 設(shè)置 cfd 非阻塞
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
// 將新節(jié)點cfd 掛到 epoll 監(jiān)聽樹上
struct epoll_event ev;
ev.data.fd = cfd;
// 邊沿非阻塞模式
ev.events = EPOLLIN | EPOLLET;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
if (ret == -1) {
perror("epoll_ctl add cfd error");
exit(1);
}
} void do_read(int cfd, int epfd) { // read cfd 小 -- 大 write 回
// 讀取一行http協(xié)議, 拆分, 獲取 get 文件名 協(xié)議號
} void epoll_run(int port) { int i = 0;
struct epoll_event all_events[MAXSIZE];
// 創(chuàng)建一個epoll監(jiān)聽樹根
int epfd = epoll_create(MAXSIZE);
if (epfd == -1) {
perror("epoll_create error");
exit(1);
}
// 創(chuàng)建lfd,并添加至監(jiān)聽樹
int lfd = init_listen_fd(port, epfd);
while (1) {
// 監(jiān)聽節(jié)點對應(yīng)事件
int ret = epoll_wait(epfd, all_events, MAXSIZE, -1);
if (ret == -1) {
perror("epoll_wait error");
exit(1);
}
for (i=0; i<ret; ++i) {
// 只處理讀事件, 其他事件默認(rèn)不處理
struct epoll_event *pev = &all_events[i];
// 不是讀事件
if (!(pev->events & EPOLLIN)) {
continue;
}
if (pev->data.fd == lfd) { // 接受連接請求
do_accept(lfd, epfd);
} else { // 讀數(shù)據(jù)
do_read(pev->data.fd, epfd);
}
}
}
} int main(int argc, char *argv[]) { // 命令行參數(shù)獲取 端口 和 server提供的目錄
if (argc < 3)
{
printf("./server port path\n");
}
// 獲取用戶輸入的端口
int port = atoi(argv[1]);
// 改變進(jìn)程工作目錄
int ret = chdir(argv[2]);
if (ret != 0) {
perror("chdir error");
exit(1);
}
// 啟動 epoll監(jiān)聽
epoll_run(port);
return 0;
}
139P-復(fù)習(xí) 請求協(xié)議: — 瀏覽器組織,發(fā)送
GET /hello.c Http1.1\r\n 2. Host: localhost:2222\r\n 3. User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:24.0) Gecko/201001 01 Firefox/24.0\r\n 4. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8\r\n 5. Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3\r\n 6. Accept-Encoding: gzip, deflate\r\n 7. Connection: keep-alive\r\n 8. If-Modified-Since: Fri, 18 Jul 2014 08:36:36 GMT\r\n 【空行】\r\n
應(yīng)答協(xié)議:
Http1.1 200 OK 2. Server: xhttpd Content-Type:text/plain; charset=iso-8859-1 3. Date: Fri, 18 Jul 2014 14:34:26 GMT 5. Content-Length: 32 ( 要么不寫 或者 傳-1, 要寫務(wù)必精確 ! ) 6. Content-Language: zh-CN 7. Last-Modified: Fri, 18 Jul 2014 08:36:36 GMT 8. Connection: close \r\n [數(shù)據(jù)起始。。。。。 。。。。 。。。數(shù)據(jù)終止]
140P-單文件通信流程分析
getline() 獲取 http協(xié)議的第一行。
從首行中拆分 GET、文件名、協(xié)議版本。 獲取用戶請求的文件名。
判斷文件是否存在。 stat()
判斷是文件還是目錄。
是文件-- open – read – 寫回給瀏覽器
先寫 http 應(yīng)答協(xié)議頭 : http/1.1 200 ok
Content-Type:text/plain; charset=iso-8859-1
141P-處理出錯返回
142P-正則表達(dá)式獲取文件名
void do_read(int cfd, int epfd) { // 讀取一行http協(xié)議, 拆分, 獲取 get 文件名 協(xié)議號
char line[1024] = {0};
char method[16], path[256], protocol[16];
int len = get_line(cfd, line, sizeof(line)); //讀 http請求協(xié)議首行 GET /hello.c HTTP/1.1
if (len == 0) {
printf("服務(wù)器,檢查到客戶端關(guān)閉....\n");
disconnect(cfd, epfd);
} else {
sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol);
printf("method=%s, path=%s, protocol=%s\n", method, path, protocol);
while (1) {
char buf[1024] = {0};
len = get_line(cfd, buf, sizeof(buf));
if (buf[0] == '\n') {
break;
} else if (len == -1)
break;
}
}
if (strncasecmp(method, "GET", 3) == 0)
{
char *file = path+1; // 取出 客戶端要訪問的文件名
http_request(cfd, file);
disconnect(cfd, epfd);
}
}
143P-判斷文件是否存在
// 處理http請求, 判斷文件是否存在, 回發(fā) void http_request(int cfd, const char *file) { struct stat sbuf;
// 判斷文件是否存在
int ret = stat(file, &sbuf);
if (ret != 0) {
// 回發(fā)瀏覽器 404 錯誤頁面
perror("stat");
exit(1);
}
if(S_ISREG(sbuf.st_mode)) { // 是一個普通文件
// 回發(fā) http協(xié)議應(yīng)答
//send_respond(cfd, 200, "OK", " Content-Type: text/plain; charset=iso-8859-1", sbuf.st_size);
send_respond(cfd, 200, "OK", "Content-Type:image/jpeg", -1);
//send_respond(cfd, 200, "OK", "audio/mpeg", -1);
// 回發(fā) 給客戶端請求數(shù)據(jù)內(nèi)容。
send_file(cfd, file);
}
}
144P-寫出http應(yīng)答協(xié)議頭
// 客戶端端的fd, 錯誤號,錯誤描述,回發(fā)文件類型, 文件長度
void send_respond(int cfd, int no, char *disp, char *type, int len) { char buf[4096] = {0};
sprintf(buf, "HTTP/1.1 %d %s\r\n", no, disp);
send(cfd, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: %s\r\n", type);
sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);
send(cfd, buf, strlen(buf), 0);
send(cfd, "\r\n", 2, 0);
}
145P-寫數(shù)據(jù)給瀏覽器
// 發(fā)送服務(wù)器本地文件 給瀏覽器 void send_file(int cfd, const char *file) { int n = 0, ret;
char buf[4096] = {0};
// 打開的服務(wù)器本地文件。 --- cfd 能訪問客戶端的 socket
int fd = open(file, O_RDONLY);
if (fd == -1) {
// 404 錯誤頁面
perror("open error");
exit(1);
}
while ((n = read(fd, buf, sizeof(buf))) > 0) {
ret = send(cfd, buf, n, 0);
if (ret == -1) {
perror("send error");
exit(1);
}
if (ret < 4096)
printf("-----send ret: %d\n", ret);
}
close(fd);
}
146P-文件類型區(qū)分
147P-錯誤原因及說明 MP3請求錯誤的原因在于,做錯誤判斷時太粗略,errno=EAGAIN或者errno=EINTR時,并不算錯誤,此時繼續(xù)執(zhí)行循環(huán)讀取數(shù)據(jù)就行。 然而原來的程序是直接退出了,所以沒接收到數(shù)據(jù)。
148P-錯誤頁面展示
錯誤頁面部分的代碼:
void send_error(int cfd, int status, char *title, char *text) { char buf[4096] = {0};
sprintf(buf, "%s %d %s\r\n", "HTTP/1.1", status, title);
sprintf(buf+strlen(buf), "Content-Type:%s\r\n", "text/html");
sprintf(buf+strlen(buf), "Content-Length:%d\r\n", -1);
sprintf(buf+strlen(buf), "Connection: close\r\n");
send(cfd, buf, strlen(buf), 0);
send(cfd, "\r\n", 2, 0);
memset(buf, 0, sizeof(buf));
sprintf(buf, "<html><head><title>%d %s</title></head>\n", status, title);
sprintf(buf+strlen(buf), "<body bgcolor=\"#cc99cc\"><h2 align=\"center\">%d %s</h4>\n", status, title);
sprintf(buf+strlen(buf), "%s\n", text);
sprintf(buf+strlen(buf), "<hr>\n</body>\n</html>\n");
send(cfd, buf, strlen(buf), 0);
return ;
}
直接看完整代碼吧: epoll_server.c
149P-關(guān)于瀏覽器請求ico文件
150P-瀏覽器請求目錄
// http請求處理 void http_request(const char* request, int cfd) { // 拆分http請求行
char method[12], path[1024], protocol[12];
sscanf(request, "%[^ ] %[^ ] %[^ ]", method, path, protocol);
printf("method = %s, path = %s, protocol = %s\n", method, path, protocol);
// 轉(zhuǎn)碼 將不能識別的中文亂碼 -> 中文
// 解碼 %23 %34 %5f
decode_str(path, path);
char* file = path+1; // 去掉path中的/ 獲取訪問文件名
// 如果沒有指定訪問的資源, 默認(rèn)顯示資源目錄中的內(nèi)容
if(strcmp(path, "/") == 0) {
// file的值, 資源目錄的當(dāng)前位置
file = "./";
}
// 獲取文件屬性
struct stat st;
int ret = stat(file, &st);
if(ret == -1) {
send_error(cfd, 404, "Not Found", "NO such file or direntry");
return;
}
// 判斷是目錄還是文件
if(S_ISDIR(st.st_mode)) { // 目錄
// 發(fā)送頭信息
send_respond_head(cfd, 200, "OK", get_file_type(".html"), -1);
// 發(fā)送目錄信息
send_dir(cfd, file);
} else if(S_ISREG(st.st_mode)) { // 文件
// 發(fā)送消息報頭
send_respond_head(cfd, 200, "OK", get_file_type(file), st.st_size);
// 發(fā)送文件內(nèi)容
send_file(cfd, file);
}
}
151P-判斷文件類型
// 通過文件名獲取文件的類型 const char *get_file_type(const char *name) { char* dot;
// 自右向左查找‘.’字符, 如不存在返回NULL
dot = strrchr(name, '.');
if (dot == NULL)
return "text/plain; charset=utf-8";
if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
return "text/html; charset=utf-8";
if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
return "image/jpeg";
if (strcmp(dot, ".gif") == 0)
return "image/gif";
if (strcmp(dot, ".png") == 0)
return "image/png";
if (strcmp(dot, ".css") == 0)
return "text/css";
if (strcmp(dot, ".au") == 0)
return "audio/basic";
if (strcmp( dot, ".wav" ) == 0)
return "audio/wav";
if (strcmp(dot, ".avi") == 0)
return "video/x-msvideo";
if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
return "video/quicktime";
if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
return "video/mpeg";
if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
return "model/vrml";
if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
return "audio/midi";
if (strcmp(dot, ".mp3") == 0)
return "audio/mpeg";
if (strcmp(dot, ".ogg") == 0)
return "application/ogg";
if (strcmp(dot, ".pac") == 0)
return "application/x-ns-proxy-autoconfig";
return "text/plain; charset=utf-8";
}
152P-漢字字符編碼和解碼 URL中的漢字默認(rèn)是存為Unicode碼
153P-libevent實現(xiàn)的web服務(wù)器 直接源碼啃起來吧
154P-telnet調(diào)試
總結(jié)
以上是生活随笔 為你收集整理的Linux网络编程——黑马程序员笔记 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔 推薦給好友。