接觸網絡編程一年多了,最近在系統的學習vnp兩本書,對基礎知識作一些總結,但願理解的更透徹清晰,但願能有更多的沉澱。編程
1.套接口地址安全
針對IPv4和IPv6地址族,分別定義了兩種類型的套接口地址:sockaddr_in和sockaddr_in6,兩種套接口地址結構以下所示: 服務器
/* IPv4地址族套接口地址結構 */ struct in_addr { in_addr_t s_addr; /* IPv4地址,網絡序存儲 */ } struct sockaddr_in { uint8_t sin_len; /* 結構體大小 */ sa_family_t sin_family; /* 地址族:AF_INET */ in_port_t sin_port; /* 16位端口號,網絡序存儲 */ struct in_addr sin_addr; /* IPv4地址,網絡序存儲 */ char sin_zero[8] /* 保留字段,未使用 */ }; /* IPv6地址族套接口地址結構 */ struct in6_addr { uint8_t s6_addr[16]; /* IPv6地址,網絡序存儲 */ } struct sockaddr_in6 { uint8_t sin6_len; /* 結構體大小 */ sa_family_t sin6_family; /* 地址族:AF_INET */ in_port_t sin6_port; /* 16位端口號,網絡序存儲 */ uint32_t sin6_flowinfo /* 流標記或優先級,網絡序存儲 */ struct in6_addr sin6_addr; /* IPv4地址,網絡序存儲 */ };
這兩個套接口地址結構在/netinet/in.h頭文件中定義,上面結構體的描述和頭文件中的定義有一些差異,好比在頭文件中sa_family_t成員經過宏定義在了公共部分,另外表示結構體大小的sin_len和sin6_len成員,在某些實現中沒有定義,兩種套接口地址結構都定義了地址族類型成員,是自描述的結構。網絡
另外爲了使套接口函數可以統一處理全部協議族的套接口地址結構,定義了通用套接口地址:架構
/* 通用套接口地址結構 */ struct sockaddr { uint8_t sa_len; /* 結構體大小 */ sa_family_t fa_family; /* 地址族 */ char sa_data[14]; /* 協議地址 */ }
通用套接口地址結構的用處只有兩個:一、用於套接口函數的聲明,任何使用套接口地址指針爲參數的函數,其指針所有聲明爲指向通用套接口地址類型。二、類型轉換,用於將特定於協議族的地址指針轉換爲通用地址指針。dom
2.套接口函數socket
socket建立套接口描述符:tcp
#include <sys/socket.h> /* 功能:建立套接口描述符-sockfd。 參數:一、domain-協議族,經常使用AF_INET、AF_INET六、AF_UNIX或AF_LOCAL等。 二、type-套接口類型,經常使用SOCK_STREAM、SOCK_DGRAM 等。 三、protocol-協議,通常設置爲0,協議有內核根據前兩個參數選擇。 返回值:成功則返回非負描述符,失敗返回-1。 */ int socket(int domain, int type, int protocol)
bind將套接口描述符綁定到特定的套接口地址:函數
#include <sys/socket.h> #include <sys/socket.h> /* 功能:將套接口描述符-sockfd綁定到特定的套接口地址。 參數:一、sockfd-套接口描述符。 二、addr-指向通用套接口地址的指針。 三、addrlen-套接口地址結構的大小。 返回值:成功則返回0,失敗返回-1,並修改errno爲相應的值。 */ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect將本地描述符鏈接到套接口地址addr指定的服務端:學習
#include <sys/socket.h> /* 功能:將本地描述符鏈接到套接口地址addr指定的服務端。 參數:一、sockfd-套接口描述符。 二、addr-指向通用套接口地址的指針。 三、addrlen-套接口地址結構的大小。 返回值:成功則返回0,失敗返回-1,並修改errno爲相應的值。 */ int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
listen將套接口描述符設置爲被動鏈接狀態,用於在指定端點進行監聽:
#include <sys/socket.h> /* 功能:將套接口描述符設置爲被動等待鏈接狀態,用於在指定端點進行監聽。 參數:一、sockfd-套接口描述符。 二、backlog-未完成鏈接對了和已完成鏈接隊列的最大值。 返回值:成功則返回0,失敗返回-1,並修改errno爲相應的值。 */ int listen(int sockfd, int backlog);
accept用於服務器端接受客戶端的一個鏈接:
#include <sys/socket.h> /* 功能:用於服務器端接受客戶端的一個鏈接。 參數:一、sockfd-套接口描述符。 二、addr-指向通用套接口地址的指針,函數返回時保存了客戶端套接口地址信息。 三、addrlen-套接口地址結構的大小。 返回值:成功則返回一個已鏈接到客戶端的套接口描述符,失敗返回-1,並修改errno爲相應的值。 */ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
3.字節序轉換函數和地址轉換函數
#include <arpa/inet.h> /* 功能:主機序轉換成網絡序。 參數:一、hostlong/hostshort-待轉換32位長整形或者16位短整形。 返回值:轉換成網絡序的32位長整形或16爲短整形。 */ uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort);
/* 功能:網絡序轉換成主機序。 參數:一、netlong/netshort-待轉換32位長整形或者16位短整形。 返回值:轉換成主機序的32位長整形或16爲短整形。 */ uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
備註:多字節類型數據的表示/存儲方式分爲小端序和大端序。小端序,高位字節存儲在高地址內存空間,低位字節存儲在低位內存空間,大端序偏偏相反。對於不一樣架構的主機序不同,有的架構主機序採用小端字節序,有的架構主機序採用大端架構,另外網絡序採用大端字節序。當在網絡上傳輸字節流時,不須要考慮字節序的問題,當在網絡上傳輸多字節類型數據時須要考慮字節序問題。
驗證本地host字節序的方法:
#include <stdlib.h> #include <stdio.h> int main(void) { union { short a; char c[sizeof(short)]; } t; t.a = 0x0102; /* 高位字節存存儲於低地址內存, * 低位字節序存儲於高地址內存 */ if (1 == t.c[0] && 2 == t.c[1]) printf("big-endian\n"); else printf("little-endian\n"); return 0; }
4.多字節操縱函數
string.h中定義了兩組多字節類型操做函數,這兩組函數不對待處理的多字節類型數據作任何假設。第一組函數由b開頭,起源於4.2BSD,當前幾乎全部支持套接口的系統都提供這一組函數。
/* 功能:將s指向大小爲n的內存空間初始化爲全0。 參數:一、s-內存地址。 二、n-內存大小 返回值:無。 */ void bzero(void *s, size_t n); /* 功能:內存拷貝。 參數:一、src-源內存地址。 二、dest-目標內存地址。 三、n-拷貝內存大小。 返回值:無。 */ void bcopy(const void *src, void *dest, size_t n); /* 功能:內存比較。 參數:一、s1-內存地址1。 二、s2-內存地址2。 三、n-比較的內存大小。 返回值:0-表示兩塊內存數據相同,非0-表示兩塊內存數據不一樣。 */ int bcmp(const void *s1, const void *s2, size_t n);
第二組函數由mem開頭,起源於ansi c,由任何支持ansi c標準的系統提供。
#include <strings.h> /* 功能:內存初始化。 參數:一、str-內存地址。 二、c-初始化值。 三、n-內存大小。 返回值:0-表示兩塊內存數據相同,非0-表示兩塊內存數據不一樣。 */ void *memset(void *str, int c, size_t n) /* 功能:內存拷貝。 參數:一、src-源內存地址。 二、dest-目標內存地址。 三、n-拷貝內存大小。 返回值:無。 */ void *memcpy(void *dest, const void *src, size_t n); /* 功能:內存比較。 參數:一、s1-內存地址1。 二、s2-內存地址2。 三、n-比較的內存大小。 返回值:0-表示兩塊內存數據相同,非0-表示兩塊內存數據不一樣。 */ int memcmp(const void *s1, const void *s2, size_t n);
除了上面的兩組函數覺得,string.h頭文件定義了專門處理字符串的函數,這些函數以str開頭好比strcpy/strcmp等,這些函數假設處理的字符串都以0結尾。
5.套接口ip地址到字符串轉換函數
#include <arpa/inet.h> /* 功能:將字符串IP地址轉換成機器IP地址,。 參數:一、af-地址族:AF_INET, AF_INET6。 二、strptr-字符串ip。 三、addrptr-地址結構。 返回值:1-成功,0-針對af, strptr不是有效的ip地址格式,。 */ int inet_pton(int af, const char *strptr, void *addrptr); /* 功能:將機器IP地址轉換成字符串IP地址,。 參數:一、af-地址族:AF_INET, AF_INET6。 二、addrptr-機器地址結構。 三、strptr-字符串ip。 四、size-strptr的長度,爲了防止內核寫溢出。 返回值:成功則返回指向strptr的指針,失敗-返回null,並設置errno爲相應值。。 */ const char *inet_ntop(int af, const void *addrptr, char *strptr, socklen_t size);
6. 套接口讀寫函數
readn從一個socket中讀取n個字節(引用unp示例代碼):
size_t Readn(int fd, void *vptr, size_t n) { size_t nleft; size_t nread; char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; /* 系統調用被信號打斷,從新調用 */ else return(-1); /* 發生異常 */ } else if (nread == 0) break; /* 讀到結束符,讀到本地tcp收到的fin時 */ nleft -= nread; ptr += nread; } return(n - nleft); /* return >= 0 */ }
Writen向套接口描述符中寫入n個字節(引用unp示例代碼):
/* 功能:從套接口中讀取n個字節 返回值:n-成功;-1:失敗*/ size_t Writen(int fd, const void *vptr, size_t n) { size_t nleft; size_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; /* 被信號打斷,再次調用 */ else return(-1); /* 寫異常:write返回0-寫一個tcp鏈接關閉的sock */ } nleft -= nwritten; ptr += nwritten; } return(n); }
Readline從描述符fd中讀取一行(引用unp示例代碼):
static int read_cnt;/* 緩衝區中可讀的字節數 */ static char *read_ptr;/* 當前緩衝區中可讀字節的指針 */ static char read_buf[MAXLINE];/* 讀緩衝區 */ /* 功能:讀去一個字節, 返回值:1-成功;0-讀到文件結束符;-1-系統調用錯誤*/ static ssize_t my_read(int fd, char *ptr) { if (read_cnt <= 0) { /* 當緩衝區中可讀字節數爲0時,從文件中讀數據到緩衝區 */ again: if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { if (errno == EINTR) /* 系統調用被信號打斷 */ goto again; return(-1); } else if (read_cnt == 0) /* 讀到文件結束符,正常返回0 */ return(0); read_ptr = read_buf; } read_cnt--; *ptr = *read_ptr++; /* 從緩衝區中讀一個字節給用戶 */ return(1); } /* 功能:從fd中讀取一行到vptr中,行的最大長度maxlen 返回值:n-成功,實際讀取的字節數;-1:讀失敗*/ ssize_t readline(int fd, void *vptr, size_t maxlen) { ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = my_read(fd, &c)) == 1) { *ptr++ = c; if (c == '\n') break; /* newline is stored, like fgets() */ } else if (rc == 0) { *ptr = 0; return(n - 1); /* EOF, n - 1 bytes were read */ } else return(-1); /* error, errno set by read() */ } *ptr = 0; /* null terminate like fgets() */ return(n); } /* 功能:從fd中讀取一行到vptr中,行的最大長度maxlen 返回值:n-成功;-1-失敗,並打印錯誤日誌*/ ssize_t Readline(int fd, void *ptr, size_t maxlen) { ssize_t n; if ( (n = readline(fd, ptr, maxlen)) < 0) err_sys("readline error"); return(n); }
Readline存在的問題:由於間接使用了靜態全局變量,所以Readline是不可重入的,不是線程安全的。