現在將擁有的數據整理一下,首先HTTP消息封包已經由應用程式打包完成,服務器IP地址也已經透過DNS[1]請求機制獲得。在兩個前提條件都滿足的狀況下,我們就可以著手思考要怎麼將這些數據發給對方服務器的應用程式
發送數據其實是調用多個socket庫函式達成的,藉由委託多個函式API進行一連串的任務交互,每個任務完成的項目不同,有建立連接部分、斷開連線等等,這些操作的用意就是為了保證雙方是否接收到消息與回應是否正常[2]
為了使雙方應用程式之間建立一條專屬的溝通管道,我們調用了socket庫函式,很多書上將建立socket形容成搭建一條無形的通道,雙方可透過這條通道來實現消息的收發操作,不過並不是說建立socket後計算機才被允許與網路進行通訊,其實早在建立socket之前計算機就可以向網路收發消息了,建立socket比較像是彼此確定我們該走哪一條傳輸通道找到對方的應用程式
順著這個思路這小節將介紹應用程式是如何調用socket庫函式向下層委託收發。我喜歡用一個網路訂房的比喻來形容建立socket連線的步驟,就像我們是使用手機app進行訂房,委託系統進行操作,真正的流程實際上是不得而知的,這就像站在應用層的視角看待socket連線[3]
目的: 依照指定類型創建socket
就下載這個訂房app吧!
我們先來看看socket create部分:
int socket(int domain, int type, int protocol);
socket(AF_INET, SOCK_STREAM, 0); // 選擇 TCP
socket(AF_INET, SOCK_STREAM, 6); // 還是 TCP
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 依然是 TCP
socket(AF_INET, SOCK_DGRAM, 0); // 這次是 UDP
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/socket.h>
// #define AF_INET 2
// #define SOCK_STREAM 1
int main(int argc, char *argv[]){
uint32_t socket_identifier = 0;
/*創建socket*/
socket_identifier = socket(AF_INET , SOCK_STREAM , 0);
switch(socket_identifier){
case 1: // 正數
case 2:
...
case n:
printf("socket create successfully!\n");
break;
case -1:
printf("socket create error!\n");
}
return 0;
}
socket創建成功一般都會回傳一個大於0的標示符,若回傳負數則代表socket創建異常
目的: 使兩個應用程式建立連線通道
就用這個帳號登入app吧
來看看連現階段吧:
int connect(int fd, struct sockaddr *server, int addrlen);
connect函數是客戶端發起的請求,目的是為了與服務器建立連線,介紹參數前,先來講講sockaddr這個結構體,它裡面裝的主要就是socket連線需要的消息,這裡暫且以IPv4為例:
struct in_addr{
ip_addr_t s_addr;
};
struct sockaddr{
unsigned char sin_family; //AF_INET所以是IPv4
unsigned short sin_port; // 應用程式端口號
struct in_addr sin_addr; // 服務器IP地址
unsigned char sin_zero[8]; // 不會用到
};
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SERV_PORT 8080
typedef sockaddr* info;
int main(argc, char* argv[]){
info serv=(info)calloc(0, sizeof(sockaddr));
uint8_t resp;
/*創建socket部分*/
/*填入socket消息*/
serv->sin_family = AF_INET;
serv->sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", serv->sin_addr);
resp = connect(socket_identifier, (info)serv, sizeof(sockaddr));
if(resp < 0)
printf("socket connect error!\n");
return 0;
}
藉由創建socket函式API,我們取得本地端的socket編號socket_identifier[4],接下來的任務就是要跟服務器上的應用程式進行連接,IP地址可以幫助我們找到服務器地址,而端口號[5]則可以幫我們找到執行在服務器上的應用程式
當我們成功建立連接後,資料就可以透過socket在兩個應用程式之間流通,接著我們可以透過使用read()/recv()來獲取資料,使用write()/send()來傳輸資料。read()/write與recv()/send()的不同只差在recv()/send()的輸入參數多了一個描述符flag,這個描述符提供操作更多的細節控制選項,不過我們以下還是使用通用的收發socket API → read()/write
目的: 將資料寫入 Socket 中並發送出去
就是這間了,趕緊下單!
ssize_t write(int fd, const void *buf, size_t nbyte);
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
define MAX 1024
char* buf=(char*)malloc(max); // buffer
int main(argc, char* argv[]){
ssize_t s_write;
/*創建socket部分*/
/*socket連線部分*/
/*socket write*/
strcpy(buf, "socket test");
s_write = write(socket_identifier, buf, MAX);
if(s_write < 0){
printf("socket write error!\n");
}
else{
printf("socket write data length=%d\n", s_read); // 送出了多少資料長度
}
...
return 0;
}
透過調用write函式將資料發送出去,回傳值可以判斷發送的資料長度,若是buffer大小為0會返回0,失敗則回傳-1。它跟待會要介紹的接收消息read()其實就是兩個死對頭,一個急著將資料壓到buffer裡,一個忙著將資料拿出來發出去
目的: 透過連線中的 Socket讀取資料
系統提示~您已經下訂成功!
ssize_t read(int fd, void* buf, size_t nbyte);
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
define MAX 1024
char* buf=(char*)malloc(max); // buffer
int main(argc, char* argv[]){
ssize_t s_read;
/*創建socket部分*/
/*socket連線部分*/
/*socket read*/
s_read = read(socket_identifier, buf, MAX);
if(s_read < 0){
printf("socket read error!\n");
}
else{
printf("socket read data length=%d\n", s_read); // 讀取buffer內資料長度
}
...
return 0;
}
藉由操作read()可以透過回傳值得知讀取狀況,負數代表有錯誤產生,0或者正數代表讀取buffer的資料長度
假如我們得到的回傳值是0可能有幾個特別意思:
目的: 關閉socket
若完成訂房,請登出帳號
int close(int fd);
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(argc, char* argv[]){
int s_close;
/*創建socket部分*/
/*socket連線部分*/
/*socket的收發操作*/
/*關閉socket*/
s_close = close(socket_identifier);
(s_close < 0) ? printf("socket close error!") : printf("close successfully!");
return 0;
}
藉由socket斷開雙方之間的通訊,執行成功返回0,若發生錯誤則返回-1
[1] :網路是怎樣連接的(四)DNS
[2] :傳輸層使用TCP協議
[3] :關於socket連線部分將在介紹傳輸層時介紹
[4] :標示符是應用程式用來識別眾多本地端socket用
[5] :客戶端服務端通訊間用來識別眾多對方socket用