网络编程笔记(五)-域名系统和高级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 结构体变量值而非字符串地址值。

image

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

多进程:父进程只需编写接收数据的代码,子进程只需编写发送数据的代码,简化了代码。

image

#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]);

父子进程传递数据,注意一个管道不能完成双向通信。

image

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——用于发送带外数据

image

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 的普通输入函数读取。

    这是因为:

    1. TCP 连接上只有一个字节可以作为带外数据发送。

    2. 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;		// 缓冲大小
}    

image

多播和广播

多播程序类似 UDP 套接字连接,不需要建立连接,但要设置相应的套接字选项。多播程序一般分为 Sender 和 Receiver。

多播可以跨域不同网络,只要加入多播组就能接收数据。

广播也是基于 UDP 完成的,但广播只能向同一网络中的主机传递数据。广播分为直接广播和本地广播。

参考资料

https://github.com/chankeh/net-lenrning-reference

posted @ 2021-10-24 16:35  CoolGin  阅读(77)  评论(0编辑  收藏  举报