?? server1.cpp
字號:
// server1.cpp : Defines the class behaviors for the application.
//作者:趙明
//EMAIL:zmpapaya@hotmail.com;papaya_zm@sina.com
//主頁:http://h2osky.126.com
#include "stdafx.h"
#include "server1.h"
#include "mysocket.h"
#include "MainFrm.h"
#include "server1Doc.h"
#include "server1View.h"
#include "linkctrl.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CServer1App
#define SIZE 20480
BEGIN_MESSAGE_MAP(CServer1App, CWinApp)
//{{AFX_MSG_MAP(CServer1App)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
// Standard file based document commands
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CServer1App construction
CServer1App::CServer1App()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}
/////////////////////////////////////////////////////////////////////////////
// The one and only CServer1App object
DWORD WINAPI clientthread(LPVOID lpparam);
//二維數組,第一維是10,對應于是個數組元素,第二維是一個字符串。
//這個二維數組是用來存儲加到List控件中的各個文件的全路徑用的。
char nameph[10][255];
CServer1App theApp;
//文件信息
struct fileinfo
{
int fileno;//文件號
int type;//消息類別。有兩種有效的取值:0和2 //客戶端想說什么(前面那兩句話,用1,2表示)
long len;//文件長度
int seek;//開始位置
char name[100];//文件名
};
int sendn(SOCKET fd,char *bp,int len);
//應該參照Visual C++實踐與提高系列叢書-網絡編程篇-配套源碼-第8章的內容,看看監聽線程
//到底應該怎么設計才對。
//監聽函數,這是一個線程函數。其實,我覺得不應該稱此函數所對應的線程為“監聽線程”,因為監聽
//只是一個設置的過程,調用listen函數,監聽過程就開始了。應該稱其為“循環等待連接線程”更好。
DWORD WINAPI listenthread(LPVOID lpparam)
{
//參數lpparam指向的是一個socket,這個socket是用來監聽客戶端的連接請求用的。
SOCKET sListening=(SOCKET)lpparam;
//用傳遞進來的參數所指向的socket進行監聽,允許接受的最大連接數是30。
//The listen function places a socket a state where it is listening for an incoming connection.
//Return Values If no error occurs, listen returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.
//這個函數調用不會發生阻塞,只有accept函數調用才會阻塞。
int rc=listen(sListening,30);
if(rc==SOCKET_ERROR)
{
CString aaa;
aaa="listen錯誤\n";
AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);
aaa.ReleaseBuffer();
return 0;
}
//用下面的這個死循環來不斷接收來自客戶端的連接請求。對于每一個連接socket,都為其啟動一個
//單獨的線程,需要注意的是:其線程函數是clientthread,而并不是本監聽線程函數。
//我感覺下面的代碼好像有問題呀,怎么退出這個死循環呢,這樣,此線程豈不是死掉了嗎?
//應該參照Visual C++實踐與提高系列叢書-網絡編程篇-第8章的內容,看看等待連接的線程到底應該怎么設計才對。
while(1)
{
//sConnect是一個局部變量。需要注意的是:雖然此變量是一個局部變量,但是,此變量所指向的
//新創建的socket卻并不是一個局部的對象,除非你調用closesocket函數關閉它,否則它會
//一直存在,直至進程結束。
//經過考證,發現SOCKET原來就是UINT_PTR數據類型,這是一個無符號指針類型。
//有一個問題,在while循環中,反復執行下面這句代碼,會產生出多個局部的sConnect變量嗎?
//可以做一個試驗程序測試一下。
//答:經過測試,發現并沒有在每一次循環的時候都產生出一個新的局部變量來,而是始終都
//只有一個局部變量sConnect,此變量的地址始終不變。
SOCKET sConnect;
//第一個參數:[in] Descriptor identifying a socket that has been placed in a listening state with the listen function. The connection is actually made with the socket that is returned by accept.
//返回值:If no error occurs, accept returns a value of type SOCKET that is a descriptor for the new socket. This returned value is a handle for the socket on which the actual connection is made.
//流程執行到這里,將會阻塞,如果沒有客戶端的連接請求發來。
sConnect=accept(sListening,NULL,NULL);
if(sConnect==INVALID_SOCKET)
{
//經過我的測試發現,當我調用closesocket函數關閉監聽socket的時候,流程會執行到
//這里面來。但是,由于進程會接著被中斷,所以,Message Box只是一閃就被關閉了,或者
//是根本連閃一下都沒有,進程就結束了。
char szTemp[10];
sprintf(szTemp,"123456789 ErrorCode=%d",WSAGetLastError());
::MessageBox(NULL,szTemp,"",0);
// continue;
return -1;//這樣設計對嗎,我不敢確定?
}
CString aa;
aa="一人聯入!\n";
//這是一個mfc函數,Call this member function to send the specified Windows message to all descendant windows.
//在本程序中的作用是:把串aa的內容,在RichEdit控件中顯示出來。
AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aa.GetBuffer(0),1);
aa.ReleaseBuffer();
DWORD dwThread;
//在這里創建了一個新的線程,但是要注意的是,這一次的線程函數變了,變成了clientthread,
//這個線程是用來處理客戶端的連接用的線程。
//在“VC 實踐與提高 網絡編程篇”的第8章的例子中,采用的是線程套線程的設計方法,
//我個人感覺,這種層層嵌套的設計方法好像不太好,還不如這種死循環的設計方法更好呢,
//不知我的感覺是否正確,有誰能告訴我理論依據是什么??????
::CreateThread(NULL,0,clientthread,(LPVOID)sConnect,0,&dwThread);
}
return 0;
}
//從客戶端讀取數據的函數。
//參數1:連接套接字;參數2:指向緩沖區的指針;參數3:緩沖區的長度;
int readn(SOCKET fd,char *bp,int len)
{
int cnt;//用來記錄緩沖區中的剩余可用空間。
int rc;
cnt=len;
while(cnt>0)
{
//Platform SDK: Windows Sockets The recv function receives data from a connected or bound socket.
//Return Values If no error occurs, recv returns the number of bytes received. If the connection has been gracefully closed, the return value is zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.
//cnt參數的值被設定為緩沖區的長度值,所以,在網絡速度足夠好的情況下,讀取cnt這么
//多的字節,沒問題。這樣的話,此while循環只需要執行一次就會正常退出了。從而,我也
//明白了while循環的作用,就是:用來保證一定要讀取cnt這么多的字節,讀不夠數就
//反復地讀,直至讀夠數了,或者是此連接被關閉掉了。
//我有一個疑問:如果客戶端只發過來50個字節的數據,會怎么樣呢?其實,這個問題,只需要查到合適的資料,就可以輕松解決,并無任何的難度可言。
//答:??????
rc=recv(fd,bp,cnt,0);
if(rc==SOCKET_ERROR)
{
CString aa;
aa="接收錯誤!\n";
AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aa.GetBuffer(0),1);
aa.ReleaseBuffer();
return -1;
}
//If the connection has been gracefully closed, the return value is zero.
if(rc==0)
return len-cnt;
//讀取了一些數據之后,緩沖區指針也應該相應地增加。
bp+=rc;
//讀取了一些數據之后,緩沖區中的剩余可用空間也應該相應地減小。
cnt-=rc;
}
return len;
}
filesendinfo zmfile[10];//記錄文件,這是一個結構體數組。
#define SIZE_OF_zmfile 1080
//向客戶端發送數據的函數。
//參數1:連接socket;參數2:緩沖區指針;參數3:要發送的字節數,其實就是緩沖區中的數據數;
int sendn(SOCKET fd,char *bp,int len)
{
int cnt;
int rc;
cnt=len;
//我覺得,之所以需要用一個while循環來發送數據,大概是因為send函數也許不能把指定數量數據
//一次都發送完吧,所以需要用一個循環來反復地發送,直至所有的數據都發送完,或者是出錯。
while(cnt>0)
{
//send The send function sends data on a connected socket.
//send函數的返回值可用來表示已經成功地發送出去了多少數據,至于客戶端能否成功地收到
//這些數據,好像并不能在send函數的返回值中體現出來。其實,能否成功地收到數據是客戶端
//的事,跟服務器端無關。也許數據被客戶端的防火墻攔截了,這樣,客戶端就永遠也不能收到
//這些數據了,不過,這些亂七八糟的事,服務器端可管不著。
rc=send(fd,bp,cnt,0);
if(rc==SOCKET_ERROR)
{
CString aa;
aa="發送錯誤!\n";
AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aa.GetBuffer(0),1);
aa.ReleaseBuffer();
return -1;
}
if(rc==0)
return len-cnt;
//發送完了一批數據后,緩沖區指針當然要向后移了。如果指定數量的數據已經發送完了,則
//bp就指向緩沖區的末尾了。
bp+=rc;
//發送完了一批數據,則cnt(用來記錄還沒發送完的數據)的數值當然需要減去已經發送了的了。
cnt-=rc;
}
return len;
}
//讀文件的函數,這個函數大概就是傳送文件的具體函數吧。
//實現多線程下載和斷點續傳的前提條件就是:服務器端要支持隨機存取文件。這就要求傳遞一個
//seek變量,因為需要用這個seek變量尋找到上次的斷點或者是把文件分成幾段分別下載。
//參數1:連接socket;參數2:隨機存取文件用的的定位指針;參數3:此次下載操作要從文件中讀取的字節數;參數4:服務器設定的可供下載的文件列表中的文件的編號(編號是從0開始的),用作nameph數組的下標;
void readfile( SOCKET so , int seek , int len , int fino )
{
//文件名全路徑。
CString myname;
//將要下載的文件的全路徑賦值給myname串。
myname.Format("%s",nameph[fino]);
CFile myFile;
//打開文件。為了支持多線程下載,必須要以共享的方式打開文件才行!
myFile.Open( myname, CFile::modeRead | CFile::typeBinary | CFile::shareDenyNone );
//定位到指定位置
//CFile::Seek The Seek function permits random access to a file's contents by moving the pointer a specified amount, absolutely or relatively. No data is actually read during the seek. If the requested position is larger than the size of the file, the file length will be extended to that position, and no exception will be thrown.
//When a file is opened, the file pointer is positioned at offset 0, the beginning of the file.
//CFile::begin Move the file pointer lOff bytes forward from the beginning of the file.
myFile.Seek(seek,CFile::begin);
//這是一個用來從文件中讀取數據,然后再發送的緩沖區。
char m_buf[SIZE];
int len2;
//開始向客戶端發送,指定文件中的,指定位置處的,指定數量的,數據。
//之所以需要使用while循環來發送數據,是因為每一次最多只能發送SIZE這么多的數據,所以,有
//可能需要進行多次的發送,這就需要用到循環了。
while(len>0)
{
//每一次發送的數據量,不能超過m_buf這個緩沖區的最大尺寸。如果客戶所要求的數據量小于
//緩沖區的尺寸,就只需要讀取len這么多的數據就行了。
len2=len>SIZE?SIZE:len;
myFile.Read(m_buf, len2);
//向客戶端發送數據。
int aa=sendn(so,m_buf,len2);
//如果發送過程出現錯誤,則
if(aa<0)
{
closesocket (so);
break;
}
//對,發送完了一批數據后,就應該從總數中把“已經發送完了的那些數據的數量”減去。
len=len-aa;
}
//對,用完了文件之后,需要關閉它。
myFile.Close();
}
//“用來處理服務器與客戶端的一次連接用的線程”的線程函數。
//其參數是:連接用的socket。
DWORD WINAPI clientthread(LPVOID lpparam)
{
//文件消息
fileinfo* fiinfo;
//接收緩存
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -