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\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);
说明一下: 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的库函数,返回的是一个指向文件结构的指针。