網路程式設計筆記(五)-域名系統和高階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 完成的,但廣播只能向同一網路中的主機傳遞資料。廣播分為直接廣播和本地廣播。