UDP 套接字
工作流程
UDP 用戶數據報協議提供的是無連接的、不可靠的數據傳輸服務,但是不需要像 TCP 那樣建立連接,所以傳輸效率高。
數據收發
UDP 使用的發送數據的函數是 sendto(),比較不同的是發送數據時需要填寫接收方的地址。
int
WSAAPI
sendto(
_In_ SOCKET s,
_In_reads_bytes_(len) const char FAR * buf,
_In_ int len,
_In_ int flags,
_In_reads_bytes_(tolen) const struct sockaddr FAR * to,
_In_ int tolen
);
接收數據的函數是 recvfrom(),接收數據時需要把發送方的地址存出來。
int
WSAAPI
recvfrom(
_In_ SOCKET s,
_Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
_In_ int len,
_In_ int flags,
_Out_writes_bytes_to_opt_(*fromlen, *fromlen) struct sockaddr FAR * from,
_Inout_opt_ int FAR * fromlen
);
sendto() 和 recvfrom() 的參數相似:
參數 | 說明 |
---|---|
s | 用來發送數據的套接字 |
buf | 指向發送數據的緩沖區 |
len | 要發送數據的長度 |
flags | 一般指定為 0 |
to/from | 指向一個包含目標地址和端口號的 sockaddr in 結構 |
tolen/fromlen | sockaddr in 結構的大小 |
程序編寫
功能設計
模擬實現 TCP 協議通信過程,要求編程實現服務器端與客戶端之間雙向數據傳遞。也就是在一條 TCP 連接中,客戶端和服務器相互發送一條數據即可。
initsock.h
#include <winsock2.h>
#pragma comment(lib, "WS2_32") // 鏈接到 WS2_32.lib
class CInitSock
{
public:
/*CInitSock 的構造器*/
CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
{
// 初始化WS2_32.dll
WSADATA wsaData;
WORD sockVersion = MAKEWORD(minorVer, majorVer);
if (::WSAStartup(sockVersion, &wsaData) != 0)
{
exit(0);
}
}
/*CInitSock 的析構器*/
~CInitSock()
{
::WSACleanup();
}
};
服務器
#include "initsock.h"
#include <iostream>
using namespace std;
CInitSock initSock; // 初始化Winsock庫
int main()
{
// 創建套接字
SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s == INVALID_SOCKET)
{
cout << "Failed socket()" << endl;
return 0;
}
// 填充sockaddr_in結構
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(4567);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
// 綁定這個套接字到一個本地地址
if (::bind(s, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
cout << "Failed bind()" << endl;
return 0;
}
cout << "服務端已啟動!\n" << endl;
// 進入循環
char buff[1024];
sockaddr_in addr;
int nLen = sizeof(addr);
while (TRUE)
{
//接收數據
int nRecv = ::recvfrom(s, buff, 1024, 0, (sockaddr*)&addr, &nLen);
if (nRecv > 0)
{
buff[nRecv] = '\0';
cout << " 接收到" << ::inet_ntoa(addr.sin_addr) << "的數據:" << buff << endl;
}
// 發送數據
char szText[] = "你好,客戶端!";
::sendto(s, szText, strlen(szText), 0, (sockaddr*)&addr, sizeof(addr));
}
::closesocket(s);
}
客戶端
#include "initsock.h"
#include <iostream>
using namespace std;
CInitSock initSock; // 初始化Winsock庫
int main()
{
// 創建套接字
SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s == INVALID_SOCKET)
{
printf("Failed socket() %d \n", ::WSAGetLastError());
return 0;
}
// 也可以在這里調用bind函數綁定一個本地地址
// 否則系統將會自動安排
// 填寫遠程地址信息
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(4567);
//填寫服務器程序所在機器的IP地址
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
// 發送數據
char szText[] = "你好,服務器!";
::sendto(s, szText, strlen(szText), 0, (sockaddr*)&addr, sizeof(addr));
char buff[1024];
sockaddr_in client_addr;
int nLen = sizeof(client_addr);
int nRecv = ::recvfrom(s, buff, 1024, 0, (sockaddr*)&client_addr, &nLen);
if (nRecv > 0)
{
buff[nRecv] = '\0';
cout << " 接收到" << ::inet_ntoa(client_addr.sin_addr) << "的數據:" << buff << endl;
}
::closesocket(s);
return 0;
}
運行效果
廣播通信
廣播
利用廣播(broadcast)可以將數據發送給本地子網上的每個機器,同時需要有一些線程在機器上監聽到來的數據。廣播的缺點是如果多個進程都發送廣播數據,網絡就會阻塞,網絡性能便會受到影響。
廣播模式設置
套接字創建之后,可以使用套接字選項和 ioctl 命令操作它的屬性,以改變套接字的默認行為。I/O 控制命令縮寫為 ioctl,它也影響套接字的行為,獲取和設置套接字選項的函數分別是 getsockopt 和 setsockopt,函數調用出錯時返回 SOCKET ERROR。
int
WSAAPI
setsockopt(
_In_ SOCKET s,
_In_ int level,
_In_ int optname,
_In_reads_bytes_opt_(optlen) const char FAR * optval,
_In_ int optlen
);
int WSAAPI getsockopt(_In_ SOCKET s, _In_ int level, _In_ int optname, _In_reads_bytes_opt_(optlen) const char FAR * optval, _In_ int optlen);
參數 | 說明 |
---|---|
s | 套接字句柄 |
level | 指定此選項被定義在哪個級別,如 SOL SOCKET、IPPROTO_TCP、IPPROTO_IP 等 |
optname | 套接字選項名稱,如 SO ACCEPTCONN |
optval | 指定一個緩沖區,所請求的選項的值將會被返回到這里 |
optlen | 指定上面 optval 所指緩沖區的大小,返回所需的大小 |
SOBROADCAST 選項設置套接字傳輸和接收廣播消息,如果給定套接字已經被設置為接收或者發送廣播數據,查詢此套接字選項將返回TRUE,此選項對於那些不是SOCK_STREAM類型的套接字有效。 |
::setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, sizeof(BOOL));
代碼編寫
功能設計
實現服務器端與客戶端之間廣播數據傳遞,服務器端向所有客戶端發送“今天是個好日子!”廣播消息,客戶端收到並在本地顯示。
initsock.h
#include <winsock2.h>
#pragma comment(lib, "WS2_32") // 鏈接到 WS2_32.lib
class CInitSock
{
public:
/*CInitSock 的構造器*/
CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
{
// 初始化WS2_32.dll
WSADATA wsaData;
WORD sockVersion = MAKEWORD(minorVer, majorVer);
if (::WSAStartup(sockVersion, &wsaData) != 0)
{
exit(0);
}
}
/*CInitSock 的析構器*/
~CInitSock()
{
::WSACleanup();
}
};
Sender
對於 UDP 來說存在一個特定的廣播地址 255.255.255.255,廣播數據都應該發送到這里。發送方程序在創建套接字后使用 setsockopt 函數打開 SO_BROADCAST選項,然后設置廣播地址 255.255.255.255,使用 sendto 函數向端口號 54321 不斷發送廣播數據。
#include "initsock.h"
#include <iostream>
#include <windows.h>
using namespace std;
CInitSock theSock;
int main()
{
SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);
// 有效SO_BROADCAST選項
BOOL bBroadcast = TRUE;
::setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, sizeof(BOOL));
// 設置廣播地址和廣播端口號
SOCKADDR_IN bcast;
bcast.sin_family = AF_INET;
bcast.sin_port = htons(54321);
bcast.sin_addr.s_addr = INADDR_BROADCAST;
// 發送廣播
cout << " 開始向端口發送廣播數據... \n" << endl;
char sz[] = "今天是個好日子! \r\n";
while (TRUE)
{
time_t now = time(0); // 基於當前系統的當前日期/時間
char* dt = ctime(&now);
::sendto(s, sz, strlen(sz), 0, (sockaddr*)&bcast, sizeof(bcast));
cout << dt << "發送廣播:" << sz << endl;
::Sleep(5000);
}
return 0;
}
Recver
服務器需要使用 recvfrom 函數,向端口號 54321 接收廣播數據。
#include "initsock.h"
#include <iostream>
#include <windows.h>
using namespace std;
CInitSock theSock;
int main()
{
SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);
// 首先要綁定一個本地地址,指明廣播端口號
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(54321);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (::bind(s, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
{
cout << " bind() failed!" << endl;
return 0;
}
// 接收廣播
cout << " 開始在端口接收廣播數據... \n" << endl;
SOCKADDR_IN addrRemote;
int nLen = sizeof(addrRemote);
char sz[256];
while (TRUE)
{
int nRet = ::recvfrom(s, sz, 256, 0, (sockaddr*)&addrRemote, &nLen);
if (nRet > 0)
{
sz[nRet] = '\0';
time_t now = time(0); // 基於當前系統的當前日期/時間
char* dt = ctime(&now);
cout << dt << "接收到廣播:" << sz << endl;
}
}
return 0;
}
運行效果
參考資料
《Windows 網絡與通信編程》,陳香凝 王燁陽 陳婷婷 張錚 編著,人民郵電出版社