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)
提供面向連接的、可靠的數據傳輸服務,數據無差錯,無重復的發送,且按發送順序接收, 對應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); }