1. 程式人生 > 其它 >網路程式設計筆記(五)-域名系統和高階I/O

網路程式設計筆記(五)-域名系統和高階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 的普通輸入函式讀取。

    這是因為:

    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;		// 緩衝大小
}    

多播和廣播

多播程式類似 UDP 套接字連線,不需要建立連線,但要設定相應的套接字選項。多播程式一般分為 Sender 和 Receiver。

多播可以跨域不同網路,只要加入多播組就能接收資料。

廣播也是基於 UDP 完成的,但廣播只能向同一網路中的主機傳遞資料。廣播分為直接廣播和本地廣播。

參考資料

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