C/C++ socket編程教程之五:使用bind()和connect()函數__Oracle

來源:互聯網
上載者:User
socket() 函數用來建立通訊端,確定通訊端的各種屬性,然後伺服器端要用 bind() 函數將通訊端與特定的IP地址和連接埠綁定起來,只有這樣,流經該IP地址和連接埠的資料才能交給通訊端處理;而用戶端要用 connect() 函數建立串連。 bind() 函數 bind() 函數的原型為:
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);  //Linuxint bind(SOCKET sock, const struct sockaddr *addr, int addrlen);  //Windows
下面以Linux為例進行講解,Windows與此類似。 sock 為 socket 檔案描述符,addr 為 sockaddr 結構體變數的指標,addrlen 為 addr 變數的大小,可由 sizeof() 計算得出。

下面的代碼,將建立的通訊端與IP地址 127.0.0.1、連接埠 1234 綁定:
 //建立通訊端 int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //建立sockaddr_in結構體變數 struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); //每個位元組都用0填充 serv_addr.sin_family = AF_INET; //使用IPv4地址 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址 serv_addr.sin_port = htons(1234); //連接埠 //將通訊端和IP、連接埠綁定 bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 這裡我們使用 sockaddr_in 結構體,然後再強制轉換為 sockaddr 類型,後邊會講解為什麼這樣做。 
 sockaddr_in 結構體 接下來不妨先看一下 sockaddr_in 結構體,它的成員變數如下: 
 struct sockaddr_in{ sa_family_t sin_family; //地址族(Address Family),也就是地址類型 uint16_t sin_port; //16位的連接埠號碼 struct in_addr sin_addr; //32位IP地址 char sin_zero[8]; //不使用,一般用0填充 }; 1) sin_family 和 socket() 的第一個參數的含義相同,取值也要保持一致。 


2) sin_prot 為連接埠號碼。uint16_t 的長度為兩個位元組,理論上連接埠號碼的取值範圍為 0~65536,但 0~1023 的連接埠一般由系統分配給特定的服務程式,例如 Web 服務的連接埠號碼為 80,FTP 服務的連接埠號碼為 21,所以我們的程式要盡量在 1024~65536 之間分配連接埠號碼。

連接埠號碼需要用 htons() 函數轉換,後面會講解為什麼。

3) sin_addr 是 struct in_addr 結構體類型的變數,下面會詳細講解。

4) sin_zero[8] 是多餘的8個位元組,沒有用,一般使用 memset() 函數填充為 0。上面的代碼中,先用 memset() 將結構體的全部位元組填充為 0,再給前3個成員賦值,剩下的 sin_zero 自然就是 0 了。 in_addr 結構體 sockaddr_in 的第3個成員是 in_addr 類型的結構體,該結構體只包含一個成員,如下所示:
 struct in_addr{ in_addr_t s_addr; //32位的IP地址 }; in_addr_t 在標頭檔 <netinet/in.h> 中定義,等價於 unsigned long,長度為4個位元組。也就是說,s_addr 是一個整數,而IP地址是一個字串,所以需要 inet_addr() 函數進行轉換,例如: 
 unsigned long ip = inet_addr("127.0.0.1"); printf("%ld\n", ip); 運行結果: 

16777343

圖解 sockaddr_in 結構體
為什麼要搞這麼複雜,結構體中嵌套結構體,而不用 sockaddr_in 的一個成員變數來指明IP地址呢。socket() 函數的第一個參數已經指明了地址類型,為什麼在 sockaddr_in 結構體中還要再說明一次呢,這不是囉嗦嗎。

這些繁瑣的細節確實給初學者帶來了一定的障礙,我想,這或許是曆史原因吧,後面的介面總要相容前面的代碼。各位讀者一定要有耐心,暫時不理解沒有關係,根據教程中的代碼“照貓畫虎”即可,時間久了自然會接受。 為什麼使用 sockaddr_in 而不使用 sockaddr bind() 第二個參數的類型為 sockaddr,而代碼中卻使用 sockaddr_in,然後再強制轉換為 sockaddr,這是為什麼呢。

sockaddr 結構體的定義如下:
 struct sockaddr{ sa_family_t sin_family; //地址族(Address Family),也就是地址類型 char sa_data[14]; //IP地址和連接埠號碼 }; 下圖是 sockaddr 與 sockaddr_in 的對比(括弧中的數字表示所佔用的位元組數): 



sockaddr 和 sockaddr_in 的長度相同,都是16位元組,只是將IP地址和連接埠號碼合并到一起,用一個成員 sa_data 表示。要想給 sa_data 賦值,必須同時指明IP地址和連接埠號碼,例如”127.0.0.1:80“,遺憾的是,沒有相關函數將這個字串轉換成需要的形式,也就很難給 sockaddr 類型的變數賦值,所以使用 sockaddr_in 來代替。這兩個結構體的長度相同,強制轉換類型時不會丟失位元組,也沒有多餘的位元組。

可以認為,sockaddr 是一種通用的結構體,可以用來儲存多種類型的IP地址和連接埠號碼,而 sockaddr_in 是專門用來儲存 IPv4 地址的結構體。 另外還有 sockaddr_in6,用來儲存 IPv6 地址,它的定義如下:
 struct sockaddr_in6 { sa_family_t sin6_family; //(2)地址類型,取值為AF_INET6 in_port_t sin6_port; //(2)16位連接埠號碼 uint32_t sin6_flowinfo; //(4)IPv6流資訊 struct in6_addr sin6_addr; //(4)具體的IPv6地址 uint32_t sin6_scope_id; //(4)介面範圍ID }; 正是由於通用結構體 sockaddr 使用不便,才針對不同的地址類型定義了不同的結構體。 
 connect() 函數 connect() 函數用來建立串連,它的原型為: 
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);  //Linuxint connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);  //Windows
各個參數的說明和 bind() 相同,不再贅述。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.