最近教学,使用到了Apache和IIS,闲着无聊,有种想自己写个小服务器的冲动。
在网上找了半天的资料,最后终于搞定了,测试可以访问。效果图如下:
因为只是处理简单的请求,然后返回请求的页面,所以没有涉及到其他高级语言(php jsp aspx...)的处理。不过还是有点意思的哈,不说了,进入正题:
开发工具:Visual Studio 2013
开发环境:.NET Framework 2.0
关键源码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Text; 5 6 namespace Guying.Project.MiniServer 7 { 8 public class INIHelper 9 { 10 private StreamReader sr; 11 private string[] strs = new string[255];//该数值也限定了INI文件最多不能超过255行 12 private int LinesOfTxt = 0; 13 //private string FileName; 14 15 public INIHelper(string iniFileName) 16 { 17 //FileName = iniFileName; 18 if (!File.Exists(iniFileName)) 19 { 20 File.CreateText(iniFileName); 21 } 22 sr = new StreamReader((System.IO.Stream)File.OpenRead(iniFileName), Encoding.Default); 23 //Console.WriteLine("Reading ini file: {0}",iniFileName); 24 while (sr.Peek() > -1) 25 { 26 strs[LinesOfTxt] = sr.ReadLine(); 27 //把空行和以“#”或";"开头的注释行去掉 28 if (!strs[LinesOfTxt].StartsWith("#") && !strs[LinesOfTxt].StartsWith(";") && (strs[LinesOfTxt] != "")) LinesOfTxt++; 29 } 30 sr.Close(); 31 } 32 33 /// <summary> 34 /// 通过给定的value获得INI文件中对应项的值 35 /// </summary> 36 public string ValueOf(string cvalue) 37 { 38 string retn = ""; 39 int i = 0, index; 40 41 while (i < LinesOfTxt) 42 { 43 index = strs[i].IndexOf(cvalue + "=", 0, strs[i].Length); 44 if (index >= 0) 45 { 46 retn = strs[i].Substring(index + cvalue.Length + 1); 47 break; 48 } 49 i++; 50 } 51 return retn; 52 } 53 54 } 55 }
读写INI配置文件的辅助类
这个辅助类是针对这个程序自己编写的,只有简单的读取和写入。
更多功能的ini通用辅助类,就像是DBHelper一样,网上有整套的代码、例如在此分享一个:
1 using System; 2 using System.Collections.Generic; 3 using System.Runtime.InteropServices; 4 using System.Text; 5 using System.IO; 6 using System.Windows.Forms; 7 using System.Diagnostics; 8 9 namespace Guying.Project.MiniServer 10 { 11 /// <summary> 12 /// INI文件辅助类 13 /// </summary> 14 public class INIHelper_API 15 { 16 17 #region 声明读写INI文件的API函数 18 [DllImport("KERNEL32.DLL", EntryPoint = "GetPrivateProfileIntA", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, ExactSpelling = true)] 19 private static extern int GetPrivateProfileInt(string lpApplicationName, string lpKeyName, int nDefault, string lpFileName); 20 21 [DllImport("KERNEL32.DLL", EntryPoint = "GetPrivateProfileSectionsNamesA", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, ExactSpelling = true)] 22 private static extern int GetPrivateProfileSectionsNames(byte[] lpszReturnBuffer, int nSize, string lpFileName); 23 24 [DllImport("KERNEL32.DLL", EntryPoint = "GetPrivateProfileStringA", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, ExactSpelling = true)] 25 private static extern int GetPrivateProfileString(string lpApplicationName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, int nSize, string lpFileName); 26 27 [DllImport("KERNEL32")] 28 private static extern int GetPrivateProfileString(string lpAppName, string lpszKey, string lpString, Byte[] lpStruct, int uSizeStruct, string lpFileName); 29 30 [DllImport("KERNEL32.DLL", EntryPoint = "GetPrivateProfileStructA", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, ExactSpelling = true)] 31 private static extern int GetPrivateProfileStruct(string lpszSections, string lpszKey, byte[] lpStruct, int uSizeStruct, string szFile); 32 33 [DllImport("KERNEL32.DLL", EntryPoint = "WritePrivateProfileSectionsA", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, ExactSpelling = true)] 34 private static extern int WritePrivateProfileSections(string lpAppName, string lpString, string lpFileName); 35 36 [DllImport("KERNEL32.DLL", EntryPoint = "WritePrivateProfileStringA", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, ExactSpelling = true)] 37 private static extern int WritePrivateProfileString(string lpApplicationName, string lpKeyName, string lpString, string lpFileName); 38 39 [DllImport("KERNEL32.DLL", EntryPoint = "WritePrivateProfileStructA", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, ExactSpelling = true)] 40 private static extern int WritePrivateProfileStruct(string lpszSections, string lpszKey, byte[] lpStruct, int uSizeStruct, string szFile); 41 #endregion 42 43 private string _INIFilePath = Environment.CurrentDirectory + "\\Configs\\{0}.ls.ini"; 44 45 #region 属性 46 private string _FilePath; 47 public string FilePath { get { return _FilePath; } set { _FilePath = value; } } 48 49 private string _ErrorMessage; 50 public string ErrorMessage { get { return _ErrorMessage; } set { _ErrorMessage = value; } } 51 #endregion 52 53 #region 构造函数 54 /// <summary> 55 /// 无参构造函数 56 /// </summary> 57 public INIHelper_API() { } 58 /// <summary> 59 /// 带参构造函数 60 /// </summary> 61 /// <param name="_iniFilePath">INI文件的绝对路径</param> 62 public INIHelper_API(string _iniFileName) 63 { 64 if (File.Exists(string.Format(_INIFilePath, _iniFileName))) 65 { 66 this.FilePath = string.Format(_INIFilePath, _iniFileName); 67 } 68 else 69 { 70 this.ErrorMessage = "系统配置文件不存在!"; 71 72 this.FilePath = string.Format(_INIFilePath, _iniFileName); 73 File.Create(this.FilePath); // 创建配置文件 74 } 75 } 76 #endregion 77 78 #region 将指定的值写入INI文件 79 /// <summary> 80 /// 将指定的值写入INI文件 81 /// </summary> 82 /// <param name="_sectionName">段落节点,格式[]</param> 83 /// <param name="_key">键</param> 84 /// <param name="_value">值</param> 85 public void WriteValue(string _sectionName, string _key, string _value) 86 { 87 // 如果当前指定的配置文件路径为空,即不存在 88 if (this.FilePath == null || string.IsNullOrEmpty(this.FilePath.Trim()) || !File.Exists(this.FilePath)) 89 { 90 this.FilePath = string.Format(_INIFilePath, new FileInfo(Application.ExecutablePath).Name); 91 92 File.Create(this.FilePath); // 创建配置文件 93 } 94 95 WritePrivateProfileString(_sectionName, _key, _value, this.FilePath); 96 } 97 #endregion 98 99 #region 读取INI配置文件信息 100 /// <summary> 101 /// 读取INI配置文件信息 102 /// </summary> 103 /// <param name="_sectionName">段落节点,格式[]</param> 104 /// <param name="_key">键名称</param> 105 /// <returns>返回目标值</returns> 106 public string ReadValue(string _sectionName, string _key) 107 { 108 // 如果当前指定的配置文件路径为空,即不存在 109 if (this.FilePath == null || string.IsNullOrEmpty(this.FilePath.Trim()) || !File.Exists(this.FilePath)) 110 { 111 this.FilePath = string.Format(_INIFilePath, new FileInfo(Application.ExecutablePath).Name); 112 113 File.Create(this.FilePath); // 创建配置文件 114 } 115 116 StringBuilder result = new StringBuilder(255); 117 GetPrivateProfileString(_sectionName, _key, "", result, 255, this.FilePath); 118 return result.ToString(); 119 } 120 #endregion 121 122 #region 读取INI配置文件 123 /// <summary> 124 /// 读取INI配置文件 125 /// </summary> 126 /// <param name="_sectionName">段落节点,格式[]</param> 127 /// <param name="_key">键名称</param> 128 /// <returns>返回byte类型的section组或键值组</returns> 129 public byte[] ReadValues(string _sectionName, string _key) 130 { 131 byte[] result = new byte[255]; 132 133 // 如果当前指定的配置文件路径为空,即不存在 134 if (this.FilePath == null || string.IsNullOrEmpty(this.FilePath.Trim()) || !File.Exists(this.FilePath)) 135 { 136 this.FilePath = string.Format(_INIFilePath, new FileInfo(Application.ExecutablePath).Name); 137 138 File.Create(this.FilePath); // 创建配置文件 139 } 140 141 GetPrivateProfileString(_sectionName, _key, "", result, 255, this.FilePath); 142 return result; 143 } 144 #endregion 145 } 146 }
网上其他的INI通用辅助类
其次就是程序运行需要的公用数据和方法:
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Text; 5 using System.Windows.Forms; 6 7 namespace Guying.Project.MiniServer 8 { 9 /// <summary> 10 /// 提供系统运行时所需的公用数据和方法。 11 /// 该类中的数据成员会被多个线程并发访问, 12 /// 因此要求数据成员为静态的,并且仅在类的初始化时确定, 13 /// 在其他函数或过程中对数据成员赋值是不安全的。 14 /// </summary> 15 public class ServerInfo 16 { 17 18 private static INIHelper _INIHelper = new INIHelper("httpsrv.ini"); 19 20 //private static string cgi=_INIHelper.ValueOf("cgi"); 21 private static string WWW_ROOT = _INIHelper.ValueOf("wwwroot"); 22 private static string NO_PAGE = _INIHelper.ValueOf("nopage"); 23 private static string DEFAULT_PAGE = _INIHelper.ValueOf("defaultpage"); 24 private static string WRONG_REQUEST_PAGE = _INIHelper.ValueOf("wrongrequest"); 25 private static string CACHE_DIR = _INIHelper.ValueOf("cachedir"); 26 private static string[] SPP_EXTS = _INIHelper.ValueOf("sppexts").Split(‘,‘);//get the knowed Server Process Page filename‘s extensions 27 private static string[] APP_HANDLE_EXT = new string[SPP_EXTS.Length];//get the application name which handle such ext 28 29 public ServerInfo() 30 { 31 FrmMain.GetInstance().ShowMessage("Loading Server Infomation..."); 32 33 for (int i = 0 ;i < sppexts.Length ; i++) AppHandleExt[i] = _INIHelper.ValueOf(sppexts[i]); 34 } 35 36 public string wwwroot 37 { 38 get 39 { 40 return WWW_ROOT; 41 } 42 } 43 44 public string nopage 45 { 46 get 47 { 48 return NO_PAGE; 49 } 50 } 51 52 public string defaultpage 53 { 54 get 55 { 56 return DEFAULT_PAGE; 57 } 58 } 59 60 public string wrongrequestpage 61 { 62 get 63 { 64 return WRONG_REQUEST_PAGE; 65 } 66 } 67 68 public string cacheDIR 69 { 70 get 71 { 72 return CACHE_DIR; 73 } 74 } 75 76 public string[] sppexts 77 { 78 get 79 { 80 return SPP_EXTS; 81 } 82 } 83 84 public string[] AppHandleExt 85 { 86 get 87 { 88 return APP_HANDLE_EXT; 89 } 90 } 91 } 92 }
程序运行需要的公用数据和方法
然后是读取配置文件,根据请求返回内容:
1 using System; 2 using System.Collections.Generic; 3 using System.Diagnostics; 4 using System.IO; 5 using System.Net; 6 using System.Net.Sockets; 7 using System.Text; 8 9 namespace Guying.Project.MiniServer 10 { 11 /// <summary> 12 /// RequestProcessor 中的所有非 static 方法,字段都有可能并发执行 13 /// </summary> 14 public class RequestProcessor 15 { 16 private static INIHelper _INIHelper = new INIHelper("MIME.ini");//用于getMIMEType()中; 17 18 public static ServerInfo _ServerInfo;// = new SrvInfo();//can get from SrvMain 19 20 public Socket sockSendData;//Notice: get from ClientSocketThread.s 21 public string RequestID;//Notice: get from ClientSocketThread.currentThread.Name,now only for log 22 23 public RequestProcessor() 24 { 25 //Console.WriteLine("Loading \"RequestProcessor MODEL\" ..."); 26 } 27 28 private void sendContent(FileStream fs, long start, long length) 29 { 30 try 31 { 32 33 //报文头发送完毕,开始发送正文 34 if (length == 0) length = fs.Length - start; 35 const int SOCKETWINDOWSIZE = 8192; 36 long r = SOCKETWINDOWSIZE; 37 int rd = 0; 38 Byte[] senddatas = new Byte[SOCKETWINDOWSIZE]; 39 //MemoryStream sr = new MemoryStream(fs); 40 fs.Seek(start, SeekOrigin.Begin); 41 do 42 { 43 r = start + length - fs.Position; 44 //fs.BeginRead(s,s,s,s,d) 以后使用的版本,用以提高读取的效率 45 if (r >= SOCKETWINDOWSIZE) rd = fs.Read(senddatas, 0, SOCKETWINDOWSIZE); 46 else rd = fs.Read(senddatas, 0, (int)r); 47 sockSendData.Send(senddatas, 0, rd, SocketFlags.None); 48 } while (fs.Position != start + length); 49 50 //if the fs is a temp FileStream then delete the file 51 //for it created by server 52 //other way, all files in cacheDIR will be deleted 53 if (fs.Name.IndexOf(_ServerInfo.cacheDIR) >= 0) 54 { 55 string s = fs.Name; 56 fs.Close(); 57 File.Delete(s); 58 } 59 } 60 catch (SocketException e) 61 { 62 if (fs.Name.IndexOf(_ServerInfo.cacheDIR) >= 0) 63 { 64 string s = fs.Name; 65 fs.Close(); 66 File.Delete(s); 67 } 68 throw e; 69 } 70 catch (IOException e) 71 { 72 throw e; 73 } 74 } 75 76 //ever used,now unused 77 private void sendContent(FileStream fs) 78 { 79 sendContent(fs, 0, 0); 80 } 81 82 private string getMIMEType(string filename) 83 { 84 string fname = filename, typename = "text/html"; 85 if ((filename != "/") && (filename.IndexOf("?") < 0)) 86 { 87 int r = fname.LastIndexOf(‘.‘) + 1; 88 fname = fname.Remove(0, r); 89 if ((typename = _INIHelper.ValueOf(fname)) == "") typename = "application/" + fname; 90 } 91 return typename; 92 } 93 94 private FileStream PreProcessAndSendHeader(string filename, long start, long length) 95 { 96 Encoding coding = Encoding.Default; 97 Byte[] sendchars = new Byte[512]; 98 string strSend; 99 FileStream fs; 100 try 101 { 102 if (filename == "/") fs = new FileStream(_ServerInfo.wwwroot + _ServerInfo.defaultpage, 103 FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 104 105 else if (isCGIorSPP(filename)) fs = ProcessCGIorSPP(filename);//get a stream that the function returned 106 107 else fs = new FileStream(_ServerInfo.wwwroot + filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 108 109 if (start == 0 && length == 0) strSend = "HTTP/1.1 200 OK"; 110 else strSend = "HTTP/1.1 206 Partial Content"; 111 sendchars = coding.GetBytes((strSend + "\r\n").ToCharArray()); 112 sockSendData.Send(sendchars, 0, sendchars.Length, SocketFlags.None); 113 114 } 115 catch (IOException)// FileNotFoundException) 116 { 117 Console.WriteLine(FrmMain.strGMTDateTime() + " ERROR ID=[{0}]: {1} Request for file :\"{2}\" But NOT found", RequestID, 118 ((IPEndPoint)sockSendData.RemoteEndPoint).ToString(), filename); 119 120 fs = new FileStream(_ServerInfo.wwwroot + _ServerInfo.nopage, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 121 filename = _ServerInfo.nopage; 122 123 strSend = "HTTP/1.1 302 Found"; 124 sendchars = coding.GetBytes((strSend + "\r\n").ToCharArray()); 125 sockSendData.Send(sendchars, 0, sendchars.Length, SocketFlags.None); 126 127 strSend = "Location: " + "http://" + ((IPEndPoint)sockSendData.LocalEndPoint).ToString() + "/" + 128 _ServerInfo.nopage; //maybe it‘s danger 129 sendchars = coding.GetBytes((strSend + "\r\n").ToCharArray()); 130 sockSendData.Send(sendchars, 0, sendchars.Length, SocketFlags.None); 131 } 132 catch (ArgumentException)//the request is WRONG 133 { 134 Console.WriteLine(FrmMain.strGMTDateTime() + " ERROR ID=[{0}]: {1} send a WRONG request :\"{2}\" ", RequestID, 135 ((IPEndPoint)sockSendData.RemoteEndPoint).ToString(), filename); 136 137 fs = new FileStream(_ServerInfo.wwwroot + _ServerInfo.wrongrequestpage, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 138 filename = _ServerInfo.wrongrequestpage; 139 140 strSend = "HTTP/1.1 302 Found"; 141 sendchars = coding.GetBytes((strSend + "\r\n").ToCharArray()); 142 sockSendData.Send(sendchars, 0, sendchars.Length, SocketFlags.None); 143 144 strSend = "Location: " + "http://" + ((IPEndPoint)sockSendData.LocalEndPoint).ToString() + "/" + 145 _ServerInfo.wrongrequestpage; //maybe it‘s danger 146 sendchars = coding.GetBytes((strSend + "\r\n").ToCharArray()); 147 sockSendData.Send(sendchars, 0, sendchars.Length, SocketFlags.None); 148 } 149 150 151 strSend = "Date: " + FrmMain.strGMTDateTime(); 152 sendchars = coding.GetBytes((strSend + "\r\n").ToCharArray()); 153 sockSendData.Send(sendchars, 0, sendchars.Length, SocketFlags.None); 154 155 156 strSend = "Server: httpsrv/1.0"; 157 sendchars = coding.GetBytes((strSend + "\r\n").ToCharArray()); 158 sockSendData.Send(sendchars, 0, sendchars.Length, SocketFlags.None); 159 160 strSend = "MIME-Version: 1.0"; 161 sendchars = coding.GetBytes((strSend + "\r\n").ToCharArray()); 162 sockSendData.Send(sendchars, 0, sendchars.Length, SocketFlags.None); 163 164 strSend = "Content-Type: " + getMIMEType(filename); 165 sendchars = coding.GetBytes((strSend + "\r\n").ToCharArray()); 166 sockSendData.Send(sendchars, 0, sendchars.Length, SocketFlags.None); ; 167 168 if (length == 0) length = fs.Length - start; 169 170 strSend = "Content-Range: Bytes " + start.ToString() + "-" + (start + length - 1).ToString() + "/" + fs.Length.ToString(); 171 sendchars = coding.GetBytes((strSend + "\r\n").ToCharArray()); 172 sockSendData.Send(sendchars, 0, sendchars.Length, SocketFlags.None); 173 174 strSend = "Content-Length: " + length.ToString(); 175 sendchars = coding.GetBytes((strSend + "\r\n").ToCharArray()); 176 sockSendData.Send(sendchars, 0, sendchars.Length, SocketFlags.None); 177 178 //发送一个空行 179 sendchars = coding.GetBytes(("\r\n").ToCharArray()); 180 sockSendData.Send(sendchars, 0, sendchars.Length, SocketFlags.None); 181 return fs; 182 } 183 184 /// <summary> 185 /// About returns: 186 /// in HTTP 1.1 maybe the client need a Keep-Alive connection 187 /// then it send header with a field "Connection: Keep-Alive" 188 /// others who need NOT send header with a field "Connection: Closed" 189 /// or have no this field in lower HTTP version 190 /// Others: 191 /// i dont check the client‘s HTTP version 192 /// and assume it is HTTP 1.1 193 /// </summary> 194 /// <param name="RequestLines"></param> 195 /// <returns>return true only if the client request a Keep-Alive connection</returns> 196 197 public bool ParseRequestAndProcess(string[] RequestLines) 198 { 199 char[] sp = new Char[1] { ‘ ‘ }; 200 string[] strs = RequestLines[0].Split(sp); 201 if (strs[0] == "GET") 202 { 203 long start = 0; 204 long length = 0; 205 foreach (string str in RequestLines) 206 { 207 if (str.StartsWith("Range:")) 208 { 209 string s = str.Substring(str.IndexOf("=") + 1); 210 string[] ss = s.Split(‘-‘); 211 start = Convert.ToInt64(ss[0]); 212 if (ss[1] != "") length = Convert.ToInt64(ss[1]) - start + 1; 213 214 } 215 } 216 if (isRequestSecurity(strs[1])) 217 { 218 sendContent(PreProcessAndSendHeader(strs[1], start, length), start, length); 219 } 220 else 221 { 222 sendContent(PreProcessAndSendHeader(_ServerInfo.wrongrequestpage, 0, 0), 0, 0); 223 Console.WriteLine(FrmMain.strGMTDateTime() + " WARNING ID=[{0}]: {1} send a WRONG request :\"{2}\" ", RequestID, 224 ((IPEndPoint)sockSendData.RemoteEndPoint).ToString(), 225 strs[1]); 226 } 227 } 228 else 229 if (strs[0] == "HEAD") 230 { 231 if (isRequestSecurity(strs[1])) 232 { 233 PreProcessAndSendHeader(strs[1], 0, 0); 234 } 235 } 236 237 foreach (string str in RequestLines) 238 { 239 if (str.StartsWith("Connection:")) 240 { 241 string s = str.Substring(12); 242 if (s == "Keep-Alive") return true; 243 } 244 } 245 return false; 246 } 247 248 private bool isRequestSecurity(string strRequest) 249 { 250 if (strRequest.IndexOf("..") >= 0) return false; 251 return true; 252 } 253 254 /// <summary> 255 /// SPP is Server-end Process Page such as ASP php etc. 256 /// </summary> 257 private bool isCGIorSPP(string filename) 258 { 259 if (filename.IndexOf("?") >= 0) return true; 260 string ext = filename.Substring(filename.LastIndexOf(".") + 1); 261 for (int i = 0; i < _ServerInfo.sppexts.Length; i++) 262 { 263 if ((ext == _ServerInfo.sppexts[i]) && (_ServerInfo.AppHandleExt[i] != "")) 264 return true; 265 } 266 return false; 267 } 268 269 /// <summary> 270 /// return a FileStream get from CGI or SPP 271 /// </summary> 272 private FileStream ProcessCGIorSPP(string filename) 273 { 274 try 275 { 276 string[] ss = new string[2]; 277 if (filename.IndexOf("?") >= 0) 278 { 279 ss[0] = filename.Substring(0, filename.IndexOf("?")); 280 ss[1] = filename.Substring(filename.IndexOf("?") + 1); 281 } 282 else 283 { 284 ss[0] = filename; ss[1] = ""; 285 } 286 while (ss[1].IndexOf("+") >= 0) ss[1] = ss[1].Replace("+", " "); 287 Process p = new Process(); 288 string ext = ""; 289 if (ss[0].LastIndexOf(".") >= 0) ext = ss[0].Substring(ss[0].LastIndexOf(".") + 1); 290 291 if ((ext != "") && (ext != "exe")) 292 for (int i = 0; i < _ServerInfo.sppexts.Length; i++) 293 { 294 if (ext == _ServerInfo.sppexts[i]) 295 { 296 if (_ServerInfo.AppHandleExt[i] == "shell") 297 { 298 p.StartInfo.FileName = _ServerInfo.wwwroot + ss[0].Remove(0, 1); 299 p.StartInfo.Arguments = ss[1]; 300 } 301 else 302 { 303 p.StartInfo.FileName = _ServerInfo.AppHandleExt[i]; 304 p.StartInfo.Arguments = _ServerInfo.wwwroot + ss[0].Remove(0, 1) + " " + ss[1]; 305 } 306 break; 307 } 308 } 309 else //ext == "" 310 { 311 p.StartInfo.FileName = _ServerInfo.wwwroot + ss[0].Remove(0, 1); 312 p.StartInfo.Arguments = ss[1]; 313 } 314 315 p.StartInfo.UseShellExecute = false; 316 p.StartInfo.CreateNoWindow = true; 317 p.StartInfo.RedirectStandardOutput = true; 318 p.StartInfo.WorkingDirectory = p.StartInfo.FileName.Substring(0, p.StartInfo.FileName.LastIndexOf("/") + 1); 319 p.Start(); 320 string s = p.StandardOutput.ReadToEnd(); 321 if (!Directory.Exists(_ServerInfo.wwwroot + _ServerInfo.cacheDIR)) 322 Directory.CreateDirectory(_ServerInfo.wwwroot + _ServerInfo.cacheDIR); 323 //ss[0] = ss[0].Substring(ss[0].LastIndexOf("/")); 324 FileStream fs = new FileStream(_ServerInfo.wwwroot + _ServerInfo.cacheDIR + RequestID + p.Id.ToString(), 325 FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); 326 fs.Write(Encoding.Default.GetBytes(s), 0, s.Length); 327 //p.WaitForExit(); 328 return fs; 329 } 330 catch (System.Runtime.InteropServices.ExternalException) 331 { 332 IOException e = new IOException(); 333 throw e; 334 } 335 catch (System.SystemException) 336 { 337 ArgumentException e = new ArgumentException(); 338 throw e; 339 } 340 341 } 342 343 } 344 }
读取配置文件,根据请求返回内容
防止程序未响应,自定义多线程处理:
1 using System; 2 using System.Collections.Generic; 3 using System.Net; 4 using System.Net.Sockets; 5 using System.Text; 6 using System.Threading; 7 8 namespace Guying.Project.MiniServer 9 { 10 /// <summary> 11 /// ClientSocketThread 的摘要说明。 12 /// </summary> 13 public class ClientSocketThread 14 { 15 public TcpListener tcpl;//Notice: get from SrvMain.tcpl 16 17 private static Encoding ASCII = Encoding.ASCII; 18 //private static int RequestID = 0; 19 20 public void HandleThread() 21 { 22 string strClientIP = ""; 23 string strClientPort = ""; 24 string strLocalIP = ""; 25 string strLocalPort = ""; 26 27 Thread currentThread = Thread.CurrentThread; 28 29 try 30 { 31 // Accept will block until someone connects 32 Socket s = tcpl.AcceptSocket(); 33 //RequestID++; 34 strLocalIP = ((IPEndPoint)s.LocalEndPoint).Address.ToString(); 35 strLocalPort = ((IPEndPoint)s.LocalEndPoint).Port.ToString(); 36 strClientIP = ((IPEndPoint)s.RemoteEndPoint).Address.ToString(); 37 strClientPort = ((IPEndPoint)s.RemoteEndPoint).Port.ToString(); 38 39 RequestProcessor aRequestProcessor = new RequestProcessor(); //Notice: 40 aRequestProcessor.sockSendData = s;//Notice: so that the processor can work 41 aRequestProcessor.RequestID = currentThread.Name; 42 43 const int BUFFERSIZE = 4096;//that‘s enough??? 44 Byte[] readclientchar = new Byte[BUFFERSIZE]; 45 char[] sps = new Char[2] { ‘\r‘, ‘\n‘ }; 46 string[] RequestLines = new string[32]; 47 48 do 49 { 50 //use BUFFERSIZE contral the receive data size to avoid the BufferOverflow attack 51 int rc = s.Receive(readclientchar, 0, BUFFERSIZE, SocketFlags.None); 52 53 string strReceive = ASCII.GetString(readclientchar, 0, rc); 54 55 RequestLines = strReceive.Split(sps); 56 Console.WriteLine(FrmMain.strGMTDateTime() + " Request ID=[{0}] {1}->{2} :{3}", 57 currentThread.Name, 58 ((IPEndPoint)s.RemoteEndPoint).ToString(), 59 ((IPEndPoint)s.LocalEndPoint).ToString(), 60 RequestLines[0]); 61 62 } while (aRequestProcessor.ParseRequestAndProcess(RequestLines)); 63 64 Console.WriteLine(FrmMain.strGMTDateTime() + " Closed ID=[{0}] {1}->{2} :Server Closed", currentThread.Name, 65 ((IPEndPoint)s.LocalEndPoint).ToString(), ((IPEndPoint)s.RemoteEndPoint).ToString()); 66 s.Close(); 67 } 68 catch (SocketException) 69 { 70 Console.WriteLine(FrmMain.strGMTDateTime() + " Closed ID=[{0}] {1}:{2}->{3}:{4} :Client Closed", currentThread.Name, 71 strClientIP, strClientPort, strLocalIP, strLocalPort); 72 currentThread.Abort(); 73 } 74 } 75 76 } 77 }
防止程序未响应,自定义多线程处理
最后,搭建测试窗体如下:
窗体调用实现方法的代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Diagnostics; 6 using System.Drawing; 7 using System.Net; 8 using System.Net.Sockets; 9 using System.Text; 10 using System.Text.RegularExpressions; 11 using System.Threading; 12 using System.Windows.Forms; 13 14 namespace Guying.Project.MiniServer 15 { 16 public partial class FrmMain : Form 17 { 18 private static FrmMain _FrmMain = null; 19 Thread _Thread = null; 20 Thread _myWorkerThread = null; 21 22 private FrmMain() 23 { 24 InitializeComponent(); 25 } 26 27 public static FrmMain GetInstance() 28 { 29 if (_FrmMain == null) 30 { 31 _FrmMain = new FrmMain(); 32 } 33 return _FrmMain; 34 } 35 36 public void ShowMessage(string _messageStr) 37 { 38 this.listBox_Logs.Items.Add(_messageStr); 39 } 40 41 static public string strGMTDateTime() 42 { 43 DateTime GMTNow = DateTime.Now.ToUniversalTime(); 44 string month; 45 switch (GMTNow.Month) 46 { 47 case 1: month = "Jan"; break; 48 case 2: month = "Feb"; break; 49 case 3: month = "Mar"; break; 50 case 4: month = "Apr"; break; 51 case 5: month = "May"; break; 52 case 6: month = "Jun"; break; 53 case 7: month = "Jul"; break; 54 case 8: month = "Aug"; break; 55 case 9: month = "Sep"; break; 56 case 10: month = "Oct"; break; 57 case 11: month = "Nov"; break; 58 case 12: month = "Dec"; break; 59 default: month = "Martian???"; break; 60 } 61 return 62 GMTNow.DayOfWeek.ToString().Substring(0, 3) + ", " + GMTNow.Day.ToString() + " " + month + " " + GMTNow.Year.ToString() + " " + GMTNow.Hour.ToString() + ":" + GMTNow.Minute.ToString() + ":" + GMTNow.Second.ToString() + ":" + DateTime.Now.Millisecond.ToString() + " " + "GMT"; 63 } 64 65 private void btn_Start_Click(object sender, EventArgs e) 66 { 67 Start(); 68 } 69 70 void Start() 71 { 72 try 73 { 74 ServerInfo _ServerInfo = new ServerInfo(); 75 //i want to block the RequestProcessor when changing _ServerInfo 76 lock (typeof(RequestProcessor)) 77 { 78 RequestProcessor._ServerInfo = _ServerInfo; 79 } 80 81 ShowMessage("Starting NetWork listening..."); 82 83 this.btn_Stop.Enabled = true; 84 this.btn_Start.Enabled = false; 85 86 TcpListener tcpListener; 87 88 try 89 { 90 tcpListener = new TcpListener(IPAddress.Parse(this.cmb_IPAddresses.Text), int.Parse(this.txt_IPPoint.Text)); // listen on port 80 91 } 92 catch (Exception) 93 { 94 ShowMessage("Wrong argument:Using [[IP] (Port)] as the options"); 95 return; 96 } 97 tcpListener.Start(); 98 99 Console.WriteLine("Listening on {0}", ((IPEndPoint)tcpListener.LocalEndpoint).ToString()); 100 Console.WriteLine("Server now waiting for clients to connect"); 101 Console.WriteLine(); 102 Console.WriteLine(strGMTDateTime() + " Server Start,Start logging"); 103 //Console.WriteLine("Press Ctrl+c to Quit..."); 104 105 int ThreadID = 0; 106 107 ThreadStart _ThreadStart = new ThreadStart(() => 108 { 109 while (true) 110 { 111 while (!tcpListener.Pending()) 112 { 113 Thread.Sleep(100); 114 } 115 116 ClientSocketThread myThreadHandler = new ClientSocketThread(); 117 myThreadHandler.tcpl = tcpListener;//Notice: dont forget do this 118 ThreadStart myThreadStart = new ThreadStart(myThreadHandler.HandleThread); 119 _myWorkerThread = new Thread(myThreadStart); 120 _myWorkerThread.Name = (ThreadID++).ToString(); 121 _myWorkerThread.Start(); 122 } 123 }); 124 125 _Thread = new Thread(_ThreadStart); 126 _Thread.Start(); 127 128 } 129 catch (SocketException socketError) 130 { 131 if (socketError.ErrorCode == 10048) 132 { 133 ShowMessage("Connection to this port failed. There is another server is listening on this port."); 134 } 135 } 136 catch (FormatException) 137 { 138 ShowMessage("invalid IP Address"); 139 } 140 catch (Exception ex) 141 { 142 ShowMessage("Ah O: " + ex.Message); 143 } 144 } 145 146 private void FrmMain_Load(object sender, EventArgs e) 147 { 148 if (System.IO.File.Exists(Application.StartupPath + "\\Guying.ssk")) 149 { 150 this.skinEngine.SkinFile = Application.StartupPath + "\\Guying.ssk"; 151 this.skinEngine.SkinAllForm = true; 152 } 153 154 this.btn_Stop.Enabled = false; 155 this.btn_Start.Enabled = true; 156 157 this.cmb_IPAddresses.Items.Add("127.0.0.1"); 158 159 try 160 { 161 this.cmb_IPAddresses.Items.Add(GetOutterIPAddress()); 162 } 163 catch (Exception) { } 164 165 IPAddress[] ipAddresses = Dns.GetHostAddresses(Environment.MachineName); 166 foreach (IPAddress ip in ipAddresses) 167 { 168 if (ip.AddressFamily == AddressFamily.InterNetwork) 169 { 170 this.cmb_IPAddresses.Items.Add(ip); 171 } 172 } 173 } 174 175 private void FrmMain_FormClosing(object sender, FormClosingEventArgs e) 176 { 177 Process.GetCurrentProcess().Kill(); 178 } 179 180 public string GetOutterIPAddress() 181 { 182 string str = null; 183 //这个负责抓IP的页。第一步先抓取这个html页的全部内容 184 string url = "http://www.ikaka.com/ip/index.asp"; 185 WebClient wc = new WebClient(); 186 wc.Credentials = CredentialCache.DefaultCredentials; 187 Byte[] pageData = wc.DownloadData(url); 188 string MyUrl = System.Text.Encoding.UTF8.GetString(pageData); 189 //正则找到页面中的IP部分,并输出。 190 Regex regex = new Regex(@"(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))"); 191 foreach (Match m in regex.Matches(MyUrl)) 192 { 193 str = m.ToString(); 194 } 195 return str; 196 197 } 198 199 private void btn_Stop_Click(object sender, EventArgs e) 200 { 201 _Thread.Abort(); 202 _myWorkerThread.Abort(); 203 204 this.btn_Start.Enabled = true; 205 this.btn_Stop.Enabled = false; 206 } 207 } 208 }
窗体调用实现方法的代码
最后,设置皮肤样式,呵呵,搞定:
在此,这个小程序就搞定了,希望有大神能帮忙加上php或者aspx等页面的支持。那就完美了。呵呵。
代码上面都有了,如果需要源码的请留言邮箱地址。
【来自:[LonelyShadow 博客] http://www.cnblogs.com/LonelyShadow】
WinForms 小型HTML服务器,布布扣,bubuko.com
时间: 2024-10-13 12:20:49