网络编程笔记(五)-域名系统和高级I/O
网络编程笔记(五)-域名系统和高级I/O
参考 《UNIX 网络编程》第 11 章,《TCP/IP 网络编程》第 8、11、12、13 章
域名与地址转换
DNS 是对 IP 地址和域名相互转换的系统,其核心是 DNS 服务器。
gethostbyname 函数:利用域名获取 IP 地址
鼓励使用 getaddrinfo 函数,可以同时处理 IPv4 和 IPv6 地址,而且是可重入的。
#include <netdb.h>
// 返回值:成功时返回hostent变量地址值,失败返回NULL指针
struct hostent * gethostbyname(const char * hostname);
struct hostent{
char * h_name; // 存有官方域名(Official domain name)
char ** h_aliases; // 同一个IP可以绑定多个域名,除了官方域名以外的其他域名
int h_addrtype; // 保存地址族信息,IPv4存有AF_INET
int h_length; // 保存IP地址长度:4
char ** h_addr_list; // address list:最重要成员,保存IP地址 // 可能分配多个IP给同一域名,利用多个服务器进行负载均衡
}
// 用法:
for(i=0;host->h_aliases[i];i++){
printf("Aliases %d: %s \n",i+1,host->h_aliases[i]);
}
for(i=0;host->h_addr_list[i];i++){
printf("IP addr %d:%s \n", i+1, inet_ntoa(*(struct in_addr*)host
->h_addr_list[i])); // 注意类型转换,先取成员变量得到一级指针,再转换一级指针为struct in_addr*,最后解引用得到IP地址!
}
注意 h_addr_list 实际指向 in_addr 结构体变量值而非字符串地址值。
gethostbyaddr:利用 IP 地址获取域名
#include <netdb.h>
// 返回值:成功时返回hostent变量地址值,失败返回NULL指针
/* 参数:
addr:含有IP地址的in_addr结构体指针
len:第一个参数字节大小,IPv4为4,IPv6为16
family:地址族信息,IPv4为AF_INET, IPv6为AF_INET6
*/
struct hostent * gethostbyaddr(const char * addr, socklen_t len, int family);
分割 I/O
多进程:父进程只需编写接收数据的代码,子进程只需编写发送数据的代码,简化了代码。
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define BUF_SIZE 30
void error_handling(char *message);
void read_routine(int sock, char *buf);
void write_routine(int sock, char *buf);
int main(int argc, char *argv[]) {
int sock;
pid_t pid;
struct sockaddr_in serv_adr;
char buf[BUF_SIZE];
if (argc != 3) {
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1) {
error_handling("socket() error");
}
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\r\n");
} else {
printf("Connected....");
}
pid = fork();
if (pid == 0) {
write_routine(sock, buf);
} else {
read_routine(sock, buf);
}
close(sock);
return 0;
}
void error_handling(char *message) {
fputs(message, stderr);
fputs("\n", stderr);
exit(1);
}
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") || !strcmp(buf, "Q\n")) {
shutdown(sock, SHUT_WR);
return;
}
write(sock, buf, strlen(buf));
}
}
进程间通信
管道函数 pipe
#include <unistd.h>
// 参数
// fileds[0]:管道出口
// fileds[1]:管道入口
// 返回值:成功返回0,失败返回-1
int pipe(int fildes[2]);
父子进程传递数据,注意一个管道不能完成双向通信。
int fds[2];
pipe(fds);
pid = fork()
// 单向通信
if (pid == 0) {
write(fds[1], str, sizeof(str));
} else {
read(fds[0], buf, BUF_SIZE);
puts(buf);
}
// 双向通信
int fds1[2], fds2[2];
pipe(fds1[2]); pipe(fds2[2]);
pid = fork();
if (pid == 0) {
write(fds1[1], str1, sizeof(str1));
read(fds2[0], buf, BUF_SIZE);
printf("Child proc output: %s \n", buf);
} else {
read(fds1[0], buf, BUF_SIZE);
printf("Parent proc output: %s \n", buf);
write(fds2[1], str2, sizeof(str2));
}
高级 IO 函数
send 和 recv 函数
send 和 recv 允许通过第四个参数从进程到内核传递标志。
send 函数
ssize_t send(int sockfd, const void* buf, size_t len, int flags);
功能:在一个已连接套接字上发送数据。
参数 :
- sockfd:一个已连接的套接字描述符。(in)
- buf:指向要传送数据的缓冲区。(in)
- len:上述缓冲区的长度(字节数)。(in)
- flags:指明发送方式:0——忽略 该选项,MSG_DONTROUTE——不进行路由,MSG_OOB——用于发送带外数据。
send 的成功只是数据被成功发出,并不意味数据已经被接收方成功接收。如果传输层没有缓冲区空间放置要发送的数据,send 将被阻塞,除非采用的是非阻塞套接字。对非阻塞面向流的套接字,send 实际所写字节数取决于客户和服务器的缓冲区空闲块大小。select 函数可以用来确定是否可以发送更多的数据。
recv 函数
ssize_t recv(int sockfd, void* buf, int len, int flags);
功能:在一个已连接套接字上发送数据。
参数 :
- sockfd:一个已连接的套接字描述符。(in)
- buf:指向要接收数据的缓冲区。(out)
- len:上述缓冲区的长度(字节数)。(in)
- flags:影响接收方式的标志。
一些 flags
-
MSG_OOB:发送紧急消息,用于创建特殊发送方法和通道以发送紧急信息。
通过 MSG_OOB 可选项传递数据不会加快数据传输速度,而且通过信号处理函数也只能读取 1 字节,剩余数据只能通过未设置 MSG_OOB 的普通输入函数读取。
这是因为:
-
TCP 连接上只有一个字节可以作为带外数据发送。
-
TCP 不存在真正的 “带外数据”:真正意义上的带外传输需要通过单独的通信路径高速传输数据,只是利用 TCP 的紧急模式进行传输。
-
-
MSG_PEEK:查看已读取的数据,而且系统不在 recv 或 recvfrom 返回后丢弃这些数据。
readv 和 writev 函数
通过 writev 可以将分散保存在多个缓冲的数据一并发送,通过 readv 函数可以由多个缓冲分别接收。即多次 write 函数可以通过一次 writev 替代,只用传送一个数据包,提高效率。
#include<sys/uio.h>
// 成功时返回接收的字节数,失败时返回-1
ssize_t readv(int fd,const struct iovec*vector,int count);
/*
结构体 iovec 中包含待发送数据的位置和大小信息。
count 为第二个参数传递的数组长度。
*/
// 成功时返回发送的字节数,失败时返回-1
ssize_t writev(int fd, const struct iovec* vector, int count);
struct iovec
{
void * iov_base; // 缓冲地址
size_t iov_len; // 缓冲大小
}
多播和广播
多播程序类似 UDP 套接字连接,不需要建立连接,但要设置相应的套接字选项。多播程序一般分为 Sender 和 Receiver。
多播可以跨域不同网络,只要加入多播组就能接收数据。
广播也是基于 UDP 完成的,但广播只能向同一网络中的主机传递数据。广播分为直接广播和本地广播。