Linux下的socket編程實踐(二)socket編程基本API簡介
Socket是什麼
socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆檔案”,都可以用“開啟open –> 讀寫write/read –> 關閉close”模式來操作。Socket就是該模式的一個實現, socket即是一種特殊的檔案,一些socket函數就是對其進行的操作(讀/寫IO、開啟、關閉)。 說白了Socket是應用程式層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。
Socket可以看成是使用者進程與核心網路通訊協定棧的介面(編程介面, 如所示), 其不僅可以用於本機處理序間通訊,可以用於網路上不同主機的處理序間通訊, 甚至還可以用於異構系統之間的通訊。
如TCP/IP協議棧已經屬於核心的一部分了,被實現好了,路由器工作於網路層(Router),Application是需要我們去實現的。Socket可以看作是使用者進程和核心網路通訊協定棧的編程介面,可以把socket看作處理序間通訊的一種方式,和管道不同,他是全雙工系統的,可用於本機和不同主機之間的處理序間通訊,異構通訊也是可以的。(例如手機和電腦,分別是ARM和X86架構)
Pv4套介面地址結構
IPv4套介面地址結構通常也稱為“網際通訊端地址結構”,它以“sockaddr_in”命名,定義在標頭檔中
struct sockaddr_in { uint8_t sin_len; sa_family_t sin_family; in_port_t sin_port; //2位元組 struct in_addr sin_addr; //4位元組 char sin_zero[8]; //8位元組 };
成員說明:
sin_len:整個sockaddr_in結構體的長度,在4.3BSD-Reno版本之前的第一個成員是sin_family.
sin_family:指定該地址家族,對於IPv4來說必須設為AF_INET(Socket不僅可以用於TCP/IP還可以用於UNIX域協議)
sin_port:連接埠
sin_addr:IPv4的地址;
sin_zero:暫不使用,一般將其設定為0
通用地址結構
用來指定與通訊端關聯的地址(可以支援其他協議).
struct sockaddr { uint8_t sin_len; sa_family_t sin_family; char sa_data[14]; //14位元組 };
說明:
sin_len:整個sockaddr結構體的長度
sin_family:指定該地址家族
sa_data:由sin_family決定它的形式。
注意:使用的時候通常把IPv4的地址結構強制轉換成通用地址結構,就像上面的sockaddr_in 轉換為sockaddr
網路位元組序
大端位元組序和小端位元組序 的出現是為了異構系統之間的使用
1.大端位元組序(Big Endian)
最高有效位(MSB:Most Significant Bit)儲存於最低記憶體位址處,最低有效位(LSB:Lowest Significant Bit)儲存於最高記憶體位址處。
2.小端位元組序(Little Endian)
最高有效位(MSB:Most Significant Bit)儲存於最高記憶體位址處,最低有效位(LSB:Lowest Significant Bit)儲存於最低記憶體位址處。
3.主機位元組序
不同的主機有不同的位元組序,如x86為小端位元組序,Motorola 6800為大端位元組序,ARM位元組序是可配置的。
4.網路位元組序
網路位元組序規定為大端位元組序
判斷自己主機的位元組序是哪一種?
//測試當前系統是否為小端模式 int main() { int data = 0x12345678; //int = 4位元組(32位) //每4個二進位位代表1位十六進位位, //則8位十六進位位代表4*8=32位二進位位 char *p = (char *)&data; printf("%x, %x, %x, %x\n",p[0],p[1],p[2],p[3]); //0x78屬於低位,如果其放在了p[0](低地址)處,則說明是小端模式 if (p[0] == 0x78) { cout << "當前系統為小端模式" << endl; //x86平台為小端模式 } else if (p[0] == 0x12) { cout << "當前系統為大端模式" << endl; //IBM為大端模式 } } 如果是小端模式,那麼原串輸出是 78 56 34 12
位元組序轉換函式(常用於連接埠轉換)
uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort); /**說明: h代表(local)host;n代表network; s代表short;l代表long; */
//測試轉換結果 int main() { int localeData = 0x12345678; char *p = (char *)&localeData; printf("Begin: %0x %0x %0x %0x\n", p[0], p[1], p[2], p[3]); //將本地位元組轉換成網路位元組 int inetData = htonl(localeData); p = (char *)&inetData; printf("After: %0x %0x %0x %0x\n", p[0], p[1], p[2], p[3]); if (p[0] == 0x12) cout << "網路系統為大端模式" << endl; else cout << "網路系統為小端模式" << endl; printf("host:%x, inet:%x\n", localeData, inetData); }
地址轉換函式(用於IP地址轉換)
#include #include int inet_aton(const char *cp, struct in_addr *inp); in_addr_t inet_addr(const char *cp); char *inet_ntoa(struct in_addr in); //in_addr定義如下: typedef uint32_t in_addr_t; struct in_addr { in_addr_t s_addr; };
//實踐 int main() { //將點分十進位轉換成十進位數 cout << inet_addr("192.168.139.137") << endl; //將十進位數轉換成點分十進位形式 struct in_addr address; address.s_addr = inet_addr("192.168.139.137"); cout << inet_ntoa(address) << endl; memset(&address,0,sizeof(address)); inet_aton("127.0.0.1", &address); cout << address.s_addr << endl; cout << inet_ntoa(address) << endl; return 0; }
通訊端類型
1)流式通訊端(SOCK_STREAM)
提供連線導向的、可靠的Data Transmission Service,資料無差錯,無重複的發送,且按發送順序接收, 對應TCP協議。
2)資料報式通訊端(SOCK_DGRAM)
提供無串連服務。不提供無錯保證,資料可能丟失或重複,並且接收順序混亂, 對應UDP協議。
3)原始通訊端(SOCK_RAW)
使我們可以跨越傳輸層直接對IP層進行封裝傳輸。(應用程式層直接和IP層)
socket函數
#include #include int socket(int domain, int type, int protocol);
建立一個通訊端用於通訊
參數:
domain:指定通訊協定族(protocol family),常用取值AF_INET(IPv4)
type:指定socket類型, 流式通訊端SOCK_STREAM,資料通訊端SOCK_DGRAM,原始通訊端SOCK_RAW
protocol:協議類型,常用取值0, 使用預設協議
傳回值:
成功: 返回非負整數,通訊端;
失敗: 返回-1
bind函數
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
綁定一個本地地址到通訊端
參數:
sockfd:socket函數返回的通訊端
addr:要綁定的地址
//sockaddr_in結構, bind時需要強制轉換成為struct sockaddr*類型 struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ };
/**樣本:INADDR_ANY的使用, 綁定本機任意地址**/ int main() { int listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd == -1) err_exit("socket error"); struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8001); //綁定原生任意一個IP地址, 作用同下面兩行語句 addr.sin_addr.s_addr = htonl(INADDR_ANY); //inet_aton("127.0.0.1", &addr.sin_addr); //addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (bind(listenfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1) err_exit("bind error"); else cout << "bind success" << endl; }
listen函數
int listen(int sockfd, int backlog);
listen函數應該用在調用socket和bind函數之後, 並且用在調用accept之前, 用於將一個通訊端從一個主動通訊端轉變成為被動通訊端。
backlog說明:
對於給定的監聽套介面,核心要維護兩個隊列:
1、已由客戶發出併到達伺服器,伺服器正在等待完成相應的TCP三路握手過程(SYN_RCVD狀態)
2、已完成串連的隊列(ESTABLISHED狀態)
但是兩個隊列長度之和不能超過backlog
backlog推薦使用SOMAXCONN(3.13.0-44-generic中該值為128), 使用等待隊列的最大值;
bind之後變成了被動通訊端,接受串連;主動通訊端是用來發起串連,例如使用connect。
accept函數
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
從已完成串連隊列返回第一個串連(the first connection request on the queue of pending connections for the listening
socket, sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket. The newly created socket is not in the listening state),如果已完成串連隊列為空白,則阻塞。The original socket sockfd is unaffected by this call.
參數:
sockfd:伺服器通訊端
addr:將返回對等方的通訊端地址, 不關心的話, 可以設定為NULL
addrlen:返回對等方的通訊端地址長度, 不關心的話可以設定成為NULL, 否則一定要初始化
傳回值: On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket. On error, -1 is returned, and errno is set appropriately.
connect函數
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
建立一個串連至addr所指定的通訊端
參數:
sockfd:未串連通訊端
addr:要串連的通訊端地址
addrlen:第二個參數addr長度
樣本:echo server/client實現
//server端代碼 int main() { int listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd == -1) err_exit("socket error"); struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8001); addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(listenfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1) err_exit("bind error"); if (listen(listenfd, SOMAXCONN) == -1) err_exit("listen error"); char buf[512]; int readBytes; struct sockaddr_in clientAddr; //謹記: 此處一定要初始化 socklen_t addrLen = sizeof(clientAddr); while (true) { int clientfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen); if (clientfd == -1) err_exit("accept error"); //列印客戶IP地址與連接埠號碼 cout << "Client information: " << inet_ntoa(clientAddr.sin_addr) << ", " << ntohs(clientAddr.sin_port) << endl; memset(buf, 0, sizeof(buf)); while ((readBytes = read(clientfd, buf, sizeof(buf))) > 0) { cout << buf; if (write(clientfd, buf, readBytes) == -1) err_exit("write socket error"); memset(buf, 0, sizeof(buf)); } if (readBytes == 0) { cerr << "client connect closed..." << endl; close(clientfd); } else if (readBytes == -1) err_exit("read socket error"); } close(listenfd); }
//client端代碼 int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) err_exit("socket error"); //填寫伺服器連接埠號碼與IP地址 struct sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8001); serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sockfd, (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1) err_exit("connect error"); char buf[512]; while (fgets(buf, sizeof(buf), stdin) != NULL) { if (write(sockfd, buf, strlen(buf)) == -1) err_exit("write socket error"); memset(buf, 0, sizeof(buf)); int readBytes = read(sockfd, buf, sizeof(buf)); if (readBytes == 0) { cerr << "server connect closed... \nexiting..." << endl; break; } else if (readBytes == -1) err_exit("read socket error"); cout << buf; memset(buf, 0, sizeof(buf)); } close(sockfd); }