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);
}