《TCP/IP網路程式設計》第2章 筆記&程式碼&註釋
注:本章內容大部分為第一章的伺服器與客戶端程式碼的解析與理解,十分重要
協議
伺服器端和客戶端為了能進行資料交換,他們必須遵循同一個協議:
建立套接字
int socket(int domain, int type, int protocol)//Linux
Socket socket(int domain, int type, int protocol)//Win
domain 引數為協議族(Protocol Family)資訊
type 為套接字資料傳輸型別資訊
protocol 為計算機通訊中使用的協議資訊
如:
hServerSock = socket(PF_INET, SOCK_STREAM, 0);
程式碼來自第一章內容中套接字的建立,其中,PF_INET,為IPv4網際網路協議族。
協議族(Protocol Family)
Linux中,標頭檔案sys/socket.h中宣告的協議族(Windows在<WinSock2.h>中):
PF_INET | IPv4協議族 |
PF_INET6 | IPv6協議族 |
PF_LOCAL | 本地通訊的UNIX協議族 |
PF_PACKET | 底層套接字的協議族 |
PF_IPX | IPX Novell協議族 |
業界常用PF_INET對應的IPv4協議族,且《TCP/IP網路程式設計》主要將重點放在這PF_INET上。
套接字型別(type)
socket()中第二個引數為套接字型別,指的是套接字的資料的傳輸方式。
決定套接字的傳遞方式的原因:每一個協議族內會存在不同的傳輸方式。
兩種代表性的傳輸方式:
1.面向連線的套接字 SOCK_STREAM
hServerSock = socket(PF_INET, SOCK_STREAM, 0);
- 傳輸過程資料不會消失
- 按順序傳輸資料
- 傳輸的資料不存在資料邊界(因為不存在資料邊界,所以要注意要在緩衝區(buffer)滿之前將資料讀出,也就是緩衝區寫入速度要小與讀出速度)
2.面向訊息的套接字 SOCK_DGRAM
- 強調快速的傳輸而非傳輸順序
- 傳輸的資料可能丟失/損毀
- 有資料邊界
- 限制每次傳輸的資料大小
這種傳輸方式與SOCK_STREAM相比突出的是傳輸的速度這一特性,但他卻無法避免資料丟失、損毀
注:面向訊息的套接字不存在連線的概念
協議的最終選擇(socket()第三個引數)
socket()內前兩個引數滿足了大部分情況,我們向他傳遞0,但如果協議族中存在多個 資料方式傳輸方式相同的協議時,傳輸方式相同,但協議不同,這時就需要第三個引數具體指定協議資訊。
int tcp_sock= socket(PF_INET, SOCK_STREAM, IPPROTO_UDP);//Linux
這裡前兩個引數指定了IPv4協議族,SOCK_STREAM是面向連線的資料傳輸:因此滿足這兩個條件的協議只有IPPROTO_UDP。
基於WINDOWS的TCP套接字示例
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
//這是將靜態庫連線到工程中
//否則會出現一串無法解析的外部符號的LINK2019的報錯
void ErrorHandling(const char* message);
int main()
{
WSADATA wsaData;
//宣告SOCKET變數儲存socket()函式的返回值
//在Windows裡sokect()的返回值是SOCKET
SOCKET hSocket;
SOCKADDR_IN servAddr;
char message[30];
int strLen = 0;
int readLen = 0;
int idx=0;
/*
if (argc != 3) {
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(1);
}*/
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
hSocket = socket(PF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET)
ErrorHandling("socket() error!");
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servAddr.sin_port = htons(8888);
if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
ErrorHandling("connect() error!");
//while中recv()讀取資料,每次1位元組
//第一章中:
//strlen = recv(hSocket, message, sizeof(message) - 1, 0);
//每次讀取的字元數是整個message的sizeof(message);
while (readLen = recv(hSocket, &message[idx++], 1, 0))
{
if(readLen==-1)
ErrorHandling("read() error!");
strLen += readLen;
if (message[idx - 1] == '\0')//字串結束符跳出迴圈
break;
}
std::cout << "Message from server : " << message << std::endl;
std::cout << "Function read call count : " << strLen << std::endl;
closesocket(hSocket);
WSACleanup();
getchar();
return 0;
}
void ErrorHandling(const char * message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
注:
#define _WINSOCK_DEPRECATED_NO_WARNINGS
是為了防止安全檢查,當然你也可以根據編譯器的報錯選用他推薦的函式。
在添加了:
#include<WinSock2.h>
之後:
#pragma comment(lib,"ws2_32.lib")
這是將靜態庫連線到工程中
否則會出現一串無法解析的外部符號的LINK2019的報錯