C++ 异步IO (一) 阻塞式HTTP客户端

风格不统一 提交于 2020-01-23 18:30:24

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\r\n" "Host: www.baidu.com\r\n" "\r\n";
    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\n",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的库函数,返回的是一个指向文件结构的指针。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!