1. 程式人生 > >非同步SOCKET程式設計-傳送和接收資料[轉] 非同步SOCKET程式設計-傳送和接收資料[轉]

非同步SOCKET程式設計-傳送和接收資料[轉] 非同步SOCKET程式設計-傳送和接收資料[轉]

Socket(套接字)

◆先看定義:
typedef unsigned int u_int;
typedef u_int SOCKET;

◆Socket相當於進行網路通訊兩端的插座,只要對方的Socket和自己的Socket有通訊聯接,雙方就可以傳送和接收資料了。其定義類似於檔案控制代碼的定義。

◆Socket有五種不同的型別:

1、流式套接字(stream socket)
定義:

#define SOCK_STREAM 1 

流式套接字提供了雙向、有序的、無重複的以及無記錄邊界的資料流服務,適合處理大量資料。它是面向聯結的,必須建立資料傳輸鏈路,同時還必須對傳輸的資料進行驗證,確保資料的準確性。因此,系統開銷較大。

2、 資料報套接字(datagram socket)

定義:

#define SOCK_DGRAM 2 

資料報套接字也支援雙向的資料流,但不保證傳輸資料的準確性,但保留了記錄邊界。由於資料報套接字是無聯接的,例如廣播時的聯接,所以並不保證接收端是否正在偵聽。資料報套接字傳輸效率比較高。

3、原始套接字(raw-protocol interface)

定義:

#define SOCK_RAW 3 

原始套接字儲存了資料包中的完整IP頭,前面兩種套接字只能收到使用者資料。因此可以通過原始套接字對資料進行分析。
其它兩種套接字不常用,這裡就不介紹了。

◆Socket開發所必須需要的檔案(以WinSock V2.0為例):

標頭檔案:Winsock2.h

庫檔案:WS2_32.LIB

動態庫:W32_32.DLL

一些重要的定義

1、資料型別的基本定義:這個大家一看就懂。

typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;

2、 網路地址的資料結構,有一個老的和一個新的的,請大家留意,如果想知道為什麼,
請發郵件給Bill Gate。其實就是計算機的IP地址,不過一般不用用點分開的IP地
址,當然也提供一些轉換函式。

◆ 舊的網路地址結構的定義,為一個4位元組的聯合:

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;
#define s_addr S_un.S_addr /* can be used for most tcp & ip code */
//下面幾行省略,反正沒什麼用處。
};

其實完全不用這麼麻煩,請看下面:

◆ 新的網路地址結構的定義:
非常簡單,就是一個無符號長整數 unsigned long。舉個例子:IP地址為127.0.0.1的網路地址是什麼呢?請看定義:

#define INADDR_LOOPBACK 0x7f000001

3、 套接字地址結構

(1)、sockaddr結構:

struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};

sa_family為網路地址型別,一般為AF_INET,表示該socket在Internet域中進行通訊,該地址結構隨選擇的協議的不同而變化,因此一般情況下另一個與該地址結構大小相同的sockaddr_in結構更為常用,sockaddr_in結構用來標識TCP/IP協議下的地址。換句話說,這個結構是通用socket地址結構,而下面的sockaddr_in是專門針對Internet域的socket地址結構。

(2)、sockaddr_in結構

struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};

sin _family為網路地址型別,必須設定為AF_INET。sin_port為服務埠,注意不要使用已固定的服務埠,如HTTP的埠80等。如果埠設定為0,則系統會自動分配一個唯一埠。sin_addr為一個unsigned long的IP地址。sin_zero為填充欄位,純粹用來保證結構的大小。

◆ 將常用的用點分開的IP地址轉換為unsigned long型別的IP地址的函式:

unsigned long inet_addr(const char FAR * cp )

用法:

unsigned long addr=inet_addr("192.1.8.84")

◆ 如果將sin_addr設定為INADDR_ANY,則表示所有的IP地址,也即所有的計算機。

#define INADDR_ANY (u_long)0x00000000

4、 主機地址:

先看定義:

struct hostent {
char FAR * h_name; /* official name of host */
char FAR * FAR * h_aliases; /* alias list */
short h_addrtype; /* host address type */
short h_length; /* length of address */
char FAR * FAR * h_addr_list; /* list of addresses */
#define h_addr h_addr_list[0] /* address, for backward compat */
};
h_name為主機名字。
h_aliases為主機別名列表。
h_addrtype為地址型別。
h_length為地址型別。
h_addr_list為IP地址,如果該主機有多個網絡卡,就包括地址的列表。

另外還有幾個類似的結構,這裡就不一一介紹了。

5、 常見TCP/IP協議的定義:

#define IPPROTO_IP 0 
#define IPPROTO_ICMP 1 
#define IPPROTO_IGMP 2 
#define IPPROTO_TCP 6 
#define IPPROTO_UDP 17 
#define IPPROTO_RAW 255 

具體是什麼協議,大家一看就知道了。

套接字的屬性

為了靈活使用套接字,我們可以對它的屬性進行設定。

1、 屬性內容:

//允許除錯輸出
#define SO_DEBUG 0x0001 /* turn on debugging info recording */
//是否監聽模式
#define SO_ACCEPTCONN 0x0002 /* socket has had listen() */
//套接字與其他套接字的地址繫結
#define SO_REUSEADDR 0x0004 /* allow local address reuse */
//保持連線
#define SO_KEEPALIVE 0x0008 /* keep connections alive */
//不要路由出去
#define SO_DONTROUTE 0x0010 /* just use interface addresses */
//設定為廣播
#define SO_BROADCAST 0x0020 /* permit sending of broadcast msgs */
//使用環回不通過硬體
#define SO_USELOOPBACK 0x0040 /* bypass hardware when possible */
//當前拖延值
#define SO_LINGER 0x0080 /* linger on close if data present */
//是否加入帶外資料
#define SO_OOBINLINE 0x0100 /* leave received OOB data in line */
//禁用LINGER選項
#define SO_DONTLINGER (int)(~SO_LINGER)
//傳送緩衝區長度
#define SO_SNDBUF 0x1001 /* send buffer size */
//接收緩衝區長度
#define SO_RCVBUF 0x1002 /* receive buffer size */
//傳送超時時間
#define SO_SNDTIMEO 0x1005 /* send timeout */
//接收超時時間
#define SO_RCVTIMEO 0x1006 /* receive timeout */
//錯誤狀態
#define SO_ERROR 0x1007 /* get error status and clear */
//套接字型別
#define SO_TYPE 0x1008 /* get socket type */

2、 讀取socket屬性:

int getsockopt(SOCKET s, int level, int optname, char FAR * optval, int FAR * optlen)

s為欲讀取屬性的套接字。level為套接字選項的級別,大多數是特定協議和套接字專有的。如IP協議應為 IPPROTO_IP。

optname為讀取選項的名稱
optval為存放選項值的緩衝區指標。
optlen為緩衝區的長度

用法:

int ttl=0; //讀取TTL值
int rc = getsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));
//來自MS platform SDK 2003

3、 設定socket屬性:

int setsockopt(SOCKET s,int level, int optname,const char FAR * optval, int optlen)

s為欲設定屬性的套接字。
level為套接字選項的級別,用法同上。
optname為設定選項的名稱
optval為存放選項值的緩衝區指標。
optlen為緩衝區的長度

用法:

int ttl=32; //設定TTL值
int rc = setsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));

套接字的使用步驟

1、啟動Winsock:對Winsock DLL進行初始化,協商Winsock的版本支援並分配必要的
資源。(伺服器端和客戶端)

int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData )

wVersionRequested為打算載入Winsock的版本,一般如下設定:
wVersionRequested=MAKEWORD(2,0)
或者直接賦值:wVersionRequested=2

LPWSADATA為初始化Socket後加載的版本的資訊,定義如下:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
} WSADATA, FAR * LPWSADATA;

如果載入成功後資料為:

wVersion=2表示載入版本為2.0。
wHighVersion=514表示當前系統支援socket最高版本為2.2。
szDescription="WinSock 2.0"
szSystemStatus="Running"表示正在執行。
iMaxSockets=0表示同時開啟的socket最大數,為0表示沒有限制。
iMaxUdpDg=0表示同時開啟的資料報最大數,為0表示沒有限制。
lpVendorInfo沒有使用,為廠商指定資訊預留。

該函式使用方法:

WORD wVersion=MAKEWORD(2,0);
WSADATA wsData;
int nResult= WSAStartup(wVersion,&wsData);
if(nResult !=0)
{
//錯誤處理
}

2、建立套接字:(伺服器端和客戶端)

SOCKET socket( int af, int type, int protocol );
af為網路地址型別,一般為AF_INET,表示在Internet域中使用。
type為套接字型別,前面已經介紹了。
protocol為指定網路協議,一般為IPPROTO_IP。

用法:

SOCKET sock=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sock==INVALID_SOCKET)
{
//錯誤處理
}

3、套接字的繫結:將本地地址繫結到所建立的套接字上。(伺服器端和客戶端)

int bind( SOCKET s, const struct sockaddr FAR * name, int namelen )
s為已經建立的套接字。
name為socket地址結構,為sockaddr結構,如前面討論的,我們一般使用sockaddr_in
結構,在使用再強制轉換為sockaddr結構。
namelen為地址結構的長度。

用法:

sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port= htons(0); //保證位元組順序
addr. sin_addr.s_addr= inet_addr("192.1.8.84")
int nResult=bind(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

4、 套接字的監聽:(伺服器端)

int listen(SOCKET s, int backlog )

s為一個已繫結但未聯接的套接字。
backlog為指定正在等待聯接的最大佇列長度,這個引數非常重要,因為伺服器一般可
以提供多個連線。
用法:

int nResult=listen(s,5) //最多5個連線
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

5、套接字等待連線::(伺服器端)

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

s為處於監聽模式的套接字。
sockaddr為接收成功後返回客戶端的網路地址。
addrlen為網路地址的長度。

用法:

sockaddr_in addr;
SOCKET s_d=accept(s,(sockaddr*)&addr,sizeof(sockaddr));
if(s==INVALID_SOCKET)
{
//錯誤處理
}

6、套接字的連結:將兩個套接字連結起來準備通訊。(客戶端)

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

s為欲連結的已建立的套接字。
name為欲連結的socket地址。
namelen為socket地址的結構的長度。

用法:

sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port=htons(0); //保證位元組順序
addr. sin_addr.s_addr= htonl(INADDR_ANY) //保證位元組順序
int nResult=connect(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

7、套接字傳送資料:(伺服器端和客戶端)

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

s為伺服器端監聽的套接字。
buf為欲傳送資料緩衝區的指標。
len為傳送資料緩衝區的長度。
flags為資料傳送標記。
返回值為傳送資料的字元數。

◆這裡講一下這個傳送標記,下面8中討論的接收標記也一樣:

flag取值必須為0或者如下定義的組合:0表示沒有特殊行為。

#define MSG_OOB 0x1 /* process out-of-band data */
#define MSG_PEEK 0x2 /* peek at incoming message */
#define MSG_DONTROUTE 0x4 /* send without using routing tables */
MSG_OOB表示資料應該帶外發送,所謂帶外資料就是TCP緊急資料。
MSG_PEEK表示使有用的資料複製到緩衝區內,但並不從系統緩衝區內刪除。
MSG_DONTROUTE表示不要將包路由出去。

用法:

char buf[]="xiaojin";
int nResult=send(s,buf,strlen(buf));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

8、 套接字的資料接收:(客戶端)

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

s為準備接收資料的套接字。
buf為準備接收資料的緩衝區。
len為準備接收資料緩衝區的大小。
flags為資料接收標記。
返回值為接收的資料的字元數。

用法:

char mess[1000];
int nResult =recv(s,mess,1000,0);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

9、中斷套接字連線:通知伺服器端或客戶端停止接收和傳送資料。(伺服器端和客戶端)

int shutdown(SOCKET s, int how)

s為欲中斷連線的套接字。
How為描述禁止哪些操作,取值為:SD_RECEIVE、SD_SEND、SD_BOTH。

#define SD_RECEIVE 0x00
#define SD_SEND 0x01
#define SD_BOTH 0x02
 

用法:

int nResult= shutdown(s,SD_BOTH);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

10、 關閉套接字:釋放所佔有的資源。(伺服器端和客戶端)

int closesocket( SOCKET s )

s為欲關閉的套接字。

用法:

int nResult=closesocket(s);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
posted @ 2006-04-27 09:31 不會飛的鳥 閱讀(221) | 評論 (0)編輯 收藏
與socket有關的一些函式介紹

1、讀取當前錯誤值:每次發生錯誤時,如果要對具體問題進行處理,那麼就應該呼叫這個函式取得錯誤程式碼。
      int  WSAGetLastError(void );
      #define h_errno   WSAGetLastError()
錯誤值請自己閱讀Winsock2.h。

2、將主機的unsigned long值轉換為網路位元組順序(32位):為什麼要這樣做呢?因為不同的計算機使用不同的位元組順序儲存資料。因此任何從Winsock函式對IP地址和埠號的引用和傳給Winsock函式的IP地址和埠號均時按照網路順序組織的。
      u_long  htonl(u_long hostlong);
      舉例:htonl(0)=0
      htonl(80)= 1342177280
3、將unsigned long數從網路位元組順序轉換位主機位元組順序,是上面函式的逆函式。
      u_long  ntohl(u_long netlong);
      舉例:ntohl(0)=0
      ntohl(1342177280)= 80
4、將主機的unsigned short值轉換為網路位元組順序(16位):原因同2:
      u_short  htons(u_short hostshort);
      舉例:htonl(0)=0
      htonl(80)= 20480
5、將unsigned short數從網路位元組順序轉換位主機位元組順序,是上面函式的逆函式。
      u_short  ntohs(u_short netshort);
      舉例:ntohs(0)=0
      ntohsl(20480)= 80
6、將用點分割的IP地址轉換位一個in_addr結構的地址,這個結構的定義見筆記(一),實際上就是一個unsigned long值。計算機內部處理IP地址可是不認識如192.1.8.84之類的資料。
      unsigned long  inet_addr( const char FAR * cp );
      舉例:inet_addr("192.1.8.84")=1409810880
      inet_addr("127.0.0.1")= 16777343
如果發生錯誤,函式返回INADDR_NONE值。

7、將網路地址轉換位用點分割的IP地址,是上面函式的逆函式。
      char FAR *  inet_ntoa( struct in_addr in );
      舉例:char * ipaddr=NULL;
      char addr[20];
      in_addr inaddr;
      inaddr. s_addr=16777343;
      ipaddr= inet_ntoa(inaddr);
      strcpy(addr,ipaddr); 
這樣addr的值就變為127.0.0.1。
注意意不要修改返回值或者進行釋放動作。如果函式失敗就會返回NULL值。

8、獲取套接字的本地地址結構:
      int  getsockname(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
      s為套接字
      name為函式呼叫後獲得的地址值
      namelen為緩衝區的大小。
 
9、獲取與套接字相連的端地址結構:
      int  getpeername(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
      s為套接字
      name為函式呼叫後獲得的端地址值
      namelen為緩衝區的大小。
 
10、獲取計算機名:
      int  gethostname( char FAR * name, int namelen );
      name是存放計算機名的緩衝區
      namelen是緩衝區的大小
      用法:
      char szName[255];
      memset(szName,0,255);
      if(gethostname(szName,255)==SOCKET_ERROR)
      {
      		//錯誤處理
      }
      返回值為:szNmae="xiaojin"
 
11、根據計算機名獲取主機地址:
      struct hostent FAR *  gethostbyname( const char FAR * name );

      name為計算機名。
      用法:
      hostent * host;
      char* ip;
      host= gethostbyname("xiaojin");
      if(host->h_addr_list[0])
      {
	      struct in_addr addr;
    	  memmove(&addr, host->h_addr_list[0],4);
	      //獲得標準IP地址
	      ip=inet_ ntoa (addr);
      }

      返回值為:hostent->h_name="xiaojin"
          hostent->h_addrtype=2    //AF_INET
          hostent->length=4
          ip="127.0.0.1"
 
Winsock 的I/O操作:

1、 兩種I/O模式
  • 阻塞模式:執行I/O操作完成前會一直進行等待,不會將控制權交給程式。套接字 預設為阻塞模式。可以通過多執行緒技術進行處理。
  • 非阻塞模式:執行I/O操作時,Winsock函式會返回並交出控制權。這種模式使用 起來比較複雜,因為函式在沒有執行完成就進行返回,會不斷地返回 WSAEWOULDBLOCK錯誤。但功能強大。
為了解決這個問題,提出了進行I/O操作的一些I/O模型,下面介紹最常見的三種:

2、select模型:

  通過呼叫select函式可以確定一個或多個套接字的狀態,判斷套接字上是否有資料,或
者能否向一個套接字寫入資料。
      int  select( int nfds, fd_set FAR * readfds, fd_set FAR * writefds, 
      fd_set FAR *exceptfds, const struct timeval FAR * timeout );
      
◆先來看看涉及到的結構的定義:
a、 d_set結構:
#define FD_SETSIZE 64?
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;      
fd_count為已設定socket的數量
fd_array為socket列表,FD_SETSIZE為最大socket數量,建議不小於64。這是微軟建
議的。

B、timeval結構:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
tv_sec為時間的秒值。
tv_usec為時間的毫秒值。
這個結構主要是設定select()函式的等待值,如果將該結構設定為(0,0),則select()函式
會立即返回。

◆再來看看select函式各引數的作用:
  1. nfds:沒有任何用處,主要用來進行系統相容用,一般設定為0。
  2. readfds:等待可讀性檢查的套接字組。
  3. writefds;等待可寫性檢查的套接字組。
  4. exceptfds:等待錯誤檢查的套接字組。
  5. timeout:超時時間。
  6. 函式失敗的返回值:呼叫失敗返回SOCKET_ERROR,超時返回0。
readfds、writefds、exceptfds三個變數至少有一個不為空,同時這個不為空的套接字組
種至少有一個socket,道理很簡單,否則要select幹什麼呢。 舉例:測試一個套接字是否可讀:
fd_set fdread;
//FD_ZERO定義
// #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)
FD_ZERO(&fdread);
FD_SET(s,&fdread); //加入套接字,詳細定義請看winsock2.h
if(select(0,%fdread,NULL,NULL,NULL)>0
{
	//成功
	if(FD_ISSET(s,&fread) //是否存在fread中,詳細定義請看winsock2.h
	{
		//是可讀的
	}
}

◆I/O操作函式:主要用於獲取與套接字相關的操作引數。

 int  ioctlsocket(SOCKET s, long cmd, u_long FAR * argp );     
s為I/O操作的套接字。
cmd為對套接字的操作命令。
argp為命令所帶引數的指標。

常見的命令:
//確定套接字自動讀入的資料量
#define FIONREAD _IOR(''''f'''', 127, u_long) /* get # bytes to read */
//允許或禁止套接字的非阻塞模式,允許為非0,禁止為0
#define FIONBIO _IOW(''''f'''', 126, u_long) /* set/clear non-blocking i/o */
//確定是否所有帶外資料都已被讀入
#define SIOCATMARK _IOR(''''s'''', 7, u_long) /* at oob mark? */
3、WSAAsynSelect模型:
WSAAsynSelect模型也是一個常用的非同步I/O模型。應用程式可以在一個套接字上接收以
WINDOWS訊息為基礎的網路事件通知。該模型的實現方法是通過呼叫WSAAsynSelect函
數 自動將套接字設定為非阻塞模式,並向WINDOWS註冊一個或多個網路時間,並提供一
個通知時使用的視窗控制代碼。當註冊的事件發生時,對應的視窗將收到一個基於訊息的通知。
      int  WSAAsyncSelect( SOCKET s, HWND hWnd, u_int wMsg, long lEvent);       
s為需要事件通知的套接字
hWnd為接收訊息的視窗控制代碼
wMsg為要接收的訊息
lEvent為掩碼,指定應用程式感興趣的網路事件組合,主要如下:
#define FD_READ_BIT 0
#define FD_READ (1 << FD_READ_BIT)
#define FD_WRITE_BIT 1
#define FD_WRITE (1 << FD_WRITE_BIT)
#define FD_OOB_BIT 2
#define FD_OOB (1 << FD_OOB_BIT)
#define FD_ACCEPT_BIT 3
#define FD_ACCEPT (1 << FD_ACCEPT_BIT)
#define FD_CONNECT_BIT 4
#define FD_CONNECT (1 << FD_CONNECT_BIT)
#define FD_CLOSE_BIT 5
#define FD_CLOSE (1 << FD_CLOSE_BIT)
用法:要接收讀寫通知:
int nResult= WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);
if(nResult==SOCKET_ERROR)
{
	//錯誤處理
}
取消通知:
      int nResult= WSAAsyncSelect(s,hWnd,0,0); 
當應用程式視窗hWnd收到訊息時,wMsg.wParam引數標識了套接字,lParam的低字標明
了網路事件,高字則包含錯誤程式碼。

4、WSAEventSelect模型
WSAEventSelect模型類似WSAAsynSelect模型,但最主要的區別是網路事件發生時會被髮
送到一個事件物件控制代碼,而不是傳送到一個視窗。

使用步驟如下:
a、 建立事件物件來接收網路事件:
#define WSAEVENT HANDLE
#define LPWSAEVENT LPHANDLE
WSAEVENT WSACreateEvent( void );
該函式的返回值為一個事件物件控制代碼,它具有兩種工作狀態:已傳信(signaled)和未傳信
(nonsignaled)以及兩種工作模式:人工重設(manual reset)和自動重設(auto reset)。預設未
未傳信的工作狀態和人工重設模式。

b、將事件物件與套接字關聯,同時註冊事件,使事件物件的工作狀態從未傳信轉變未
已傳信。
      int  WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents );  
s為套接字
hEventObject為剛才建立的事件物件控制代碼
lNetworkEvents為掩碼,定義如上面所述

c、I/O處理後,設定事件物件為未傳信
BOOL WSAResetEvent( WSAEVENT hEvent );

Hevent為事件物件

成功返回TRUE,失敗返回FALSE。

d、等待網路事件來觸發事件控制代碼的工作狀態:

DWORD WSAWaitForMultipleEvents( DWORD cEvents,
const WSAEVENT FAR * lphEvents, BOOL fWaitAll,
DWORD dwTimeout, BOOL fAlertable );

lpEvent為事件控制代碼陣列的指標
cEvent為為事件控制代碼的數目,其最大值為WSA_MAXIMUM_WAIT_EVENTS 
fWaitAll指定等待型別:TRUE:當lphEvent陣列重所有事件物件同時有訊號時返回;
FALSE:任一事件有訊號就返回。
dwTimeout為等待超時(毫秒)
fAlertable為指定函式返回時是否執行完成例程

對事件陣列中的事件進行引用時,應該用WSAWaitForMultipleEvents的返回值,減去
預宣告值WSA_WAIT_EVENT_0,得到具體的引用值。例如:

nIndex=WSAWaitForMultipleEvents(…);
MyEvent=EventArray[Index- WSA_WAIT_EVENT_0];

e、判斷網路事件型別:

int WSAEnumNetworkEvents( SOCKET s,
WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );

s為套接字
hEventObject為需要重設的事件物件
lpNetworkEvents為記錄網路事件和錯誤程式碼,其結構定義如下:

typedef struct _WSANETWORKEVENTS {
	long lNetworkEvents;
	int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;

f、關閉事件物件控制代碼:

BOOL WSACloseEvent(WSAEVENT hEvent);

呼叫成功返回TRUE,否則返回FALSE。

posted @ 2006-04-27 09:30 不會飛的鳥 閱讀(122) | 評論 (0)編輯 收藏

2006年4月25日 #

一、簡介
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)){...}
posted @ 2006-04-25 13:12 不會飛的鳥 閱讀(136) | 評論 (0)編輯 收藏 一、客戶機/伺服器模式
在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 面向連線的應用程式流程圖
 

posted @ 2006-04-25 13:11 不會飛的鳥 閱讀(131) | 評論 (0)編輯 收藏 一、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)相對應。