1. 程式人生 > >c++網路程式設計2:TCP連線概念及程式設計

c++網路程式設計2:TCP連線概念及程式設計

一.TCP建立連線的三次握手

在TCP/IP協議中,TCP協議提供可靠的連線服務,採用三次握手建立一個連線。
第一次握手:建立連線時,客戶端傳送SYN包(SYN=j)到伺服器,並進入SYN_SEND狀態,等待伺服器確認;【客戶端->服務端:SYN(j)】
第二次握手:伺服器收到SYN包,必須確認客戶的SYN(ACK=j+1),同時自己也傳送一個SYN包(SYN=k),即SYN+ACK包,此時伺服器進入SYN_RECV狀態;【服務端->客戶端:SYN(k),ACK(j+1)】
第三次握手:客戶端收到伺服器的SYN+ACK包,向伺服器傳送確認包ACK(ACK=k+1),此包傳送完畢,客戶端和伺服器進入ESTABLISHED狀態,完成三次握手。【客戶端->服務端:ACK(k+1)】
完成三次握手,客戶端與伺服器開始傳送資料。上述是一個完整的TCP連線過程。

然則為什麼必然要進行三次握手來包管連線是雙工的呢,一次不可麼?兩次不可麼?我們舉一個實際生活中的兩個人進行說話溝通的例子來模仿三次握手。

第一次對話:

老婆讓甲出去打醬油,半路碰著乙,甲問了一句:哥們你吃飯了麼?

結果乙帶著耳機聽歌呢,根本沒聽到,沒反響。甲心裡想:跟你說話也沒個音,不跟你說了,溝通失敗。說明乙接管不到甲傳過來的資訊的情況下溝通必然是失敗的。

若是乙聽到了甲說的話,那麼第一次對話成功,接下來進行第二次對話。
第二次對話:

乙聽到了甲說的話,然則他是老外,中文不好,不知道甲說的啥意思也不知道如何答覆,於是隨便答覆了一句學過的中文 :我去廁所了。甲一聽立即笑噴了,“去廁所吃飯”?道不合不相為謀,離你遠點吧,溝通失敗。說明乙無法做出正確應答的情況下溝通失敗。

若是乙聽到了甲的話,做出了正確的應答,並且還進行了反問:我吃飯了,你呢?那麼第二次握手成功。

經由過程前兩次對話證瞭然乙可以或許聽懂甲說的話,並且能做出正確的應答。接下來進行第三次對話。
第三次對話:

甲剛和乙打了個招呼,忽然老婆喊他,“你個死鬼,打個醬油咋這麼半天,看我回家咋收拾你”,甲是個妻管嚴,聽完嚇得二話不說就跑回家了,把乙本身晾那了。乙心想:這什麼人啊,得,我也回家吧,溝通失敗。說明甲無法做出應答的情況下溝通失敗。

若是甲也做出了正確的應答:我也吃了。那麼第三次對話成功,兩人已經建立起了順暢的溝通渠道,接下來開始聊天。

經由過程第二次和第三次的對話證明甲可以或許聽懂乙說的話,並且能做出正確的應答。

可見,兩個人進行有效的說話溝通,這三次對話的過程是必須的。
同理對於TCP為什麼須要進行三次握手我們可以一樣的懂得:

為了讓服務端能接收到客戶端的資訊並能做出正確的應答而進行前兩次(第一次和第二次)握手,為了讓客戶端可以接收到服務端的資訊並能做出正確的應答而進行後兩次(第二次和第三次)握手。

TCP半開連線數概念:

 在完成三次握手之後,TCP連線已經建立,那麼對系統來說,這就是一個TCP連線數。系統對這樣一個 沒有限-制。如果三次握手沒有完成,客戶端與伺服器沒有開始傳送資料,那麼,這樣試圖完成三次握手的事件發生 的 是被系統所限-制的。而這個數就是所謂的半開連線數了。

二.TCP客戶端的程式設計

   1.首先載入系統的套接字型檔

WORD wVersionRequested;
wVersionRequested=MAKEWORD(1,1);//指定Winsock庫的版本:1.1
WSADATA wsaData;
int err;
//使用WSAStartup載入套接字型檔,以及確定使用的套接字型檔的版本,這裡使用的是1.1的版本
err=WSAStartup(wVersionRequested,&wsaData);
if(err!=0)
   return false;
if(LOBYTE(wsaData.wVersion)!=1||HIBYTE(wsaData.wVersion)!=1)
{
	//如果套接字型檔載入成功,但是版本不正確,那麼呼叫WSACleanup	釋放對套接字型檔的佔用資源
	WSACleanup();
	return false;
}

  2.建立客戶端的套接字

SOCKET sockClient;
unsigned long mode=1;//0:阻塞
sockClient=socket(AF_INET,SOCK_STREAM,0);

注意:套接字是通訊的基礎,可以認為套接字是程式設計師對一個通訊端進行操作的物件,比如說你想將本機的客戶端連線到遠端機器,就需要使用客戶端產生的套接字去連線,你操作的物件就是套機字,他是一個很抽象的概念,但是確實程式設計所必須的,每個要進行通訊的終端(不管是伺服器還是客戶端)都必須產生一個套接字。 

3.將套接字繫結到本地的某個地址和埠上;在當前客戶端的系統上開啟一個埠與伺服器上的埠進行資料互動,這個過程稱為埠的繫結,TCP客戶端可以不需要這個過程,因為在呼叫connect介面連線服務的時候會自動開啟一個埠,當然如果有特殊需求必須要繫結一個埠的話,就這樣做:

//sockClient為客戶端套接字介面,localIP為客戶端的IP(可以不填),localPort為客戶端需要開啟的埠
bool BindSocketEx(SOCKET sockClient,LPCSTR localIP,UINT localPort)
{
 //LPCTSTR在unicode環境下是const wchar *,非unicode環境下是const char *
 //INADDR_ANY表示如果一臺機器上(伺服器)有多塊網絡卡,每個網絡卡都有各自的IP,則INADDR_ANY
 //會將套接字繫結到該機器上的所有網絡卡地址和埠上
 SOCKADDR_IN addrSrv;
 if(localIP==NULL)
  addrSrv.sin_addr.S_un.S_addr= htonl(INADDR_ANY);//由於這裡需要網路位元組序,所以將主機位元組序轉換成網路位元組序
 else
  addrSrv.sin_addr.S_un.S_addr=inet_addr(localIP);//inet_addr將字串ip轉換成long型
 addrSrv.sin_family=AF_INET;
 addrSrv.sin_port=htons(localPort);//由於這裡需要網路位元組序,所以將主機位元組序轉換成網路位元組序
 //下面的程式碼等效於這行程式碼return bind(sockClient,(SOCKADDR *)&addrSrv,sizeof(SOCKADDR))==0;
 if(bind(sockClient,(SOCKADDR *)&addrSrv,sizeof(SOCKADDR))==0)
    return true;
 else
    return false;
}

注意:網路位元組序和主機位元組序,這裡牽扯到機器本身的大端模式(其實地址存放高位位元組)和小端模式(起始地址存放低位位元組),intel的CPU是屬於小端模式,而網路位元組序是大端模式,所以需要使用htons將主機位元組序轉換成網路位元組序。

網路->主機:ntohs  ntohl              主機->網路:htons  htonl(h:主機   n:網路    s:short    l:long)

4.連線伺服器

//sockClient為客戶端的套接字,svrIP為伺服器IP,svrPort為伺服器的埠
bool TCP_Client_ConnectSocketEx(SOCKET sockClient,LPCSTR svrIP,USHORT svrPort)
{
	
	if(svrIP==NULL||svrPort==0)
		return NULL;
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr=inet_addr(svrIP);//伺服器IP,inet_addr將字串ip轉換成long型
	addrSrv.sin_family=AF_INET;
	addrSrv.sin_port=htons(svrPort);//伺服器埠,主機位元組序轉換成網路位元組序
	//呼叫connect  內部實現對TCP的三次握手建立連線 返回0表示成功
	return connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR))==0;
   
}

這裡的connect是阻塞的方式,如果網路問題,無法連線上伺服器,那麼connect就會等待過長的時間,所以如果不想造成這種後果,就應該把socket置為非阻塞的方式,具體操作如下:

//sockClient為客戶端的套接字,svrIP為伺服器IP,svrPort為伺服器的埠
bool TCP_Client_ConnectSocketEx(SOCKET sockClient,LPCSTR svrIP,USHORT svrPort)
{
	
if(svrIP==NULL||svrPort==0)
   return NULL;
unsigned long on = 1;
struct timeval timeout ;
fd_set r;
int ret;
 ioctlsocket(hSocket, FIONBIO, &on);//將套接字設為非阻塞,on=1為非阻塞,on=0阻塞
    SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr(svrIP);//伺服器IP,inet_addr將字串ip轉換成long型
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(svrPort);//伺服器埠,主機位元組序轉換成網路位元組序
//呼叫connect  內部實現對TCP的三次握手建立連線 返回0表示成功
connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
FD_ZERO(&r);
FD_SET(sockClient, &r);
timeout.tv_sec = 5000/1000; //連線超時5秒
<timeout.tv_usec =TimeOut%1000;
ret = select(0, 0, &r, 0, &timeout);//select阻塞5秒 連線超時返回小於等於0的值
if ( ret <= 0 )
{
closesocket( sockClient);
return false;
}
return true;
   
}



5.客戶端接受資料(從伺服器發過來的)

int RLen=0;
RLen=recv(sockClient,(char *)RecvBuffer,RecvBufferLen,0);
if(RLen<=0)
  return false;

RecvBuffer是接受到的資料,RecvBufferLen是需要一次接收的資料長度,RLen是實際接收的資料長度,客戶端如果想要不停的接受從伺服器返回的資料,就必須開個執行緒,將recv放入執行緒中接收資料

注意: 雖然這裡可以設定一次接收的資料長度RecvBufferLen,但是這個長度必須需要系統快取的支援,系統有一個接收資料的快取,這個快取負責將網路傳過來的資料放入其中,然後recv是從這個系統快取取資料,系統預設的SOCKET接受快取的大小為8688B(8.6K左右),如果sock函式recv或recvFrom從系統快取中接受資料太慢,那麼系統接受到新資料後就會將原快取給覆蓋了,這樣在次呼叫recv函式就獲取到了新資料,老資料就丟失了,這時候就要使用setsockopt方法將系統快取開到32K最合適,如下:

if(setsockopt(sockClient,SOL_SOCKET,SO_RCVBUF,(const char *)&Max_RecvFrom_Buffer_Size,sizeof(int))!=0)
{
	//設定失敗
	return INVALID_SOCKET;
}

Max_RecvFrom_Buffer_Size=32*1024B,在建立一個客戶端的套接字之後就要使用setsockopt來設定系統快取的大小了,也就是第2部做完後就要設定系統快取

6.客戶端傳送資料(給伺服器)

int Ret;
Ret =send(sockClient,SendBuffer,sendBufLen,0);
if ( Ret == SOCKET_ERROR )
{
 int err = GetLastError( );
 if ( err == WSAEWOULDBLOCK || err == WSAETIMEDOUT )
 {
				
	Sleep(1);
					
 }
 else	//網路斷了
 {
	Ret = 0;
	return false;
 }
}

SendBuffer是傳送的資料,sendBufLen是一次傳送資料的長度,這裡的長度要考慮網路最大的傳輸單元MTU=1500B,傳送的資料長度控制在1400B較好。

三.TCP服務端的程式設計

1.載入套接字(同客戶端)

2.建立服務端套接字socketService(同客戶端)

3.在服務端繫結套接字(同客戶端,但是服務端是一定要繫結套接字的,也就是開啟監聽埠)

4.將服務端的套接字設為監聽模式

bool ListenSocketEx(SOCKET socketService,UINT connectNums)
{
   if(connectNums==0)
	return listen(socketService,SOMAXCONN)==0;
   else
	return listen(socketService,connectNums)==0;

}

connectNums為客戶端連線最大數,SOMAXCONN為最大連線數

5.上面四步服務端已經將基本的初始化了,接下來是最重要的一步,開啟執行緒,然後呼叫介面accept監聽客戶端是否有連線過來,服務端其實就是一個等待的過程,等待客戶端的連線,然後處理資料,在發給客戶端

UINT ClientSocketListenProc(LPVOID lpParameter)
{
 while(true)
 {
   SOCKADDR_IN addrClient;
   int addrDataLen=sizeof(SOCKADDR);//非常重要
   SOCKET clientSock=accept(socketService,(sockaddr *)&addrClient,&addrDataLen);

 }
}

accept返回的是連線客戶端的socket,這個socket有什麼用呢??服務端建立了一個socket,但是如果很多客戶端連線服務端,服務端在傳送給客戶端資料和接收來自客戶端資料的時候使用服務端建立的這一個socket顯然不合理,因為它不知道發給哪個客戶端,這時候就需要accept返回的客戶端socket,這個返回的clientSocket是唯一的,服務端使用它來發送和接收資料,就能保證資料的正確性。所以accept返回的socket服務端需要儲存起來,使用map容器儲存,這裡不寫程式碼。

 注意:accept是阻塞模式的,它的第三個引數非常的重要,如果填的不是SOCKADDR的長度&addrDataLen,就會收到一些非法連線的客戶端,然後accept就會不停的接受204.204.204.204這個IP來的連線

6.接收客戶端發的資料

int RLen=0;
RLen=recv(acptClientSocket,(char *)RecvBuffer,RecvBufferLen,0);
if(RLen<=0)
  return false;

這裡的接收資料和客戶端一樣,注意的是recv第一個引數socket是accept返回的客戶端連線socket----acptClientSocket,不是服務端建立的socket

7.傳送資料給客戶端

int Ret;
Ret =send(acptClientSocket,SendBuffer,sendBufLen,0);
if ( Ret == SOCKET_ERROR )
{
	int err = GetLastError( );
	if ( err == WSAEWOULDBLOCK || err == WSAETIMEDOUT )
	{
				
		Sleep(1);
					
	}
	else	//網路斷了
	{
		Ret = 0;
		return false;
	}
}

這裡的傳送資料和客戶端一樣,注意的是send第一個引數socket是accept返回的客戶端連線socket----acptClientSocket,不是服務端建立的socket

相關推薦

c++網路程式設計2TCP連線概念程式設計

一.TCP建立連線的三次握手 在TCP/IP協議中,TCP協議提供可靠的連線服務,採用三次握手建立一個連線。 第一次握手:建立連線時,客戶端傳送SYN包(SYN=j)到伺服器,並進入SYN_SEND狀態,等待伺服器確認;【客戶端->服務端:SYN(j)】 第二次握手

Linux----網路程式設計(守護程序相關概念程式設計流程)

一、守護程序的相關定義 守護程序:也稱為精靈程序,執行在後臺的一種特殊程序。守護程序獨立於控制終端並且週期性的執行某種任務或者等待處理髮生的事件。所以守護程序不因為使用者、終端或其他的變化而受影響。 守護程序的特點:  1. 執行週期長                 

2.22 網路通訊4TCP廣播

服務端實現 import ( "fmt" "net" "os" "strings" ) //儲存客戶端連線, key,ip埠,value 連結物件 var onlineConnsMap = make(map[str

《Linux網路程式設計連線和麵向連線協議的區別

網路程式設計中最基本的概念就是面向連線(connection-oriented)和無連線(connectionless)協議。儘管本質上來說,兩者之間的區別並不難理解,但對那些剛剛開始進行網路程式設計的人來說,卻是個很容易混淆的問題。這個問題與上下文有些關聯:很顯然,如果兩臺計算機要進行通訊,就必須

網路程式設計TCP伺服器的簡單實現

說到TCP伺服器,就不得不提socket程式設計,我們知道,在TCP/IP協議中,“IP地址+TCP或UDP埠號”唯一標識⽹絡通訊中的唯一一個程序,“IP地址+埠號”就稱為socket。 在TCP協

《Linux網路程式設計 TCP程式設計

TCP程式設計 TCP 程式設計的 C/S 架構 基於 TCP 的網路程式設計開發分為伺服器端和客戶端兩部分,常見的核心步驟和流程如下:   TCP 客戶端程式設計 對於 TCP 客戶端程式設計流程,有點類似於打電話過程:找個可以通話的手機(socke

2.21 網路通訊3TCP互動通訊

服務端實現 import ( "fmt" "net" "os" "strings" ) func CheckErrorS(err error) { if

網路程式設計學習tcp/ip程式設計

tcp與udp的比較分析 tcp :不記錄訊息邊界 udp:記錄訊息邊界 用到的結構體 struct sockaddr_un { __SOCKADD

原創】Java併發程式設計系列2執行緒概念與基礎操作

## 【原創】Java併發程式設計系列2:執行緒概念與基礎操作 > 偉大的理想只有經過忘我的鬥爭和犧牲才能勝利實現。 本篇為【Dali王的技術部落格】Java併發程式設計系列第二篇,講講有關執行緒的那些事兒。主要內容是如下這些: - 執行緒概念 - 執行緒基礎操作 ### 執行緒概念 程序代表了執

C#學習篇2過載和重寫 虛方法和抽象方法

本篇問轉載文章,僅供學習使用。。。 過載(overload): 在同一個作用域(一般指一個類)的兩個或多個方法函式名相同,引數列表不同的方法叫做過載,它們有三個特點(俗稱兩必須一可以): 方法名必須相同 引數列表必須不相同 返回值型別可以不相同 例如: publ

網路基礎筆記_TCP/IP與Socket程式設計

一.TCP/IP   TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標準的協議集,它是為廣域網(WANs)設計的。UDP(User Data Protocol,使用者資料報協議)是與TCP相對應的

併發程式設計2認識併發程式設計的利與弊

讀完本文你將瞭解: 多執行緒的優點 1提高資源利用率 2響應更快 多執行緒的缺點 1增加資源消耗 2上下文切換的開銷 3設計編碼測試的複雜度增加 Java 記憶體模型與 CPU 記憶體簡介 Java 中的堆 Java 中的棧 計算機中的記憶體暫存器快取 多執行緒可能出現的問

網路通訊3TCP互動通訊

服務端實現 import ( "fmt" "net" "os" "strings" ) func CheckErrorS(err error) { if err != nil { fmt

網路通訊4TCP廣播

服務端實現 import ( "fmt" "net" "os" "strings" ) //儲存客戶端連線, key,ip埠,value 連結物件 var onlineConnsMap =

《SQL必知必會——第1、2sql基礎概念檢索資料》

       現在還會有這種想法,出於對技術的敬畏,在開始真正使用某項技術之前,都會翻翻相關書籍,潛心學習一下,然後在開始,所謂出生牛犢不怕虎,在軟體開發這個行業還是不提倡,一旦亂用技術,後面的坑實在難填。程式碼的改動就意味著除錯、測試,以及對生產環境的影響,是很花團隊時間和

.NET預設一個客戶端對同一個伺服器地址同時只能建立2TCP連線

做一個客戶端的測試小程式測試web service的併發處理。開始用async task做,不管建立多少個task,用netstat看同時只有兩個tcp連線。以為是async task的問題,改用BackgroundWorker和多執行緒都是同樣的問題,經google得知原來是.NET預設一個客戶端對同一個伺

網路協議》圖解 TCP 連線建立與釋放

注:TCP 連線的建立和釋放在網路協議中是比較重要的,由於本人理解也不是很透徹,歡迎各位批評指正。 前言         TCP 是面向連線的、可靠的位元組流協議。因此,在傳輸資料之前通訊雙方必須建立一個 TCP 連線,建立 TCP 連線需要在伺服器和客戶端之間進行三次握

C++併發實戰2thread::join和thread::detach

    thread::join()是個簡單暴力的方法,主執行緒等待子程序期間什麼都不能做,一般情形是主執行緒建立thread object後做自己的工作而不是簡單停留在join上。thread::join()還會清理子執行緒相關的記憶體空間,此後thread object

C# 控制元件 [2] ProgressBar (顯示百分比)

1 繼承關係 Object→MarshalByRefObject→Component→Control→ProgressBar ProgressBar表示Windows進度欄控制元件。 2 重要屬性 序號 屬性 型別 用法

Windows網路程式設計之面向非連線的Socket程式設計

byzxy,Java/C++程式設計交流群:168424095 面向非連線的Socket通訊是基於UDP的。 UDP是User Datagram Protocol的簡稱,中文名是使用者資料包協議,