相信众多的 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 下通过。