C++ Socket資料整理
Server 端執行流程:
Step1. link Winsock Library(windows環境下)
Step2. 初始化 Windows Sockets DLL(windows環境下)
Step3. 使用 socket() 建立 socket descriptor
Step4. 設定 Server 位址資訊
Step5. 設定 listen() -> 等待連線 -> 接受連線 accect()
Step6. 傳送接收資料
Client 端執行流程:
Step1. link Winsock Library(windows環境下)
Step2. 初始化 Windows Sockets DLL(windows環境下)
Step3. 使用 socket() 建立 socket descriptor
Step4. 設定 Server 位址資訊
Step5. 連線到 Server 端 connect()
Step6. 傳送接收資料
[server端] [client端]
Step1. 在 Windows 使用 Socket 需要 link Winsock Library。
link方式:
專案的「屬性」 ->「組態屬性」->「連結器」->「輸入」->「其他相依性」加入 wsock32.lib 或 Ws2_32.lib
也可以在程式中,使用以下方式加入#pragma comment(lib, "wsock32.lib") 或 #pragma comment(lib, "Ws2_32.lib")
wsock32.lib 和 Ws2_32.lib 的區別:
wsock32.lib 是較舊的 1.1 版本,Ws2_32.lib 是較新的 2.0 版本。
wsock32.lib 跟 winsock.h 一起使用,Ws2_32.lib 跟 WinSock2.h 一起使用。
winsock.h 和 WinSock2.h 不能同時使用,WinSock2.h 是用來取代 winsock.h,而不是擴展 winsock.h。
[server端] [client端]
Step2. 初始化 Windows Sockets DLL。
範例:
WSAData wsaData;
WORD version = MAKEWORD(2, 2); // 版本
int iResult = WSAStartup(MAKEWORD(2,2), &wsaData); // 成功回傳 0
if (iResult != 0) {
// 初始化Winsock 失敗
}
[server端] [client端]
Step3. 建立socket描述符 (socket descriptor)
函式 : SOCKET socket (int af,int type, int protocol);
Int af : 使用何種通訊家族.
例如:
AF_INET:使用IPv4
AF_INET6:使用IPv6
Int type: the type specification for the new socket.
- 能使用的值跟第1個參數有關.
- 例如 :
SOCKET_STREAM : 使用 TCP 協議
SOCKET_DGRAM : 使用UDP協議
Int protocol : The protocol to be used.
- 能用的值跟前面兩個參數有關
- 例如:
IPPROTO_TCP : 使用TCP
IPPROTO_UDP : 使用UDP
成功回傳 socket descriptor,失敗回傳INVALID_SOCKET
範例:
SOCKET sListen = INVALID_SOCKET;
sListen= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sListen== INVALID_SOCKET) {
// 建立失敗
}
[server端][client端]
Step4. 設定位址資訊的資料 ( SOCKADDR_IN)
結構: 使用IP4格式結構struct sockaddr_in (in 表示 internet)設定internet位址資訊。
範例:
SOCKADDR_IN addr;
memset (&addr, 0, sizeof (addr)) ; // 清空,將資料設為 0
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 設定 IP,後面解釋 inet_addr()
// addr.sin_addr.s_addr = INADDR_ANY; // 若設定 INADDR_ANY 表示任何 IP
addr.sin_family = AF_INET;
addr.sin_port = htons(1234); // 設定 port,後面解釋 htons()
struct sockaddr_in : IP4格式使用
struct sockaddr_in6: IP6格式使用
struct sockaddr : 通用格式
struct sockaddr_un : UNIX domain 格式
htons()
使用 htons() 是因為網路位元順序 (Network Byte Order, 縮寫NBO) 可能跟電腦主機位元順序(Host Byte Order, 縮寫HBO)不同,所以需要要函式來轉換,以增加移植性。
htons():"h(host)" "to" "n(network)" "s(short)",Host to Network Short,將 short (2byte) 資料順序從 host 轉換至 network。
htonl():Host to Network Long,將 long(4byte) 資料順序從 host 轉換至 network。
ntohs():Network to Host Short,將 short (2byte) 資料順序從 network 轉換至 host。
ntohl():Network to Host Long,將 long(4byte) 資料順序從 network 轉換至 host。
Inet_addr()
將 IP 轉換為 unsigned long 格式,轉換後,已是網路位元順序 (Network Byte Order,所以不用再使用 htons()
[Server端]
Step5. 綁定socket的位址資訊(bind)
函式:int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
int sockfd:socket() 函式回傳的 socket descriptor
struct sockaddr *my_addr:用來通訊的位址資料(IP、PORT)
int addrlen:位址長度 ,sizeof(my_addr)
綁定成功回傳 0,失敗回傳 SOCKET_ERROR
範例:
// addr 為 sockaddr_in 結構,強制轉型為 sockaddr 結構
int r = bind(sListen, (SOCKADDR*)&addr, sizeof(addr));
assert (r != SOCKET_ERROR);
[server端]
Step6. 監聽連線 (listen)。
函式:int listen(SOCKET s, int backlog)
SOCKET s:socket() 函式回傳的 socket descriptor。
int backlog:最大可監聽多少連線(佇列、排隊)。設定 SOMAXCONN 表示系統最大值。
成功回傳 0,失敗回傳 SOCKET_ERROR。
connection-oriented ( SOCK_STREAM ) 的 server 程式端程式才使用。
範例:
// addr 為 sockaddr_in 結構,強制轉型為 sockaddr 結構
int r = listen(sListen, SOMAXCONN);
assert (r != SOCKET_ERROR);
[server端]
Step7.處理連線 (accept)。
函式:SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen)
SOCKET s:socket() 函式回傳的 socket descriptor。
struct sockaddr *addr:結果參數,預先配置 SOCKADDR 結構的指標,用來存放客戶端位址,不存放可設置為 NULL。
int *addrlen:結果參數,第二個參數的大小,不存放也可以設為 NULL。
成功回傳 socket descriptor,失敗回傳 INVALID_SOCKET,可用 WSAGetLastError() 取得 error code。
所以成功時,會產生一個新的 socket descriptor,而原本的 socket descriptor 持續監聽原來的端口。
connection-oriented ( SOCK_STREAM ) 的 server 程式端程式才使用。
範例:
SOCKET sConnect;
struct sockaddr_in clientAddr; // client 端位址資訊
int clientAddrLen = sizeof(clientAddr);
sConnect = accept(sListen, (SOCKADDR*)&clientAddr, &clientAddrLen);
// sConnect = accept(sListen, NULL, NULL);
if (sConnect != INVALID_SOCKET)
{
// 有 client 端成功連線過來
printf("server: got connection from %s", inet_ntoa(clientAddr.sin_addr));
}
[client端]
Step8. 連線到 socket Server。
函式:int connect(SOCKET s, const struct sockaddr *name, int namelen)
SOCKET s:socket() 函式回傳的 socket descriptor。
const struct sockaddr *name:Server 端的位址資料。
int namelen:第二個參數的大小。
成功回傳 0,失敗回傳 SOCKET_ERROR,可用 WSAGetLastError() 取得 error code。
int r = connect(sConnect, (SOCKADDR*)&addr, sizeof(addr));
if(r != SOCKET_ERROR){
// 連線成功
}
[server端][client端]
Step9. 傳送訊息
函式 1:int send(SOCKET s, const char *buf, int len, int flags)
函式 2:int sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen)
send() 和 sendto() 的差異在於,send() 只能用在 connection-oriented ( SOCK_STREAM ) 的連線,所以 sendto() 比 send() 多最後兩個參數,用來指定目的地的位址資訊。
SOCKET s:socket() 函式回傳的 socket descriptor。
const char *buf:訊息的指標。
int len:訊息的長度。
int flags:The flags parameter can be used to influence the behavior of the function beyond the options specified for the associated socket。(一般設 0)
MSG_DONTROUTE:不將訊息送給 gateway,而直接送給 host。(Specifies that the data should not be subject to routing. A Windows Sockets service provider can choose to ignore this flag.)
MSG_OOB:Sends OOB data (stream-style socket such as SOCK_STREAM only)。
const struct sockaddr *to:目的地位址資訊。
int tolen:目的地位址資訊的大小。
成功回傳傳送的資料長度,失敗回傳 SOCKET_ERROR,可用 WSAGetLastError() 取得 error code。
char *sendbuf = "sending data test";
send(sConnect, sendbuf, (int)strlen(sendbuf), 0);
[server端][client端]
Step10. 接收訊息
函式 1:int recv(SOCKET s, char *buf, int len, int flags)
函式 2:int recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen)
recv() 和 recvfrom() 的差異在於,recv() 只能用在 connection-oriented ( SOCK_STREAM ) 的連線,所以 recvfrom() 比 recv() 多最後兩個參數,用來指定接收來源的位址資訊。
SOCKET s:socket() 函式回傳的 socket descriptor。
const char *buf:訊息的指標。
int len:訊息的長度。
int flags:The flags parameter can be used to influence the behavior of the function invocation beyond the options specified for the associated socket.。(一般設 0)
MSG_PEEK:Peeks at the incoming data。只看訊息內容,但不將訊息從 queue 移除。
MSG_OOB:Processes Out Of Band (OOB) data。
const struct sockaddr *to:目的地位址資訊。
int tolen:目的地位址資訊的大小。
成功回傳接收的資料長度,連線被關閉回傳 0。失敗回傳 SOCKET_ERROR,可用 WSAGetLastError() 取得 error code。
char message[200];
ZeroMemory(message, 200);
recv(sConnect, message, sizeof(message), 0);
[server端] [client端]
Step11. 設定 socket option 選項。
函式 :int setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen)
用來設定建立的 socket 一些特性,例如是否強制關閉等。
使用範例可參考: https://blog.csdn.net/qinmi/article/details/1523081
[server端] [client端]
Step12. 關閉 socket。
函式 :int closesocket(SOCKET s)
[server端 Sample Code]
#pragma comment(lib, "Ws2_32.lib")
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <Windows.h>
#include <string>
using namespace std;
#define PORT_NUM (9527)
int main()
{
/*
struct WSAData
{
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYSSTATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR* lpVendorInfo;
};
參數
wVersion
Windows 通訊端 DLL 要求呼叫端使用的 Windows 通訊端規格版本。
wHighVersion
這個 DLL 可支援的最高 Windows 通訊端規格版本 (編碼如上所示)。 通常,這會與相同wVersion。
szDescription
Windows 通訊端 DLL 在以 null 終止之 ASCII 字串複製 Windows Sockets 實作的描述,包括廠商身分識別。 文字 (長度最多 256 個字元) 可以包含任何字元,不過,廠商會被告誡包含控制項和格式化字元:應用程式最可能將此值顯示 (可能以截斷方式) 在狀態訊。
szSystemStatus
Windows 通訊端 DLL 在以 null 終止之 ASCII 字串複製相關狀態或組態資訊。 Windows Sockets DLL 的資訊可能會有所幫助使用者或支援人員; 時,才使用這個欄位不應視為的延伸szDescription欄位。
iMaxSockets
單一處理序可能會開啟的最大通訊端數目。 Windows 通訊端實作可提供通訊端全域集區以配置到任何處理序,或者也依照通訊端的每個處理序資源進行配置。 數字完全反映 Windows 通訊端 DLL 或網路軟體組態的設定方式。 應用程式撰寫者可以使用這個數字當成 Windows Sockets 實作是否可由應用程式使用的指示。 例如,X Windows 伺服器可能會檢查iMaxSockets第一次啟動︰ 如果少於 8,應用程式會顯示錯誤訊息指示使用者重新設定網路軟體。 (這是情況szSystemStatus文字可能會使用。)很顯然並不保證特定應用程式可以實際配置iMaxSockets通訊端,因為可能有其他使用中的 Windows Sockets 應用程式。
iMaxUdpDg
可由 Windows 通訊端應用程式傳送或接收的最大使用者資料包通訊協定 (UDP) 資料包的位元組大小。 如果實作沒有施加限制, iMaxUdpDg為零。 在 Berkeley 通訊端的許多實作中,UDP 資料包上有 8192 位元組的隱含限制 (必要時會分散)。 Windows 通訊端實作可以根據片段重組緩衝區的配置施加限制。 最小值iMaxUdpDg相容 Windows 通訊端實作是 512。 請注意,不論值iMaxUdpDg,不得嘗試傳送大於比最大傳輸單位 (MTU) 的網路廣播的資料包。 (Windows 通訊端 API 不提供機制來尋找 MTU,不過不得少於 512 位元組)。
lpVendorInfo
廠商特定資料結構的遠端指標。 這個結構的定義 (如果有提供) 超出 Windows 通訊端規格的範圍。
*/
WSADATA wsaData;
WORD DLLVersion;
DLLVersion = MAKEWORD(2,1);//winsocket-dll version
// 用 WSAStartup 開始 Winsocket-DLL
int err = WSAStartup(DLLVersion,&wsaData);
if (err!=0)
{
// Tell the user that we could not find a usable Winsock DLL.
printf("WSAStartup failed with error: %d\n",err);
return 1;
}
if (LOBYTE(wsaData.wVersion)!=2 || HIBYTE(wsaData.wVersion)!=1)
{
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return 1;
}
else
{
printf("The Winsock 2.1 dll was found okay\n");
}
// 宣告 socket位址資訊 (不同的通訊,有不同的位址資訊,所以會有不同的資料結構存放這些位址資訊)
/*
/*
*
* Socket address, internet style.
struct sockaddr_in
{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
#ifndef s_addr
* Internet address (old style... should be updated)
struct in_addr
{
union
{
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
}
*/
SOCKADDR_IN addr;
int addrlen = sizeof(addr);
// Create socket
SOCKET sListen ; //listening for an incoming connection
SOCKET sConnection ; //oerating if a connection was found
// AF_INET : 表示建立的Socket屬於internet family
// SOCK_STREAM : 表示建立的socket是connection-oriented socket
sConnection = socket(AF_INET,SOCK_STREAM,NULL);
// 設定位址資訊的資料
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT_NUM);
// 設定Listen
sListen = socket(AF_INET,SOCK_STREAM,NULL);
if (sListen == INVALID_SOCKET)
{
printf("socket function failed with error : %u \n",WSAGetLastError());
WSACleanup();
return 1;
}
// Bind the socket
int iResult = bind (sListen,(SOCKADDR*)&addr,sizeof(addr));
if (iResult == SOCKET_ERROR)
{
printf("Bind failed with error : %u \n", WSAGetLastError());
closesocket(sListen);
WSACleanup();
return 1;
}
//SOMAXCONN: listening without any limit
if(listen(sListen, SOMAXCONN) == SOCKET_ERROR)
{
printf("listen function failed with error: %d \n", WSAGetLastError());
closesocket(sListen);
WSACleanup();
return 1;
}
else
printf("Listening on socket...\n");
// 等待連線
SOCKADDR_IN clientAddr;
while (1)
{
cout << "Waitting for connect... "<<endl;
if(sConnection = accept(sListen,(SOCKADDR*)&clientAddr,&addrlen))
{
cout << "a connection was found."<<endl;
printf("Server : got a connection from : %s\n",inet_ntoa(addr.sin_addr));
//Send message to client
char *sendbuf = "sending data test";
printf("Send buf to client (0x%x) \n", &sendbuf);
//----------------------
// Send an initial buffer
iResult = send(sConnection,sendbuf,(int)strlen(sendbuf),0);
if (iResult == SOCKET_ERROR)
{
printf("send failed with error :%d \n", WSAGetLastError());
closesocket(sConnection);
WSACleanup();
return 1;
}
}
}
getchar();
getchar();
}
[client端Sample Code]
#pragma comment(lib, "Ws2_32.lib")
#include <WinSock2.h>
#include <iostream>
#include <string>
using namespace std;
#define PORT_NUM (9527)
void main()
{
string confirm;
char message[200];
//開始 Winsock-DLL
int r;
WSAData wsaData;
WORD DLLVersion;
DLLVersion = MAKEWORD(2,1);
r = WSAStartup(DLLVersion, &wsaData);
//宣告給 socket 使用的 sockadder_in 結構
SOCKADDR_IN addr;
int addlen = sizeof(addr);
//設定 socket
SOCKET sConnect;
//AF_INET: internet-family
//SOCKET_STREAM: connection-oriented socket
sConnect = socket(AF_INET, SOCK_STREAM, NULL);
//設定 addr 資料
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT_NUM);
cout << "connect to server?[Y] or [N]" << endl;
cin >> confirm;
if(confirm == "N")
{
exit(1);
}else{
if(confirm == "Y")
{
connect(sConnect, (SOCKADDR*)&addr, sizeof(addr));
//接收 server 端的訊息
ZeroMemory(message, 200);
r = recv(sConnect, message, sizeof(message), 0);
cout << message << endl;
//設定 closesocket 時,不經過 TIME-WAIT 過程,直接關閉socket
//BOOL bDontLinger = FALSE;
//setsockopt(sConnect,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
//若之後不再使用,可用 closesocket 關閉連線
closesocket(sConnect);
getchar();
getchar();
}
}
}
留言列表