UDP部分
p196
UDP的connnect函數
UDP調用connect後,內核只是檢查是否存在立即可知的錯誤(例如一個顯然不可達的目的地),記錄對端的IP地址和端口號,然後立即返回到調用進程。
相對於未connected的UDP套接字,發生了三個變化:
可以對UDP套接字多次調用connect
1)指定新的IP地址和端口號;
2)斷開套接字;把套接字地址族成員設置爲AF_UNSPEC。
Raw socket(原始套接字) 它和其他的套接字的不同之處在於它工作在網絡層或數據鏈路層,而其他類型的套接字工作在傳輸層,只能進行傳輸層數據操作。
p32:
1、ACK中的確認號是發送這個ACK的一端所期待的下一個序列號,SYN和FIN都會佔據一個字節的序列號空間。
client: socket->connect; server: socket->bind-> listen -> accept;
2、建立TCP鏈接就好比一個電話系統:
socket:有電話可用,
#include <sys/socket.h> int socket(int family, int type, int protocol); return sockfd, else -1;
bind:告訴別人你的電話號碼,這樣他們可以稱呼你;
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); return 0, else -1;
bind的作用就是綁定端口,如果不調用bind,則後面調用listen內核會自動選取一個臨時端口,然而作爲server,需要一個公開端口。
可以指定ip地址,對於client,這就指定了發送包的源地址,對於server,只接受目的地址爲此地址的包。
listen:打開電話振鈴,這樣當有一個外來呼叫到達時,你就可以聽到,僅由服務器端設定。
#include<sys/socket.h> int listen(int sockfd, int backlog); return 0 else -1;
內核爲任何一個給定的監聽套接字維護兩個隊列:
1、未完成鏈接隊列,每個SYN分節對應其中一項。處於SYN_RCVD;
2、已完成鏈接隊列,處於ESTABLISHED狀態。
兩個隊列之和不超過backlog。
connect:要求我們知道對方的電話號碼並撥打他。
#include <sys/socket.h> int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
對tcp來說,connect發起三次握手。
accept:發生在被呼叫的人應答電話之時,由accept返回的客戶的標識(即客戶的IP地址和端口號)
類似於讓電話機的呼叫者ID功能部件顯示呼叫者的電話號碼,
accept只在鏈接建立之後返回客戶的標識。
#include<sys/socket.h> int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); return connfd, else -1;
DNS:提供一種類似於電話薄的服務,
getaddrinfo:類似於在電話薄中查找某個人的電話號碼
getnameinfo:類似於有一本按照電話號碼而不是按照用戶名排序的電話薄。
p37
主動關閉的那端經歷TIME_WAIT狀態:這個狀態持續時間是最長分節生命期(MSL)的兩倍。
存在理由:
1)可靠的實現TCP全雙工鏈接的終止;(ACK丟失)
2)允許老的重複分節在網絡中消逝;(防止第二次同樣的鏈接收到上次鏈接的數據)
p94
#include<sys/socket.h>
#include getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
返回本地IP地址和本地端口;
#include getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
可用於獲取某個套接字的地址族。
書中舉得例子:inetd fork並exec某個TCP服務器程序,
如何將connfd傳遞給exec的程序呢?
1、將connfd變爲字符串,作爲命令行參數傳遞;
2、約定在 調用exec之前,總是把某個特定描述符置爲所接受的已連接套接字的描述符,
例如,inetd採用0、1、2描述符。
#include <unistd.h> pid_t fork(void); return 0 or pid else -1;
fork之後,父子進程只共享描述符;
fork有兩個典型的用法;
1、一個進程創建一個自身的副本,每個副本幹各自的事情。這是網絡服務器的典型用法;
2、一個進程想要執行另一個程序,先創建副本,然後其中一個副本調用exec,把自身替換成新程序。
常見有6個exec函數(最終都是調用execve系統調用),他們之間的主要區別是:
1、待執行的程序文件由文件名(filename)還是路徑名(pathname)指定;
2、新程序的參數是一一列出還是由一個指針數組來引用;
3、把調用進程的環境傳遞給想新進程還是給新進程 指定新的環境;
#include <sys/socket.h> int shutdown(int sockfd, int howto);
由close可以終止網絡連接,但是close有兩個限制:
1、close把描述符減一,僅在該計數變爲0時才關閉套接字,我們用shutdown可以直接激發TCP的正常終止序列;
2、close是終止讀和寫兩個方向上的數據傳送,而shutdown可以選擇(howto參數);
p103
信號(signal)是告知某個進程發生了某個事件的通知,也稱爲軟件中斷(software interrupt),通常是異步的,
也就是在進程預先不知道信號的準確發生時刻。
信號可以由一個進程發給另一個進程或由內核發給某個進程。
可以調用sigaction函數(POSIX方法)來設定一個信號的處置,有三個選擇:
1、提供一個信號處理函數(signal handler),捕獲(catching)信號,有兩個信號不能被捕獲(SIGKILL, SIGSTOP)
void handler(int signo);
2、忽略(ignore)某一個信號,有兩個信號不能被忽略(SIGKILL, SIGSTOP);
3、設置爲SIG_DFL來啓動它的默認(default)處置。
有些信號的默認行爲是終止進程,還有些就是忽略掉。
p107:
處理被中斷的系統調用:
慢系統調用(slow system call): read accpet等網絡函數,還有對管道和終端設備的讀和寫
當阻塞於某個慢系統調用的一個進程捕獲某個信號且相應信號處理函數返回時,該系統調用可能返回一個EINTER錯誤
有些系統會自動重啓系統調用,有些不會,所以需要我們自己重啓
for(;;){
clien = sizeof(cliaddr);
if( (connfd = accept(listenfd, (SA*)&cliaddr, &clilne)) < 0){
if(errno == EINTER)
continue;
else
err_sys("accept error");
}
}
對accept read write select open等函數可以重啓,但是對connect就不可以調用,否則會返回錯誤(EADDRINUSE),這時必須調用select來等待連接完成,
當select成功或失敗時返回。
p108:
問題1:僵死子進程
wait和waitid用來處理已終止的子進程
#include<sys/wait.h> pid_t wait(int *statloc); pid_t waitpid(pid_t pid, int *statloc, int options); return pid else 0 or -1;
信號時不排隊的,所以有多個信號時,應該用waitpid以免留下僵死進程
void sig_chid(int signo){
pid_t pid;
int stat;
// WNOHANG:告知waitpid在尚未有終止的子進程時不要阻塞
// -1: 表示等待第一個終止的子進程
while((pid = waitpid(-1, &stat, WNOHANG)) > 0){
printf("child %d terminated\n",pid);
}
}
結論:
1、在fork子進程的時候,必須捕獲SIGCHLD信號;
2、當捕獲信號時,必須處理被中斷的系統調用;
3、SIGCHLD的信號處理函數必須正確編寫,應使用waitpid函數以免留下殭屍進程。
問題2:當服務器進程終止時,客戶進程沒有被告知;
當服務器終止時,會向客戶進程發送FIN,但是由於客戶進程正阻塞於等待用戶輸入而未接收到該通知,
可以用select或poll函數來處理這種情形。
問題3:服務器主機崩潰的情形要等到客戶想服務器發送了數據才能檢測到,如何快速檢測?
可以利用SO_KEEPALIVE套接字選項來解決該問題(7.5)。
問題4:當客戶和服務器之間傳遞數值數據時,字節序和所支持的長整數的大小不一致
解決方案:
1、把所有的數值數據當做文本串來傳遞。前提假設客戶和服務器主機有相同的字符集;
2、顯示定義所支持數據類型的二進制格式(位數,字節序)。RPC就是用這種方法。
p122
I/O複用是一種讓進程預先告知內核能力,使得內核一旦發現進程預先告知時指定的一個或多個I/O條件(就是描述符)就緒(可以讀/寫了),內核就通知進程。
linux有4個調用可實現I/O複用:select、poll繼承自Unix系統。pselect是select到Posix版。epoll是linux2.6內核特有的。
網絡應用場合:
1)當客戶處理多個描述符(通常爲交互式輸入和網絡套接字);
2)一個客戶同時處理多個套接字;
3)如果一個TCP服務器既要處理監聽套接字,又要處理已連接套接字;
4)如果一個服務器既要處理TCP,又要處理UDP;
5)如果一個服務器要處理多個服務或者多個協議(inetd);
p127
select函數允許進程指示內核等待多個事件中的任何一個發生,並只在有一個或多個事件發生或經歷一段指定的時間後才喚醒它。
select會有最大描述符數的限制。
#include <sys/select.h> #include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout); return count of descriptor or 0 for timeout or -1 for error.
struct timeval{
long tv_sec; /* seconds*/
lont tv_usec; /* microseconds*/
};
void FD_ZERO(fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
p130
關於描述符就緒條件,可讀嗎?可寫嗎?異常嗎?等等
套接字可讀:
1)接收緩衝區的可讀數據大於低水位標記,可用SO_RCVLOWAT套接字選項設置;
2)接收到FIN信號,讀的話不阻塞,返回0;
3)監聽套接字,已完成連接數不爲0,則accept後不阻塞。
4)套接字上有錯誤待處理,讀則不阻塞並返回-1;
套接字可寫:
1)發送緩衝區的可用空間大於或等於低水位標記,可用SO_SNDLOWAT套接字選項設置;
2)套接字寫半部關閉,如果寫的話,產生SIGPIPE信號;
3)使用非阻塞connect的套接字鏈接,或connect已經失敗告終;
4)如果有套接字錯誤待處理,對其寫將不阻塞並返回-1;
p142
服務器易受拒絕服務攻擊(DOS),可能的解決辦法包括:
1)使用非阻塞式I/O(第16章)
2)讓每個客戶由單獨的控制線程提供服務(例如爲每個客戶創建一個子進程或線程)
3)對I/O操作設置一個超時。
p144
poll 函數最初侷限於流設備,後來取消限制,它提供的功能與select類似,不過在處理流設備時,它能夠提供額外的信息,它不用擔心最大描述符數目的限制。
#include<poll.h> int poll(strcut polld* fdarray, unsigned long nfds, int timeout)
struct pollfd{
int fd;
short events;
short revents;
}
有關select、pselect、poll這塊看的不是很清楚。
第六章看的不是很好。
第七章:套接字選項
有很多方法來獲取和設置影響套接字的選項:
1、getsockopt和setsockopt函數
2、fcntl函數
3、ioctl函數
套接字選項大約可分爲兩大基本類型:一是啓用或禁止某個特性的二元選項;二是取得並返回我們可以設置或檢查的特定值的選項。
最常用的選項是1:SO_KEEPALIVE; 2:SO_RCVBUF; 3:SO_SNDBUF; 4:SO_REUSEADDR; 5:SO_LINGER;
1:SO_KEEPALIVE,如果設置這個選項,如果2h內該套接字的任意方向上沒有數據交換,TCP就自動給對端發送keep-alive probe.對端必須響應,
會導致以下三種情況之一:
1)對端以期望的ACK響應。應用程序得不到通知;
2)對端以RST響應,他告知本端TCP,對端已崩潰且已重啓。ECONNRESET錯誤,套接字關閉;
3)對端沒有任何響應。ETIMEOUT錯誤,套接字關閉;
此選項一般是服務器段使用,檢測半開鏈接並終止他們。
4: SO_REUSEADDR 一般來說,一個端口釋放後會等待兩分鐘之後才能再被使用,SO_REUSEADDR是讓端口釋放後立即就可以被再次使用,即沒有time_wait狀態了。
這個套接字選項通知內核,如果端口忙,但TCP狀態位於 TIME_WAIT ,可以重用端口。如果端口忙,而TCP狀態位於其他狀態,重用端口時依舊得到一個錯誤信息,指明"地址已經使用中"。如果你的服務程序停止後想立即重啓,而新套接字依舊使用同一端口,此時SO_REUSEADDR 選項非常有用。必須意識到,此時任何非期望數據到達,都可能導致服務程序反應混亂,不過這只是一種可能,事實上很不可能。
5:SO_LINGER,指定close函數對面向鏈接的協議如何操作,默認操作室close立即返回,
第12章:IPv4 與 IPv6 的互操作性
本章假設主機都運行着雙棧(dual stacks),指IPv4和IPv6協議棧。
IPv4客戶與IPv6服務器進行通信的步驟總結如下:
1)IPv6服務器啓動後創建一個IPv6的監聽套接字,我們假設服務器把統配地址捆綁到該套接字;
2)IPv4客戶調用gethostbyname找到服務器主機的一個A記錄;
3)客戶調用connect,導致客戶主機發送一個IPv4 SYN到服務器主機;
4)服務器主機接收這個目的地址爲IPv6監聽套接字的IPv4 SYN,設置一個標誌指示本鏈接應使用IPv4映射的IPv6地址,然後相應一個IPv4 SYN/ACK。該鏈接建立後,有accept返回給服務器的地址就是這個IPv4映射的IPv6地址。
5)當服務器主機往這個IPv4映射的IPv6地址發送TCP分節時,其IP棧產生目的地址爲IPv4地址的IPv4載送數據報。因此,客戶和服務器之間的所有通信都使用IPv4的載送數據報。
6)除非服務器顯示檢查這個IPv6地址是不是IPv4映射的IPv6地址,否則它永遠也不知道是在和IPv6服務器通信。
上述的前提假設是雙棧服務器既有一個IPv4地址,也有一個IPv6地址。
IPv6客戶與IPv4服務器進行通信的步驟總結如下:
1)一個IPv4服務器在只支持IPv4的主機上啓動後創建一個IPv4的監聽套接字;
2)IPv6客戶啓動後調用getadrinfo單純查找IPv6地址。既然只支持IPv4的那個服務器主機只有A記錄,那麼給客戶的是一個IPv4映射的IPv6地址;
3)IPv6客戶在作爲函數參數的IPv6套接字地址結構中設置這個IPv4映射的IPv6地址後調用connect。內核檢測到這個映射後 自動發送一個IPv4 SYN到服務器。
4)服務器相應一個IPv4 SYN/ACK,鏈接於是通過IPv4數據報建立。
第13章:守護進程和inetd超級服務器
p286
守護進程(daemon)是在後臺運行且不與任何控制終端關聯的進程。通常由系統初始化腳本啓動。當然也可以從某個終端由用戶在shell提示符下鍵入命令行,這樣的守護進程必須親自脫離於控制終端的關聯,從而避免與作業控制、終端會話管理、終端產生信號等發生任何不期望的交互,也可避免在後臺運行的守護進程非預期の輸出到終端,syslogd守護進程接受來自守護進程的消息。
守護進程有很多啓動方法:
1)在系統啓動階段由系統初始化腳本啓動,例如inetd超級服務器、Web服務器、郵件服務器(sendmail)、syslogd守護進程、cron守護進程;
2)許多網絡服務器由inetd啓動,inetd監聽網絡請求(Telnet、FTP等),每當有一個請求時,啓動相應的服務器;
3)cron守護進程會定期執行一些程序;
4)at命令用於指定將來某個時刻程序執行,當來時,由cron守護進程啓動;
5)還可以由用戶終端在前臺或後臺啓動,是爲了測試守護程序或重啓守護進程。
p289
由於在Linux中,每一個系統與用戶進行交流的界面稱爲終端,每一個從此終端開始運行的進程都會依附於這個終端,這個終端就稱爲這些進程的控制終端,當控制終端被關閉時,相應的進程都會自動關閉。但是守護進程卻能夠突破這種限制,它從被執行開始運轉,直到整個系統關閉時才退出。如果想讓某個進程不因爲用戶或終端或其他地變化而受到影響,那麼就必須把這個進程變成一個守護進程。
將普通進程轉換成守護進程(在linux有daemon函數)
掌握創建順序
Linux守護進程(http://blog.chinaunix.net/uid-21411227-id-1826736.html)
1)創建子進程,父進程推出:子進程會成爲孤兒進程,由init進程收養;
2)在子進程中創建新會話:由系統函數setsid完成,setsid函數作用:
setsid函數用於創建一個新的會話,並擔任該會話組的組長。調用setsid有下面的3個作用:
(1)讓進程擺脫原會話的控制
(2)讓進程擺脫原進程組的控制
(3)讓進程擺脫原控制終端的控制
在創建守護進程時爲什麼要調用setsid函數呢?由於創建守護進程的第一步調用了fork函數來創建子進程,再將父進程退出。由於在調用了fork函數時,子進程全盤拷貝了父進程的會話期、進程組、控制終端等,雖然父進程退出了,但會話期、進程組、控制終端等並沒有改變,因此,還還不是真正意義上的獨立開來,而setsid函數能夠使進程完全獨立出來,從而擺脫其他進程的控制。
進程組:是一個或多個進程的集合。進程組有進程組ID來唯一標識。除了進程號(PID)之外,進程組ID也是一個進程的必備屬性。每個進程組都有一個組長進程,其組長進程的進程號等於進程組ID。且該進程組ID不會因組長進程的退出而受到影響。
會話週期:會話期是一個或多個進程組的集合。通常,一個會話開始與用戶登錄,終止於用戶退出,在此期間該用戶運行的所有進程都屬於這個會話期。
3)改變當前目錄爲根目錄:由於在進程運行中,當前目錄所在的文件系統(如“/mnt/usb”)是不能卸載的,這對以後的使用會造成諸多的麻煩(比如系統由於某種原因要進入用戶模式)。當然,如有特殊需要,也可以把當前工作目錄換成其他的路徑,如/tmp。改變工作目錄的常見函數式chdir。
4)重設文件權限掩碼:把文件權限掩碼設置爲0,可以大大增強該守護進程的靈活性。設置文件權限掩碼的函數是umask。在這裏,通常的使用方法爲umask(0)。
5)關閉文件描述符:同文件權限碼一樣,用fork函數新建的子進程會從父進程那裏繼承一些已經打開了的文件。這些被打開的文件可能永遠不會被守護進程讀寫,但它們一樣消耗系統資源,而且可能導致所在的文件系統無法卸下。
這樣,一個簡單的守護進程就建立起來了。
例子:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#define MAXFILE 65535
int main(){
pid_t pc;
int i,fd,len;
char *buf="this is a Dameon\n";
len = strlen(buf);
pc = fork(); //第一步
if(pc<0){
printf("error fork\n");
exit(1);
}else if(PC>0)
exit(0);
setsid(); //第二步
chdir("/"); //第三步
umask(0); //第四步
for(i=0;i<MAXFILE;i++) //第五步
close(i);
//以下的daemon開始工作了
while(1){
if((fd=open("/tmp/dameon.log",O_CREAT|O_WRONLY|O_APPEND,0600))<0){
perror("open");
exit(1);
}
write(fd,buf,len+1);
close(fd);
sleep(10);
}
}
inetd守護進程
1)通過由inetd處理普通守護進程的大部分啓動細節以簡化守護進程的編寫。這麼一來每個服務器不再有調用deamon_init函數的必要;
2)單個進程(inetd)就能爲多個服務等待外來的客戶請求,以此取代每個服務一個進程的做法,這麼做減少系統總進程數。
p524
針對一個套接字使用信號驅動式I/O(SIGIO)要求進程執行以下3個步驟:
1)建立SIGIO信號的信號處理函數;
2)設置該套接字的屬主,通常使用fcntl的F_SETOWN命令設置;
3)開啓該套接字的信號驅動式I/O,通常通過使用fcntl的F_SETFL命令打開O_ASYNC標誌完成;
不幸的是,信號驅動式I/O對於TCP套接字近乎無用,問題在於該套接字產生得過於頻繁,並且它的出現沒有告訴我們發生了什麼。
唯一實例:基於UDP的NTP服務器程序,是爲了時間精確。
p534
同一進程內的所有線程共享:
1)全局變量;2)進程指令;3)大多數數據;4)打開的文件(文件描述符);
5)信號處理函數和信號處置;6)當前的工作目錄;7)用戶ID和組ID;
每個線程各自的:
1)線程ID;2)寄存器集合,包括pc和棧指針;3)棧(用於存放局部變量和返回地址);
4)錯誤符(errno);5)信號掩碼(umask);6)優先級;
線程終止的方法:
1)調用pthread_exit(void *status);
2)通過return;
3)如果進程的main函數或任何線程調用了exit,整個進程就終止(任意線程也是);
感覺有必要把這些過程好好的弄清楚。
p542
線程特定數據
暫時沒看
p552
互斥鎖(mutex, mutual exclusion)
#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mptr);
int pthread_mutex_unlock(pthread_mutex_t *mptr);
條件變量:提供信號機制,條件變量被用來進行線承間的同步,彌補了互斥鎖的不足,它常和互斥鎖一起使用。
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);
該函數把調用線程投入睡眠並釋放調用線程持有的互斥鎖。當返回時,該線程在此持有該互斥鎖。
int pthread_cond_signal(pthread_cond_t *cptr); 喚醒等在相應條件變量上的單個線程。
第30章:客戶/服務器程序設計範式【這個還沒總結】
WebStone、Webbench:服務器性能基準程序;
1)TCP迭代服務器程序;
2)TCP併發服務器程序,每個客戶一個子進程;
DNS輪詢:大多域名註冊商都支持多條A記錄的解析,其實這就是DNS輪詢,DNS服務器將解析請求按照A記錄的順序,逐一分配到不同的IP上,這樣就完成了簡單的負載均衡。
int main()
{
int listenfd, connfd;
pid_t childpid;
void sig_chld(int), sig_int(int), web_child(int);
socklen_t clilen, addrlen;
struct sockaddr* cliaddr;
if(argc == 2)
listenfd = Tcp_listen(NULL, argv[1], &addrlen);
else if(argc == 3)
listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
else
err_quit("usage:serv01 [<host>] <port#>");
cliaddr = Malloc(addrlen);
Signal(SIGCHLD, sig_chld);
Signal(SIGINT, sig_int);
for(;;){
clilen = addrlen;
if( (connfd = accept(listenfd, cliaddr, &clilen)) < 0){
if(errno == EINTR)
continue;
else
err_sys("accept error");
}
if( (childpid = Fork()) == 0){ // child process
Close(listenfd); // close listening socket
web_child(connfd); // process request
exit(0);
}
Close(connfd); //parent closes connected socket;
}
return 0;
}
long atol(const char *nptr);將字符串變爲long正數。
3)預先派生子進程。
(1)如果有多個進程阻塞在引用同一實體(例如socket套接字或普通文件,由file借個直接或間接描述)的描述符上,那麼最好直接阻塞在accept上而不是select(容易發生衝突);
在accept上也會發生錯誤,解決方法是在調用accept前後安置某種形式的鎖(lock),例如以fcntl函數呈現的POSIX文件上鎖功能。
(2)線程上鎖保護accept,不僅適用於同一進程內各線程之間的上鎖,而且適用於不同進程之間的上鎖。
不同進程之間使用線程上鎖要求:1:互斥鎖變量必須存放在共享內存中;2:必須告知線程函數庫這是在不同進程之間共享的互斥鎖。
(3)傳遞描述符,只讓父進程調用accept,然後讓所接受的已連接套接字“傳遞給”某個子進程,繞過了爲所有子進程的accept調用提供上鎖保護的可能需求,但是父進程必須跟蹤子進程的忙閒狀態,以便給空閒的子進程傳遞新的套接字。通過字節流管道(一對Unix域字節流套接字)來實現。
4)TCP併發服務器程序,每個客戶一個線程。
5)TCP預先穿件服務器程序,每個線程各自accept,我們可以用互斥鎖來保證每個時刻只有一個線程在調用accept。
6)TCP預先創建線程服務器程序,主線程統一accept,既然每個線程可以共享內存,那麼只需用線程鎖和條件變量就可以輕鬆實現啦。(這個比較贊!!!)
在第三部分,今天(2014年8月16日)大體看了下,還需要好好看。
這個我大致看了下,下面還要繼續看,加油。
%%%%%%%%%%%%%%%%%%%%%%《TCP/IP高效編程》%%%%%%%%%%%%%%%%%%%
p45
對於讀取socket定長數據的readn應該這麼寫
int readn(SOCKET fd, char* bp, size_t len){
int cnt;
int rc;
cnt = len;
while(cnt > 0){
rc = recv(fd, bp, cnt, 0);
if(rc < 0){
if(errno == EINTR) // interupeted
continue;
return -1;
}
if(rc == 0)
return len-cnt;
bp += rc;
cnt -= rc;
}
return len;
}