相信衆多的 TCP 網絡服務器,均是由 TIdTCPServer 來提供服務,我的《網絡存儲服務器》也是。
即然是TCP服務器,若是需要相應的客戶端來支持,那麼,由這個服務端口(TCP)直接來提供客戶端安裝程序 WEB 下載是不是變得很方便?再也不用另加一個TIdHTTPServer來做了, 當然,若是需要提供 WEB 管理,還得麻煩他老人家。
HTTP協議:
在 IE 由 http://127.0.0.1:3001 連接到這個端口(Port=3001)時,發過來以下內容:
GET / HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322)
Host: 127.0.0.1:3001
Connection: Keep-Alive
Cookie: iscookies=0
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322)
Host: 127.0.0.1:3001
Connection: Keep-Alive
Cookie: iscookies=0
只有第一行纔是我所需要的並且是有用的,後面的可省略,假定下載的客戶端安裝程序文件名是 ClientSetup.exe, 此時提供下載時,IE 會有不知道是什麼文件的麻煩,因此必須將客戶端IE地址改成 http://127.0.0.1:3001/ClientSetup.exe 他才能知道是什麼文件與文件名,通常是採用JAVA網頁轉向技術(見後面的代碼)。
GET /ClientSetup.exe HTTP/1.1
...... 下面的省略
...... 下面的省略
其他服務器應迴應 IE 的內容,見下面:
// TIdTCPServer * IdTcpServer 的 OnExecute 事件處理函數
void __fastcall TForm1::IdTCPServerExecute(TIdContext *AContext)
{
AnsiString ReadLn;
TIdIOHandler * IOHandler = AContext->Connection->IOHandler;
try
{
while (IdTCPServer->Active)
{
ReadLn = IOHandler->ReadLn();
if (ReadLn.Length())
if (ReadLn.Pos("GET ") == 1)
{
ReadLn.SetLength(ReadLn.Pos(" HTTP/")-1);
ReadLn.Delete(1, 4); // 刪除前面的 GET 與空格
while (IOHandler->ReadLn().Length()); // 讀取完後面的內容
if (!FileExixts("ClientSetup.exe"))
{
AnsiString HTML = "<HTML> <HEAD> ";
HTML += "<script LANGUAGE="JavaScript"> ";
HTML += "function closeit() {";
HTML += "setTimeout("self.close()",10000)"; //
HTML += "} ";
HTML += "closeit()";
HTML += "</script> ";
HTML += "</HEAD> ";
HTML += "<BODY>客戶端安裝程序不存在,正在關閉窗口...</BODY> ";
HTML += "</HTML>";
ReadLn = "HTTP/1.1 200 OK ";
ReadLn += "Server: Goldray Network Storage Server 2.0 ";
ReadLn += "Date: " + DateTimeToHTTPFormat(Now()) + " ";
ReadLn += "Content-Length: " + IntToStr(HTML.Length()) + " ";
ReadLn += "Content-Type: text/html ";
ReadLn += "Expires: " + DateTimeToHTTPFormat(Now()) + " ";
ReadLn += "Set-Cookie: path=/ ";
ReadLn += "Cache-control: no-cache";
IOHandler->WriteLn(ReadLn);
IOHandler->WriteLn();
Sleep(100);
IOHandler->WriteLn(HTML);
IOHandler->WriteLn();
}
else if (ReadLn.UpperCase() == "/CLIENTSETUP.EXE")
{
ReadLn = "HTTP/1.1 200 OK ";
ReadLn += "Server: Goldray Network Storage Server 2.0 ";
ReadLn += "Date: " + DateTimeToHTTPFormat(Now()) + " ";
ReadLn += "Content-Type: application/octet-stream ";
ReadLn += "Accept-Ranges: bytes ";
ReadLn += "Last-Modified: ";
ReadLn += DateTimeToHTTPFormat(FileDateToDateTime(FileAge("ClientSetup.exe")));
ReadLn += " ";
ReadLn += "Content-Length: " + IntToStr(FileGetSize("ClientSetup.exe"));
IOHandler->WriteLn(ReadLn); // 發送HTTP前導
IOHandler->WriteLn();
IOHandler->WriteFile("ClientSetup.exe");
Sleep(100);
AContext->Connection->Disconnect(); // 斷開吧,已經不需要了
break;
}
else // 若 IE 不是 http://......./ClientSetup.exe 讀取的,總是轉向這裏
{
AnsiString HTML = "<HTML> <HEAD> ";
HTML += "<script LANGUAGE="JavaScript"> ";
HTML += "<!-- ";
HTML += "window.location="http://";
HTML += AContext->Binding()->IP + ":";
HTML += IntToStr(AContext->Binding()->Port) + "/ClientSetup.exe" ";
HTML += "// --> ";
HTML += "</script> ";
HTML += "</HEAD> ";
HTML += "<BODY>正在轉向客戶端下載,請稍候...</BODY> ";
HTML += "</HTML>";
ReadLn = "HTTP/1.1 200 OK ";
ReadLn += "Server: Goldray Network Storage Server 2.0 ";
ReadLn += "Date: " + DateTimeToHTTPFormat(Now()) + " ";
ReadLn += "Content-Length: " + IntToStr(HTML.Length()) + " ";
ReadLn += "Content-Type: text/html ";
ReadLn += "Expires: " + DateTimeToHTTPFormat(Now()) + " ";
ReadLn += "Set-Cookie: path=/ ";
ReadLn += "Cache-control: no-cache";
IOHandler->WriteLn(ReadLn);
IOHandler->WriteLn();
Sleep(100);
IOHandler->WriteLn(HTML);
IOHandler->WriteLn();
}
}
else
{
// ... 這裏纔是你的 TCP 服務器指令處理過程
}
}
}
catch (...)
{
Sleep(100);
}
}
void __fastcall TForm1::IdTCPServerExecute(TIdContext *AContext)
{
AnsiString ReadLn;
TIdIOHandler * IOHandler = AContext->Connection->IOHandler;
try
{
while (IdTCPServer->Active)
{
ReadLn = IOHandler->ReadLn();
if (ReadLn.Length())
if (ReadLn.Pos("GET ") == 1)
{
ReadLn.SetLength(ReadLn.Pos(" HTTP/")-1);
ReadLn.Delete(1, 4); // 刪除前面的 GET 與空格
while (IOHandler->ReadLn().Length()); // 讀取完後面的內容
if (!FileExixts("ClientSetup.exe"))
{
AnsiString HTML = "<HTML> <HEAD> ";
HTML += "<script LANGUAGE="JavaScript"> ";
HTML += "function closeit() {";
HTML += "setTimeout("self.close()",10000)"; //
HTML += "} ";
HTML += "closeit()";
HTML += "</script> ";
HTML += "</HEAD> ";
HTML += "<BODY>客戶端安裝程序不存在,正在關閉窗口...</BODY> ";
HTML += "</HTML>";
ReadLn = "HTTP/1.1 200 OK ";
ReadLn += "Server: Goldray Network Storage Server 2.0 ";
ReadLn += "Date: " + DateTimeToHTTPFormat(Now()) + " ";
ReadLn += "Content-Length: " + IntToStr(HTML.Length()) + " ";
ReadLn += "Content-Type: text/html ";
ReadLn += "Expires: " + DateTimeToHTTPFormat(Now()) + " ";
ReadLn += "Set-Cookie: path=/ ";
ReadLn += "Cache-control: no-cache";
IOHandler->WriteLn(ReadLn);
IOHandler->WriteLn();
Sleep(100);
IOHandler->WriteLn(HTML);
IOHandler->WriteLn();
}
else if (ReadLn.UpperCase() == "/CLIENTSETUP.EXE")
{
ReadLn = "HTTP/1.1 200 OK ";
ReadLn += "Server: Goldray Network Storage Server 2.0 ";
ReadLn += "Date: " + DateTimeToHTTPFormat(Now()) + " ";
ReadLn += "Content-Type: application/octet-stream ";
ReadLn += "Accept-Ranges: bytes ";
ReadLn += "Last-Modified: ";
ReadLn += DateTimeToHTTPFormat(FileDateToDateTime(FileAge("ClientSetup.exe")));
ReadLn += " ";
ReadLn += "Content-Length: " + IntToStr(FileGetSize("ClientSetup.exe"));
IOHandler->WriteLn(ReadLn); // 發送HTTP前導
IOHandler->WriteLn();
IOHandler->WriteFile("ClientSetup.exe");
Sleep(100);
AContext->Connection->Disconnect(); // 斷開吧,已經不需要了
break;
}
else // 若 IE 不是 http://......./ClientSetup.exe 讀取的,總是轉向這裏
{
AnsiString HTML = "<HTML> <HEAD> ";
HTML += "<script LANGUAGE="JavaScript"> ";
HTML += "<!-- ";
HTML += "window.location="http://";
HTML += AContext->Binding()->IP + ":";
HTML += IntToStr(AContext->Binding()->Port) + "/ClientSetup.exe" ";
HTML += "// --> ";
HTML += "</script> ";
HTML += "</HEAD> ";
HTML += "<BODY>正在轉向客戶端下載,請稍候...</BODY> ";
HTML += "</HTML>";
ReadLn = "HTTP/1.1 200 OK ";
ReadLn += "Server: Goldray Network Storage Server 2.0 ";
ReadLn += "Date: " + DateTimeToHTTPFormat(Now()) + " ";
ReadLn += "Content-Length: " + IntToStr(HTML.Length()) + " ";
ReadLn += "Content-Type: text/html ";
ReadLn += "Expires: " + DateTimeToHTTPFormat(Now()) + " ";
ReadLn += "Set-Cookie: path=/ ";
ReadLn += "Cache-control: no-cache";
IOHandler->WriteLn(ReadLn);
IOHandler->WriteLn();
Sleep(100);
IOHandler->WriteLn(HTML);
IOHandler->WriteLn();
}
}
else
{
// ... 這裏纔是你的 TCP 服務器指令處理過程
}
}
}
catch (...)
{
Sleep(100);
}
}
當然,實際使用時建議文件名使用含路徑的全稱, 使用變量是不錯的選擇。
哦,還小了兩個函數,一併附上:
// HTML 迴應時間必須是 GMT 格式
AnsiString DateTimeToHTTPFormat(TDateTime ADateTime)
{
TSystemTime GMTTime;
TFileTime DT;
DateTimeToSystemTime(ADateTime, GMTTime);
SystemTimeToFileTime(&GMTTime, &DT);
LocalFileTimeToFileTime(&DT, &DT);
FileTimeToSystemTime(&DT, &GMTTime);
AnsiString wdays[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
AnsiString monthnames[12] = {"Jan", "Feb", "Mar", "Apr", "May",
"Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
AnsiString Result = wdays[GMTTime.wDayOfWeek];
Result += ", ";
TVarRec argd[1] = {GMTTime.wDay};
TVarRec args[4] = {GMTTime.wYear, GMTTime.wHour, GMTTime.wMinute, GMTTime.wSecond};
Result += Format("%.2d ", argd, 0);
Result += monthnames[GMTTime.wMonth-1];
Result += Format(" %.4d %.2d:%.2d:%.2d", args, 3);
Result += " GMT";
return Result;
}
AnsiString DateTimeToHTTPFormat(TDateTime ADateTime)
{
TSystemTime GMTTime;
TFileTime DT;
DateTimeToSystemTime(ADateTime, GMTTime);
SystemTimeToFileTime(&GMTTime, &DT);
LocalFileTimeToFileTime(&DT, &DT);
FileTimeToSystemTime(&DT, &GMTTime);
AnsiString wdays[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
AnsiString monthnames[12] = {"Jan", "Feb", "Mar", "Apr", "May",
"Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
AnsiString Result = wdays[GMTTime.wDayOfWeek];
Result += ", ";
TVarRec argd[1] = {GMTTime.wDay};
TVarRec args[4] = {GMTTime.wYear, GMTTime.wHour, GMTTime.wMinute, GMTTime.wSecond};
Result += Format("%.2d ", argd, 0);
Result += monthnames[GMTTime.wMonth-1];
Result += Format(" %.4d %.2d:%.2d:%.2d", args, 3);
Result += " GMT";
return Result;
}
下面的代碼,網上到處都是啦,也就來湊一下字數吧
// 取文件大小
#include <stdio.h>
__int64 __fastcall FileGetSize(AnsiString AFileName)
{
__int64 fSize = 0;
if (FileExists(AFileName))
{
FILE * fp = fopen(AFileName.c_str(),"rb");
if(fp)
{
fseek(fp,0,SEEK_END);
fSize = ftell(fp);
fclose(fp);
}
}
return fSize;
}
#include <stdio.h>
__int64 __fastcall FileGetSize(AnsiString AFileName)
{
__int64 fSize = 0;
if (FileExists(AFileName))
{
FILE * fp = fopen(AFileName.c_str(),"rb");
if(fp)
{
fseek(fp,0,SEEK_END);
fSize = ftell(fp);
fclose(fp);
}
}
return fSize;
}
以上程序在 C++Builder 2006 + Indy10 下通過。