TCP/IP 網絡編程 (三)


server端未處理高併發請求一般採用例如如下方式:css

  • 多進程:經過建立多個進程提供服務
  • 多路複用:經過捆綁並統一管理 I/O 對象提供服務
  • 多線程:經過生成和客戶端等量的線程提供服務

多進程server端

#include <unistd.h> pid_t fork(); // 成功返回進程 ID, 失敗返回-1

fork函數將建立調用的函數副本。子進程將使用新的內存空間複製當前函數的環境。linux

  • 父進程:函數返回子進程ID
  • 子進程:函數返回 0

可以理解爲調用該函數以後將存在兩個pid_t,分別存在父子進程中,所以它們將會依據不一樣的函數值運行對應的進程代碼。編程

在調用函數前的所有變化都會在子進程中保持一致。數組

調用函數以後的變化將不會影響彼此,因爲它們全然不相干。儘管可以經過進程間通訊交換信息。bash

殭屍(zombie)進程

exit函數傳遞的參數值和return語句返回的值會傳遞給操做系統。markdown

操做系統不會銷燬子進程。而是將這些值傳遞給產生該子進程的父進程。處在這樣的狀態下的進程就是殭屍進程。多線程

exit(0) 表示程序正常, exit(1)/exit(-1)表示程序異常退出;
exit() 結束當前進程/當前程序/。在整個程序中,僅僅要調用 exit ,就結束.併發

exit(0):正常運行程序並退出程序;
exit(1):非正常運行致使退出程序;
return():返回函數。若在main主函數中,則會退出函數並返回一值。可以寫爲return(0)。或return 0socket

exit表示進程終結,不管是在哪一個函數調用中。即便還存在被調函數。
return表示函數返回,假設是在主函數中意味着進程的終結。假設不是在主函數中那麼會返回到上一層函數調用。tcp

具體說:

  1. return返回函數值。是關鍵字;exit是一個函數。

  2. return是語言級別的。它表示了調用堆棧的返回;而exit是系統調用級別的,它表示了一個進程的結束。
  3. return是函數的退出(返回);exit是進程的退出。
  4. returnC語言提供的,exit是操做系統提供的(或者函數庫中給出的)。

  5. return用於結束一個函數的運行,將函數的運行信息傳出給其它調用函數使用;exit函數是退出應用程序,刪除進程使用的內存空間。並將應用程序的一個狀態返回給OS,這個狀態標識了應用程序的一些運行信息,這個信息和機器和操做系統有關。一般是 0 爲正常退出。非0 爲非正常退出。

  6. 非主函數中調用returnexit效果很是明顯。但是在main函數中調用returnexit的現象就很是模糊,多數狀況下現象都是一致的。

銷燬殭屍進程

利用 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.h

    for(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;
        else
            puts("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;
        }
        else
            close(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);
    else
        read_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 30

int 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 30 int 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;
        else
            puts("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;
        }
        else
            close(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; // seconds
    long 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 30

int 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: 發送緊急數據

帶外數據概念其實是向接收端傳送三個不一樣的信息:

  1. 發送端進入緊急模式這個事實。接收進程得以通知這個事實的手段不外乎SIGURG信號或select調用。

    本通知在發送進程發送帶外字節後由發送端TCP立刻發送。即便往接收端的不論什麼數據發送因流量控制而中止了。TCP仍然發送本通知。

    本通知可能致使接收端進入某種特殊處理模式,以處理接收的不論什麼後繼數據。

  2. 帶外字節的位置。也就是它相對於來自發送端的其他數據的發送位置:帶外標記。
  3. 帶外字節的實際值。

    既然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
相關文章
相關標籤/搜索