前兩年, 就買了《TCP/IP網絡編程》這本書, 因爲自身基礎薄弱, 只是蜻蜓點水翻閱了幾張。編程
後來工做了這些年, 愈來愈感到瓶頸期已經來臨, 再花式的 curd 也俘獲不了領導的芳心了。數組
因而, 打算仔細學習下 《TCP/IP網絡編程》, 爲了讓本身更深入記憶, 特作筆記。bash
#include <sys/scoket.h>
int socket(int domain, int type, int protocol)
domain : 套接字中實用的協議族信息
type : 套接字數據傳輸類型信息
protocol : 計算機通訊中實用的協議信息
複製代碼
名稱 | 協議族 |
---|---|
PF_INET | IPv4互聯網協議族 |
PF_INET6 | IPv6互聯網協議族 |
PF_LOCAL | 本地通訊unix協議族 |
... | ... |
2.1 面向連接的套接字類型 (SOCK_STREAM)服務器
傳輸方式特徵:網絡
1.1 傳輸過程數據不會丟失
1.2 按序傳輸數據
1.3 不存在數據邊界
複製代碼
這幾個特性其實就是咱們常說的 TCP協議。dom
緩衝區概念:socket
收發數據的套接字內部有緩衝(buffer), 簡言之就是字節數組. 經過套接字傳輸的數據將保存到該數組。 所以, 咱們 read、write其實讀取緩衝區的內容。tcp
那麼當緩衝區滿, 會發生什麼狀況呢。 在ICP/IP網絡編程書中介紹, 若是read函數讀取的速度比接收數據的速度慢, 則緩衝區有可能填滿。 此時套接字將沒法再接收數據, 傳輸端套接字將中止傳輸。函數
2.2 面向消息的套接字類型 (SOCK_STREAM)學習
傳輸方式特徵:
1. 強調快速傳輸而非傳輸順序
2. 傳輸數據可能丟失也可能毀損
3. 傳輸的數據存在數據邊界
複製代碼
其實就是咱們常說的UDP協議
這裏咱們不作選擇, 爲0便可。
//建立套接字(IPv4協議族, TCP套接字, TCP協議)
int sock = socket(PF_INET, SOCK_STREAM, 0);
複製代碼
返回的爲 文件描述符, 失敗返回-1
#include <sys/socket.h>
int bind(int socketfd, struct sockaddr *myaddr, socklen_t addrlen);
socketfd 要分配的套接字文件描述符
myaddr 存儲地址信息的結構體變量地址值
addrlen 第二個結構體變量的長度
複製代碼
socketfd 不用多說, 便是咱們的socket函數返回的文件描述符
咱們看到他是一個 sockaddr 結構體的指針類型。
sockaddr結構體:
struct sockaddr {
__uint8_t sa_len;
sa_family_t sa_family; //地址組
char sa_data[14]; //地址信息
};
複製代碼
在sa_data一個成員裏,包含了ip、port的地址信息, 這樣寫起來很麻煩, 因此有了新的結構體 sockaddr_in (IP和端口進行了拆分)
sockaddr_in結構體
struct sockaddr_in {
__uint8_t sin_len;
sa_family_t sin_family; //地址族
in_port_t sin_port; // TCP/UDP端口號
struct in_addr sin_addr; //IP地址
char sin_zero[8];
};
複製代碼
在上面的結構體中, 又嵌套了 in_addr 結構體,記錄 IP 地址
struct in_addr {
in_addr_t s_addr; //32位IPv4地址
};
複製代碼
地址族 | 含義 |
---|---|
AF_INET | IPv4互聯網使用的地址族 |
AF_INET6 | IPv6互聯網使用的地址族 |
AF_LOCAL | 本地通訊unix使用的地址族 |
... | ... |
16位端口號
32位 ip 地址信息, 以網絡字節序保存
無特殊含義, 爲與sockaddr 大小保持一致, 寫入0 便可。
傳遞地址信息的長度
//分配內存-構造服務端地址端口
memset(&serv_addr, 0, sizeof(serv_addr));
//IPv4中的地址族
serv_addr.sin_family = AF_INET;
//32位的IPv4地址, INADDR_ANY表示當前ip
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//16位tcp/udp端口號
serv_addr.sin_port = htons(atoi(argv[1]));
//分配地址
if (bind(serv_sock, (struct sockaddr*) &serv_addr,sizeof(serv_addr) )==-1){
printf("bind() error");
exit(0);
}
複製代碼
bind函數以前, 構造了 sockaddr_in 結構體的數據, 其中介紹幾個點.
- INADDR_ANY 會自動獲取當前服務器的IP
- 咱們看到使用到了 htonl、htons 函數,構造IP地址和端口
首先咱們來看下這幾個函數的含義
地址族 | 含義 |
---|---|
htons | 把short型數據從主機字節序轉化爲網絡字節序 |
htonl | 把long型數據從主機字節序轉化爲網絡字節序 |
ntohs | 把short型數據從網絡字節序轉化爲主機字節序 |
ntohl | 把long型數據從網絡字節序轉化爲主機字節序 |
... | ... |
數據傳輸採用的網絡字節序, 那在傳輸前應直接把數據轉換成網絡字節序, 接收的數據也須要轉換城主機字節序再保存 上面這句話是有問題的, 緣由是數據收發過程當中是有自動轉換機制的.
除了 socketaddr_in 結構體變量手動填充數據轉換外, 其餘狀況不須要考慮字節序問題。
1.主機字節序:主機內部內存中數據的處理方式。
2.網絡字節序:網絡字節順序是TCP/IP中規定好的一種數據表示格式,它與具體的CPU類型、操做系統等無關,從而能夠保證數據在不一樣主機之間傳輸時可以被正確解釋。網絡字節順序採用big endian(大端)排序方式。
字節序:是指整數在內存中保存的順序。
cpu向內存保存數據字節序有兩種實現方式:
小端字節序(little endian):低字節數據存放在內存低地址處,高字節數據存放在內存高地址處。
大端字節序(bigendian):高字節數據存放在低地址處,低字節數據存放在高地址處。
圖例:
大字節序更符合咱們的閱讀習慣。可是咱們的主機使用的是哪一種字節序取決於CPU,不一樣的CPU型號有不一樣的選擇。
當咱們兩臺計算機是須要網絡通訊時, 規範統一約定爲大端序進行通信處理.
###客戶端代碼分析 咱們在服務端設置ip時候, 使用了 INADDR_ANY 會自動獲取當前服務器的IP, 咱們看下客戶端的鏈接代碼
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
char message[30];
//建立套接字(IPv4協議族, TCP套接字, TCP協議)
int sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1 ){
printf("socket() error");
exit(1);
}
//分配內存-構造服務端地址端口
memset(&serv_addr, 0, sizeof(serv_addr));
//IPv4中的地址族
serv_addr.sin_family = AF_INET;
//32位的IPv4地址
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
//16位tcp/udp端口號
serv_addr.sin_port = htons(atoi(argv[2]));
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))) {
printf("connect() error");
exit(1);
}
int length = read(sock, message, sizeof(message)-1);
if (length==-1){
printf("read() error");
exit(1);
}
複製代碼
設置服務端 serv_addr.sin_addr.s_addr 地址, 使用了函數 inet_addr
int_addr_t inet_addr(const char * string);
//成功時32位大端序整數值, 失敗時返回 INADDR_NONE.
複製代碼
例:
printf("%d",inet_addr("192.168.2.1"));
//output: 16951488
printf("%d",inet_addr("192.168.2.256"));
//output: -1
複製代碼
相同功能函數, 只是簡化了向 serv_addr.sin_addr.s_addr 賦值操做
int inet_aton(const char *string, struct in_addr * addr);
//成功時返回1(true) 失敗時返回0(false)
inet_aton(addr, &addr_inet.sin_addr)
複製代碼
其餘函數:
char * inet_ntao(struct in_addr adr);
//成功時返回轉換的字符串地址值, 失敗時返回-1.
複製代碼
● atoi():將字符串轉換爲整型值。
● atol():將字符串轉換爲長整型值。
printf("%d",atoi("123"));
//output : 123
複製代碼
服務端
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//16位tcp/udp端口號
serv_addr.sin_port = htons(atoi(argv[1]));
複製代碼
客戶端
//32位的IPv4地址
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
//16位tcp/udp端口號
serv_addr.sin_port = htons(atoi(argv[2]));
複製代碼
這裏麪包含上面講到的一些知識點.
參考資料:
《TCP/IP 網絡編程》