TCP/IP網絡編程-前三章學習筆記

開篇語

前兩年, 就買了《TCP/IP網絡編程》這本書, 因爲自身基礎薄弱, 只是蜻蜓點水翻閱了幾張。編程

後來工做了這些年, 愈來愈感到瓶頸期已經來臨, 再花式的 curd 也俘獲不了領導的芳心了。數組

因而, 打算仔細學習下 《TCP/IP網絡編程》, 爲了讓本身更深入記憶, 特作筆記。bash

建立套接字(socket)

#include <sys/scoket.h>

int socket(int domain, int type, int protocol)

domain : 套接字中實用的協議族信息
type : 套接字數據傳輸類型信息
protocol : 計算機通訊中實用的協議信息
複製代碼

1. domain參數 協議族

名稱 協議族
PF_INET IPv4互聯網協議族
PF_INET6 IPv6互聯網協議族
PF_LOCAL 本地通訊unix協議族
... ...

2. type參數 套接字類型

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協議

3. protocol參數 協議最終選擇

這裏咱們不作選擇, 爲0便可。

4. 最終咱們使用TCP連接模式寫法

//建立套接字(IPv4協議族, TCP套接字, TCP協議)
int sock = socket(PF_INET, SOCK_STREAM, 0);
複製代碼

返回的爲 文件描述符, 失敗返回-1

向套接字分配網絡地址(bind)

#include <sys/socket.h>

int bind(int socketfd, struct sockaddr *myaddr, socklen_t addrlen);

socketfd 要分配的套接字文件描述符
myaddr  存儲地址信息的結構體變量地址值
addrlen 第二個結構體變量的長度
複製代碼

1. socketfd 參數

socketfd 不用多說, 便是咱們的socket函數返回的文件描述符

2. myaddr 參數

咱們看到他是一個 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地址
};
複製代碼

結構體 sockaddr_in 的成員分析

成員 sin_family
地址族 含義
AF_INET IPv4互聯網使用的地址族
AF_INET6 IPv6互聯網使用的地址族
AF_LOCAL 本地通訊unix使用的地址族
... ...
成員 sin_port

16位端口號

成員 sin_addr

32位 ip 地址信息, 以網絡字節序保存

成員 sin_zero

無特殊含義, 爲與sockaddr 大小保持一致, 寫入0 便可。

3. addrlen參數

傳遞地址信息的長度

4. 最終咱們使用bind綁定地址方式

//分配內存-構造服務端地址端口
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 結構體的數據, 其中介紹幾個點.

  1. INADDR_ANY 會自動獲取當前服務器的IP
  2. 咱們看到使用到了 htonl、htons 函數,構造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);
}
複製代碼

知識點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.
複製代碼

知識點2

● 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]));  
複製代碼

這裏麪包含上面講到的一些知識點.

  1. 服務端由於使用INADDR_ANY實際等於 inet_addr("0.0.0.0"), 獲取本機的IP地址
  2. 由於客戶端接收了字符串IP地址, 因此使用了顯示 inet_addr, 返回32位大端序整型數值
  3. htons 將短整型轉換爲網絡字節序, 對於端口來講是比較合適的, 而對於IP類轉換的整型數值, 通常須要 htonl 進行轉換

參考資料:

《TCP/IP 網絡編程》

blog.csdn.net/stalin_/art…

相關文章
相關標籤/搜索