TCP和UDP屬於傳輸層協議。其中TCP提供IP環境下的數據可靠傳輸,它事先為要發送的數據開辟好連接通道(三次握手),然后再進行數據發送;而UDP則不為IP提供可靠性,一般用於實時的視頻流傳輸,像rtp、rtsp就是建立在udp的基礎上的。
首先談談tcp socket
tcp簡單的三次握手過程如圖,
SYN(Synchronize Sequence Numbers):同步標志
ACK(Acknowledgement Number) :確認標志
圖中可以看出,三次握手的過程是在c的connect()和s的bind()、listen()、accept()函數中完成的,這樣開辟了相對可靠的連接通道,來傳輸數據。
UDP的socket編程過程如下圖所示:
下面翠花上代碼啦!
服務端:
- #include <stdio.h>
- #include <Winsock2.h> //windows socket的頭文件
- #pragma comment( lib, "ws2_32.lib" )// 鏈接Winsock2.h的靜態庫文件
- void main()
- {
- //初始化winsocket
- WORD wVersionRequested;
- WSADATA wsaData;
- int err;
- wVersionRequested = MAKEWORD( 1, 1 );//第一個參數為低位字節;第二個參數為高位字節
- err = WSAStartup( wVersionRequested, &wsaData );//對winsock DLL(動態鏈接庫文件)進行初始化,協商Winsock的版本支持,並分配必要的資源。
- if ( err != 0 )
- {
- return;
- }
- if ( LOBYTE( wsaData.wVersion ) != 1 ||HIBYTE( wsaData.wVersion ) != 1 )//LOBYTE()取得16進制數最低位;HIBYTE()取得16進制數最高(最左邊)那個字節的內容
- {
- WSACleanup( );
- return;
- }
- SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);//創建socket。AF_INET表示在Internet中通信;SOCK_STREAM表示socket是流套接字,對應tcp;0指定網絡協議為TCP/IP
- SOCKADDR_IN addrSrv;
- addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY); //htonl用來將主機字節順序轉換為網絡字節順序(to network long)
- //INADDR_ANY就是指定地址為0.0.0.0的地址,
- //表示不確定地址,或“任意地址”。”
- addrSrv.sin_family=AF_INET;
- addrSrv.sin_port=htons(4000);//htons用來將主機字節順序轉換為網絡字節順序(to network short)
- bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//將本地地址綁定到所創建的socket上,以使在網絡上標識該socket
- listen(sockSrv,5);//socket監聽,准備接受連接請求。
- SOCKADDR_IN addrClient;
- int len=sizeof(SOCKADDR);
- while(1)
- {
- SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);//為一個連接請求提供服務。addrClient包含了發出連接請求的客戶機IP地址信息;返回的新socket描述服務器與該客戶機的連接
- char sendBuf[50];
- sprintf(sendBuf,"Welcome %s to here!",inet_ntoa(addrClient.sin_addr));//inet_ntoa網絡地址轉換轉點分十進制的字符串指針
- send(sockConn,sendBuf,strlen(sendBuf)+1,0);
- char recvBuf[50];
- recv(sockConn,recvBuf,50,0);
- printf("%s\n",recvBuf);
- closesocket(sockConn);
- Sleep(2000);//2000毫秒
- }
- WSACleanup();
- }
客戶端:
- #include <stdio.h>
- #include <Winsock2.h>
- #pragma comment( lib, "ws2_32.lib" )
- void main()
- {
- WORD wVersionRequested;
- WSADATA wsaData;
- int err;
- wVersionRequested = MAKEWORD( 1, 1 );//第一個參數為低位字節;第二個參數為高位字節
- err = WSAStartup( wVersionRequested, &wsaData );//對winsock DLL(動態鏈接庫文件)進行初始化,協商Winsock的版本支持,並分配必要的資源。
- if ( err != 0 )
- {
- return;
- }
- if ( LOBYTE( wsaData.wVersion ) != 1 ||HIBYTE( wsaData.wVersion ) != 1 )//LOBYTE()取得16進制數最低位;HIBYTE()取得16進制數最高(最左邊)那個字節的內容
- {
- WSACleanup( );
- return;
- }
- for(int index=0;;index++)
- {
- SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);
- SOCKADDR_IN addrClt;//需要包含服務端IP信息
- addrClt.sin_addr.S_un.S_addr=inet_addr("192.168.0.30");// inet_addr將IP地址從點數格式轉換成網絡字節格式整型。
- addrClt.sin_family=AF_INET;
- addrClt.sin_port=htons(4000);
- connect(sockClient,(SOCKADDR*)&addrClt,sizeof(SOCKADDR));//客戶機向服務器發出連接請求
- char recvBuf[50];
- recv(sockClient,recvBuf,50,0);
- printf("my reply is : %s\n",recvBuf);
- char sendBuf[50];
- sprintf(sendBuf,"%3d,",index);
- strcat(sendBuf,"server node of: yaopeng");
- send(sockClient,sendBuf,strlen(sendBuf)+1,0);
- closesocket(sockClient);
- Sleep(2000);
- }
- WSACleanup();
- }
對於tcp socket,有幾點需要注意:
一、TCP的TIME_WAIT狀態(等待客戶端的相應)
注* TIME_WAIT 狀態最大保持時間是2 * MSL,也就是1-4分鍾(MSL是最大分段生存期,指明TCP報文在Internet上最長生存時間)
當服務器端socket綁定本地地址並占用了端口,此時如果匆忙結束;或者連接的服務器異常退出,這個時候被占用的端口不能馬上釋放,需要TIME_WAIT。即便調用closesocket()一般也不會立即關閉socket,仍可繼續重用該socket。所以重新啟動服務器時可能會出現問題。例如MFC中在子窗口中實現socket通信,那么關閉子窗口再打開就會出問題了。
解決方法是在bind()之前添加setsockopt()函數,解除端口綁定。
介紹setsockopt()之前我們再來回顧一下三次握手協議的具體流程:
第一次握手:建立連接時,客戶端發送syn包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。
完成三次握手,客戶端與服務器開始傳送數據。
setsockopt()使用方法如下:
1. 如果在已經處於 ESTABLISHED狀態下的socket(一般由端口號和標志符區分)調用closesocket(一般不會立即關閉而經歷TIME_WAIT的過程)后想繼續重用該socket:
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
2. 如果要已經處於連接狀態的soket在調用closesocket后強制關閉,不經歷TIME_WAIT的過程:
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
更多setsockopt()函數用例可參考百度百科:http://baike.baidu.com/view/569217.htm
二、對於大型文件,一般需要將其剁碎了一部分一部分的傳。TCP不能保證接收方順序的收到包,對於需要實時顯示的文件可以在發送方發出包后設置來自接收方的響應,即對方收到前一個包后再發送下一個包。
目前就這么多,各位看官有其他的注意事項拜托請留言補充,小弟感激啊。
下面簡單說下UDP socket
UDP不能保證雙方的可靠連接,容易出現丟包現象。
UDP的socket編程過程如下圖所示:
上代碼了,哈哈。
服務端:
- #include <stdio.h>
- #include <Winsock2.h>
- #pragma comment( lib, "ws2_32.lib" )
- void main()
- {
- WORD wVersionRequested;
- WSADATA wsaData;
- int err;
- wVersionRequested = MAKEWORD( 1, 1 );
- err = WSAStartup( wVersionRequested, &wsaData );
- if ( err != 0 ) {
- return;
- }
- if ( LOBYTE( wsaData.wVersion ) != 1 ||
- HIBYTE( wsaData.wVersion ) != 1 ) {
- WSACleanup( );
- return;
- }
- SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);
- int len=sizeof(SOCKADDR);
- SOCKADDR_IN from;
- SOCKADDR_IN local;
- local.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
- local.sin_family=AF_INET;
- local.sin_port=htons(27015);
- int a = bind(sockSrv,(SOCKADDR*)&local,len);
- while(1)
- {
- char recvBuf[50];
- recvfrom(sockSrv,recvBuf,50,0,(SOCKADDR*)&from,&len);//from收到客戶端的IP信息
- printf("%s\n",recvBuf);
- printf("%s\n",inet_ntoa(local.sin_addr));
- char sendBuf[50];
- sprintf(sendBuf,"Welcome %s to here!",inet_ntoa(from.sin_addr));
- sendto(sockSrv,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&from,len);
- Sleep(2000);
- }
- closesocket(sockSrv);
- WSACleanup();
- }
客戶端:
- #include <stdio.h>
- #include <Winsock2.h>
- #pragma comment( lib, "ws2_32.lib" )
- void main()
- {
- WORD wVersionRequested;
- WSADATA wsaData;
- int err;
- wVersionRequested = MAKEWORD( 1, 1 );
- err = WSAStartup( wVersionRequested, &wsaData );
- if ( err != 0 ) {
- return;
- }
- if ( LOBYTE( wsaData.wVersion ) != 1 ||
- HIBYTE( wsaData.wVersion ) != 1 ) {
- WSACleanup( );
- return;
- }
- for(int index=0;;index++)
- {
- SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);
- int len = sizeof(SOCKADDR);
- SOCKADDR_IN local;
- local.sin_addr.S_un.S_addr=inet_addr("192.168.0.30");
- local.sin_family=AF_INET;
- local.sin_port=htons(27015);
- char sendBuf[30];
- sprintf(sendBuf,"%3d,",index);
- strcat(sendBuf,"server node of: yaopeng");
- sendto(sockClient,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&local,len);
- char recvBuf[50];
- recvfrom(sockClient,recvBuf,50,0,(SOCKADDR*)&local,&len);
- printf("my reply is : %s\n",recvBuf);
- printf("%s\n",inet_ntoa(local.sin_addr));
- closesocket(sockClient);
- Sleep(2000);
- WSACleanup();
- }
- }
完了。關於socket編程還有很多函數沒有涉及,急待跟進完善。歡迎大家給我留言!