TCP/IP 网络编程 (三)
server端未處理高并發請求通常採用例如以下方式:
- 多進程:通過創建多個進程提供服務
- 多路復用:通過捆綁并統一管理 I/O 對象提供服務
- 多線程:通過生成和客戶端等量的線程提供服務
多進程server端
#include <unistd.h>pid_t fork(); // 成功返回進程 ID, 失敗返回-1fork函數將創建調用的函數副本。子進程將使用新的內存空間復制當前函數的環境。
- 父進程:函數返回子進程ID
- 子進程:函數返回 0
能夠理解為調用該函數之后將存在兩個pid_t,分別存在父子進程中,因此它們將會依據不同的函數值運行對應的進程代碼。
在調用函數前的全部變化都會在子進程中保持一致。
調用函數之后的變化將不會影響彼此,由于它們全然不相干。盡管能夠通過進程間通信交換信息。
僵尸(zombie)進程
向exit函數傳遞的參數值和return語句返回的值會傳遞給操作系統。
操作系統不會銷毀子進程。而是將這些值傳遞給產生該子進程的父進程。處在這樣的狀態下的進程就是僵尸進程。
exit(0) 表示程序正常, exit(1)/exit(-1)表示程序異常退出;
exit() 結束當前進程/當前程序/。在整個程序中,僅僅要調用 exit ,就結束.
exit(0):正常運行程序并退出程序;
exit(1):非正常運行導致退出程序;
return():返回函數。若在main主函數中,則會退出函數并返回一值。能夠寫為return(0)。或return 0。
exit表示進程終結,無論是在哪個函數調用中。即使還存在被調函數。
return表示函數返回,假設是在主函數中意味著進程的終結。假設不是在主函數中那么會返回到上一層函數調用。
具體說:
銷毀僵尸進程
利用 wait 函數
#include <sys/wait.h>pid_t wait(int * statloc); // 成功返回終止的子進程 ID。失敗返回 -1當有子進程終止時,子進程終止時傳遞的返回值將保存在該函數參數所指內存空間,參數指向的單元中還包括其它信息。須要使用宏進行分離。
通過調用該函數之前終止的子進程相關信息將保存在參數變量中,同一時候。相關子進程被全然銷毀。調用是假設沒有已終止的進程,那么程序將會堵塞直到有子進程終止。
- WIFEXITED: 子進程正常終止時返回真
- WEXITSTATUS: 返回子進程的返回值
也就是說。向wait函數傳遞變量status的地址時,調用wait函數后應編寫例如以下代碼:
if(WIFWXITED(status)) {puts("Normal termination!");printf("Child pass num: %d", WEXITSTATUS(status)); }利用 waitpid 函數
#include <sys/wait.h>pid_t waitpid(pid_t pid, int * statloc, int options); // 同上~ pid: 等待終止的目標子進程的ID, 若傳遞-1,則等效于 wait, 能夠等待隨意子進程終止 ~ statloc: 同上 ~ options: 傳遞常量 WNOHANG, 即使沒有終止的子進程也不會進入堵塞狀態,而是返回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)) //假設沒有進程終止,就循環等待{sleep(3);puts("sleep 3 sec.");}if(WIFEXITED(status))printf("Child send %d \n", WEXITSTATUS(status));}return 0; }信號處理
子進程終止的識別主題是操作系統,因此在子進程終止的時候由操作系統將這些信息通知忙碌的父進程,父進程停下手上的工作處理相關事宜。
為此,我們引入信號處理。此處的“信號”是指在特定時間發生時由操作系統向進程發送的消息。為了響應該消息,運行與消息相關的自己定義操作的過程稱為“處理”或“信號處理”。
信號和 signal 函數
#include <signal.h>void (*signal(int signo, void (*func)(int)))(int); // 在產生信號時調用。返回之前注冊的函數指針發生第一個參數代表的情況時。調用第二個參數所指的情況。
第一個參數可能對應的常數:
- SIGALRM: 已到通過alarm函數注冊的時間
- SIGINT: 輸入CTRL + C
- SIGCHLD: 子進程終止
代碼演示樣例:
#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); // 注冊處理函數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: 對應于第一個參數的信號處理函數信息 ~ oldact: 通過此參數獲取之前注冊注冊的信號處理函數指針。不須要則傳遞0 struct sigaction {void (*sa_handler)(int); // 信號處理的函數指針sigset_s sa_mask; // 用于指定信號相關的選項和特性int sa_flags; }使用上和之前的signal沒有明顯差別。
struct sigaction act; act.sa_handler=timeout; sigemptyset(&act.sa_mask); act.sa_flags=0; sigaction(SIGALRM, &act, 0); ...利用信號處理技術消滅僵尸進程
代碼演示樣例:
#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信號,父進程共暫停5次,每次間隔5秒。
發生信號時。父進程將被喚醒,因此實際暫停不到25秒。
基于多任務的并發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 函數拷貝文件描寫敘述符
上述演示樣例中父進程將兩個文件描寫敘述符(server套接字和客戶端套接字)復制給子進程。
實際上僅僅是復制了文件描寫敘述符,沒有復制套接字。
由于套接字并不是進程全部—嚴格來說,套接字屬于操作系統—僅僅是進程擁有代表對應套接字的文件描寫敘述符。
如上圖所看到的,僅僅有兩個文件描寫敘述符都終止后才干銷毀套接字。即使子進程銷毀了與客戶端的套接字文件描寫敘述符也不能全然銷毀套接字。因此,調用fork函數之后,要將無關的套接字文件描寫敘述符關掉。例如以下圖所看到的:
切割 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); }進程間通信
通過管道實現進程間通信
管道屬于操作系統資源,因此。兩個進程通過操作系統提供的內存空間進行通信。以下是創建管道的函數:
#include <unistd.h>int pipe(int filedes[2]) // 成功返回0, 失敗返回-1~ filedes[0]: 通過管道接收數據時使用的文件描寫敘述符 ~ filedes[1]: 通過管道發送數據時使用的文件描寫敘述符演示樣例代碼:
#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; }上述代碼的示比例如以下:
通過管道進行進程間的雙向通信
模型例如以下:
這里有一個問題,“向管道中傳遞數據的時候。先讀的進程會把數據讀走”。
簡而言之,數據進入到管道中就成為無主數據。無論誰先讀取數據都能夠將數據讀走。
綜上所述,使用一個管道實現雙向通道并不是易事。由于須要預測并控制通信流,這是不現實的。因此能夠使用兩個管道實現雙向通信。模型例如以下:
#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; }運用進程間通信
代碼演示樣例:
#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 復用
這里我們討論使用 I/O 復用解決每一個客戶端請求都創建進程的資源消耗弊端。
運用select函數是最具有代表性的實現復用server端方法。使用該函數的時候能夠將多個文件描寫敘述符集中到一起進行監視。
- 是否存在套接字接收數據?
- 無需堵塞數據傳輸的套接字有哪些?
- 哪些套接字發生了異常?
我們將監視項稱為事件。發生了監視項對應的情況時,稱“發生了事件”。
select函數非常難使用,可是為了實現I/O復用server端,我們應該掌握該函數。并運用到套接字編程中。覺得“select函數時I/O復用的全部內容”并不為過。
設置文件描寫敘述符
將要監視的套接字集合在一起,集中式也要依照監視項(接收、傳輸、異常)進行區分。使用fd_set數組變量運行此項操作。
在數組中注冊或者更改值的操作應該由下列宏完畢:
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): 是否監視上述定義可在以下直觀看到效果:
設置監視范圍及超時
#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: 監視對象文件描寫敘述符的數量 ~ readset: 是否存在待讀取數據 ~ writeset: 是否可傳輸無堵塞數據 ~ exceptset: 是否發生異常 ~ timeout: 調用該函數之后,為防止陷入無限堵塞的狀態,傳遞超時信息 返回值: 錯誤發生返回-1, 超時返回0,因發生關注的事件返回時。返回大于0的值,該值是發生事件的文件描寫敘述符數“文件描寫敘述符的監視范圍?”
函數要求通過第一個參數傳遞監視對象文件描寫敘述符的數量。因此須要得到注冊在fdset變量中的文件描寫敘述符數。
但每次新建文件描寫敘述符時。其值都會加1,故將最大的文件描寫敘述符加1再傳遞到函數就可以。加1是由于文件描寫敘述符的值從0開始。
“怎樣設置超時時間?”
struct timeval {long tv_sec; // secondslong tv_usec; // microseconds }函數僅僅有在監視的文件描寫敘述符發生變化時才返回。假設未發生變化就會進入堵塞狀態。指定超時時間就是為了防止這樣的情況的發生。
即使文件描寫敘述符未發生變化,僅僅要到了指定時間函數也會返回。當然,返回值是0。假設不設置超時,傳遞NULL就可以。
調用函數后查看結果
假設函數返回值大于0,說明對應數量的文件描寫敘述符發生變化。
文件描寫敘述符變化是指監視的文件描寫敘述符發生了對應的監視事件
那么,怎樣得知哪些文件描寫敘述符發生了變化呢?
向函數傳遞的第二個到第四個參數傳遞的fd_set變量將發生例如以下圖所看到的的變化:
函數調用之后,向其傳遞的fd_set變量將發生變化,原來為1的全部位將變成0,但發生變化的位除外。換句話說就是。調用之后,發生變化的文件描寫敘述符的位將為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; }實現I/O復用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 函數
Linux 中的 send & recv
#include <sys/socket.h>ssize_t send(int sockfd, const void * buf, size_t nbytes, int flags); // 成功返回發送的字節數。 失敗返回-1 #include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);MSG_OOB: 發送緊急數據
帶外數據概念實際上是向接收端傳送三個不同的信息:
本通知在發送進程發送帶外字節后由發送端TCP馬上發送。即使往接收端的不論什么數據發送因流量控制而停止了。TCP仍然發送本通知。
本通知可能導致接收端進入某種特殊處理模式,以處理接收的不論什么后繼數據。
既然TCP是一個不解釋應用進程所發送數據的字節流協議。帶外字節就能夠是不論什么8位值。
對于TCP的緊急模式。我們能夠覺得URG標志時通知(信息1),緊急指針是帶外標記(信息2),數據字節是其本身(信息3)。
與這個帶外數據概念相關的問題有:
- 每一個連接僅僅有一個TCP緊急指針;
- 每一個連接僅僅有一個帶外標記;
- 每一個連接僅僅有一個單字節的帶外緩沖區(該緩沖區僅僅有在數據非在線讀入時才需考慮)。假設帶外數據時在線讀入的,那么當心的帶外數據到達時。先前的帶外字節字節并未丟失,只是他們的標記卻因此被新的標記代替而丟失了。
帶外數據的一個常見用途體如今rlogin程序中。當客戶中斷運行在server主機上的程序時。server須要告知客戶丟棄全部已在server排隊的輸出,由于已經排隊等著從server發送到客戶的輸出最多有一個窗體的大小。
server向客戶發送一個特殊字節。告知后者清刷全部這些輸出(在客戶看來是輸入),這個特殊字節就作為帶外數據發送。
客戶收到由帶外數據引發的SIGURG信號后。就從套接字中讀入直到碰到帶外數據。
客戶收到由帶外數據引發的SIGURG信號后,就從套接字中讀入直到碰到帶外標記,并丟棄到標記之前的全部數據。
這樣的情形下即使server相繼地高速發送多個帶外字節,客戶也不受影響。由于客戶僅僅是讀到最后一個標記為止,并丟棄全部讀入的數據。
總之。帶外數據是否實用取決于應用程序使用它的目的。假設目的是告知對端丟棄直到標記處得普通數據,那么丟失一個中間帶外字節及其對應的標記不會有什么不良后果。可是假設不丟失帶外字節本身非常重要,那么必須在線收到這些數據。另外。作為帶外數據發送的數據字節應該差別于普通數據,由于當前新的標記到達時,中間的標記將被覆寫,從而其實把帶外字節混雜在普通數據之中。
舉例來說,telnet在客戶和server之間普通的數據流中發送telnet自己的命令。手段是把值為255的一個字節作為telnet命令的前綴字節。(值為255的單個字節作為數據發送須要2個相繼地值為255的字節。
)這么做使得telnet能夠區分其命令和普通用戶數據,只是要求客戶進程和server進程處理每一個數據字節以尋找命令。
除緊急指針(URG指針)指向的一個字節外,數據接收方將通過調用經常使用輸入函數讀取剩余部分。
檢查輸入緩沖
設置MSG_PEEK選項并調用recv函數之后,即使讀取了輸入緩沖的數據也不會刪除。因此,該選項通常與MSG_DONTWAIT合作。用于調用非堵塞方式驗證待讀取數據存在與否。
readv & writev 函數
對數據進行整合傳輸及發送的函數
通過writev函數能夠將分散保存在多個緩沖中的數據一并發送。適當使用這兩個函數能夠降低I/O函數的調用次數。
#include <sys/uio.h>ssize_t writev(int filedes, const struct iovec * iov, int iovcnt); ~ filedes: 數據傳輸對象的套接字文件描寫敘述符 ~ iov: iovec結構體數組的地址,結構體中包括待發送數據的位置和大小信息 ~ iovcnt: 第二個參數的數組長度 struct iovec {void * iov_base; // 緩沖地址size_t iov_len; // 緩沖大小 }關系模型例如以下:
#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總結
以上是生活随笔為你收集整理的TCP/IP 网络编程 (三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python浓缩(14)执行环境
- 下一篇: mactex中文配置