今天學(xué)習(xí)了些關(guān)于VC網(wǎng)絡(luò)編程中關(guān)于Winsock的知識,并做了相應(yīng)的整理。在這里根據(jù)了一個(gè)聊天室例子幫助我了解這些知識的用法。
(1)Server端調(diào)用函數(shù)順序:
WSAStartup()初始化Winsock
socket()創(chuàng)建一個(gè)監(jiān)聽Socket
bind()為監(jiān)聽Socket指定通信對象
listen()設(shè)置等待連接狀態(tài)
accept()接收連接并生成會話Socket
send和recv進(jìn)行對話
closesocket()關(guān)閉socket
(2)Client端調(diào)用函數(shù)順序
WSAStartup()初始化Winsock
socket()創(chuàng)建一個(gè)監(jiān)聽Socket
connect()與Server端連接
send和recv進(jìn)行對話
closesocket()關(guān)閉socket
1、程序頭部分,包括引用的頭文件、宏定義、全局變量和函數(shù)聲明
#include "stdafx.h"
#include "resource.h"
#include <VECTOR> //采用STL
#include <algorithm>
#include <WINSOCK2.H> //調(diào)用winsock
#include <ASSERT.H>

#define BUFFER_SIZE 4096 //緩沖區(qū)的大小
#define SERVER_MESSAGE WM_USER+100 //服務(wù)器SOCKET異步消息ID
#define CLIENT_MESSAGE WM_USER+101 //客戶機(jī)SOCKET異步消息ID

// Global Variables:
SOCKET g_ListenSocket; //Listen socket (for server)
std::vector<SOCKET> g_DataSockets; //All data sockets with clients (for server)
SOCKET g_ClientSocket; //Client data socket (for client)
BOOL g_bActive; //A tag of active socket (for both server and client)
std::string g_ChatWords;
BOOL g_bClient; //TRUE as a client, FALSE as a server

BOOL ServerInit(HWND hWnd,UINT port);
BOOL ClientInit(HWND hWnd,UINT port,const char* serverIP);
int OnClientMessage(HWND hWnd, WPARAM wParam, LPARAM lParam);
int OnServerMessage(HWND hWnd, WPARAM wParam, LPARAM lParam);
void SendMessageToPeer(HWND hWnd);
void Send(SOCKET sock, const char* buffer, int length);
void RefreshScreen(HWND hWnd);
void ExitChat(HWND hWnd);
void GetErrorReason();
2、Win32程序函數(shù)入口點(diǎn)WinMain:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)


{
// Perform application initialization:
g_bActive=FALSE;
DialogBox(hInstance, MAKEINTRESOURCE(IDD_CHAT_DIALOG), NULL, (DLGPROC)WndProc);
return 0;
}
3、對話框的消息循環(huán)函數(shù)WndProc:
在WndProc消息循環(huán)中,只處理4中消息:對話框初始化、對話框控件、服務(wù)器SOCKET異步和客戶端SOCKET異步,在對話框初始化中,程序啟動了SOCKET棧。在對話框控件中,程序分別響應(yīng)了各種按鈕消息以及文字輸入框消息EN_UPDATE。在服務(wù)器SOCKET異步消息中,調(diào)用OnServerMessage處理。在客戶端SOCKET異步消息中,調(diào)用了OnclientMessage處理。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)


{
HWND subWnd;
char buffer[BUFFER_SIZE];
UINT port;
char serverIP[16];
switch (message)

{
case WM_INITDIALOG:

{
WSADATA wsaData;
if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)

{
MessageBox(hWnd,"Socket Stack Error!","Error",IDOK);
return -1;
}
}
return TRUE;
case WM_COMMAND:

{
switch(LOWORD(wParam))

{
case IDOK:
case IDCANCEL:
EndDialog(hWnd,LOWORD(wParam));
break;
case IDC_CONNECT:

{
subWnd=GetDlgItem(hWnd,IDC_LISTEN_PORT_C);
GetWindowText(subWnd,buffer, BUFFER_SIZE);
port= UINT(atoi(buffer));
subWnd=GetDlgItem(hWnd,IDC_SERVERIP);
GetWindowText(subWnd,buffer,BUFFER_SIZE);
strncpy(serverIP,buffer,16);
ClientInit(hWnd,port,serverIP);
//g_ChatWords="";
}
break;
case IDC_LISTEN:

{
subWnd=GetDlgItem(hWnd,IDC_LISTEN_PORT_S);
GetWindowText(subWnd,buffer, BUFFER_SIZE);
port= UINT(atoi(buffer));
ServerInit(hWnd,port);
//g_ChatWords="";
}
break;
case IDC_INPUTTEXT:

{
if(HIWORD(wParam)==EN_UPDATE)

{
SendMessageToPeer(hWnd);
}
}
default:
break;
}
}
break;
case SERVER_MESSAGE:

{
OnServerMessage(hWnd,wParam, lParam);
}
break;
case CLIENT_MESSAGE:

{
OnClientMessage(hWnd, wParam, lParam);
}
break;
}
return 0;
}

1)int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);
wVersionRequested:表示欲使用的Windows Sockets API版本,使用宏MAKEWORD(X,Y)設(shè)置參數(shù)。表示版本為X.Y(本例中為2.2)
4、客戶端初始化函數(shù)ClientInit:
當(dāng)單擊“連接服務(wù)器“按鈕時(shí),程序分別從子窗口IDC_LISTEN_POST_C和IDC_SERVERIP中提取服務(wù)器的IP和監(jiān)聽端口,然后調(diào)用客戶端函數(shù)ClientInit。
BOOL ClientInit(HWND hWnd,UINT port,const char* serverIP)


{
sockaddr_in addr;
ExitChat(hWnd);
g_bClient=TRUE;
if(inet_addr(serverIP)==INADDR_NONE)

{
MessageBox(hWnd,"Invalid IP Address!","Warnning",IDOK);
return FALSE;
}

//create tcp/stream data socket
g_ClientSocket = socket(AF_INET, SOCK_STREAM,0);
assert(g_ClientSocket != INVALID_SOCKET);

//set the socket as async selection tag, the related message is CLIENT_MESSAGE
//the async events contain: read, write, close, connect
WSAAsyncSelect(g_ClientSocket,hWnd,CLIENT_MESSAGE,FD_READ|FD_WRITE|FD_CLOSE|FD_CONNECT);

//set the server address and port
addr.sin_family = AF_INET;
addr.sin_addr.S_un.S_addr = inet_addr(serverIP);
addr.sin_port = htons(port); //short from host to network format
//connect to server
connect(g_ClientSocket, (sockaddr*)&addr, sizeof(addr));

g_ChatWords+="Connecting
\r\n";
RefreshScreen(hWnd);
return TRUE;
}

1)SOCKET socket(int af,int type,int protocol);
af:指協(xié)議的地址族,如果想建立一個(gè)UDP或TCP的套接字,則該值設(shè)置成AF_INET,表示在網(wǎng)絡(luò)層采用網(wǎng)際協(xié)議(IP)
type:協(xié)議的套接字類型,可以是SOCK_STREAM、SOCK_DGRAM 、SOCK_RAW、SOCK_RDM、SOCK_SEQPACKET。 當(dāng)采用流連接方式時(shí)用SOCK_STREAM,采用數(shù)據(jù)報(bào)文方式用SOCK_DGRAM
proctocol:協(xié)議字段,當(dāng)af和type都指定之后,該值的取值范圍就被去定了,默認(rèn)值為0。
2)int PASCAL FAR WSAAsyncSelect ( SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent );
S:標(biāo)識一個(gè)需要事件通知的套接口的描述符.
hWnd:標(biāo)識一個(gè)在網(wǎng)絡(luò)事件發(fā)生時(shí)需要接收消息的窗口句柄.
wMsg :在網(wǎng)絡(luò)事件發(fā)生時(shí)要接收的消息.
lEvent:位屏蔽碼,用于指明應(yīng)用程序感興趣的網(wǎng)絡(luò)事件集合.
3)int connect(SOCKET s,const struct sockaddr FAR * name,int namelen);
S是建立好的套接字,name指向描述通信對象地址信息的結(jié)構(gòu)體的指針,namelen是該結(jié)構(gòu)體的長度。
4)struct sockaddr_in {short sin_family;u_short sin_port;struct in_addr sin_addr;char sin_zero[8];};
5)unsigned long PASCAL FAR inet_addr( const struct FAR* cp);
cp:一個(gè)以Internet標(biāo)準(zhǔn)“.”間隔的字符串。
本函數(shù)解釋cp參數(shù)中的字符串,這個(gè)字符串用Internet的“.”間隔格式表示一個(gè)數(shù)字的Internet地址。
6)u_short PASCAL FAR htons( u_short hostshort);
hostshort:主機(jī)字節(jié)順序表達(dá)的16位數(shù)。
本函數(shù)將一個(gè)16位數(shù)從主機(jī)字節(jié)順序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)順序。
其中sin_family是指地址族;sin_port是制定的端口號;sin_addr是制定的IP地址;sin_zero冗余字段,使sockaddr_in結(jié)構(gòu)和SOCKADDR結(jié)構(gòu)大小相同
5、服務(wù)器初始化函數(shù)ServerInit:
單擊“建立服務(wù)器“按鈕時(shí),程序從子窗口IDC_LISTEN_PORT_S中提取監(jiān)聽端口,然后調(diào)用服務(wù)器初始化函數(shù)ServerInit。
BOOL ServerInit(HWND hWnd,UINT port)


{
sockaddr_in addr;
char buffer[BUFFER_SIZE];
ExitChat(hWnd);
g_bClient=FALSE;
//create a tcp/stream based socket
g_ListenSocket = socket(AF_INET, SOCK_STREAM,0);
assert(g_ListenSocket != INVALID_SOCKET);

//set the socket as async selection tag, the related message is SERVER_MESSAGE
//the async events contain: accept, read, write, close
WSAAsyncSelect(g_ListenSocket, hWnd, SERVER_MESSAGE, FD_ACCEPT|FD_READ|FD_WRITE|FD_CLOSE);

addr.sin_family = AF_INET; //IP layer takes IP router protocol
addr.sin_addr.S_un.S_addr = INADDR_ANY;
addr.sin_port = htons(port);//port from short of host to short of network

//binding listening socket
if(bind(g_ListenSocket, (sockaddr*)&addr, sizeof(sockaddr)) == SOCKET_ERROR)

{
sprintf(buffer,"Port %d has been taken. Change another port.\r\n",port);
g_ChatWords+=buffer;
RefreshScreen(hWnd);
MessageBox(hWnd,"Binding Error","Warnning",IDOK);
return FALSE;
}
//listening
if(listen(g_ListenSocket, 5) == SOCKET_ERROR)

{
strcpy(buffer,"Listen error.\r\n");
g_ChatWords+=buffer;
RefreshScreen(hWnd);
MessageBox(hWnd,"Listen Error.","Warnning",IDOK);
return FALSE;
}

g_ChatWords+="Server is listening
\r\n";
RefreshScreen(hWnd);
g_bActive=TRUE;
return TRUE;
}

1) int bind(SOCKET s,const struct sockaddr FAR * name,int namelen);
S是建立好的套接字,name指向描述通信對象地址信息的結(jié)構(gòu)體的指針,namelen是該結(jié)構(gòu)體的長度。
2)int listen(SOCKET s,int backlog);
S是建立好的套接字,backlog是并發(fā)連接等待隊(duì)列的長度。
6、SendMessageToPeer函數(shù):
當(dāng)用戶在文本框中輸入文字時(shí),程序會接收到該文本框的EN_UPDATE消息,函數(shù)SendMessageToPeer負(fù)責(zé)處理該消息。在SendMessageToPeer中,一旦檢測到用戶按下了回車鍵,程序就將文本內(nèi)容刷新到上面的顯示框中,如果當(dāng)前在線,文本內(nèi)容還將發(fā)送到會話的另一端。
void SendMessageToPeer(HWND hWnd)


{
HWND subwnd;
char buffer[BUFFER_SIZE];
int i;
static int oldNumOfChars=0;

subwnd=GetDlgItem(hWnd,IDC_INPUTTEXT);
GetWindowText(subwnd,buffer,BUFFER_SIZE-2);
if(oldNumOfChars!=strlen(buffer))

{
oldNumOfChars=strlen(buffer);
return;
}
//CString s;
//s("%d還是%d",oldNumOfChars,strlen(buffer));
// g_ChatWords+=s;
//empty content of input edit box
SetWindowText(subwnd,"");
oldNumOfChars=0;
if(!g_bActive)

{
g_ChatWords+=buffer;
g_ChatWords.erase(g_ChatWords.size(),1);
g_ChatWords+="(Hint: you are isolated now.)\r\n";
RefreshScreen(hWnd);
return;
}
strcat(buffer,"\r\n");
g_ChatWords+=buffer;
RefreshScreen(hWnd);
if(g_bClient)

{
Send(g_ClientSocket, buffer, strlen(buffer));
}
else

{
for(i=0;i<g_DataSockets.size();i++)
Send(g_DataSockets[i],buffer,strlen(buffer));
}
}
void Send(SOCKET sock, const char* buffer, int length)


{
int ret,i;
i=0;
while(length>0)

{
ret=send(sock,&(buffer[i]),length,0);
if(ret==0)
break;
else if(ret == SOCKET_ERROR)

{
g_ChatWords+="Error sending.\r\n";
break;
}
length-=ret;
i+=ret;
}
}
1)int send(SOCKET s,const char FAR * buf,int len,int flags);
參數(shù)s是建立連接的套接字。buf和len是發(fā)送的數(shù)據(jù)包及其長度。flags一般取0,還有MSG_DONTROUTE要求套接字傳輸?shù)臄?shù)據(jù)不要路由,如果傳輸協(xié)議不支持該選項(xiàng),則這個(gè)要求被忽略。MSG_OOB表示套接字此時(shí)傳輸?shù)臄?shù)據(jù)室外帶數(shù)據(jù),需要緊急處理。
7、OnClientMessage函數(shù):
在主循環(huán)的消息循環(huán)中,OnClientMessage函數(shù)用來處理客戶端SOCKET的一部消息CLIENT_MESSAGE。需要指出的是:當(dāng)某個(gè)網(wǎng)絡(luò)消息到達(dá)窗口時(shí),可能暗含著套接字上發(fā)生了某種網(wǎng)絡(luò)錯(cuò)誤。此時(shí),應(yīng)該先判斷套接字上是否發(fā)生了錯(cuò)誤,如果有錯(cuò)誤發(fā)生就直接關(guān)閉套接字然后返回,只有在沒有錯(cuò)誤發(fā)生的情況下,才需要處理具體的網(wǎng)絡(luò)消息。在實(shí)現(xiàn)上,需要利用消息參數(shù)lParam的高字節(jié),宏WSAGETSELECTERROR可以檢測錯(cuò)誤。而具體的消息類型是存儲在lParam的低字節(jié),宏WSAGETSELECTEVENT用來獲取該低字節(jié)值。
int OnClientMessage(HWND hWnd, WPARAM wParam, LPARAM lParam)


{
char buffer[BUFFER_SIZE+1];
int retCode;
sockaddr_in name;
int namelen=sizeof(sockaddr_in);

if(WSAGETSELECTERROR(lParam))

{
closesocket(g_ClientSocket);
strcpy(buffer,"Server has no response.\r\n");
g_ChatWords+=buffer;
RefreshScreen(hWnd);
g_bActive=FALSE;
return 0;
}

switch(WSAGETSELECTEVENT(lParam))

{
case FD_CONNECT:
getpeername(g_ClientSocket,(sockaddr*)&name,&namelen);
sprintf(buffer,"Successfully connected to %s:%d.\r\n",inet_ntoa(name.sin_addr),ntohs(name.sin_port));
g_ChatWords+=buffer;
RefreshScreen(hWnd);
g_bActive=TRUE;
break;
case FD_READ:
retCode=recv(g_ClientSocket,buffer,BUFFER_SIZE,0);
if(retCode!=SOCKET_ERROR)

{
buffer[retCode]=NULL;
g_ChatWords+=buffer;
}
else
GetErrorReason();
RefreshScreen(hWnd);
break;
case FD_WRITE:
break;
case FD_CLOSE:
closesocket(g_ClientSocket);
strcpy(buffer,"Server close session.Successfully log out.\r\n");
g_ChatWords+=buffer;
RefreshScreen(hWnd);
g_bActive=FALSE;
break;
}
return 0;
}
1)char FAR* PASCAL FAR inet_ntoa( struct in_addr in);
in:一個(gè)表示Internet主機(jī)地址的結(jié)構(gòu)。
本函數(shù)將一個(gè)用in參數(shù)所表示的Internet地址結(jié)構(gòu)轉(zhuǎn)換成以“.” 間隔的諸如“a.b.c.d”的字符串形式。
2) int recv(SOCKET s,char FAR * buf,int len,int flags);
參數(shù)與send一致
3)int closesocket(SOCKET s);
關(guān)閉套接字
8、OnServerMessage函數(shù):
在主程序的消息循環(huán)中,OnServerMessage函數(shù)用來處理客戶端SOCKET的一部消息SERVER_MESSAGE。在服務(wù)器初始化函數(shù)中的g_ListenSocket被設(shè)置成異步監(jiān)聽socket,所有被g_ListenSocket接收的數(shù)據(jù)socket將同時(shí)被設(shè)置成異步socket,并共享同一個(gè)消息SERVER_MESSAGE。及時(shí)服務(wù)器關(guān)閉了監(jiān)聽socket,會話socket的連接依然有效,除非服務(wù)器同時(shí)也將會話socket關(guān)閉。
int OnServerMessage(HWND hWnd, WPARAM wParam, LPARAM lParam)


{
SOCKET socket;
int i,retCode,namelen;
char buffer[BUFFER_SIZE+1];
std::vector<SOCKET>::iterator ite;
sockaddr_in name;
namelen=sizeof(name);

if(WSAGETSELECTERROR(lParam))

{
getpeername((SOCKET)wParam,(sockaddr*)&name,&namelen);
closesocket((SOCKET)wParam);
//erase the client socket from client socks list
ite=std::find(g_DataSockets.begin(), g_DataSockets.end(),(SOCKET)wParam);
assert(ite!=g_DataSockets.end());
g_DataSockets.erase(ite);
//refresh screen
sprintf(buffer, "Client %s:%d lost contact with us.\r\n", inet_ntoa(name.sin_addr),ntohs(name.sin_port));
for(i=0; i<g_DataSockets.size(); i++)
Send(g_DataSockets[i],buffer,strlen(buffer));
g_ChatWords+=buffer;
RefreshScreen(hWnd);
return 0;
}

switch(WSAGETSELECTEVENT(lParam))

{
case FD_ACCEPT:
//accept the client request
socket= accept(g_ListenSocket,NULL,NULL);
//in fact we can get the peer when calling accept, here we use getpeername instead
getpeername(socket,(sockaddr*)&name,&namelen);
//send a message of logining to other clients
sprintf(buffer,"A guest joins us.(%s:%d)\r\n",inet_ntoa(name.sin_addr),ntohs(name.sin_port));
g_ChatWords+=buffer;
for(i=0;i<g_DataSockets.size();i++)
Send(g_DataSockets[i],buffer,strlen(buffer));
//send a welcome message to current client
sprintf(buffer, "Welcome !(You ID is: %s:%d.)\r\n",inet_ntoa(name.sin_addr),ntohs(name.sin_port));
//send(socket,buffer,strlen(buffer),0);
Send(socket,buffer,strlen(buffer));
//refresh srceen text
RefreshScreen(hWnd);
//push the client socket down to the client sockets list
g_DataSockets.push_back(socket);
break;
case FD_READ:

{
//get the client message, the client socket ID is wParam
retCode=recv((SOCKET)wParam,buffer,BUFFER_SIZE,0);
buffer[retCode]=NULL;
//send the received message to other clients
for(i=0;i<g_DataSockets.size();i++)

{
if(wParam!=g_DataSockets[i])
Send(g_DataSockets[i],buffer,strlen(buffer));
}
g_ChatWords+=buffer;
//refresh screen
RefreshScreen(hWnd);
}
break;
case FD_WRITE:
return 0;
case FD_CLOSE: //Client gracefully close the socket
//close the client socket who has left, the client socket ID is wParam
getpeername((SOCKET)wParam,(sockaddr*)&name,&namelen);
closesocket((SOCKET)wParam);
//erase the client socket from client socks list
ite=std::find(g_DataSockets.begin(), g_DataSockets.end(),(SOCKET)wParam);
assert(ite!=g_DataSockets.end());
g_DataSockets.erase(ite);
//refresh screen
sprintf(buffer, "Client %s:%d left.\r\n", inet_ntoa(name.sin_addr),ntohs(name.sin_port));
for(i=0; i<g_DataSockets.size(); i++)
Send(g_DataSockets[i],buffer,strlen(buffer));
g_ChatWords+=buffer;
RefreshScreen(hWnd);
break;
}
return 0;
}
1)int PASCAL FAR getpeername( SOCKET s, struct sockaddr FAR* name,int FAR* namelen);
s:標(biāo)識一已連接套接口的描述字。
name:接收端地址的名字結(jié)構(gòu)。
namelen:一個(gè)指向名字結(jié)構(gòu)的指針。
2)u_short PASCAL FAR ntohs( u_short netshort);
netshort:一個(gè)以網(wǎng)絡(luò)字節(jié)順序表達(dá)的16位數(shù)。
本函數(shù)將一個(gè)16位數(shù)由網(wǎng)絡(luò)字節(jié)順序轉(zhuǎn)換為主機(jī)字節(jié)順序。
3) SOCKET accept(SOCKET s,struct sockaddr FAR * addr,int FAR * addrlen);
如果是服務(wù)器并不關(guān)心對方的地址,則可將accept函數(shù)中后兩個(gè)參數(shù)設(shè)置為NULL。
9、最后,介紹3個(gè)輔助函數(shù):ExitChat、GetErrorReason和RefreshScreen
當(dāng)新建服務(wù)器或鏈接服務(wù)器的時(shí)候,首先需要將上一次會話關(guān)閉,ExitChat函數(shù)完成此功能。
RefreshScreen函數(shù)負(fù)責(zé)刷新顯示內(nèi)容并滾動顯示文本框。
GetErrorReason函數(shù)負(fù)責(zé)獲得當(dāng)前操作中的具體錯(cuò)誤原因。
void ExitChat(HWND hWnd)


{
int i;
char buffer[BUFFER_SIZE];
if(g_bActive)

{
if(g_bClient)

{
closesocket(g_ClientSocket);
strcpy(buffer,"Successfully log out.\r\n");
g_ChatWords+=buffer;
RefreshScreen(hWnd);
}
else

{
strcpy(buffer,"I will leave. Pls clients log out ASAP.\r\n");
for(i=0; i<g_DataSockets.size(); i++)

{
Send(g_DataSockets[i],buffer,strlen(buffer));
closesocket(g_DataSockets[i]);
}
g_DataSockets.clear();
closesocket(g_ListenSocket);
strcpy(buffer,"Successfully close server.\r\n");
g_ChatWords+=buffer;
RefreshScreen(hWnd);
}
}
g_bActive=FALSE;
}

void GetErrorReason()


{
LPVOID lpMsgBuf;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,WSAGetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf,0,NULL);
g_ChatWords+=(char *)lpMsgBuf;
LocalFree( lpMsgBuf );
}
void RefreshScreen(HWND hWnd)


{
HWND subWnd;
std::string::size_type pos;
int n;
subWnd=GetDlgItem(hWnd,IDC_SHOWTEXT);
SetWindowText(subWnd, g_ChatWords.c_str());
n=0;
pos=0;
while((pos=g_ChatWords.find('\n',pos))!=std::string::npos)

{
pos++;
n++;
}
SendMessage(subWnd, EM_LINESCROLL, 0, n);
}

posted on 2009-06-21 00:23
The_Moment 閱讀(1937)
評論(0) 編輯 收藏 引用 所屬分類:
VC理論