天天看點

C++ 異步IO (一) 阻塞式HTTP用戶端

An IO call is synchronous if, when you call it, it does not return until the operation is completed, or until enough time has passed that your network stack gives up.

簡單來說,IO的函數後面的代碼不會被執行,除非IO函數已傳回,或函數逾時了。

/* For sockaddr_in */
#include <netinet/in.h>
/* For socket functions */
#include <sys/socket.h>
/* For gethostbyname */
#include <netdb.h>

#include <unistd.h>
#include <string.h>
#include <stdio.h>

int main(int c, char **v)
{
    const char query[] =
        "GET / HTTP/1.0
" "Host: www.baidu.com
" "
";
    const char hostname[] = "www.baidu.com";
    struct sockaddr_in sin;
    struct hostent *h;
    const char *cp;
    int fd;
    ssize_t n_written, remaining;
    char buf[1024];

    /* Look up the IP address for the hostname.   Watch out; this isn't
       threadsafe on most platforms. */
    h = gethostbyname(hostname);
    if (!h) {
        fprintf(stderr, "Couldn't lookup %s: %s", hostname, hstrerror(h_errno));
        return 1;
    }
    if (h->h_addrtype != AF_INET) {
        fprintf(stderr, "No ipv6 support, sorry.");
        return 1;
    }

    /* Allocate a new socket */
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("socket");
        return 1;
    }

    /* Connect to the remote host. */
    sin.sin_family = AF_INET;
    sin.sin_port = htons(80);
    sin.sin_addr = *(struct in_addr*)h->h_addr;
    if (connect(fd, (struct sockaddr*) &sin, sizeof(sin))) {
        perror("connect");
        close(fd);
        return 1;
    }

    /* Write the query. */
    /* XXX Can send succeed partially? */
    cp = query;
    remaining = strlen(query);
    while (remaining) {
      n_written = send(fd, cp, remaining, 0);
      if (n_written <= 0) {
        perror("send");
        return 1;
      }
      remaining -= n_written;
      cp += n_written;
    }

    /* Get an answer back. */
    while (1) {
        ssize_t result = recv(fd, buf, sizeof(buf), 0);
        if (result == 0) {
            break;
        } else if (result < 0) {
            perror("recv");
            close(fd);
            return 1;
        }
        fwrite(buf, 1, result, stdout);
    }

    close(fd);
    return 0;
}      

All of the network calls in the code above are blocking: the gethostbyname does not return until it has succeeded or failed in resolving www.baidu.com; the connect does not return until it has connected; the recv calls do not return until they have received data or a close; and the send call does not return until it has at least flushed its output to the kernel’s write buffers.

sockaddr_in 和 sockaddr 這兩個結構體用來處理網絡通信位址。在各種系統調用中,隻要和網絡位址打交道,就得用到這兩個結構體。

網絡位址包含三方面的屬性

1. 位址類型: IPV4 (AF_INET) IPV6

2. IP 位址

3. 端口号

include <netinet/in.h>

struct sockaddr {
    unsigned short    sa_family;    // 2 bytes address family, AF_xxx
    char              sa_data[14];     // 14 bytes of protocol address
};

// IPv4 AF_INET sockets:

struct sockaddr_in {
    short            sin_family;       // 2 bytes e.g. AF_INET, AF_INET6
    unsigned short   sin_port;    // 2 bytes e.g. htons(3490)
    struct in_addr   sin_addr;     // 4 bytes see struct in_addr, below
    char             sin_zero[8];     // 8 bytes zero this if you want to
};

struct in_addr {
    unsigned long s_addr;          // 4 bytes load with inet_pton()
};      

這兩個結構體大小是一樣的,都是16位元組。不同之處是sockaddr_in将端口号和IP位址分開, 最好還用8個填充位元組和sockaddr大小一樣。

程式員不應該直接使用 sockaddr, 它是給作業系統用的。

程式員應該使用sockaddr_in來表示位址,它區分了端口号和IP位址,語義性更強。

一般的用法:

程式員把類型,IP位址,端口填充sockaddr_in結構體,然後強制轉換成sockaddr,作為參數傳遞給調用函數。

一段典型代碼:

int sockfd;
struct sockaddr_in servaddr;

sockfd = Socket(AF_INET, SOCK_STREAM, 0);

/* 填充struct sockaddr_in */
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);

/* 強制轉換成struct sockaddr */
connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
       

struct hostent* gethostbyname(const char* name)

這個函數的傳入值是主機名或域名,傳回值是hostent結構體。如果函數調用失敗,傳回NULL。

struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};      

h_addrtype 傳回是IPV4還是IPV6, h_length 傳回IP位址的長度。

h_addr_list傳回的是主機IP位址, 列印IP位址時需要inet_ntop()

const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) :

這個函數将類型為af的網絡位址結構src轉化成主機序的字元串形式,存放在長度為cnt的字元串中。

inet_ntoa 和 inet_ntop 的差別

inet_ntop 是線程安全的。使用 inet_ntoa 的話,就不能在同一個函數的幾個參數裡出現兩次inet_ntoa, 或者第一個inet_ntoa結束之前不能使用第二個,亦或者使用兩個數組儲存位址

printf("%s:%d--->",inet_ntoa(ip_protocol->ip_src_address),ntohs(tcp_protocol->tcp_src_port) );

printf("%s:%d

",inet_ntoa(ip_protocol->ip_dst_address),ntohs(tcp_protocol->tcp_dst_port));

Socket 程式設計

1.

int

client_socket = socket(AF_INET,SOCK_STREAM,0);

建立用于 internet的流協定(TCP)socket, 用 client_socket 代表客戶機 socket

2. connect(client_socket, (struct sockaddr*) &sin, sizeof(sin)))

向伺服器發起連接配接,連接配接成功後client_socket代表了客戶機到伺服器的一個socket連接配接

3. int written = send(client_socket,buffer,BUFFER_SIZE,0);

向伺服器發送buffer中的資料, 如果written 記錄發送成功的位元組數

4. ssize_t result = recv(client_socket, buf, sizeof(buf), 0);

接收伺服器的回複,result傳回寫入buf的位元組數

檔案操作

1. fwrite(buf, 1, result, stdout);

将buf中的資料寫入标準輸出

說明一下:
fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind等
緩沖檔案系統
緩沖檔案系統的特點是:在記憶體開辟一個“緩沖區”,為程式中的每一個檔案使用,當執行讀檔案的操作時,
從磁盤檔案将資料先讀入記憶體“緩沖區”, 裝滿後再從記憶體“緩沖區”依此讀入接收的變量。執行寫檔案的
操作時,先将資料寫入記憶體“緩沖區”,待記憶體“緩沖區”裝滿後再寫入檔案。由此可以看出,記憶體 
“緩沖區”的大小,影響着實際操作外存的次數,記憶體“緩沖區”越大,則操作外存的次數就少,
執行速度就快、效率高。一般來說,檔案“緩沖區”的大小随機器 而定。

open, close, read, write, getc, getchar, putc, putchar 等
非緩沖檔案系統
非緩沖檔案系統是借助檔案結構體指針來對檔案進行管理,通過檔案指針來對檔案進行通路,既可以讀寫字元、
字元串、格式化資料,也可以讀寫二進制數 據。非緩沖檔案系統依賴于作業系統,通過作業系統的功能對
檔案進行讀寫,是系統級的輸入輸出,它不設檔案結構體指針,隻能讀寫二進制檔案,但效率高、速度快,
由于ANSI标準不再包括非緩沖檔案系統,是以建議大家最好不要選擇它。

open等屬于低級IO,
fopen等是進階IO。

open等傳回一個檔案描述符(使用者程式區的),
fopen等傳回一個檔案指針。

open等無緩沖,fopen等有緩沖。

fopen等是在open等的基礎上擴充而來的,在大多數情況下,用fopen等。

open 是系統調用 傳回的是檔案句柄,檔案的句柄是檔案在檔案描述符表裡的索引,
fopen是C的庫函數,傳回的是一個指向檔案結構的指針。