?? server_notes.c
字號:
/****************************************************
*Windows環境下使用VC編寫的TCP通信服務器端程序
*在編譯這個程序時,需要在工程設置項目--〉連接項目中添加如下兩項:
* Ws2_32.lib和Winmm.lib
*否則編譯后鏈接生成可執行文件時會出錯!
*服務器程序在單機內的測試運行方法:
* server [監聽端口:缺省為9999]
*
*程序中的函數列表:
*int InitSockets(void): 插口(套接字)初始化
*void ServeAClient(LPVOID lpv):為客戶提供服務的函數
*int ServerLoop(SOCKET sd_listen, int isMultiTasking):
* 服務器循環函數,服務期間總是在這個函數中循環
*********************************************************/
#include <stdio.h>
#include <winsock.h>
#include <stdlib.h>
/*
由于Winsock目前有兩個版本:2.2和1.1,所以我們首先必須判斷系統所支持的Winsock版本,這就要靠WSAStartup函數了。
另外還有一個WSACleanup函數,這兩個函數是Winsock編程必須調用的,其中WSAStartup函數的功能是初始化Winsock DLL,因為在Windows下,Socket是以DLL的形式實現的。1.1版本的DLL為Winsock.dll,而2.2版本的DLL則為Wsock32.dll,其中在2.2版本的系統中,對Winsock1.1函數的調用會由Wsock32.dll自動映射到Winsock.dll。WSAStartup函數的功能就是初始化DLL,其函數原型為: int WSAStartup (WORD wVersionRequested,LPWSADATA lpWSAData); 其第一個參數為你所想需要的Winsock版本!低字節為主版本,高字節為副版本!由于目前Winsock有兩個版本:1.1和2.2,因此該參數可以是0x101或0x202;第2個參數是一個WSADATA結構,用于接收函數的返回信息!WSAStartup函數調用成功會返回0,否則返回非0值!
由于Win 95,WinNT4自帶的Winsock是1.1版本的,所以如果你的程序是基于Winsock2.2的,那很可能無法在上面運行。因此,如果你希望你寫的程序被所有Windows平臺支持的話,最好將其聲明成1.1版的,不過這樣將無法使用很多Winsock2.2才有的特性!至于WSACleanup的用法很簡單,用“WSACleanup();”就行了!另外,在DLL內部維持著一個計數器,只有第一次調用WSAStartup才真正裝載DLL,以后的調用只是簡單的增加計數器,而WSACleanup函數的功能則剛好相反,每調用一次使計數器減1,當計數器減到0時,DLL就從內存中被卸載!因此,你調用了多少次WSAStartup,就應相應的調用多少次的WSACleanup!
*/
int InitSockets(void)
{
WSADATA wsaData;
WORD wVersionRequested;
int err;
/* Ask for Winsock 1.1 functionality */
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
printf("Error %d: Winsock not available\n", err);
return 1;
}
return 0;
}
#define WSA_ERROR(x) { printf("Error %d: %s\n", \
WSAGetLastError(), x); return 1; }
int ServerLoop(SOCKET sd_listen, int isMultiTasking);
main(int argc, char **argv)
{
SOCKET sd_listen;
int err;
u_short iPort;
struct sockaddr_in addr_srv;
struct hostent *ptrHost;
iPort = (argc >= 2) ? atoi(argv[1]) : 9999;//從命令行中提取服務器監控的端口號,如果命令行沒有帶數字格式的端口號,則定為9999
InitSockets();//初始化套接字
/*
創建套接字有兩個函數,socket和WSASocket,前者是標準的Socket函數,而后者是微軟對Socket的擴展函數。socket函數有3個參數,第一個是指定通信發生的區域,在UNIX下有AF_UNIX、AF_INET、AF_NS等,而在Winsock1.1下只支持AF_INET,到了2.2則添了AF_IRDA(紅外線通信)、AF_ATM(異步網絡通信)、AF_NS、AF_IPX等;第2個參數是套接字的類型,在AF_INET地址族下,有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW三種套接字類型。SOCK_STREAM也就是通常所說的TCP,而SOCK_DGRAM則是通常所說的UDP,而SOCK_RAW則是用于提供一些較低級的控制的;第3個參數依賴于第2個參數,用于指定套接字所用的特定協議,設為0表示使用默認的協議。socket函數調用成功返回一個套接字描述符,錯誤則返回SOCKET_ERROR。
*/
sd_listen = socket(PF_INET, SOCK_STREAM, 0);
if (sd_listen == INVALID_SOCKET) {
printf("Error: out of socket resources\n");
return 1;
}
/*
接下來要為服務器端定義的這個監聽的socket指定一個地址及端口(Port),這樣客戶端才知道要連接哪一個地址的哪個端口,為此我們首先設置數據結構struct sockaddr中的參數,隨之調用bind()函數將socket邦定在指定的端口和地址上,該函數調用成功返回0,否則返回SOCKET_ERROR。sockaddr_in結構的sin_addr.s_addr成員被設置為INADDR_ANY,意味著由操作系統給socket自動指定一個最有效的IP地址。
*/
addr_srv.sin_family = PF_INET;
addr_srv.sin_addr.s_addr = htonl(INADDR_ANY);
addr_srv.sin_port = htons(iPort);
err = bind(sd_listen, (const struct sockaddr *) &addr_srv,
sizeof(addr_srv));
if (err == INVALID_SOCKET)
WSA_ERROR("Error: unable to bind socket\n")
/*
當服務器端的Socket對象綁定完成之后,服務器端必須通知操作系統為這個socket建立一個監聽隊列來接收客戶端的連接請求。listen()函數使服務器端的Socket 進入監聽狀態,并設定可以建立的最大連接數。該函數調用成功返回0,否則返回SOCKET_ERROR。
*/
err = listen(sd_listen, SOMAXCONN);
if (err == INVALID_SOCKET)
WSA_ERROR("Error: listen failed\n")
//程序進入服務循環函數
ServerLoop(sd_listen, 1);
printf("Server is down\n");
WSACleanup();
return 0;
}
/*
如果客戶端發來HELLO SERVER,
那么服務器端將回送HELLO CLIENT,
不符合的輸入,服務器端將沒有響應
*/
void ServeAClient(LPVOID lpv)
{
SOCKET sd_accept = (SOCKET) lpv;
const char *msg = "HELLO CLIENT";
char response[4096];
memset(response, 0, sizeof(response));
recv(sd_accept, response, sizeof(response), 0);
if (strcmp(response, "HELLO SERVER")) {
printf("Application: client not using expected "
"protocol %s\n", response);
}
else
send (sd_accept, msg, strlen(msg)+1, 0);
closesocket(sd_accept);
}
#define MAX_SERVED 3
//服務器端的循環函數,服務期間總是在這個函數中運行
int ServerLoop(SOCKET sd_listen, int isMultiTasking)
{
SOCKET sd_accept;
struct sockaddr_in addr_client;
int err, nSize;
int numServed = 0;
HANDLE handles[MAX_SERVED];
int myID;
//主循環體,服務期間總是在這個結構內循環
while (numServed < MAX_SERVED) {
nSize = sizeof(addr_client);
//在套接字sd_listen上接收到客戶端的連接請求后,建立一個新的套接字sd_accept,
//使用套接字sd_accept與客戶端進行通信
sd_accept = accept(sd_listen, (struct sockaddr *)
&addr_client, &nSize);
if (sd_accept == INVALID_SOCKET)
WSA_ERROR("Error: accept failed\n")
printf("Accepted connection from client at %s\n",
inet_ntoa(addr_client.sin_addr));
//如果服務器端允許工作在并發多任務模式(isMultiTasking為程序員設定的標志)
if (isMultiTasking) {
#ifdef _WIN32 //適應不同版本的編譯環境,較新的系統都定義了_WIN32
// 建立一個新的線程,線程程序代碼為ServeAClient,傳遞的參數為通信套接字sd_accept
handles[numServed] = CreateThread(NULL, 1000,
(LPTHREAD_START_ROUTINE)ServeAClient,
(LPVOID) sd_accept, 0, &myID);
#else
myID = fork();
if (myID == 0) { /* I am child process */
ServeAClient ((LPVOID) sd_accept);
exit(0);
}
handles[numServed] = myID;
#endif
}
else
ServeAClient((LPVOID) sd_accept);
numServed++;
}
if (isMultiTasking) {
#ifdef _WIN32
//等待線程組handles中的線程結束,并顯示運行狀態
err = WaitForMultipleObjects(MAX_SERVED, handles,
TRUE, INFINITE);
printf("Last thread to finish was thread #%d\n", err);
#endif
}
return 0;
}
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -