套接字編程(VC_Win32)


目錄

套接字概述
相關函數
套接字編程

(本章節中例子都是用 VS2005 編譯調試的)

相關文獻:

Socket模型詳解(轉)

Socket通信中的多進程編程實例


套接字概述

簡介(源於維基)

Berkeley套接字(也作BSD套接字應用程序接口)剛開始是4.2BSD Unix操作系統(於1983發布)的一套應用程序接口。然而,由於AT&T的專利保護着UNIX,所以只有在1989年Berkeley大學才能自由地發布自己的操作系統和網絡庫。
Berkeley套接字接口,一個應用程序接口(API),使用一個Internet套接字的概念,使主機間或者一台計算機上的進程間可以通訊。 它可以在很多不同的輸入/輸出設備和驅動之上運行,盡管這有賴於操作系統的具體實現。 接口實現用於TCP/IP協議,因此它是維持Internet的基本技術之一。 它是由加利福尼亞的伯克利大學開發,最初用於Unix系統。 如今,所有的現代操作系統都有一些源於Berkeley套接字接口的實現,它已成為連接Internet的標准接口。
套接字接口的接入有三個不同的級別,最基礎的也是最有效的就是raw socket級別接入。 很少的應用程序需要在外向通訊控制的這個級別接入,所以raw socket級別是只為了用於開發計算機Internet相關技術的。 最近幾年,大多數的操作系統已經實現了對它的全方位支持,包括Windows XP。

應用程序網絡數據傳輸

  • 端口:
    • 定義:    一種抽象的軟件結構,應用程序通過系統調用與某端口建立連接后,傳輸層給該端口的數據都被相應的進程接收,相應的進程發給傳輸層的數據都通過該端口輸出
    • 端口號:   一個整形標示符,來表示端口,取值為0~65535,1024以下的端口保留給預定義的服務
    • 注意:    TCP/IP傳輸層的兩個協議TCP與UDP是完全獨立的兩個軟件模塊,因此各自端口獨立,也就是說TCP/UDP可以擁有相同的端口號
  • IP 地址:
    • 所謂IP地址就是給每個連接在Internet上的主機分配的一個32bit地址。按照TCP/IP協議規定,IP地址用二進制來表示,每個IP地址長32bit,比特換算成字節,就是4個字節

套接字

  • 說明
    • 套接字存在於通信域中,通信域也叫地址簇,它是一個抽象的概念,主要用於將通過套接字通信的進程的共有特性,綜合在一起,套接字通常只與同一區域的套接字交換數據,(也可能跨區通信,但這只是在執行某種轉換進程后才能實現).Windows Sockets只支持一個通信域,網際域(AF_INET),這個域被使用網際協議簇通信進程使用
  • 套接字類型
    • SOCK_STREAM    流式套接字,提供面向連接的數據傳輸服務,數據無差錯,無重復的發送,並且按發送順序接送,流式套接字實際上是基於TCP實現的
    • SOCK_DGRAM      數據報式套接字,提供無連接服務,數據包以獨立包形式發送,不提供無錯保證,數據可能存在丟失或重復,並且接受順序混亂,數據報式套接字實際上是基於UDP協議實現的
    • SOCK_RAM      原始套接字

網絡字節順序(套接字與地址簇中使用)

由於不同的計算機存放數據字節的順序不同,所以通信雙方必須協商出統一的存放字節順序,這樣才能發送方的數據可以被接收方准確無誤的讀取,否則接收方讀到的是一堆不知名的數據,所以通信前雙方必須協商統一的用網絡字節順序,保證通信的正常進行

基於消息的異步套接字

  • 兩種模式 (Windows套接字在兩種模式下執行I/O操作)
    • 阻塞     在阻塞模式下,在I/O操作完成前,執行操作的Winsock函數會一直等待下去,不會立即返回程序(將控制權交還給程序)
    • 非阻塞    在非阻塞模式下,Winsock函數無論如何都會立即返回
  • 消息驅動
    • Windows Sockets為了支持Windows消息驅動機制,使應用程序開發者能夠方便地處理網絡通信,它對網絡事件采用了基於消息的異步存取策略
    • Windows Sockets的異步選擇函數WSAAsyncSelect()提供了消息機制的網絡事件選擇,當使用它登記的網絡事件發生時,Windows應用程序相應的窗口函數將收到一個消息,消息中指示了發生的網絡事件,以及與事件相關的一些信息

C/S模式 


相關函數

[win32 API 相關套接字函數][相關結構體及宏][基於消息的套接字編程][MFC套接字相關函數]

Win32 API 相關套接字常用函數

顯示相關函數

[套接字版本協商][創建套接字][綁定端口][點分十進制轉換成無符號長整形][無符號長整形轉換成點分十進制][主機字節順序轉換為網絡字節順序][TCP套接字相關函數][UDP套機制相關函數]

套接字版本協商

函數原型

int WSAStartup (
WORD wVersionRequested, 
LPWSADATA lpWSAData 
);

參數說明

  • wVersionRequested:  用來指定准備加載的Winsock庫的版本.高字節位指定所需要的Winsock庫的副版本,而低字節則是主版本.通常版本為2.1其中2為主版本,1為副版本號
  • lpWSAdata:            這是一個返回值,指向WSADATA結構的指針,WSAStartup函數用其來加載的庫版本有關的信息填在這個結構中

返回值

  • 如果Ws_32.dll或底層網絡子系統沒有正確初始化或被找到,函數返回WSASYSNOTREADY.
  • 如果請求的版本低於Winsock動態庫所支持的最低版本,WSAStartup函數將返回WSAVERNOTSUPPORTED.
  • 如果請求的版本等於或者高於Winsock動態庫所支持的最低版本WSAData的wVersion成員包含你的應用程序應該使用的版本,它是動態庫所支持的最高版本與請求版本中較小的那個

創建套接字

函數原型

SOCKET socket (
int af, 
int type, 
int protocol 
);

參數說明

  • af:        指定地址簇,對於TCP/IP協議的套接字,它只能寫成AF_INET(或PF_INET)
  • type:      指定套接字類型,它只支持兩種套接字,SOCK_STREAM(流式套接字),SOCK_DGRAM(數據報式套接字)
  • protocol:   指定與特定地址家族相關的協議,如果指定為0,那么系統就會根據地址格式和套接字類別,自動選擇一個合適的協議

返回值

  • 如果成功,返回一個新的SOCKET數據類型的套接字描述符
  • 如果失敗,返回一個INVALID_SOCKET,錯誤信息可以通過WSAGetLastError函數返回

綁定端口(服務器)

函數原型

int bind (
SOCKET s, 
const struct sockaddr FAR* name, 
int namelen 
);

參數說明

  • s:          指定要綁定的的套接字
  • name:      指定該套接字的本地地址信息,指向SOCKADDR結構體的指針
  • namelen:  指定該地址結構的長度

返回值  如果成功,返回0,如果失敗,返回SOCKET_ERROR,錯誤信息可以通過WSAGetLastError函數返回

點分十進制轉換成無符號長整形

函數原型

unsigned long inet_addr (const char FAR * cp );

參數說明:  cp      一個點分十進制的IP地址形式的字符串

返回值    一個對應cp點分十進制的unsigned long類型的數值

無符號長整形轉換成點分十進制

函數原型

char FAR * inet_ntoa (struct in_addr in );

參數說明  in:     一個點分十進制的unsigned long類型的數值

返回值     一個對應in點分十進制的IP地址形式的字符串

主機字節順序轉換為網絡字節順序

16位數值

  • 函數原型
    u_short htons (u_short hostshort );
  • 參數說明:  hostshort  一個以主機字節順序表示的16位數值
  • 返回值:    把一個u_short類型的值從主機字節順序轉換為網絡字節順序

32位數值

  • 函數原型
    u_long htonl (u_long hostlong );
  • 參數說明:  hostlong:  一個以主機字節順序表示的32位數值
  • 返回值:    把一個u_long類型的值從主機字節順序轉換為網絡字節順序

相關結構體及宏

顯示相關函數

[套接字版本結構][地址結構][TCP/IP地址結構][地址表示][MAKEWORD]

套接字版本結構

struct WSAData {
WORD wVersion;//打算使用的Winsock版本號
WORD wHighVersion;//容納的是現有的Winsock最高版本號,以高字節代表的是Winsock的副版本,低字節表示的是搞版本號
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYSSTATUS_LEN+1];
//以下兩個參數一般不設置它
unsigned short iMaxSockets;//同時最多可以打開多少套接字
unsigned short iMaxUdpDg;//數據報的最大長度
//同時最多可以打開套接字數目很大程度上和可用物理內存的多少有關
char FAR * lpVendorInfo; //這個參數Winsock實施方案有關的指定廠商信息預留的,任何一個Win32平台上都沒有使用這個字段
};

地址結構

struct sockaddr {
unsigned short sa_family;//指定地址家族,對於TCP/IP協議的套接字,必須設置為AF_INET
char sa_data[14];// 僅僅表示要求一塊內存分配區,啟到占位作用,該區域中指定與協議相關的具體地址信息,由於實際要求的只是內存區,所以對於不同的協議家族,用不同的協議家族,用不同的結構來替換sockaddr
};

TCP/IP地址結構

struct sockaddr_in{
short sin_family;///指定地址家族,對於TCP/IP協議的套接字,必須設置為AF_INET
unsigned short sin_port;//指定要分配給套接字的端口
struct in_addr sin_addr;//套接字的主機的IP地址
char sin_zero[8];//一個填充占位符
};//在TCP/IP編程中用這個結構體來替換sockaddr結構體

地址表示

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;
};
//用於記入地址的一個結構,若將IP地址設置為INADDR_ANY,允許套接字向任何分配給本機的IP地址發送或接收數據,用INADDR_ANY可以簡化編程,這樣程序便可以接收發自多個接口的回應

MAKEWORD宏

MAKEWORD(x,y)

作用:  用於設置DWORD類型的版本號,x是高字節,y是低字節

基於消息套接字編程相關相關函數

顯示相關函數

[獲得系統中安裝的網絡協議的相關信息][注冊網絡事件][創建套接字][UDP消息接收][UDP消息發送]

獲得系統中安裝的網絡協議的相關信息

函數原型

int WSAEnumProtocols( 
LPINT lpiProtocols, 
LPWSAPROTOCOL_INFO lpProtocolBuffer, 
ILPDWORD lpdwBufferLength
)

參數說明

  • lpiProtocols:            一個以NULL結尾的協議標識號數組。這個參數是可選的,如果lpiProtocols為NULL,則返回所有可用協議的信息,否則,只返回數組中列出的協議信息,
  • lpProtocolBuffer[out]:      一個用WSAPROTOCOL_INFO結構體填充的緩沖區。 WSAPROTOCOL_INFO結構體用來存放或得到一個指定協議的完整信息
  • lpdwBufferLength[in, out]:   在輸入時,指定傳遞給WSAEnumProtocols()函數的lpProtocolBuffer緩沖區的長度;在輸出時,存有獲取所有請求信息需傳遞給WSAEnumProtocols ()函數的最小緩沖區長度。這個函數不能重復調用,傳入的緩沖區必須足夠大以便能存放所有的元素。這個規定降低了該函數的復雜度,並且由於一個 機器上裝載的協議數目往往是很少的,所以並不會產生問題

返回值  如果函數沒有錯誤發生,函數返回協議報告信息,如果錯誤返回SOCKET_ERROR和在WSAGetLastError函數中查詢相關詳細信息

說明   Win32平台支持多種不同的網絡協議,采用Winsock2,就可以編寫可直接使用任何一種協議的網絡應用程序了。通過WSAEnumProtocols函數可以獲得系統中安裝的網絡協議的相關信息

注冊網絡事件

函數原型

int WSAAsyncSelect (
SOCKET s, 
HWND hWnd, 
unsigned int wMsg, 
long lEvent 
);

參數說明

  • s:     標識請求網絡事件通知的套接字
  • hWnd:  指定網絡事件發生時接收消息的窗口句柄
  • wMsg:  指定網絡事件發生時窗口接收的消息
  • lEvent:   指定應用程序感興趣的網絡事件,可以是下面的組合

        取值                 說明                                

    • FD_READ                應用程序想接收有關是否可讀的通知
    • FD_WRITE              應用程序想接收有關是否可讀的通知
    • FD_OBB                應用程序想要接收是否外帶(OBB)數據抵達的通知
    • FD_ACCEPT              應用程序想要接收與進入連接有關的通知
    • FD_CONNECT              應用程序想要接收連接操作以完成的通知
    • FD_CLOSE               應用程序想要接收與套接字關閉有關的通知
    • FD_QOS                應用程序想要接收套接字"服務質量"發生更改的通知
    • FD_GROUP_QOS              應用程序想要接收套接字組""服務質量"發生更改的通知
    • FD_ROUTING_INTERFACE_CHANGE   應用程序想要接收在指定的方向上,與路由接口發生變化有關的通知
    • FD_ADDRESS_LIST_CHANGE        應用程序想要接收,針對套接字的協議家族,本地地址列表發生變化的通知

返回值   如果函數成功返回值是0,如果函數失敗則返回值是SOCKET_ERROR並調用WSAGetLastError獲得更多錯誤信息

說明    該函數為指定的套接字請求基於Windows消息的網絡事件通知,並自動將該套接字設置為非阻塞模式

創建套接字

函數原型

SOCKET WSASocket(
int af,
int type,
int protocol, 
LPWSAPROTOCOL_INFO lpProtocolInfo,
GROUP g, 
DWORD dwFlags
);

參數說明

  • af:         指定地址簇,對於TCP/IP協議的套接字,它只能寫成AF_INET(或PF_INET)
  • type:         指定套接字類型,它只支持兩種套接字,SOCK_STREAM(流式套接字),SOCK_DGRAM(數據報式套接字)
  • protocol:      指定與特定地址家族相關的協議,如果指定為0,那么系統就會根據地址格式和套接字類別,自動選擇一個合適的協議
  • lpProtocolInfo:   一個指向WSAPROTOCOL_INFO結構體的指針,該結構定義了所創建的套接字的特性。如果lpProtocolInfo為NULL,則WinSock2 DLL使用前三個參數來決定使用哪一個服務提供者,它選擇能夠支持規定的地址族、套接字類型和協議值的第一個傳輸提供者。如果lpProtocolInfo不為NULL,則套接字綁定到與指定的結構WSAPROTOCOL_INFO相關的提供者
  • g:          保留
  • dwFlags:       套接字屬性的描述

返回值

  • 如果成功,返回一個新的SOCKET數據類型的套接字描述符
  • 如果失敗,返回一個INVALID_SOCKET,錯誤信息可以通過WSAGetLastError函數返回

MFC常用函數

顯示相關函數

[初始化套接字]

初始化套接字 AfxSocketInit

函數原型 

BOOL AfxSocketInit(WSADATA* lpwsaData = NULL);

作用:    MFC提供的創建套接字庫的函數
返回值:   若函數調用成功時候返回非零值,否則返回零
注意:    應該在應用程序類重載的InitInstance函數中調用AfxSocketInit函數在MFC應用程序運行時需要的一些必要的預編譯頭文件stdafx.h中添加函數的頭文件afxsock.h
優點:    使用這個函數的優點,它可以確保在應用程序終止前,調用WSACleapup函數以終止對套接字的使用,並且利用AfxSocketInit函數也不用在加載套接字庫時,手動為工程添加到ws2_32.lib的鏈接庫文件設置

TCP套接字相關函數

顯示相關函數

[監聽請求(服務器)][接收請求(服務器)][發送數據][接收數據][建立連接(客服端)]

監聽請求(服務器)

函數原型

int listen (
SOCKET s, 
int backlog 
);

參數說明

  • s:       對應於用於監聽用的套接字
  • backlog:    是等待隊列的最大長度,如果設置為SOMAXCONN,那么下層的服務提供者將負責將這個套接字設置為最大的合理值(設置這個值是為了設置等待連接隊列的最大長度,而不是在一個端口上同時可以進行連接的數目,例如將backlog設置為2,當有3個請求同時到達時候,前兩個連接請求會被放到請求連接隊列中,然后由應用程序依次為這些請求服務,而第三個請求就被拒絕了)

接收請求(服務器)

函數原型

SOCKET accept (
SOCKET s, 
struct sockaddr FAR* addr, 
int FAR* addrlen 
);

參數說明

  • s:         對應於用於監聽的套接字
  • addr:      指向一個緩沖區的指針,該緩沖區用來接收連接實體的地址,也就是當客戶端向服務器發起的連接,服務器接收這個連接時,保存發起連接的這個客戶端的IP地址信息和端口信息
  • addrlen:    是一個返回值,指向一個整形,返回包含地址信息的長度

返回值

如果沒有錯誤發生,函數返回一個建立好連接的SOCKET套接字,如果失敗則返回INVALID_SOCKET

發送數據

函數原型

int send (
SOCKET s, 
const char FAR * buf, 
int len, 
int flags 
);

參數說明

  • s:       是指向一個已建立連接的套接字
  • buf:    指向一個緩沖區的指針,該緩沖包含將要傳遞的數據
  • len:    是緩沖區的長度
  • flags:  設定的值將影響函數行為,一般設置為0,MSG_PEEK會使有用的數據被復制到接收緩沖區內,但沒有從系統緩沖區中將其刪除MSG_OOB表示處理帶外數據

接收數據

函數原型

int recv (
SOCKET s, 
char FAR* buf, 
int len, 
int flags 
); 

參數說明

  • s:       是指向一個已建立連接的套接字
  • buf:    指向一個緩沖區的指針,該緩沖用來接收保存數據
  • len:    是緩沖區的長度
  • flags:  設定的值將影響函數行為,一般設置為0,MSG_PEEK會使有用的數據被復制到接收緩沖區內,但沒有從系統緩沖區中將其刪除MSG_OOB表示處理帶外數據


建立連接(客服端)

函數原型

int connect (
SOCKET s, 
const struct sockaddr FAR* name, 
int namelen 
);

參數說明

  • s:          用來建立連接的套接字
  • name:        設定連接的服務器端的地址信息
  • namelen:    指定服務器端地址信息的長度

UDP套接字相關函數

顯示相關函數

[接收消息][發送數據][接收消息(基於消息)][發送數據(基於消息)]

接收數據

函數原型

int recvfrom (
SOCKET s, 
char FAR* buf, 
int len, 
int flags, 
struct sockaddr FAR* from, 
int FAR* fromlen 
);

參數說明

  • s:         准備接收數據的套接字
  • buf:        指向一個緩沖區的指針,該緩沖包含將要傳遞的數據
  • len:       是緩沖區的長度
  • flags:      設定的值將影響函數行為,一般設置為0
  • from:       一個指向地址結構類型的指針,主要是用來接收發送數據方的地址信息
  • fromlen:   它是一個in/out類型參數,表面在調用需要給它一個初始值,當函數調用成功后,會通知這個參數返回一個值,fai返回值是地址結構的大小

返回值:  如果沒有錯誤發送返回值是接收數據的字節數,如果連接關閉則返回0,否則返回SOCKET_ERROR

發送數據

函數原型

int sendto (
SOCKET s, 
const char FAR * buf, 
int len, 
int flags, 
const struct sockaddr FAR * to, 
int tolen 
);

參數說明

  • s:       准備發送數據的套接字
  • buf:    指向一個緩沖區的指針,該緩沖用來接收保存數據
  • len:      是緩沖區的長度
  • flags:    設定的值將影響函數行為,一般設置為0
  • to:      一個指向地址結構類型的指針,主要是用來要指定目標套截止的地址
  • tolen:   指定目標套接字的地址的長度

返回值:   如果沒有錯誤返回值是發送數據的字節數,否則返回SOCKET_ERROR

消息接收(基於消息機制)

函數原型

int WSARecvFrom(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
struct sockaddr FAR *lpFrom,
LPINT lpFromlen,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

參數說明

  • s:   標識套接字的描述符
  • lpBuffers[in, out]:   一個指向WSABUF結構體的指針。每一個WSABUF結構體包含一個緩沖區的指針和緩沖區的長度
    typedef struct __WSABUF {
    u_longlen; // buffer length
    char FAR *buf; // pointer to buffer
    } WSABUF, FAR * LPWSABUF;
  • dwBufferCount:   lpBuffers數組中WSABUF結構體的數目
  • lpNumberOfBytesRecvd[out]:   如果接收操作立即完成,則為一個指向本次調用所接收的字節數的指針
  • lpFlags[in, out] 一個指向標志位的指針,可以對以下進行組合
        值         說明                           
    • MSG_PEEK      瀏覽到來的數據,這些數據將復制到緩沖區,但並不從輸入隊列中移除,此標記僅對非重疊套接字有效
    • MSG_OBB      處理外帶(OBB)數據
    • MSG_PARTIAL   此標記僅用於面向消息的套接字,作為輸出參數時,此標記表面數據是發送方傳送的一部分,消息的剩余部分將在隨后的接收操作中被傳送,如果隨后的某個接收操作沒有此標志,就表明這時發送方發送消息的尾部,作為輸入參數時,此標記,表面接收操作是完成的,即使只是一條消息部分數據已被服務提供者所接收
  • lpFrom[out]:   可選指針,指向重疊操作完成后存放源地址的緩沖區
  • lpFromlen[in, out]:   指向from緩沖區大小的指針,僅當指定了lpFrom才需要
  • lpOverlapped:   一個指向WSAOVERLAPPED結構體的指針(對於非重疊套接字則忽略)
  • lpCompletionRoutine:   一個指向接收操作完成時調用的完成例程的指針(對於非重疊套接字則忽略)
    如果創建的是重疊套接字,在使用函數時,一定要注意后面兩個參數值,因為這時采用重疊IO操作,函數會立即返回,但接收到數據這一操作完成以后操作系統會調用lpCompletionRoutine參數指定的例程類通知調用進程,函數原型
  • void CALLBACK CompletionROUTINE(
    IN DWORDdwError,
    IN DWORDcbTransferred,
    IN LPWSAOVERLAPPEDlpOverlapped,
    IN DWORDdwFlags
    );

返回值:   若函數失敗則返回SOCKET_ERROR

消息發送(基於消息機制)

函數原型

int WSASendTo(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
const struct sockaddr FAR *lpTo,
int iToLen,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

參數說明

  • s:   標識一個套接字(可能已連接)的描述符
  • lpBuffers:   一個指向WSABUF結構體的指針。每一個WSABUF結構體包含一個緩沖區的指針和緩沖區的長度
  • dwBufferCount:   lpBuffers數組中WSABUF結構體的數目
  • lpNumberOfBytesSent[out]:   如果發送操作立即完成,則為一個指向本次調用所發送的字節數的指針
  • dwFlags:   指示影響操作行為的標志位
       Value         Meaning                        
    • MSG_DONTROUTE     Specifies that the data should not be subject to routing. A Windows Socket service provider may choose to ignore this flag.
    • MSG_OOB       Send out-of-band data (stream-style socket such as SOCK_STREAM only).
    • MSG_PARTIAL     Specifies that lpBuffers only contains a partial message. Note that the error code WSAEOPNOTSUPP will be returned by transports that do not support partial message transmissions.
  • lpTo:   可選指針,指向目標套接字的地址
  • iToLen:   lpTo中地址的長度
  • lpOverlapped:   一個指向WSAOVERLAPPED結構的指針(對於非重疊套接字則忽略)
  • lpCompletionRoutine:   一個指向接收操作完成時調用的完成例程的指針(對於非重疊套接字則忽略)

返回值:   若函數失敗則返回SOCKET_ERROR


編寫套接字通信

[編寫基於 UDP 套接字通信][編寫基於 TCP 套接字通信][編寫基於消息機制的 UDP 套接字通信][通過域名獲得 IP 地址]

編寫基於 UDP 套接字通信

流程圖:

代碼示例:

查看本機 IP

服務器端

View Code
#include <Winsock2.h>
#include <iostream>
#include <string>
#pragma comment(lib,"ws2_32.lib")
using namespace std;

void main()
{
    //加載套接字庫
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD( 1, 1 );
    err = WSAStartup( wVersionRequested, &wsaData );//該函數的功能是加載一個Winsocket庫版本
    if ( err != 0 ) {
        return;
    }
    if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
            WSACleanup( );
            return; 
    }

    //創建套接字
    SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);
    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(6000);

    //將套接字綁定到端口上
    bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

    SOCKADDR_IN addrClient;
    int len = sizeof(SOCKADDR);
    char recvBuffer[300];//接收字符數據
    memset((void*)recvBuffer,'\0',300);
    cout<<"等待對方發送數據... "<<endl;

    //接收數據
    recvfrom(sockSrv,recvBuffer,300,0,(SOCKADDR*)&addrClient,&len);
    cout<<"對方的地址為: "<<inet_ntoa(addrClient.sin_addr)<<endl;
    cout<<"接收的內容為: "<<recvBuffer<<endl;

    //發送數據
    string sendBuffer = "this is server";
    cout<<"向客戶端方發送數據: "<<sendBuffer.c_str()<<endl;
    sendto(sockSrv,sendBuffer.c_str(),sendBuffer.length() +1,0,(SOCKADDR*)&addrClient,sizeof(SOCKADDR));

    closesocket(sockSrv);//關閉服務器套接字
    WSACleanup();//結束套接字庫的調用
    system("pause");

}

客戶端

View Code
#include <Winsock2.h>
#include <iostream>
#include <string>
//加載動態連接庫ws2_32.dll,提供了網絡相關API的支持
#pragma comment(lib,"ws2_32.lib")
using namespace std;

void main()
{
    //加載套接字庫
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD( 1, 1 );
    err = WSAStartup( wVersionRequested, &wsaData );//該函數的功能是加載一個Winsocket庫版本
    if ( err != 0 ) {
        return;
    }
    if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
            WSACleanup( );
            return; 
    }

    //建立通訊 socket
    SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);
    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr=inet_addr("220.160.249.188");
    addrSrv.sin_family=AF_INET;
    addrSrv.sin_port=htons(6000);

    //發送數據
    string sendBuffer = "this is client!";
    cout<<"向服務器方發送數據: "<<sendBuffer.c_str()<<endl;
    sendto(sockClient,sendBuffer.c_str(),sendBuffer.length()+1,0,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

    //接收數據
    char recvBuffer[300];//接收字符數據
    memset((void*)recvBuffer,'\0',300);
    int len = sizeof(SOCKADDR);
    cout<<"等待對方發送數據... "<<endl;
    recvfrom(sockClient,recvBuffer,300,0,(SOCKADDR*)&addrSrv,&len);
    cout<<"主機的地址為: "<<inet_ntoa(addrSrv.sin_addr)<<endl;
    cout<<"接收的內容為: "<<recvBuffer<<endl;

    //結束通信
    closesocket(sockClient);//關閉服務器套接字
    WSACleanup();//結束套接字庫的調用
    system("pause");
}

運行結果(先運行服務器,再運行客戶端.然后結果為下圖所示上面為服務器下面為客戶端)

編寫基於 TCP 套接字通信

流程圖:

 

代碼示例:

查看本機 IP

服務器端

View Code
#include <Winsock2.h>
#include <iostream>
#include <string>
#pragma comment(lib,"ws2_32.lib")
using namespace std;

void main()
{
    //加載套接字庫
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD( 1, 1 );
    err = WSAStartup( wVersionRequested, &wsaData );//該函數的功能是加載一個Winsocket庫版本
    if ( err != 0 ) {
        return;
    }
    if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
            WSACleanup( );
            return; 
    }

    //創建套接字
    SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);
    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(6000);

    //將套接字綁定到端口上
    bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

    //將套接字設置為監聽模式
    listen(sockSrv,5);

    //等待客戶請求來到,當請求來到時候,接受請求,接受連接請求,返回一個新的對應於此連接套接字
    SOCKADDR_IN addrClient;
    int len = sizeof(SOCKADDR);
    //開始監聽
    cout<<"等待用戶連接"<<endl;
    SOCKET sockConn = accept(sockSrv,(SOCKADDR*)&addrClient,&len);//sockConn用於建立連接的套接字
    cout<<"用戶連接到來"<<endl;

    //和客戶通信
    //接收數據
    char recvBuffer[300];//接收字符數據
    memset((void*)recvBuffer,'\0',300);
    cout<<"等待對方發送數據... "<<endl;
    recv(sockConn,recvBuffer,100,0);
    cout<<"對方的地址為: "<<inet_ntoa(addrClient.sin_addr)<<endl;
    cout<<"接收的內容為: "<<recvBuffer<<endl;

    //發送數據
    string sendBuffer = "this is server";
    cout<<"向客戶端方發送數據: "<<sendBuffer.c_str()<<endl;    
    send(sockConn,sendBuffer.c_str(),sendBuffer.size(),0);

    //關閉本次連接的通道
    closesocket(sockConn);


    closesocket(sockSrv);//關閉服務器套接字
    WSACleanup();//結束套接字庫的調用
    system("pause");

}

客戶端

View Code
#include <Winsock2.h>
#include <iostream>
#include <string>
//加載動態連接庫ws2_32.dll,提供了網絡相關API的支持
#pragma comment(lib,"ws2_32.lib")
using namespace std;

void main()
{
    //加載套接字庫
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD( 1, 1 );
    err = WSAStartup( wVersionRequested, &wsaData );//該函數的功能是加載一個Winsocket庫版本
    if ( err != 0 ) {
        return;
    }
    if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
            WSACleanup( );
            return; 
    }

    //建立通訊 socket
    SOCKET sockClient = socket(AF_INET,SOCK_STREAM,0);
    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr=inet_addr("220.160.249.188");
    addrSrv.sin_family=AF_INET;
    addrSrv.sin_port=htons(6000);

    //發出連接請求
    cout<<"請求與服務器連接"<<endl;
    if(connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)) != SOCKET_ERROR)
    {    
        cout<<"與服務器建立連接"<<endl;

        //和服務器通信
        //發送數據
        string sendBuffer = "this is client!";
        cout<<"向服務器方發送數據: "<<sendBuffer.c_str()<<endl;
        send(sockClient,sendBuffer.c_str(),sendBuffer.size(),0);

        //接收數據
        char recvBuffer[300];//接收字符數據
        memset((void*)recvBuffer,'\0',300);
        int len = sizeof(SOCKADDR);
        cout<<"等待對方發送數據... "<<endl;
        recv(sockClient,recvBuffer,100,0);
        cout<<"主機的地址為: "<<inet_ntoa(addrSrv.sin_addr)<<endl;
        cout<<"接收的內容為: "<<recvBuffer<<endl;    
    }
    //結束通信
    closesocket(sockClient);//關閉服務器套接字
    WSACleanup();//結束套接字庫的調用
    system("pause");
}

運行結果(先運行服務器,再運行客戶端.然后結果為下圖所示上面為服務器下面為客戶端)

編寫基於消息機制的 UDP 套接字通信

流程圖:

程序代碼(源於孫鑫第十六講代碼):

先添加一個對話框工程工程工程名為 Chat ,因為服務器和客戶端這里寫在一起所以就一個工程就好.

資源界面設計

相關代碼 

在 stdafx.h 頭文件中加載動態連接庫的引入庫和相關頭文件

#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")

在程序初始化的時候, 加載套接字庫和進行套接字庫協商,這個工作放在 Chat.cpp ,主線程的初始化工作函數 InitInstance 中.

View Code
BOOL CChatApp::InitInstance()
{
    //套接字版本協商------------------------------------
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    
    wVersionRequested = MAKEWORD( 2, 2 );
    
    err = WSAStartup( wVersionRequested, &wsaData );
    if ( err != 0 ) {
    
        return FALSE;
    }
    

    if ( LOBYTE( wsaData.wVersion ) != 2 ||
        HIBYTE( wsaData.wVersion ) != 2 ) {

        WSACleanup( );
        return FALSE; 
    }
    //----------------------------------------------------

    AfxEnableControlContainer();

    // Standard initialization
    // If you are not using these features and wish to reduce the size
    //  of your final executable, you should remove from the following
    //  the specific initialization routines you do not need.

#ifdef _AFXDLL
    Enable3dControls();            // Call this when using MFC in a shared DLL
#else
    Enable3dControlsStatic();    // Call this when linking to MFC statically
#endif

    CChatDlg dlg;
    m_pMainWnd = &dlg;
    int nResponse = dlg.DoModal();
    if (nResponse == IDOK)
    {
        // TODO: Place code here to handle when the dialog is
        //  dismissed with OK
    }
    else if (nResponse == IDCANCEL)
    {
        // TODO: Place code here to handle when the dialog is
        //  dismissed with Cancel
    }

    // Since the dialog has been closed, return FALSE so that we exit the
    //  application, rather than start the application's message pump.
    return FALSE;
}

接着來定義一個網絡事件,和網絡事件響應函數

View Code
/* 在 ChatDlg.h 中 ******************************************************/
//添加網絡事件定義
#define UM_SOCK        WM_USER+1

//然后在 CChatDlg 類中添加消息響應函數原型
afx_msg LRESULT OnSock(WPARAM,LPARAM);




/* 在 ChatDlg.cppp 中 ***************************************************/
//在消息映射中添加消息映射
BEGIN_MESSAGE_MAP(CChatDlg, CDialog)
    //{{AFX_MSG_MAP(CChatDlg)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_BTN_SEND, OnBtnSend)
    //}}AFX_MSG_MAP
    ON_MESSAGE(UM_SOCK,OnSock)   //網絡事件的消息映射
END_MESSAGE_MAP()

//添加消息事件響應函數定義
afx_msg LRESULT CChatDlg::OnSock(WPARAM wParam,LPARAM lParam)
{
    switch(LOWORD(lParam))
    {
    //接收數據
    case FD_READ:
        WSABUF wsabuf;
        wsabuf.buf=new char[200];
        wsabuf.len=200;
        DWORD dwRead;
        DWORD dwFlag=0;
        SOCKADDR_IN addrFrom;
        int len=sizeof(SOCKADDR);
        CString str;
        CString strTemp;
        HOSTENT *pHost;
        if(SOCKET_ERROR==WSARecvFrom(m_socket,&wsabuf,1,&dwRead,&dwFlag,
                        (SOCKADDR*)&addrFrom,&len,NULL,NULL))
        {
            MessageBox("接收數據失敗!");
            return 0;
        }
        pHost=gethostbyaddr((char*)&addrFrom.sin_addr.S_un.S_addr,4,AF_INET);
        //str.Format("%s說 :%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);
        str.Format("%s說 :%s",pHost->h_name,wsabuf.buf);
        str+="\r\n";
        GetDlgItemText(IDC_EDIT_RECV,strTemp);
        str+=strTemp;
        SetDlgItemText(IDC_EDIT_RECV,str);
        break;
    }
    return 0;
}

定義好網絡世界和網絡世界響應函數后我們來進行套接字的創建和和端口綁定,以及網絡事件注冊,這個功能我們封裝在 InitSocket 並且在對話框初始化時候我們就打算調用它.

View Code
/* 在 ChatDlg.h 中 ******************************************************/
//首先我們為 CChatDlg 類添加一個套接字成員變量,用於網絡通信
SOCKET m_socket;

//然后在 CChatDlg 類中添加函數原型
BOOL InitSocket();




/* 在 ChatDlg.cppp 中 ***************************************************/
//定義 InitSocket 函數
BOOL CChatDlg::InitSocket()
{
    //套接字創建
    m_socket=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,0);
    if(INVALID_SOCKET==m_socket)
    {
        MessageBox("創建套接字失敗!");
        return FALSE;
    }
    
    //服務器端的端口綁定
    SOCKADDR_IN addrSock;
    addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
    addrSock.sin_family=AF_INET;
    addrSock.sin_port=htons(6000);    
    if(SOCKET_ERROR==bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR)))
    {
        MessageBox("綁定失敗!");
        return FALSE;
    }

    //注冊網絡事件
    if(SOCKET_ERROR==WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ))
    {
        MessageBox("注冊網絡讀取事件失敗!");
        return FALSE;
    }

    return TRUE;
}

在定義發送按鈕消息事件讓其能發送對應消息

View Code
void CChatDlg::OnBtnSend() 
{
    // TODO: Add your control notification handler code here
    DWORD dwIP;
    CString strSend;
    WSABUF wsabuf;
    DWORD dwSend;
    int len;
    CString strHostName;
    SOCKADDR_IN addrTo;
    HOSTENT* pHost;

    //客戶端
    //獲取服務器端 IP 地址
    ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
    addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
    //綁定端口和設置IP協議
    addrTo.sin_family=AF_INET;
    addrTo.sin_port=htons(6000);

    //獲得發送文本
    GetDlgItemText(IDC_EDIT_SEND,strSend);
    len=strSend.GetLength();
    wsabuf.buf=strSend.GetBuffer(len);
    wsabuf.len=len+1;

    SetDlgItemText(IDC_EDIT_SEND,"");

    //發送數據
    if(SOCKET_ERROR==WSASendTo(m_socket,&wsabuf,1,&dwSend,0,
            (SOCKADDR*)&addrTo,sizeof(SOCKADDR),NULL,NULL))
    {
        MessageBox("發送數據失敗!");
        return;
    }
}

運行結果

 

通過域名獲得 IP 地址

相關函數:

代碼示例:

View Code
#include <Winsock2.h>
#include <iostream>
#include <string>
#pragma comment(lib,"ws2_32.lib")
using namespace std;

void main()
{
    //加載套接字庫
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD( 1, 1 );
    err = WSAStartup( wVersionRequested, &wsaData );//該函數的功能是加載一個Winsocket庫版本
    if ( err != 0 ) {
        return;
    }
    if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
            WSACleanup( );
            return; 
    }

    //解析域名獲得 IP 地址
    hostent* pHostent = gethostbyname("www.baidu.com"); 
    sockaddr_in  sa; 
    ZeroMemory(&sa, sizeof(sa));

    //獲得 IP 地址
    memcpy(&sa.sin_addr.s_addr,pHostent->h_addr_list[0],pHostent->h_length); 
    
    //將 ID 地址轉為字符串形式,輸出 IP 地址
    string strTemp = inet_ntoa(sa.sin_addr);
    cout<<strTemp<<endl;
    
    //結束套接字庫的調用
    WSACleanup();
    system("pause");
}

運行結果:

在控制台中 ping www.baidu.com 的運行結果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM