1. 程式人生 > >《TCP/IP網路程式設計》第2章 筆記&程式碼&註釋

《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的報錯

伺服器端程式碼:https://github.com/ChristmasError/TCP-IP-Network-programming/tree/master/%E7%AC%AC%E4%B8%80%E7%AB%A0%20%E5%A5%97%E6%8E%A5%E5%AD%97%E5%92%8C%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/%E6%9C%8D%E5%8A%A1%E5%99%A8

客戶端程式碼:https://github.com/ChristmasError/TCP-IP-Network-programming/tree/master/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E5%A5%97%E6%8E%A5%E5%AD%97%E7%B1%BB%E5%9E%8B%E4%B8%8E%E5%8D%8F%E8%AE%AE%E8%AE%BE%E7%BD%AE