inet_pton:IP 字符串 → 網絡字節序地址
ntohl:網絡字節序 → 主機字節序
TCP狀態轉換圖(重點)?
可以通過下面這行代碼查看目前網絡狀態
netstat -apn | grep client
?
1、主動發起請求端? close-->SYN-->SYN_SENT-->接收ACK、SYN-->SYN_SENT-->發送ACK-->ESTABLISHED(數據通信狀態).
2、主動關閉請求端ESTABLISHED-->發送FIN-->FIN_WAIT_1-->接收ACK-->FIN_WAIT_2(半關閉)-->接收對端FIN-->FIN_WAIT_2-->發送ACK-->TIME_WAIT-->等待2MSL時長-->CLOSE.
只有主動關閉連接方會經歷TIME_WAIT狀態。
3、被動接收連接請求端 close-->LISTEN-->接收SYN-->LISTEN-->發送ACK、SYN-->SYN_RCVD-->接收ACK-->ESTABLISHED.
4、被動關閉連接請求端ESTABLISHED-->接收SYN-->ESTABLISHED-->發送ACK-->CLOSE_WAIT-->發送FIN-->LAST_ACK-->接收ACK-->CLOSE.?
當被動關閉連接請求端處于CLOSE_WAIT的時候,主動關閉連接請求端處于半關閉狀態。
重點記憶:ESTABLISHED、FIN_WAIT_2 <---->CLOSE_WAIT、TIME_WAIT(2MSL).
2MSL時長
一定出現在主動發送請求端。
保證最后一個ACK能成功被對端接收。(等待期間,對端沒收到我發的ACK,對端會再次發送FIN請求)。
端口復用(讓端口重復利用,固定操作)
在server代碼的socket和bind之間插入下面代碼
int opt = 1; // 取值一般只有兩個 0/1
setsockopt(listenfd , SOL_SOCKET , SO_REUSEADDR , (void*)&opt , sizeof(opt));SO_REUSEADDR:允許重用本地地址
SO_RESUEPORT:允許重用本地端口成功返回0,失敗-1
半關閉
通信雙方中只有一端關閉通信 ---FIN_WAIT_2.
close(cfd);
close(cfd);
shutdown(int fd , int how);
how: SHUT_RD; 關讀端 SHUT_WR; 關寫端SHUT_RDWR; 關讀寫端
這個函數不是很重要,結論:shutdown在關閉多個文件描述符應用的文件時,采用全關閉的方法,close只關閉一個(主要在dup2的時候用到的區別)。
select多路IO轉接
借助內核,select來監聽客戶端連接、數據通信事件。
之前做的CS模型中都是服務器端自己做連接以及read/write等操作,這種方法相當于是有了一個秘書select,服務器端創建了lfd之后交給select,select監聽是否有要建立連接的客戶端,如果有,反饋給服務器端,讓服務器端調用accept函數,創建cfd,之后再交給select,如果select監聽到客戶端有傳數據的需求再反饋給服務器端。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);nfds:所有監聽的最大的文件描述符+1
fd_set *readfds, fd_set *writefds, fd_set *exceptfds:傳入傳出參數
fd_set *readfds:讀文件描述符監聽集合
fd_set *writefds:寫文件描述符監聽集合
fd_set *exceptfds:異常文件描述符監聽集合
通常寫和異常不使用,一般用NULL
可以參考位圖timeout > 0 設置監聽超時時長
timeout = 0 非阻塞監聽,輪詢
timeout = NULL 阻塞監聽返回值 > 0 , 所有監聽集合(3)中 , 滿足對應事件總數
返回值 = 0 , 所有監聽集合(3)中 , 沒有滿足對應事件總數
返回值 = -1 , errno
?FD_ZERO
清空一個文件描述符集合。
void FD_ZERO(fd_set *set);eg. fd_set rset;
FD_ZERO(&rset);
?FD_SET
將待監聽的文件描述符,添加到監聽集合中。
void FD_SET(int fd, fd_set *set);
eg.
fd_set rset;
FD_ZERO(&rset);
FD_SET(3 , &rset);
FD_CLR
將一個文件描述符從監聽集合中移除。
void FD_CLR(int fd, fd_set *set);
FD_ISSET
判斷一個文件描述符是否在監聽集合中。
int FD_ISSET(int fd, fd_set *set);
返回值:在返回1 不在返回0
多路復用IO轉接代碼思路
lfd = socket(); //創建套接字
bind(); //綁定地址結構
listen(); //設置監聽上限
fd_set rset , allset; //創建讀監聽集合
FD_ZERO(&rset);
FD_ZERO(&allset); //將讀監聽集合清零
FD_SET(lfd , &allset); //將lfd添加至讀監聽集合中
while(1){rset = allset;ret = select(lfd + 1 , &rset , NULL , NULL , NULL); //監聽文件描述符集合對應的事件if(ret > 0){if(FD_ISSET(lfd , &rset)) //1 在 , 0不在{ cfd = accept();FD_SET(cfd , &allset);}for(i = lfd + 1 ; i <= 最大文件描述符; i++){FD_ISSET(i , &rset);read();toupper();write();}}
}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#define SER_PORT 9003void sys_err(char* s)
{perror(s);exit(1);
}int main(int argc , char *argv[])
{int lfd , cfd ,maxfd , i , n , k;char buf[BUFSIZ];struct sockaddr_in serv_addr , clit_addr;socklen_t clit_addr_len;bzero(&serv_addr , sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SER_PORT);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);lfd = socket(AF_INET , SOCK_STREAM , 0);if(lfd == -1)sys_err("socket error");int opt = 1;setsockopt(lfd , SOL_SOCKET , SO_REUSEADDR , (void*)&opt , sizeof(opt));int ret = bind(lfd , (struct sockaddr*)&serv_addr , sizeof(serv_addr));if(ret == -1)sys_err("bind error");listen(lfd , 128);fd_set rset , allset; //定義讀集合和備份集合FD_ZERO(&rset);FD_ZERO(&allset);FD_SET(lfd , &allset); //添加到讀集合中maxfd = lfd; //最大文件描述符while(1){rset = allset; //備份ret = select(maxfd + 1 , &rset , NULL , NULL , NULL); //使用select監聽clit_addr_len = sizeof(clit_addr);if(ret > 0){if(FD_ISSET(lfd , &rset)){cfd = accept(lfd , (struct sockaddr*)&clit_addr , &clit_addr_len); //建立連接,不會阻塞。FD_SET(cfd , &allset); //將新產生的cfd添加到監聽讀集合中,監聽數據讀事件if(maxfd < cfd)maxfd = cfd;if(ret == 1) //說明select只返回一個并且是lfd,后續無需執行。continue;}for(i = lfd + 1 ; i <= maxfd ; i++){ //處理滿足讀事件的fdif(FD_ISSET(i , &rset)){ //找到滿足讀事件的fdn = read(i , buf , sizeof(buf));if(n == 0){ //檢測到客戶端關閉close(i); FD_CLR(i , &allset); // 移除}else if(n < 0){sys_err("read error");}else{for(k = 0 ; k < n ; k++){buf[k] = toupper(buf[k]);}write(STDOUT_FILENO , buf , n);write(i , buf , n);}}}}else if(ret < 0){sys_err("select error");}}close(cfd);return 0 ;
}
但是上述代碼有個問題,就是當rset為[3,6,1023],相當于要循環判斷1023次,運行效率低。可以自定義一個數組,將要監聽的文件描述符放到數組中。然后根據下標索引取出需要的文件描述符。
select的優缺點
缺點:
1、監聽上限受文件描述符限制,最大1024。
2、檢測滿足條件的fd,自己添加業務邏輯提高小。提高了編碼難度。
優點:
1、跨平臺。win、Linux、macos、unix、mips。
?