1. 程式人生 > >最簡單的TCP、UDP案例及各函式的詳細解釋

最簡單的TCP、UDP案例及各函式的詳細解釋

TCP:

server

#include "stdafx.h"
#include<iostream>
#define  BUF_SZIE	 64
#include "winsock2.h"
#pragma comment(lib, "ws2_32.lib")

using namespace std;


int main(int argc, char* argv[])
{
	WSADATA			wsd;			//WSADATA變數
	SOCKET			sServer;		//伺服器套接字
	SOCKET			sClient;		//客戶端套接字
	SOCKADDR_IN		addrServ;;		//伺服器地址  SOCKADDR_IN只是SOCKADDR在TCP/IP協議下的一個特例
	char			buf[BUF_SZIE];	//接收資料緩衝區
	int				retVal;			//返回值

	//初始化套結字動態庫,載入Windows Socket動態庫DLL
	if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)//wsd返回被載入動態庫的有關資訊
	{
		cout<<"WSAStartup failed!\n";
		return 1;
	}
	if(LOBYTE(wsd.wVersion)!=2||HIBYTE(wsd.wHighVersion)!=2)
	{
		WSACleanup();//釋放套接字資源;
		return  -1;
	}
	//建立套接字
	sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	
	if(INVALID_SOCKET == sServer)
	{
		cout<<"socket failed!\n";
		WSACleanup();//釋放套接字資源;
		return  -1;
	}
	
	//伺服器套接字地址 
	addrServ.sin_family = AF_INET;
	addrServ.sin_port = htons(4999);  //從主機位元組順序轉換為網路位元組順序 host to  net long/short  相關函式htonl   ntohl() ntohs()
	addrServ.sin_addr.s_addr = INADDR_ANY;		
	//繫結套接字
	retVal = bind(sServer, (LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));
	if(SOCKET_ERROR == retVal)
	{	
		cout<<"bind failed!\n";
		closesocket(sServer);	//關閉套接字
		WSACleanup();			//釋放套接字資源;
		return -1;
	}
	
	//開始監聽 
	retVal = listen(sServer, 1);
	if(SOCKET_ERROR == retVal)
	{
		cout<<"listen failed!\n";		
		closesocket(sServer);	//關閉套接字
		WSACleanup();			//釋放套接字資源;
		return -1;
	}
	
	//接受客戶端請求
	sockaddr_in addrClient;
	int addrClientlen = sizeof(addrClient);
	sClient = accept(sServer,(sockaddr FAR*)&addrClient, &addrClientlen);
	if(INVALID_SOCKET == sClient)
	{
		cout<<"accept failed!\n";		
		closesocket(sServer);	//關閉套接字
		WSACleanup();			//釋放套接字資源;
		return -1;
	}
	
	//接收客戶端資料
	ZeroMemory(buf, BUF_SZIE);
	retVal = recv(sClient, buf, BUF_SZIE, 0);
	if (SOCKET_ERROR == retVal)
	{
		cout<<"recv failed!\n";		
		closesocket(sServer);	//關閉套接字
		closesocket(sClient);	//關閉套接字		
		WSACleanup();			//釋放套接字資源;
		return -1;
	}

	cout<< buf;	//輸出"MyTCP"	

	//退出
	closesocket(sServer);	//關閉套接字,同時關閉網路連線
	closesocket(sClient);	//關閉套接字
	WSACleanup();			//釋放套接字資源;


	system("pause");
	return 0;
}

client

#include "stdafx.h"
#include<iostream>
#define	BUF_SZIE	64
#include "winsock2.h"
#pragma comment(lib, "ws2_32.lib")

using namespace std;

int main(int argc, char* argv[])
{

	WSADATA			wsd;			//WSADATA變數
	SOCKET			sHost;			//伺服器套接字
	SOCKADDR_IN		servAddr;		//伺服器地址
	char			buf[BUF_SZIE];	//接收資料緩衝區
	int				retVal;			//返回值
	
	//初始化套結字動態庫
	if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
	{
		cout<<"WSAStartup failed!\n";
		return -1;
	}
	
	//建立套接字
	sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	
	if(INVALID_SOCKET == sHost)
	{
		cout<<"socket failed!\n";
		WSACleanup();//釋放套接字資源
		return  -1;
	}

	//設定伺服器地址
	servAddr.sin_family =AF_INET;
	servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //實現網路地址和“.”間隔字串地址之間的轉換
	//char* pAddr=inet_ntoa(*(in_addr*)(&servAddr.sin_addr.s_addr));
	servAddr.sin_port = htons((short)4999);
	int	nServAddlen  = sizeof(servAddr);

	//連線伺服器
	retVal=connect(sHost,(LPSOCKADDR)&servAddr, sizeof(servAddr));	
	if(SOCKET_ERROR == retVal)
	{
		cout<<"connect failed!\n";	
		closesocket(sHost);	//關閉套接字
		WSACleanup();		//釋放套接字資源
		return -1;
	}
	
	//向伺服器傳送資料
	ZeroMemory(buf, BUF_SZIE);
	strcpy(buf, "MyTCP");
	retVal = send(sHost, buf, strlen(buf), 0);
	if (SOCKET_ERROR == retVal)
	{
		cout<<"send failed!\n";
		closesocket(sHost);	//關閉套接字
		WSACleanup();		//釋放套接字資源
		return -1;
	}
	
	//退出
	closesocket(sHost);	//關閉套接字
	WSACleanup();		//釋放套接字資源


	system("pause");
	return 0;
}

UDP:

server

#include "stdafx.h"
#include<iostream>
#define  BUF_SZIE	 64
#include "winsock2.h"
#pragma comment(lib, "ws2_32.lib")

using namespace std;


int main(int argc, char* argv[])
{
	
	WSADATA			wsd;			//WSADATA變數
	SOCKET			s;				//套接字
	SOCKADDR_IN		servAddr;		//伺服器地址
	char			buf[BUF_SZIE];	//接收資料緩衝區
	
	//初始化套結字動態庫
	if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
	{
		cout<<"WSAStartup failed!\n";
		return 1;
	}
	
	//建立套接字
	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s == INVALID_SOCKET)
	{
		cout<<"socket() failed; %d\n"<<WSAGetLastError();
		WSACleanup();//釋放套接字資源
		return 1;
	}
	
	int nErrCode;	//返回值
	int nBufLen;	//接收資料緩衝區大小
	int nOptlen = sizeof(nBufLen);
	//獲取接收資料緩衝區大小
	nErrCode = getsockopt(s, SOL_SOCKET, SO_RCVBUF,(char*)&nBufLen, &nOptlen);
	if (SOCKET_ERROR == nErrCode)
	{
		//處理失敗
	}

	//設定接收資料緩衝區為原來的10倍
	nBufLen *= 10;
	nErrCode = setsockopt(s, SOL_SOCKET, SO_RCVBUF,(char*)&nBufLen, nOptlen);
	if (SOCKET_ERROR == nErrCode)
	{
		//失敗處理
	}

	//檢查設定系統接收資料緩衝區是否成功
	int uiNewRcvBuf;
	getsockopt(s, SOL_SOCKET, SO_RCVBUF,(char*)&uiNewRcvBuf, &nOptlen);
	if (SOCKET_ERROR == nErrCode || uiNewRcvBuf != nBufLen)
	{
		//失敗處理
	}


	//伺服器地址
	servAddr.sin_family = AF_INET;
	servAddr.sin_port = htons((short)5000);			//埠
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);	//IP
	
	//繫結
	if (bind(s, (SOCKADDR *)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
	{
		cout<<"bind() failed: %d\n"<<WSAGetLastError();
		closesocket(s);	//關閉套接字
		WSACleanup();	//釋放套接字資源
		return 1;
	}	
	
	//接收資料
	SOCKADDR_IN clientAddr;
	int nClientLen = sizeof(clientAddr);
	ZeroMemory(buf, BUF_SZIE);
	if(recvfrom(s, buf, BUF_SZIE, 0, (SOCKADDR*)&clientAddr, &nClientLen) == SOCKET_ERROR)
	{
		cout<<"recvfrom() failed: %d\n"<<WSAGetLastError();
		closesocket(s);	//關閉套接字
		WSACleanup();	//釋放套接字資源
		return 1;
	}	

	cout<<buf;//輸出"MyUDP"
	
	closesocket(s);		//關閉套接字
	WSACleanup();		//釋放套接字資源


	system("pause");
	return 0;
}

client

#include "stdafx.h"
#include<iostream>
#define  BUF_SZIE	 64
#include "winsock2.h"
#pragma comment(lib, "ws2_32.lib")

using namespace std;

int main(int argc, char* argv[])
{
	
	WSADATA        wsd;				//WSADATA變數
	SOCKET         s;				//套接字
	SOCKADDR_IN    servAddr;		//伺服器地址
	char			buf[BUF_SZIE];	//傳送資料緩衝區
	
	//初始化套結字動態庫
	if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
	{
		cout<<"WSAStartup failed!\n";
		return 1;
	}

	//建立套接字
	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s == INVALID_SOCKET)
	{
		cout<<"socket() failed; %d\n"<<WSAGetLastError();
		WSACleanup();//釋放套接字資源
		return 1;
	}
	
	//賦值“MyUDP”
	ZeroMemory(buf, BUF_SZIE);
	strcpy(buf, "MyUDP");
	
	//伺服器地址
	servAddr.sin_family =AF_INET;
	servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	servAddr.sin_port = htons((short)5000);
	int	nServAddlen  = sizeof(servAddr);

	//傳送資料
	if(sendto(s, buf, BUF_SZIE, 0, (SOCKADDR*)&servAddr, nServAddlen) == SOCKET_ERROR)
	{
		cout<<"recvfrom() failed: %d\n"<<WSAGetLastError();
		closesocket(s);	//關閉套接字
		WSACleanup();	//釋放套接字資源
		return 1;
	}
	
	closesocket(s);		//關閉套接字	  
	WSACleanup();		//釋放套接字資源

	system("pause");
	return 0;
}

套接字選項

建立套接字後,通過套接字選項對其各種屬性進行設定,便可對套接字的行為產生影響。
getsockopt()函式用於獲取套接字選項資訊,宣告如下

int getsockopt(SOCKET s,int level,int optname,char FAR* optval,int FAR* optlen);
s:      套接字
level:  選項級別,有SOL_SOCKET和IPPROTO_TCP兩個級別
optname:套接字選項名稱
optval: 接收資料緩衝區,該引數返回套接字選項名稱對應的值。
optlen: 緩衝區大小

setsockopt()函式用於設定套接字選項,宣告如下int setsockopt(SOCKET s,int level,int optname, const char FAR* optval,int FAR* optlen);
s:      套接字
level:  選項級別,有SOL_SOCKET和IPPROTO_TCP兩個級別
optname:套接字選項名稱
optval: 該引數用於設定套接字選項的值。
optlen: 緩衝區大小

SOL_SOCKET選項級別

Socket選項

型別

預設值

說明

SO_ACCEPTCON

BOOL

FALSE

套介面正在監聽

SO_BROADCAST

BOOL

FALSE

允許套介面傳送廣播資訊。

SO_DEBUG

BOOL

FALSE

記錄除錯資訊。

SO_DONTLINGER

BOOL

FALSE

不要因為資料未傳送就阻塞關閉操作。

SO_DONTROUTE

BOOL

FALSE

禁止選徑;直接傳送。

SO_ERROR

int

0

獲取錯誤狀態並清除。

SO_KEEPALIVE 

BOOL

FALSE

傳送“保持活動”包。

SO_LINGER

struct linger FAR*

0

關閉時有未傳送資料,則逗留。

SO_OOBINLINE

BOOL

FALSE

在常規資料流中接收帶外資料。

SO_RCVBUF

int

設定或者獲取接收緩衝區的大小。

SO_REUSEADDR

BOOL

FALSE

允許套介面和一個已在使用中的地址捆綁

SO_SNDBUF

int

設定或者獲取傳送緩衝區的大小。

SO_TYPE

int

返回套接字型別,如SOCK_DGRAM,SOCK_STREAM。

SO_SNDTIMEO

Int

設定或獲取套接字在傳送資料的超時時間。

SO_RCVTIMEO

int

設定或獲取套接字在接收資料的超時時間。

TCP_NODELAY

BOOL

FALSE

禁止傳送合併的Nagle演算法

詳細用法
1.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));


3.在send(),recv()過程中有時由於網路狀況等原因,發收不能預期進行,而設定收發時限:

int nNetTimeout=1000;//1秒
//傳送時限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收時限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

4.在send()的時候,返回的是實際傳送出去的位元組(同步)或傳送到socket緩衝區的位元組
(非同步);系統預設的狀態傳送和接收一次為8688位元組(約為8.5K);在實際的過程中傳送資料
和接收資料量比較大,可以設定socket緩衝區,而避免了send(),recv()不斷的迴圈收發:

// 接收緩衝區
int nRecvBuf=32*1024;//設定為32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//傳送緩衝區
int nSendBuf=32*1024;//設定為32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

5. 如果在傳送資料的時,希望不經歷由系統緩衝區到socket緩衝區的拷貝而影響程式的效能:

int nZero=0; setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char*)&nZero,sizeof(nZero));

6.同上在recv()完成上述功能(預設情況是將socket緩衝區的內容拷貝到系統緩衝區):

int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));


7.一般在傳送UDP資料報的時候,希望該socket傳送的資料具有廣播特性:

BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));


8.在client連線伺服器過程中,如果處於非阻塞模式下的socket在connect()的過程中可以設定connect()延時,
直到accpet()被呼叫(本函式設定只有在非阻塞的過程中有顯著的作用,在阻塞的函式呼叫中作用不大)

BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));



9.如果在傳送資料的過程中(send()沒有完成,還有資料沒傳送)而呼叫了closesocket(),
以前我們一般採取的措施是"從容關閉"shutdown(s,SD_BOTH),但是資料是肯定丟失了,
如何設定讓程式滿足具體應用的要求(即讓沒發完的資料傳送出去後在關閉socket)

struct linger {
    u_short l_onoff;
    u_short l_linger;
};
linger m_sLinger;
m_sLinger.l_onoff=1;//(在closesocket()呼叫,但是還有資料沒傳送完畢的時候容許逗留)
// 如果m_sLinger.l_onoff=0;則功能和2.)作用相同;
m_sLinger.l_linger=5;//(容許逗留的時間為5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));