TCP/IP 网络编程 (三)
server端未處理高并發(fā)請求通常採用例如以下方式:
- 多進(jìn)程:通過創(chuàng)建多個進(jìn)程提供服務(wù)
- 多路復(fù)用:通過捆綁并統(tǒng)一管理 I/O 對象提供服務(wù)
- 多線程:通過生成和客戶端等量的線程提供服務(wù)
多進(jìn)程server端
#include <unistd.h>pid_t fork(); // 成功返回進(jìn)程 ID, 失敗返回-1fork函數(shù)將創(chuàng)建調(diào)用的函數(shù)副本。子進(jìn)程將使用新的內(nèi)存空間復(fù)制當(dāng)前函數(shù)的環(huán)境。
- 父進(jìn)程:函數(shù)返回子進(jìn)程ID
- 子進(jìn)程:函數(shù)返回 0
能夠理解為調(diào)用該函數(shù)之后將存在兩個pid_t,分別存在父子進(jìn)程中,因此它們將會依據(jù)不同的函數(shù)值運(yùn)行對應(yīng)的進(jìn)程代碼。
在調(diào)用函數(shù)前的全部變化都會在子進(jìn)程中保持一致。
調(diào)用函數(shù)之后的變化將不會影響彼此,由于它們?nèi)徊幌喔伞1M管能夠通過進(jìn)程間通信交換信息。
僵尸(zombie)進(jìn)程
向exit函數(shù)傳遞的參數(shù)值和return語句返回的值會傳遞給操作系統(tǒng)。
操作系統(tǒng)不會銷毀子進(jìn)程。而是將這些值傳遞給產(chǎn)生該子進(jìn)程的父進(jìn)程。處在這樣的狀態(tài)下的進(jìn)程就是僵尸進(jìn)程。
exit(0) 表示程序正常, exit(1)/exit(-1)表示程序異常退出;
exit() 結(jié)束當(dāng)前進(jìn)程/當(dāng)前程序/。在整個程序中,僅僅要調(diào)用 exit ,就結(jié)束.
exit(0):正常運(yùn)行程序并退出程序;
exit(1):非正常運(yùn)行導(dǎo)致退出程序;
return():返回函數(shù)。若在main主函數(shù)中,則會退出函數(shù)并返回一值。能夠?qū)憺閞eturn(0)。或return 0。
exit表示進(jìn)程終結(jié),無論是在哪個函數(shù)調(diào)用中。即使還存在被調(diào)函數(shù)。
return表示函數(shù)返回,假設(shè)是在主函數(shù)中意味著進(jìn)程的終結(jié)。假設(shè)不是在主函數(shù)中那么會返回到上一層函數(shù)調(diào)用。
具體說:
銷毀僵尸進(jìn)程
利用 wait 函數(shù)
#include <sys/wait.h>pid_t wait(int * statloc); // 成功返回終止的子進(jìn)程 ID。失敗返回 -1當(dāng)有子進(jìn)程終止時,子進(jìn)程終止時傳遞的返回值將保存在該函數(shù)參數(shù)所指內(nèi)存空間,參數(shù)指向的單元中還包括其它信息。須要使用宏進(jìn)行分離。
通過調(diào)用該函數(shù)之前終止的子進(jìn)程相關(guān)信息將保存在參數(shù)變量中,同一時候。相關(guān)子進(jìn)程被全然銷毀。調(diào)用是假設(shè)沒有已終止的進(jìn)程,那么程序?qū)氯钡接凶舆M(jìn)程終止。
- WIFEXITED: 子進(jìn)程正常終止時返回真
- WEXITSTATUS: 返回子進(jìn)程的返回值
也就是說。向wait函數(shù)傳遞變量status的地址時,調(diào)用wait函數(shù)后應(yīng)編寫例如以下代碼:
if(WIFWXITED(status)) {puts("Normal termination!");printf("Child pass num: %d", WEXITSTATUS(status)); }利用 waitpid 函數(shù)
#include <sys/wait.h>pid_t waitpid(pid_t pid, int * statloc, int options); // 同上~ pid: 等待終止的目標(biāo)子進(jìn)程的ID, 若傳遞-1,則等效于 wait, 能夠等待隨意子進(jìn)程終止 ~ statloc: 同上 ~ options: 傳遞常量 WNOHANG, 即使沒有終止的子進(jìn)程也不會進(jìn)入堵塞狀態(tài),而是返回0并退出代碼演示樣例:
#include <stdio.h> #include <unistd.h> #include <sys/wait.h>int main(int argc, char *argv[]) {int status;pid_t pid = fork();if(pid == 0){sleep(15);return 34;}else{while(!waitpid(-1, &status, WNOHANG)) //假設(shè)沒有進(jìn)程終止,就循環(huán)等待{sleep(3);puts("sleep 3 sec.");}if(WIFEXITED(status))printf("Child send %d \n", WEXITSTATUS(status));}return 0; }信號處理
子進(jìn)程終止的識別主題是操作系統(tǒng),因此在子進(jìn)程終止的時候由操作系統(tǒng)將這些信息通知忙碌的父進(jìn)程,父進(jìn)程停下手上的工作處理相關(guān)事宜。
為此,我們引入信號處理。此處的“信號”是指在特定時間發(fā)生時由操作系統(tǒng)向進(jìn)程發(fā)送的消息。為了響應(yīng)該消息,運(yùn)行與消息相關(guān)的自己定義操作的過程稱為“處理”或“信號處理”。
信號和 signal 函數(shù)
#include <signal.h>void (*signal(int signo, void (*func)(int)))(int); // 在產(chǎn)生信號時調(diào)用。返回之前注冊的函數(shù)指針發(fā)生第一個參數(shù)代表的情況時。調(diào)用第二個參數(shù)所指的情況。
第一個參數(shù)可能對應(yīng)的常數(shù):
- SIGALRM: 已到通過alarm函數(shù)注冊的時間
- SIGINT: 輸入CTRL + C
- SIGCHLD: 子進(jìn)程終止
代碼演示樣例:
#include <stdio.h> #include <unistd.h> #include <signal.h>void timeout(int sig) // 信號處理器 {if(sig == SIGALRM)puts("Time out!");alarm(2); }void keycontrol(int sig) {if(sig == SIGINT)puts("Ctrl + C Pressed"); }int main() {int i;signal(SIGALRM, timeout); // 注冊處理函數(shù)signal(SIGINT, keycontrol);alarm(2); // unistd.hfor(i=0; i < 3; i++){puts("wait ... ");sleep(100);}return 0; }利用 sigaction 處理信號
#include <signal.h>int sigaction(int signo, const struct sigaction * act, struct sigaction * oldact); // 成功返回0, 失敗返回-1~ signo: 傳遞信號信息 ~ act: 對應(yīng)于第一個參數(shù)的信號處理函數(shù)信息 ~ oldact: 通過此參數(shù)獲取之前注冊注冊的信號處理函數(shù)指針。不須要則傳遞0 struct sigaction {void (*sa_handler)(int); // 信號處理的函數(shù)指針sigset_s sa_mask; // 用于指定信號相關(guān)的選項和特性int sa_flags; }使用上和之前的signal沒有明顯差別。
struct sigaction act; act.sa_handler=timeout; sigemptyset(&act.sa_mask); act.sa_flags=0; sigaction(SIGALRM, &act, 0); ...利用信號處理技術(shù)消滅僵尸進(jìn)程
代碼演示樣例:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h>void read_childproc(int sig) {int status;pid_t id = waitpid(-1, &status, WNOHANG);if(WIFEXITED(status)){printf("Remove proc id : %d \n", id);printf("Child send : %d \n", WEXITSTATUS(status));} }int main(int argc, char * argv[]) {pid_t pid;struct sigaction act;act.sa_handler = read_childproc;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, 0);pid = fork();if(pid == 0){puts("Hi! I'am child proc");sleep(10);return 12;}else{printf("Child proc id : %d \n", pid);pid = fork();if(pid == 0){puts("Hi! I'am child proc");sleep(10);exit(24);}else{int i;printf("Child proc id : %d \n", pid);for(i=0; i < 5; i++){puts("wait ... ");sleep(5);}}}return 0; }為了等待SIGCHLD信號,父進(jìn)程共暫停5次,每次間隔5秒。
發(fā)生信號時。父進(jìn)程將被喚醒,因此實(shí)際暫停不到25秒。
基于多任務(wù)的并發(fā)server
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <arpa/inet.h> #include <sys/socket.h>#define BUF_SIZE 30 void errorhandling(char *message); void read_childproc(int sig);int main(int argc, char *argv[]) {int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;pid_t pid;struct sigaction act;socklen_t adr_sz;int str_len, state;char buf[BUF_SIZE];if(argc != 2){printf("Usage : %s <port> \n", argv[0]);exit(1);}act.sa_handler = read_childproc;sigemptyset(&act.sa_mask);act.sa_flags = 0;state = sigaction(SIGCHLD, &act, 0);serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)errorhandling("bind() error");puts("bind sucess");if(listen(serv_sock, 5) == -1)errorhandling("listen() error");puts("listen success");while(1){adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);if(clnt_sock == -1)continue;elseputs("new client connected ..");pid = fork();if(pid == -1){puts("pid = -1");close(clnt_sock);continue;}if(pid == 0){puts("child proc.");close(serv_sock);while((str_len = read(clnt_sock, buf, BUF_SIZE)) != 0)write(clnt_sock, buf, str_len);close(clnt_sock);puts("client disconnected ..");return 0;}elseclose(clnt_sock);}close(serv_sock);return 0;}void read_childproc(int sig){pid_t pid;int status;pid = waitpid(-1, &status, WNOHANG);printf("remove proc id : %d \n", pid);}void errorhandling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);}通過 fork 函數(shù)拷貝文件描寫敘述符
上述演示樣例中父進(jìn)程將兩個文件描寫敘述符(server套接字和客戶端套接字)復(fù)制給子進(jìn)程。
實(shí)際上僅僅是復(fù)制了文件描寫敘述符,沒有復(fù)制套接字。
由于套接字并不是進(jìn)程全部—嚴(yán)格來說,套接字屬于操作系統(tǒng)—僅僅是進(jìn)程擁有代表對應(yīng)套接字的文件描寫敘述符。
如上圖所看到的,僅僅有兩個文件描寫敘述符都終止后才干銷毀套接字。即使子進(jìn)程銷毀了與客戶端的套接字文件描寫敘述符也不能全然銷毀套接字。因此,調(diào)用fork函數(shù)之后,要將無關(guān)的套接字文件描寫敘述符關(guān)掉。例如以下圖所看到的:
切割 TCP 的 I/O 程序
切割模型例如以下:
在客戶端中將讀寫分離。這樣就不用再寫之前等待讀操作的完畢。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>#define BUF_SIZE 30 void error_handling(char * message); void read_routine(int sock, char *message); void write_routine(int sock, char *message);int main(int argc, char *argv[]) {int sock;pid_t pid;char buf[BUF_SIZE];struct sockaddr_in serv_adr;if(argc != 3){printf("Usage : %s <IP> <port> \n", argv[0]);exit(1);}sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = inet_addr(argv[1]);serv_adr.sin_port = htons(atoi(argv[2]));if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)error_handling("connect() error ");pid = fork();if(pid == 0)write_routine(sock, buf);elseread_routine(sock, buf);close(sock);return 0; }void read_routine(int sock, char *buf) {while(1){int str_len = read(sock, buf, BUF_SIZE);if(str_len == 0)return ;buf[str_len] = 0;printf("Message from server : %s", buf);} }void write_routine(int sock, char *buf) {while(1){fgets(buf, BUF_SIZE, stdin);if(!strcmp(buf, "Q\n")){shutdown(sock, SHUT_WR);return;}write(sock, buf, strlen(buf));} }void error_handling(char * message) {fputs(message, stderr);fputc('\n', stderr);exit(1); }進(jìn)程間通信
通過管道實(shí)現(xiàn)進(jìn)程間通信
管道屬于操作系統(tǒng)資源,因此。兩個進(jìn)程通過操作系統(tǒng)提供的內(nèi)存空間進(jìn)行通信。以下是創(chuàng)建管道的函數(shù):
#include <unistd.h>int pipe(int filedes[2]) // 成功返回0, 失敗返回-1~ filedes[0]: 通過管道接收數(shù)據(jù)時使用的文件描寫敘述符 ~ filedes[1]: 通過管道發(fā)送數(shù)據(jù)時使用的文件描寫敘述符演示樣例代碼:
#include <stdio.h> #include <unistd.h> #define BUF_SIZE 30int main(int argc, char *argv[]) {int fds[2];char str[] = "Who are you?";char buf[BUF_SIZE];pid_t pid;pipe(fds);printf("fds[0] : %d, fsds1[1] : %d\n", fds[0], fds[1]);pid = fork();if(pid == 0){printf("child proc : %d \n", pid);read(fds[0], buf, BUF_SIZE);puts(buf);}else{printf("parent proc : %d \n", pid);write(fds[1], str, sizeof(str));}return 0; }上述代碼的示比例如以下:
通過管道進(jìn)行進(jìn)程間的雙向通信
模型例如以下:
這里有一個問題,“向管道中傳遞數(shù)據(jù)的時候。先讀的進(jìn)程會把數(shù)據(jù)讀走”。
簡而言之,數(shù)據(jù)進(jìn)入到管道中就成為無主數(shù)據(jù)。無論誰先讀取數(shù)據(jù)都能夠?qū)?shù)據(jù)讀走。
綜上所述,使用一個管道實(shí)現(xiàn)雙向通道并不是易事。由于須要預(yù)測并控制通信流,這是不現(xiàn)實(shí)的。因此能夠使用兩個管道實(shí)現(xiàn)雙向通信。模型例如以下:
#include <stdio.h> #include <unistd.h> #define BUF_SIZE 30int main(int argc, char *argv[]) {int fds1[2], fds2[2];char str1[] = "Who are you?"
; char str2[] = "Thank you!"; char buf[BUF_SIZE]; pid_t pid; pipe(fds1); pipe(fds2); printf("fds1[0] : %d, fds1[1] : %d\n", fds1[0], fds1[1]); printf("fds2[0] : %d, fds2[1] : %d\n", fds2[0], fds2[1]); pid = fork(); if(pid == 0) { printf("child proc : %d \n", pid); write(fds1[1], str1, sizeof(str1)); read(fds2[0], buf, BUF_SIZE); printf("Child proc output: %s \n", buf); } else { printf("parent proc : %d \n", pid); read(fds1[0], buf, BUF_SIZE); printf("Parent proc output: %s \n", buf); write(fds2[1], str2, sizeof(str2)); sleep(3); } return 0; }運(yùn)用進(jìn)程間通信
代碼演示樣例:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <arpa/inet.h> #include <sys/socket.h>#define BUF_SIZE 30 void errorhandling(char *message); void read_childproc(int sig);int main(int argc, char *argv[]) {int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;pid_t pid;int fds[2];struct sigaction act;socklen_t adr_sz;int str_len, state;char buf[BUF_SIZE];if(argc != 2){printf("Usage : %s <port> \n", argv[0]);exit(1);}act.sa_handler = read_childproc;sigemptyset(&act.sa_mask);act.sa_flags = 0;state = sigaction(SIGCHLD, &act, 0);serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)errorhandling("bind() error");puts("bind sucess");if(listen(serv_sock, 5) == -1)errorhandling("listen() error");pipe(fds);pid = fork();if(pid == 0){FILE * fp = fopen("echomsg.txt", "wt");char msgbuf[BUF_SIZE];int i, len;for(i=0; i<10; i++){len = read(fds[0], msgbuf, BUF_SIZE);fwrite((void *)msgbuf, 1, len, fp);}fclose(fp);return 0;}while(1){adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);if(clnt_sock == -1)continue;elseputs("new client connected ..");pid = fork();if(pid == 0){puts("child proc.");close(serv_sock);while((str_len = read(clnt_sock, buf, BUF_SIZE)) != 0){write(clnt_sock, buf, str_len);write(fds[1], buf, str_len);}close(clnt_sock);puts("client disconnected ..");return 0;}elseclose(clnt_sock);}close(serv_sock);return 0; }void read_childproc(int sig){pid_t pid;int status;pid = waitpid(-1, &status, WNOHANG);printf("remove proc id : %d \n", pid);}void errorhandling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);}上述代碼涉及到的模型例如以下:
I/O 復(fù)用
這里我們討論使用 I/O 復(fù)用解決每一個客戶端請求都創(chuàng)建進(jìn)程的資源消耗弊端。
運(yùn)用select函數(shù)是最具有代表性的實(shí)現(xiàn)復(fù)用server端方法。使用該函數(shù)的時候能夠?qū)⒍鄠€文件描寫敘述符集中到一起進(jìn)行監(jiān)視。
- 是否存在套接字接收數(shù)據(jù)?
- 無需堵塞數(shù)據(jù)傳輸?shù)奶捉幼钟心男?#xff1f;
- 哪些套接字發(fā)生了異常?
我們將監(jiān)視項稱為事件。發(fā)生了監(jiān)視項對應(yīng)的情況時,稱“發(fā)生了事件”。
select函數(shù)非常難使用,可是為了實(shí)現(xiàn)I/O復(fù)用server端,我們應(yīng)該掌握該函數(shù)。并運(yùn)用到套接字編程中。覺得“select函數(shù)時I/O復(fù)用的全部內(nèi)容”并不為過。
設(shè)置文件描寫敘述符
將要監(jiān)視的套接字集合在一起,集中式也要依照監(jiān)視項(接收、傳輸、異常)進(jìn)行區(qū)分。使用fd_set數(shù)組變量運(yùn)行此項操作。
在數(shù)組中注冊或者更改值的操作應(yīng)該由下列宏完畢:
FD_ZERO(fd_set * fdset): 全部位初始化為0 FD_SET(int fd, fd_set * fdset): 注冊文件描寫敘述符fd 的信息 FD_CLR(int fd, fd_set * fdset): 清楚文件描寫敘述符的信息 FD_ISSET(int fd, fd_set * fdset): 是否監(jiān)視上述定義可在以下直觀看到效果:
設(shè)置監(jiān)視范圍及超時
#include <sys/select.h> #include <sys/time.h>int select(int maxfd, fd_set * readset, fd_set * writeset, fd_set * excepyset, const struct timeval * timeout); // 成功時返回大于0的值。 失敗返回-1~ maxfd: 監(jiān)視對象文件描寫敘述符的數(shù)量 ~ readset: 是否存在待讀取數(shù)據(jù) ~ writeset: 是否可傳輸無堵塞數(shù)據(jù) ~ exceptset: 是否發(fā)生異常 ~ timeout: 調(diào)用該函數(shù)之后,為防止陷入無限堵塞的狀態(tài),傳遞超時信息 返回值: 錯誤發(fā)生返回-1, 超時返回0,因發(fā)生關(guān)注的事件返回時。返回大于0的值,該值是發(fā)生事件的文件描寫敘述符數(shù)“文件描寫敘述符的監(jiān)視范圍?”
函數(shù)要求通過第一個參數(shù)傳遞監(jiān)視對象文件描寫敘述符的數(shù)量。因此須要得到注冊在fdset變量中的文件描寫敘述符數(shù)。
但每次新建文件描寫敘述符時。其值都會加1,故將最大的文件描寫敘述符加1再傳遞到函數(shù)就可以。加1是由于文件描寫敘述符的值從0開始。
“怎樣設(shè)置超時時間?”
struct timeval {long tv_sec; // secondslong tv_usec; // microseconds }函數(shù)僅僅有在監(jiān)視的文件描寫敘述符發(fā)生變化時才返回。假設(shè)未發(fā)生變化就會進(jìn)入堵塞狀態(tài)。指定超時時間就是為了防止這樣的情況的發(fā)生。
即使文件描寫敘述符未發(fā)生變化,僅僅要到了指定時間函數(shù)也會返回。當(dāng)然,返回值是0。假設(shè)不設(shè)置超時,傳遞NULL就可以。
調(diào)用函數(shù)后查看結(jié)果
假設(shè)函數(shù)返回值大于0,說明對應(yīng)數(shù)量的文件描寫敘述符發(fā)生變化。
文件描寫敘述符變化是指監(jiān)視的文件描寫敘述符發(fā)生了對應(yīng)的監(jiān)視事件
那么,怎樣得知哪些文件描寫敘述符發(fā)生了變化呢?
向函數(shù)傳遞的第二個到第四個參數(shù)傳遞的fd_set變量將發(fā)生例如以下圖所看到的的變化:
函數(shù)調(diào)用之后,向其傳遞的fd_set變量將發(fā)生變化,原來為1的全部位將變成0,但發(fā)生變化的位除外。換句話說就是。調(diào)用之后,發(fā)生變化的文件描寫敘述符的位將為1。
#include <stdio.h> #include <unistd.h> #include <sys/time.h> #include <sys/select.h> #define BUF_SIZE 30int main(int argc, char *argv[]) {fd_set reads, temps;int result, str_len;char buf[BUF_SIZE];struct timeval timeout;FD_ZERO(&reads);FD_SET(0, &reads);while(1){temps = reads;timeout.tv_sec = 5;timeout.tv_usec = 0;result = select(1, &temps, 0, 0, &timeout);if(result == -1){puts("select() error");break;}else if(result == 0){puts("Time out");}else{if(FD_ISSET(0, &temps)){str_len = read(0, buf, BUF_SIZE);buf[str_len] = 0;printf("message from console : %s", buf);}}}return 0; }實(shí)現(xiàn)I/O復(fù)用server端
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/select.h>#define BUF_SIZE 100 void errorhandling(char *message);int main(int argc, char *argv[]) {int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;struct timeval timeout;fd_set reads, cpy_reads;socklen_t adr_sz;int str_len, fd_max, fd_num, i;char buf[BUF_SIZE];if(argc != 2){printf("Usage : %s <port> \n", argv[0]);exit(1);}serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)errorhandling("bind() error");puts("bind sucess");if(listen(serv_sock, 5) == -1)errorhandling("listen() error");puts("listen success");FD_ZERO(&reads);FD_SET(serv_sock, &reads);fd_max = serv_sock;while(1){cpy_reads = reads;timeout.tv_sec = 5;timeout.tv_usec = 5000;if((fd_num = select(fd_max+1, &cpy_reads, 0, 0, &timeout)) == -1){errorhandling("select() error");break;}if(fd_num == 0){puts("Time out");continue;}for(i=0; i < fd_max + 1; i++){if(FD_ISSET(i, &cpy_reads)){if( i == serv_sock) //continue request{adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);FD_SET(clnt_sock, &reads);if(fd_max < clnt_sock)fd_max = clnt_sock;printf("connected client : %d \n", clnt_sock);}else // read message{str_len = read(i, buf, BUF_SIZE);if(str_len == 0) // close request{FD_CLR(i, &reads);close(i);printf("closed client : %d \n", i);}else{write(i, buf, str_len); //echo}}}}}close(serv_sock);return 0; }void errorhandling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);}多種 I/O 函數(shù)
Linux 中的 send & recv
#include <sys/socket.h>ssize_t send(int sockfd, const void * buf, size_t nbytes, int flags); // 成功返回發(fā)送的字節(jié)數(shù)。 失敗返回-1 #include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);MSG_OOB: 發(fā)送緊急數(shù)據(jù)
帶外數(shù)據(jù)概念實(shí)際上是向接收端傳送三個不同的信息:
本通知在發(fā)送進(jìn)程發(fā)送帶外字節(jié)后由發(fā)送端TCP馬上發(fā)送。即使往接收端的不論什么數(shù)據(jù)發(fā)送因流量控制而停止了。TCP仍然發(fā)送本通知。
本通知可能導(dǎo)致接收端進(jìn)入某種特殊處理模式,以處理接收的不論什么后繼數(shù)據(jù)。
既然TCP是一個不解釋應(yīng)用進(jìn)程所發(fā)送數(shù)據(jù)的字節(jié)流協(xié)議。帶外字節(jié)就能夠是不論什么8位值。
對于TCP的緊急模式。我們能夠覺得URG標(biāo)志時通知(信息1),緊急指針是帶外標(biāo)記(信息2),數(shù)據(jù)字節(jié)是其本身(信息3)。
與這個帶外數(shù)據(jù)概念相關(guān)的問題有:
- 每一個連接僅僅有一個TCP緊急指針;
- 每一個連接僅僅有一個帶外標(biāo)記;
- 每一個連接僅僅有一個單字節(jié)的帶外緩沖區(qū)(該緩沖區(qū)僅僅有在數(shù)據(jù)非在線讀入時才需考慮)。假設(shè)帶外數(shù)據(jù)時在線讀入的,那么當(dāng)心的帶外數(shù)據(jù)到達(dá)時。先前的帶外字節(jié)字節(jié)并未丟失,只是他們的標(biāo)記卻因此被新的標(biāo)記代替而丟失了。
帶外數(shù)據(jù)的一個常見用途體如今rlogin程序中。當(dāng)客戶中斷運(yùn)行在server主機(jī)上的程序時。server須要告知客戶丟棄全部已在server排隊的輸出,由于已經(jīng)排隊等著從server發(fā)送到客戶的輸出最多有一個窗體的大小。
server向客戶發(fā)送一個特殊字節(jié)。告知后者清刷全部這些輸出(在客戶看來是輸入),這個特殊字節(jié)就作為帶外數(shù)據(jù)發(fā)送。
客戶收到由帶外數(shù)據(jù)引發(fā)的SIGURG信號后。就從套接字中讀入直到碰到帶外數(shù)據(jù)。
客戶收到由帶外數(shù)據(jù)引發(fā)的SIGURG信號后,就從套接字中讀入直到碰到帶外標(biāo)記,并丟棄到標(biāo)記之前的全部數(shù)據(jù)。
這樣的情形下即使server相繼地高速發(fā)送多個帶外字節(jié),客戶也不受影響。由于客戶僅僅是讀到最后一個標(biāo)記為止,并丟棄全部讀入的數(shù)據(jù)。
總之。帶外數(shù)據(jù)是否實(shí)用取決于應(yīng)用程序使用它的目的。假設(shè)目的是告知對端丟棄直到標(biāo)記處得普通數(shù)據(jù),那么丟失一個中間帶外字節(jié)及其對應(yīng)的標(biāo)記不會有什么不良后果。可是假設(shè)不丟失帶外字節(jié)本身非常重要,那么必須在線收到這些數(shù)據(jù)。另外。作為帶外數(shù)據(jù)發(fā)送的數(shù)據(jù)字節(jié)應(yīng)該差別于普通數(shù)據(jù),由于當(dāng)前新的標(biāo)記到達(dá)時,中間的標(biāo)記將被覆寫,從而其實(shí)把帶外字節(jié)混雜在普通數(shù)據(jù)之中。
舉例來說,telnet在客戶和server之間普通的數(shù)據(jù)流中發(fā)送telnet自己的命令。手段是把值為255的一個字節(jié)作為telnet命令的前綴字節(jié)。(值為255的單個字節(jié)作為數(shù)據(jù)發(fā)送須要2個相繼地值為255的字節(jié)。
)這么做使得telnet能夠區(qū)分其命令和普通用戶數(shù)據(jù),只是要求客戶進(jìn)程和server進(jìn)程處理每一個數(shù)據(jù)字節(jié)以尋找命令。
除緊急指針(URG指針)指向的一個字節(jié)外,數(shù)據(jù)接收方將通過調(diào)用經(jīng)常使用輸入函數(shù)讀取剩余部分。
檢查輸入緩沖
設(shè)置MSG_PEEK選項并調(diào)用recv函數(shù)之后,即使讀取了輸入緩沖的數(shù)據(jù)也不會刪除。因此,該選項通常與MSG_DONTWAIT合作。用于調(diào)用非堵塞方式驗(yàn)證待讀取數(shù)據(jù)存在與否。
readv & writev 函數(shù)
對數(shù)據(jù)進(jìn)行整合傳輸及發(fā)送的函數(shù)
通過writev函數(shù)能夠?qū)⒎稚⒈4嬖诙鄠€緩沖中的數(shù)據(jù)一并發(fā)送。適當(dāng)使用這兩個函數(shù)能夠降低I/O函數(shù)的調(diào)用次數(shù)。
#include <sys/uio.h>ssize_t writev(int filedes, const struct iovec * iov, int iovcnt); ~ filedes: 數(shù)據(jù)傳輸對象的套接字文件描寫敘述符 ~ iov: iovec結(jié)構(gòu)體數(shù)組的地址,結(jié)構(gòu)體中包括待發(fā)送數(shù)據(jù)的位置和大小信息 ~ iovcnt: 第二個參數(shù)的數(shù)組長度 struct iovec {void * iov_base; // 緩沖地址size_t iov_len; // 緩沖大小 }關(guān)系模型例如以下:
#include <stdio.h> #include <sys/uio.h>int main() {struct iovec vec[2];char buf1[] = "ABCDEFG";char buf2[] = "1234567";int str_len;vec[0].iov_base = buf1;vec[0].iov_len = 3;vec[1].iov_base = buf2;vec[1].iov_len = 4;str_len = writev(1, vec, 2);puts("");printf("Writen bytes : %d \n", str_len);return 0; } #include <sys/uio.h>ssize_t readv(int filedes, const struct iovec * iov, int iovcnt); readv.c總結(jié)
以上是生活随笔為你收集整理的TCP/IP 网络编程 (三)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python浓缩(14)执行环境
- 下一篇: mactex中文配置