日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

WinSock网络编程实用宝典(一)

發布時間:2025/4/14 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 WinSock网络编程实用宝典(一) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、TCP/IP?體系結構與特點?
??1、TCP/IP體系結構
??TCP/IP協議實際上就是在物理網上的一組完整的網絡協議。其中TCP是提供傳輸層服務,而IP則是提供網絡層服務。TCP/IP包括以下協議:(結構如圖1.1)

(圖1.1)?
??IP:?網間協議(Internet?Protocol)?負責主機間數據的路由和網絡上數據的存儲。同時為ICMP,TCP,???UDP提供分組發送服務。用戶進程通常不需要涉及這一層。

??ARP:?地址解析協議(Address?Resolution?Protocol)
???此協議將網絡地址映射到硬件地址。

??RARP:?反向地址解析協議(Reverse?Address?Resolution?Protocol)
???此協議將硬件地址映射到網絡地址

??ICMP:?網間報文控制協議(Internet?Control?Message?Protocol)
???此協議處理信關和主機的差錯和傳送控制。

??TCP:?傳送控制協議(Transmission?Control?Protocol)
???這是一種提供給用戶進程的可靠的全雙工字節流面向連接的協議。它要為用戶進程提供虛電路服務,并為數據可靠傳輸建立檢查。(注:大多數網絡用戶程序使用TCP)

??UDP:?用戶數據報協議(User?Datagram?Protocol)
???這是提供給用戶進程的無連接協議,用于傳送數據而不執行正確性檢查。

??FTP:?文件傳輸協議(File?Transfer?Protocol)
???允許用戶以文件操作的方式(文件的增、刪、改、查、傳送等)與另一主機相互通信。

??SMTP:?簡單郵件傳送協議(Simple?Mail?Transfer?Protocol)
???SMTP協議為系統之間傳送電子郵件。

??TELNET:終端協議(Telnet?Terminal?Procotol)
???允許用戶以虛終端方式訪問遠程主機

??HTTP:?超文本傳輸協議(Hypertext?Transfer?Procotol)
??
??TFTP:?簡單文件傳輸協議(Trivial?File?Transfer?Protocol)?
??2、TCP/IP特點

??TCP/IP協議的核心部分是傳輸層協議(TCP、UDP),網絡層協議(IP)和物理接口層,這三層通常是在操作系統內核中實現。因此用戶一般不涉及。編程時,編程界面有兩種形式:一、是由內核心直接提供的系統調用;二、使用以庫函數方式提供的各種函數。前者為核內實現,后者為核外實現。用戶服務要通過核外的應用程序才能實現,所以要使用套接字(socket)來實現。

??圖1.2是TCP/IP協議核心與應用程序關系圖。


(圖1.2)?
??二、專用術語

??1、套接字
??套接字是網絡的基本構件。它是可以被命名和尋址的通信端點,使用中的每一個套接字都有其類型和一個與之相連聽進程。套接字存在通信區域(通信區域又稱地址簇)中。套接字只與同一區域中的套接字交換數據(跨區域時,需要執行某和轉換進程才能實現)。WINDOWS?中的套接字只支持一個域——網際域。套接字具有類型。

??WINDOWS?SOCKET?1.1?版本支持兩種套接字:流套接字(SOCK_STREAM)和數據報套接字(SOCK_DGRAM)?

??2、WINDOWS?SOCKETS?實現

??一個WINDOWS?SOCKETS?實現是指實現了WINDOWS?SOCKETS規范所描述的全部功能的一套軟件。一般通過DLL文件來實現?
??3、阻塞處理例程

??阻塞處理例程(blocking?hook,阻塞鉤子)是WINDOWS?SOCKETS實現為了支持阻塞套接字函數調用而提供的一種機制。?
??4、多址廣播(multicast,多點傳送或組播)

??是一種一對多的傳輸方式,傳輸發起者通過一次傳輸就將信息傳送到一組接收者,與單點傳送
(unicast)和廣播(Broadcast)相對應。

一、客戶機/服務器模式

??在TCP/IP網絡中兩個進程間的相互作用的主機模式是客戶機/服務器模式(Client/Server?model)。該模式的建立基于以下兩點:1、非對等作用;2、通信完全是異步的。客戶機/服務器模式在操作過程中采取的是主動請示方式:?
??首先服務器方要先啟動,并根據請示提供相應服務:(過程如下)

??1、打開一通信通道并告知本地主機,它愿意在某一個公認地址上接收客戶請求。

??2、等待客戶請求到達該端口。

??3、接收到重復服務請求,處理該請求并發送應答信號。

??4、返回第二步,等待另一客戶請求

??5、關閉服務器。

??客戶方:

??1、打開一通信通道,并連接到服務器所在主機的特定端口。

??2、向服務器發送服務請求報文,等待并接收應答;繼續提出請求……

??3、請求結束后關閉通信通道并終止。?
??二、基本套接字

??為了更好說明套接字編程原理,給出幾個基本的套接字,在以后的篇幅中會給出更詳細的使用說明。

??1、創建套接字——socket()

??功能:使用前創建一個新的套接字

??格式:SOCKET?PASCAL?FAR?socket(int?af,int?type,int?procotol);

??參數:af:?通信發生的區域

??type:?要建立的套接字類型

??procotol:?使用的特定協議?
??2、指定本地地址——bind()

??功能:將套接字地址與所創建的套接字號聯系起來。

??格式:int?PASCAL?FAR?bind(SOCKET?s,const?struct?sockaddr?FAR?*?name,int?namelen);

??參數:s:?是由socket()調用返回的并且未作連接的套接字描述符(套接字號)。

??其它:沒有錯誤,bind()返回0,否則SOCKET_ERROR

??地址結構說明:

struct?sockaddr_in
{
short?sin_family;//AF_INET
u_short?sin_port;//16位端口號,網絡字節順序
struct?in_addr?sin_addr;//32位IP地址,網絡字節順序
char?sin_zero[8];//保留
}?
??3、建立套接字連接——connect()和accept()

??功能:共同完成連接工作

??格式:int?PASCAL?FAR?connect(SOCKET?s,const?struct?sockaddr?FAR?*?name,int?namelen);

??SOCKET?PASCAL?FAR?accept(SOCKET?s,struct?sockaddr?FAR?*?name,int?FAR?*?addrlen);

??參數:同上?
??4、監聽連接——listen()

??功能:用于面向連接服務器,表明它愿意接收連接。

??格式:int?PASCAL?FAR?listen(SOCKET?s,?int?backlog);
??5、數據傳輸——send()與recv()

??功能:數據的發送與接收

??格式:int?PASCAL?FAR?send(SOCKET?s,const?char?FAR?*?buf,int?len,int?flags);

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

??參數:buf:指向存有傳輸數據的緩沖區的指針。?

??6、多路復用——select()

??功能:用來檢測一個或多個套接字狀態。

??格式:int?PASCAL?FAR?select(int?nfds,fd_set?FAR?*?readfds,fd_set?FAR?*?writefds,?
fd_set?FAR?*?exceptfds,const?struct?timeval?FAR?*?timeout);

??參數:readfds:指向要做讀檢測的指針

?????writefds:指向要做寫檢測的指針

?????exceptfds:指向要檢測是否出錯的指針

?????timeout:最大等待時間?
??7、關閉套接字——closesocket()

??功能:關閉套接字s

??格式:BOOL?PASCAL?FAR?closesocket(SOCKET?s);

三、典型過程圖

??2.1?面向連接的套接字的系統調用時序圖



??2.2?無連接協議的套接字調用時序圖



???2.3?面向連接的應用程序流程圖

?

Windows?Socket1.1?程序設計

一、簡介

??Windows?Sockets?是從?Berkeley?Sockets?擴展而來的,其在繼承?Berkeley?Sockets?的基礎上,又進行了新的擴充。這些擴充主要是提供了一些異步函數,并增加了符合WINDOWS消息驅動特性的網絡事件異步選擇機制。

??Windows?Sockets由兩部分組成:開發組件和運行組件。

??開發組件:Windows?Sockets?實現文檔、應用程序接口(API)引入庫和一些頭文件。

??運行組件:Windows?Sockets?應用程序接口的動態鏈接庫(WINSOCK.DLL)。?
??二、主要擴充說明?
??1、異步選擇機制:

??Windows?Sockets?的異步選擇函數提供了消息機制的網絡事件選擇,當使用它登記網絡事件發生時,應用程序相應窗口函數將收到一個消息,消息中指示了發生的網絡事件,以及與事件相關的一些信息。

??Windows?Sockets?提供了一個異步選擇函數?WSAAsyncSelect(),用它來注冊應用程序感興趣的網絡事件,當這些事件發生時,應用程序相應的窗口函數將收到一個消息。

??函數結構如下:?
int?PASCAL?FAR?WSAAsyncSelect(SOCKET?s,HWND?hWnd,unsigned?int?wMsg,long?lEvent);
??參數說明:

???hWnd:窗口句柄

???wMsg:需要發送的消息

???lEvent:事件(以下為事件的內容)
值:含義:FD_READ期望在套接字上收到數據(即讀準備好)時接到通知FD_WRITE期望在套接字上可發送數據(即寫準備好)時接到通知FD_OOB期望在套接字上有帶外數據到達時接到通知FD_ACCEPT期望在套接字上有外來連接時接到通知FD_CONNECT期望在套接字連接建立完成時接到通知FD_CLOSE期望在套接字關閉時接到通知
??例如:我們要在套接字讀準備好或寫準備好時接到通知,語句如下:?
rc=WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);
??如果我們需要注銷對套接字網絡事件的消息發送,只要將?lEvent?設置為0?
??2、異步請求函數

??在?Berkeley?Sockets?中請求服務是阻塞的,WINDOWS?SICKETS?除了支持這一類函數外,還增加了相應的異步請求函數(WSAAsyncGetXByY();)。?

??3、阻塞處理方法

??Windows?Sockets?為了實現當一個應用程序的套接字調用處于阻塞時,能夠放棄CPU讓其它應用程序運行,它在調用處于阻塞時便進入一個叫“HOOK”的例程,此例程負責接收和分配WINDOWS消息,使得其它應用程序仍然能夠接收到自己的消息并取得控制權。

??WINDOWS?是非搶先的多任務環境,即若一個程序不主動放棄其控制權,別的程序就不能執行。因此在設計Windows?Sockets?程序時,盡管系統支持阻塞操作,但還是反對程序員使用該操作。但由于?SUN?公司下的?Berkeley?Sockets?的套接字默認操作是阻塞的,WINDOWS?作為移植的?SOCKETS?也不可避免對這個操作支持。

??在Windows?Sockets?實現中,對于不能立即完成的阻塞操作做如下處理:DLL初始化→循環操作。在循環中,它發送任何?WINDOWS?消息,并檢查這個?Windows?Sockets?調用是否完成,在必要時,它可以放棄CPU讓其它應用程序執行(當然使用超線程的CPU就不會有這個麻煩了^_^)。我們可以調用?WSACancelBlockingCall()?函數取消此阻塞操作。

??在?Windows?Sockets?中,有一個默認的阻塞處理例程?BlockingHook()?簡單地獲取并發送?WINDOWS?消息。如果要對復雜程序進行處理,Windows?Sockets?中還有?WSASetBlockingHook()?提供用戶安裝自己的阻塞處理例程能力;與該函數相對應的則是?SWAUnhookBlockingHook(),它用于刪除先前安裝的任何阻塞處理例程,并重新安裝默認的處理例程。請注意,設計自己的阻塞處理例程時,除了函數?WSACancelBlockingHook()?之外,它不能使用其它的?Windows?Sockets?API?函數。在處理例程中調用?WSACancelBlockingHook()函數將取消處于阻塞的操作,它將結束阻塞循環。?
??4、出錯處理

??Windows?Sockets?為了和以后多線程環境(WINDOWS/UNIX)兼容,它提供了兩個出錯處理函數來獲取和設置當前線程的最近錯誤號。(WSAGetLastEror()和WSASetLastError())?
??5、啟動與終止

??使用函數?WSAStartup()?和?WSACleanup()?啟動和終止套接字。?

三、Windows?Sockets網絡程序設計核心?

??我們終于可以開始真正的?Windows?Sockets?網絡程序設計了。不過我們還是先看一看每個?Windows?Sockets?網絡程序都要涉及的內容。讓我們一步步慢慢走。
??1、啟動與終止

??在所有?Windows?Sockets?函數中,只有啟動函數?WSAStartup()?和終止函數?WSACleanup()?是必須使用的。

??啟動函數必須是第一個使用的函數,而且它允許指定?Windows?Sockets?API?的版本,并獲得?SOCKETS的特定的一些技術細節。本結構如下:
int?PASCAL?FAR?WSAStartup(WORD?wVersionRequested,?LPWSADATA?lpWSAData);
??其中?wVersionRequested?保證?SOCKETS?可正常運行的?DLL?版本,如果不支持,則返回錯誤信息。
我們看一下下面這段代碼,看一下如何進行?WSAStartup()?的調用
WORD?wVersionRequested;//?定義版本信息變量
WSADATA?wsaData;//定義數據信息變量
int?err;//定義錯誤號變量
wVersionRequested?=?MAKEWORD(1,1);//給版本信息賦值
err?=?WSAStartup(wVersionRequested,?&wsaData);//給錯誤信息賦值
if(err!=0)
{
return;//告訴用戶找不到合適的版本
}
//確認?Windows?Sockets?DLL?支持?1.1?版本
//DLL?版本可以高于?1.1
//系統返回的版本號始終是最低要求的?1.1,即應用程序與DLL?中可支持的最低版本號
if(LOBYTE(wsaData.wVersion)!=?1||?HIBYTE(wsaData.wVersion)!=1)
{
WSACleanup();//告訴用戶找不到合適的版本
return;
}
//Windows?Sockets?DLL?被進程接受,可以進入下一步操作?
??關閉函數使用時,任何打開并已連接的?SOCK_STREAM?套接字被復位,但那些已由?closesocket()?函數關閉的但仍有未發送數據的套接字不受影響,未發送的數據仍將被發送。程序運行時可能會多次調用?WSAStartuo()?函數,但必須保證每次調用時的?wVersionRequested?的值是相同的。
??2、異步請求服務

??Windows?Sockets?除支持?Berkeley?Sockets?中同步請求,還增加了了一類異步請求服務函數?WSAAsyncGerXByY()。該函數是阻塞請求函數的異步版本。應用程序調用它時,由?Windows?Sockets?DLL?初始化這一操作并返回調用者,此函數返回一個異步句柄,用來標識這個操作。當結果存儲在調用者提供的緩沖區,并且發送一個消息到應用程序相應窗口。常用結構如下:
HANDLE?taskHnd;
char?hostname="rs6000";
taskHnd?=?WSAAsyncBetHostByName(hWnd,wMsg,hostname,buf,buflen);?
??需要注意的是,由于?Windows?的內存對像可以設置為可移動和可丟棄,因此在操作內存對象是,必須保證?WIindows?Sockets?DLL?對象是可用的。?

??3、異步數據傳輸

??使用?send()?或?sendto()?函數來發送數據,使用?recv()?或recvfrom()?來接收數據。Windows?Sockets?不鼓勵用戶使用阻塞方式傳輸數據,因為那樣可能會阻塞整個?Windows?環境。下面我們看一個異步數據傳輸實例:

??假設套接字?s?在連接建立后,已經使用了函數?WSAAsyncSelect()?在其上注冊了網絡事件?FD_READ?和?FD_WRITE,并且?wMsg?值為?UM_SOCK,那么我們可以在?Windows?消息循環中增加如下的分支語句:?
case?UM_SOCK:
switch(lParam)
{
case?FD_READ:
len?=?recv(wParam,lpBuffer,length,0);
break;
case?FD_WRITE:
while(send(wParam,lpBuffer,len,0)!=SOCKET_ERROR)
break;
}
break;?
??4、出錯處理

??Windows?提供了一個函數來獲取最近的錯誤碼?WSAGetLastError(),推薦的編寫方式如下:?
len?=?send?(s,lpBuffer,len,0);
of((len==SOCKET_ERROR)&&(WSAGetLastError()==WSAWOULDBLOCK)){...}?
?
基于Visual?C++的Winsock?API研究
為了方便網絡編程,90年代初,由Microsoft聯合了其他幾家公司共同制定了一套WINDOWS下的網絡編程接口,即Windows?Sockets規范,它不是一種網絡協議,而是一套開放的、支持多種協議的Windows下的網絡編程接口。現在的Winsock已經基本上實現了與協議無關,你可以使用Winsock來調用多種協議的功能,但較常使用的是TCP/IP協議。Socket實際在計算機中提供了一個通信端口,可以通過這個端口與任何一個具有Socket接口的計算機通信。應用程序在網絡上傳輸,接收的信息都通過這個Socket接口來實現。

??微軟為VC定義了Winsock類如CAsyncSocket類和派生于CAsyncSocket?的CSocket類,它們簡單易用,讀者朋友當然可以使用這些類來實現自己的網絡程序,但是為了更好的了解Winsock?API編程技術,我們這里探討怎樣使用底層的API函數實現簡單的?Winsock?網絡應用程式設計,分別說明如何在Server端和Client端操作Socket,實現基于TCP/IP的數據傳送,最后給出相關的源代碼。

??在VC中進行WINSOCK的API編程開發的時候,需要在項目中使用下面三個文件,否則會出現編譯錯誤。

??1.WINSOCK.H:?這是WINSOCK?API的頭文件,需要包含在項目中。

??2.WSOCK32.LIB:?WINSOCK?API連接庫文件。在使用中,一定要把它作為項目的非缺省的連接庫包含到項目文件中去。?

??3.WINSOCK.DLL:?WINSOCK的動態連接庫,位于WINDOWS的安裝目錄下。

??一、服務器端操作?socket(套接字)

??1)在初始化階段調用WSAStartup()

??此函數在應用程序中初始化Windows?Sockets?DLL?,只有此函數調用成功后,應用程序才可以再調用其他Windows?Sockets?DLL中的API函數。在程式中調用該函數的形式如下:WSAStartup((WORD)((1<<8|1),(LPWSADATA)&WSAData),其中(1<<8|1)表示我們用的是WinSocket1.1版本,WSAata用來存儲系統傳回的關于WinSocket的資料。

??2)建立Socket

??初始化WinSock的動態連接庫后,需要在服務器端建立一個監聽的Socket,為此可以調用Socket()函數用來建立這個監聽的Socket,并定義此Socket所使用的通信協議。此函數調用成功返回Socket對象,失敗則返回INVALID_SOCKET(調用WSAGetLastError()可得知原因,所有WinSocket?的函數都可以使用這個函數來獲取失敗的原因)。

SOCKET?PASCAL?FAR?socket(?int?af,?int?type,?int?protocol?)
參數:?af:目前只提供?PF_INET(AF_INET);
type:Socket?的類型?(SOCK_STREAM、SOCK_DGRAM);
protocol:通訊協定(如果使用者不指定則設為0);

如果要建立的是遵從TCP/IP協議的socket,第二個參數type應為SOCK_STREAM,如為UDP(數據報)的socket,應為SOCK_DGRAM。

??3)綁定端口

??接下來要為服務器端定義的這個監聽的Socket指定一個地址及端口(Port),這樣客戶端才知道待會要連接哪一個地址的哪個端口,為此我們要調用bind()函數,該函數調用成功返回0,否則返回SOCKET_ERROR。
int?PASCAL?FAR?bind(?SOCKET?s,?const?struct?sockaddr?FAR?*name,int?namelen?);

參?數:?s:Socket對象名;
name:Socket的地址值,這個地址必須是執行這個程式所在機器的IP地址;
namelen:name的長度;

??如果使用者不在意地址或端口的值,那么可以設定地址為INADDR_ANY,及Port為0,Windows?Sockets?會自動將其設定適當之地址及Port?(1024?到?5000之間的值)。此后可以調用getsockname()函數來獲知其被設定的值。

??4)監聽

??當服務器端的Socket對象綁定完成之后,服務器端必須建立一個監聽的隊列來接收客戶端的連接請求。listen()函數使服務器端的Socket?進入監聽狀態,并設定可以建立的最大連接數(目前最大值限制為?5,?最小值為1)。該函數調用成功返回0,否則返回SOCKET_ERROR。

int?PASCAL?FAR?listen(?SOCKET?s,?int?backlog?);
參?數:?s:需要建立監聽的Socket;
backlog:最大連接個數;?
??服務器端的Socket調用完listen()后,如果此時客戶端調用connect()函數提出連接申請的話,Server?端必須再調用accept()?函數,這樣服務器端和客戶端才算正式完成通信程序的連接動作。為了知道什么時候客戶端提出連接要求,從而服務器端的Socket在恰當的時候調用accept()函數完成連接的建立,我們就要使用WSAAsyncSelect()函數,讓系統主動來通知我們有客戶端提出連接請求了。該函數調用成功返回0,否則返回SOCKET_ERROR。

int?PASCAL?FAR?WSAAsyncSelect(?SOCKET?s,?HWND?hWnd,unsigned?int?wMsg,?long?lEvent?);
參數:?s:Socket?對象;
hWnd?:接收消息的窗口句柄;
wMsg:傳給窗口的消息;
lEvent:被注冊的網絡事件,也即是應用程序向窗口發送消息的網路事件,該值為下列值FD_READ、FD_WRITE、FD_OOB、FD_ACCEPT、FD_CONNECT、FD_CLOSE的組合,各個值的具體含意為FD_READ:希望在套接字S收到數據時收到消息;FD_WRITE:希望在套接字S上可以發送數據時收到消息;FD_ACCEPT:希望在套接字S上收到連接請求時收到消息;FD_CONNECT:希望在套接字S上連接成功時收到消息;FD_CLOSE:希望在套接字S上連接關閉時收到消息;FD_OOB:希望在套接字S上收到帶外數據時收到消息。?
??具體應用時,wMsg應是在應用程序中定義的消息名稱,而消息結構中的lParam則為以上各種網絡事件名稱。所以,可以在窗口處理自定義消息函數中使用以下結構來響應Socket的不同事件:??

switch(lParam)?
??{case?FD_READ:
????…??
??break;
case?FD_WRITE、
????…
??break;
????…
}?
??5)服務器端接受客戶端的連接請求

??當Client提出連接請求時,Server?端hwnd視窗會收到Winsock?Stack送來我們自定義的一個消息,這時,我們可以分析lParam,然后調用相關的函數來處理此事件。為了使服務器端接受客戶端的連接請求,就要使用accept()?函數,該函數新建一Socket與客戶端的Socket相通,原先監聽之Socket繼續進入監聽狀態,等待他人的連接要求。該函數調用成功返回一個新產生的Socket對象,否則返回INVALID_SOCKET。

SOCKET?PASCAL?FAR?accept(?SCOKET?s,?struct?sockaddr?FAR?*addr,int?FAR?*addrlen?);
參數:s:Socket的識別碼;
addr:存放來連接的客戶端的地址;
addrlen:addr的長度?
??6)結束?socket?連接

??結束服務器和客戶端的通信連接是很簡單的,這一過程可以由服務器或客戶機的任一端啟動,只要調用closesocket()就可以了,而要關閉Server端監聽狀態的socket,同樣也是利用此函數。另外,與程序啟動時調用WSAStartup()憨數相對應,程式結束前,需要調用?WSACleanup()?來通知Winsock?Stack釋放Socket所占用的資源。這兩個函數都是調用成功返回0,否則返回SOCKET_ERROR。

int?PASCAL?FAR?closesocket(?SOCKET?s?);
參?數:s:Socket?的識別碼;
int?PASCAL?FAR?WSACleanup(?void?);
參?數:?無?

二、客戶端Socket的操作

??1)建立客戶端的Socket

??客戶端應用程序首先也是調用WSAStartup()?函數來與Winsock的動態連接庫建立關系,然后同樣調用socket()?來建立一個TCP或UDP?socket(相同協定的?sockets?才能相通,TCP?對?TCP,UDP?對?UDP)。與服務器端的socket?不同的是,客戶端的socket?可以調用?bind()?函數,由自己來指定IP地址及port號碼;但是也可以不調用?bind(),而由?Winsock來自動設定IP地址及port號碼。

??2)提出連接申請

??客戶端的Socket使用connect()函數來提出與服務器端的Socket建立連接的申請,函數調用成功返回0,否則返回SOCKET_ERROR。

int?PASCAL?FAR?connect(?SOCKET?s,?const?struct?sockaddr?FAR?*name,?int?namelen?);
參?數:s:Socket?的識別碼;
name:Socket想要連接的對方地址;
namelen:name的長度?
??三、數據的傳送

??雖然基于TCP/IP連接協議(流套接字)的服務是設計客戶機/服務器應用程序時的主流標準,但有些服務也是可以通過無連接協議(數據報套接字)提供的。先介紹一下TCP?socket?與UDP?socket?在傳送數據時的特性:Stream?(TCP)?Socket?提供雙向、可靠、有次序、不重復的資料傳送。Datagram?(UDP)?Socket?雖然提供雙向的通信,但沒有可靠、有次序、不重復的保證,所以UDP傳送數據可能會收到無次序、重復的資料,甚至資料在傳輸過程中出現遺漏。由于UDP?Socket?在傳送資料時,并不保證資料能完整地送達對方,所以絕大多數應用程序都是采用TCP處理Socket,以保證資料的正確性。一般情況下TCP?Socket?的數據發送和接收是調用send()?及recv()?這兩個函數來達成,而?UDP?Socket則是用sendto()?及recvfrom()?這兩個函數,這兩個函數調用成功發揮發送或接收的資料的長度,否則返回SOCKET_ERROR。

int?PASCAL?FAR?send(?SOCKET?s,?const?char?FAR?*buf,int?len,?int?flags?);
參數:s:Socket?的識別碼
buf:存放要傳送的資料的暫存區
len?buf:的長度
flags:此函數被調用的方式?
??對于Datagram?Socket而言,若是?datagram?的大小超過限制,則將不會送出任何資料,并會傳回錯誤值。對Stream?Socket?言,Blocking?模式下,若是傳送系統內的儲存空間不夠存放這些要傳送的資料,send()將會被block住,直到資料送完為止;如果該Socket被設定為?Non-Blocking?模式,那么將視目前的output?buffer空間有多少,就送出多少資料,并不會被?block?住。flags?的值可設為?0?或?MSG_DONTROUTE及?MSG_OOB?的組合。

int?PASCAL?FAR?recv(?SOCKET?s,?char?FAR?*buf,?int?len,?int?flags?);
參數:s:Socket?的識別碼
buf:存放接收到的資料的暫存區
len?buf:的長度
flags:此函數被調用的方式?
??對Stream?Socket?言,我們可以接收到目前input?buffer內有效的資料,但其數量不超過len的大小。

??四、自定義的CMySocket類的實現代碼:

??根據上面的知識,我自定義了一個簡單的CMySocket類,下面是我定義的該類的部分實現代碼:

//
CMySocket::CMySocket()?:?file://類的構造函數
{?
?WSADATA?wsaD;?
?memset(?m_LastError,?0,?ERR_MAXLENGTH?);
?//?m_LastError是類內字符串變量,初始化用來存放最后錯誤說明的字符串;
?//?初始化類內sockaddr_in結構變量,前者存放客戶端地址,后者對應于服務器端地址;
?memset(?&m_sockaddr,?0,?sizeof(?m_sockaddr?)?);?
?memset(?&m_rsockaddr,?0,?sizeof(?m_rsockaddr?)?);
?int?result?=?WSAStartup((WORD)((1<<8|1),?&wsaD);//初始化WinSocket動態連接庫;
?if(?result?!=?0?)?//?初始化失敗;
?{?set_LastError(?"WSAStartup?failed!",?WSAGetLastError()?);
??return;
?}
}

//
CMySocket::~CMySocket()?{?WSACleanup();?}//類的析構函數;

int?CMySocket::Create(?void?)
?{//?m_hSocket是類內Socket對象,創建一個基于TCP/IP的Socket變量,并將值賦給該變量;
??if?(?(m_hSocket?=?socket(?AF_INET,?SOCK_STREAM,?IPPROTO_TCP?))?==?INVALID_SOCKET?)
??{
???set_LastError(?"socket()?failed",?WSAGetLastError()?);
???return?ERR_WSAERROR;
??}
??return?ERR_SUCCESS;?
?}
///
int?CMySocket::Close(?void?)//關閉Socket對象;
{
?if?(?closesocket(?m_hSocket?)?==?SOCKET_ERROR?)
?{
??set_LastError(?"closesocket()?failed",?WSAGetLastError()?);
??return?ERR_WSAERROR;
?}
?file://重置sockaddr_in?結構變量;
?memset(?&m_sockaddr,?0,?sizeof(?sockaddr_in?)?);?
?memset(?&m_rsockaddr,?0,?sizeof(?sockaddr_in?)?);
?return?ERR_SUCCESS;
}
/
int?CMySocket::Connect(?char*?strRemote,?unsigned?int?iPort?)//定義連接函數;
{
?if(?strlen(?strRemote?)?==?0?||?iPort?==?0?)
??return?ERR_BADPARAM;
?hostent?*hostEnt?=?NULL;
?long?lIPAddress?=?0;
?hostEnt?=?gethostbyname(?strRemote?);//根據計算機名得到該計算機的相關內容;
?if(?hostEnt?!=?NULL?)
?{
??lIPAddress?=?((in_addr*)hostEnt->h_addr)->s_addr;
??m_sockaddr.sin_addr.s_addr?=?lIPAddress;
?}
?else
?{
??m_sockaddr.sin_addr.s_addr?=?inet_addr(?strRemote?);
?}
?m_sockaddr.sin_family?=?AF_INET;
?m_sockaddr.sin_port?=?htons(?iPort?);
?if(?connect(?m_hSocket,?(SOCKADDR*)&m_sockaddr,?sizeof(?m_sockaddr?)?)?==?SOCKET_ERROR?)
?{
??set_LastError(?"connect()?failed",?WSAGetLastError()?);
??return?ERR_WSAERROR;
?}
?return?ERR_SUCCESS;
}
///
int?CMySocket::Bind(?char*?strIP,?unsigned?int?iPort?)//綁定函數;
{
?if(?strlen(?strIP?)?==?0?||?iPort?==?0?)
??return?ERR_BADPARAM;
?memset(?&m_sockaddr,0,?sizeof(?m_sockaddr?)?);
?m_sockaddr.sin_family?=?AF_INET;
?m_sockaddr.sin_addr.s_addr?=?inet_addr(?strIP?);
?m_sockaddr.sin_port?=?htons(?iPort?);
?if?(?bind(?m_hSocket,?(SOCKADDR*)&m_sockaddr,?sizeof(?m_sockaddr?)?)?==?SOCKET_ERROR?)
?{
??set_LastError(?"bind()?failed",?WSAGetLastError()?);
??return?ERR_WSAERROR;
?}
?return?ERR_SUCCESS;
}
//
int?CMySocket::Accept(?SOCKET?s?)//建立連接函數,S為監聽Socket對象名;
{?
?int?Len?=?sizeof(?m_rsockaddr?);
?memset(?&m_rsockaddr,?0,?sizeof(?m_rsockaddr?)?);
?if(?(?m_hSocket?=?accept(?s,?(SOCKADDR*)&m_rsockaddr,?&Len?)?)?==?INVALID_SOCKET?)
?{
??set_LastError(?"accept()?failed",?WSAGetLastError()?);
??return?ERR_WSAERROR;
?}
?return?ERR_SUCCESS;
}
/
int?CMySocket::asyncSelect(?HWND?hWnd,?unsigned?int?wMsg,?long?lEvent?)
file://事件選擇函數;
{
?if(?!IsWindow(?hWnd?)?||?wMsg?==?0?||?lEvent?==?0?)
??return?ERR_BADPARAM;
?if(?WSAAsyncSelect(?m_hSocket,?hWnd,?wMsg,?lEvent?)?==?SOCKET_ERROR?)
?{
??set_LastError(?"WSAAsyncSelect()?failed",?WSAGetLastError()?);
??return?ERR_WSAERROR;
?}
?return?ERR_SUCCESS;
}

int?CMySocket::Listen(?int?iQueuedConnections?)//監聽函數;
{
?if(?iQueuedConnections?==?0?)
??return?ERR_BADPARAM;
?if(?listen(?m_hSocket,?iQueuedConnections?)?==?SOCKET_ERROR?)
?{
??set_LastError(?"listen()?failed",?WSAGetLastError()?);
??return?ERR_WSAERROR;
?}
?return?ERR_SUCCESS;
}

int?CMySocket::Send(?char*?strData,?int?iLen?)//數據發送函數;
{
?if(?strData?==?NULL?||?iLen?==?0?)
??return?ERR_BADPARAM;
?if(?send(?m_hSocket,?strData,?iLen,?0?)?==?SOCKET_ERROR?)
?{
??set_LastError(?"send()?failed",?WSAGetLastError()?);
??return?ERR_WSAERROR;
?}
?return?ERR_SUCCESS;
}
/
int?CMySocket::Receive(?char*?strData,?int?iLen?)//數據接收函數;
{
?if(?strData?==?NULL?)
??return?ERR_BADPARAM;
?int?len?=?0;
?int?ret?=?0;
?ret?=?recv(?m_hSocket,?strData,?iLen,?0?);
?if?(?ret?==?SOCKET_ERROR?)
?{
??set_LastError(?"recv()?failed",?WSAGetLastError()?);
??return?ERR_WSAERROR;
?}
?return?ret;
}
void?CMySocket::set_LastError(?char*?newError,?int?errNum?)
file://WinSock?API操作錯誤字符串設置函數;
{
?memset(?m_LastError,?0,?ERR_MAXLENGTH?);?
?memcpy(?m_LastError,?newError,?strlen(?newError?)?);
?m_LastError[strlen(newError)+1]?=?'\0';
}?
??有了上述類的定義,就可以在網絡程序的服務器和客戶端分別定義CMySocket對象,建立連接,傳送數據了。例如,為了在服務器和客戶端發送數據,需要在服務器端定義兩個CMySocket對象ServerSocket1和ServerSocket2,分別用于監聽和連接,客戶端定義一個CMySocket對象ClientSocket,用于發送或接收數據,如果建立的連接數大于一,可以在服務器端再定義CMySocket對象,但要注意連接數不要大于五。

??由于Socket?API函數還有許多,如獲取遠端服務器、本地客戶機的IP地址、主機名等等,讀者可以再此基礎上對CMySocket補充完善,實現更多的功能。

TCP/IP?Winsock編程要點

利用Winsock編程由同步和異步方式,同步方式邏輯清晰,編程專注于應用,在搶先式的多任務操作系統中(WinNt、Win2K)采用多線程方式效率基本達到異步方式的水平,應此以下為同步方式編程要點。?

??1、快速通信?

??Winsock的Nagle算法將降低小數據報的發送速度,而系統默認是使用Nagle算法,使用?

int?setsockopt(?

SOCKET?s,?

int?level,?

int?optname,?

const?char?FAR?*optval,?

int?optlen?

);函數關閉它?
??例子:?

SOCKET?sConnect;?

sConnect=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);?

int?bNodelay?=?1;?

int?err;?

err?=?setsockopt(?

sConnect,?

IPPROTO_TCP,?

TCP_NODELAY,?

(char?*)&bNodelay,?

sizoeof(bNodelay));//不采用延時算法?

if?(err?!=?NO_ERROR)?

TRACE?("setsockopt?failed?for?some?reason\n");;?
??2、SOCKET的SegMentSize和收發緩沖?

??TCPSegMentSize是發送接受時單個數據報的最大長度,系統默認為1460,收發緩沖大小為8192。?

??在SOCK_STREAM方式下,如果單次發送數據超過1460,系統將分成多個數據報傳送,在對方接受到的將是一個數據流,應用程序需要增加斷幀的判斷。當然可以采用修改注冊表的方式改變1460的大小,但MicrcoSoft認為1460是最佳效率的參數,不建議修改。?

??在工控系統中,建議關閉Nagle算法,每次發送數據小于1460個字節(推薦1400),這樣每次發送的是一個完整的數據報,減少對方對數據流的斷幀處理。?

??3、同步方式中減少斷網時connect函數的阻塞時間?

??同步方式中的斷網時connect的阻塞時間為20秒左右,可采用gethostbyaddr事先判斷到服務主機的路徑是否是通的,或者先ping一下對方主機的IP地址。?

??A、采用gethostbyaddr阻塞時間不管成功與否為4秒左右。?

??例子:?

LONG?lPort=3024;?

struct?sockaddr_in?ServerHostAddr;//服務主機地址?

ServerHostAddr.sin_family=AF_INET;?

ServerHostAddr.sin_port=::htons(u_short(lPort));?

ServerHostAddr.sin_addr.s_addr=::inet_addr("192.168.1.3");?

HOSTENT*?pResult=gethostbyaddr((const?char?*)?&?

(ServerHostAddr.sin_addr.s_addr),4,AF_INET);?

if(NULL==pResult)?

{?

int?nErrorCode=WSAGetLastError();?

TRACE("gethostbyaddr?errorcode=%d",nErrorCode);?

}?

else?

{?

TRACE("gethostbyaddr?%s\n",pResult->h_name);;?

}?
??B、采用PING方式時間約2秒左右?

??暫略?

轉載于:https://www.cnblogs.com/qq78292959/archive/2010/05/20/2077058.html

總結

以上是生活随笔為你收集整理的WinSock网络编程实用宝典(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。