Socket模拟HTTP协议之火车票购票软件
前段时间我发布过一篇文章描述Socket进行HTTP/HTTPS操作,但是还是很多朋友觉得多次一举,放着简单的HttpWebRequest不用!
实际是有些人根本没看文章就乱说了,我们的目地是提高访问速率,了解HTTP协议与一般网络开发,并非奔着简单去!
正好这年末,大家抢火车票抢的火热,但是我们很多程序员朋友却只没有应用好自己的专业知识为自己购得回家的车票。
网上已经不乏一些抢票软件、以及对12306流程分析的文章,
google code中也已经有了java版的全自动购票程序,自动AI+OCR识别的。有兴趣的可以去搜索下看看。
回到文章中来,我这次也是通过编写一下购票软件来实践下上次文章中的内容:
(http://www.cnblogs.com/cxwx/archive/2011/10/25/2224105.html)
下面的这款小软件还没有完全完成,软件是WPF缩写,基本的MVVM模式(有需要学习MVVM的也可以参考学习下)
下面就按照截图走流程+编码说明:
一:登录
我们知道,首次请求一个页面,服务端会为客户端创建一个Session来保存客户端状态,我们通常在登录页面提交信息成功后,服务端会在其他页面跳转之后判断
Seesion是否已经成功登录。
对于存在验证码的网站,其实请求图片与请求页面没有什么区别,如果是首次请求那么HTTP返回协议一般都带附带Cookies信息,即Set-Cookies 信息!
对于12306网站来说,我们的登录页面只要请求验证码图片即可,因为这个请求的HTTP返回协议会包含服务端为本次请求创建的Cookies,
如果您利用上一篇文章讲解的方法来请求,你会发现请求返回协议类似如下:
1 Response Headers Value 2 (Status-Line) HTTP/1.1 200 OK 3 Date Sun, 08 Jan 2012 23:57:29 GMT 4 Server asfep/2.3.0 svn:3075 5 Content-Type text/html;charset=UTF-8 6 Transfer-Encoding chunked 7 X-Powered-By Servlet 2.5; JBoss-5.0/JBossWeb-2.1 8 Pragma no-cache 9 Cache-Control no-cache10 Expires Wed, 31 Dec 1969 23:59:59 GMT11 Content-Encoding gzip12 X-Cache MISS from cache.51cdn.com13 X-Via 1.1 hbts205:8090 (Cdn Cache Server V2.0)14 Connection keep-alive
在这段返回头中,你会发现不存在Content-Length协议头,那我们如何知道返回的数据包长度呢;
如果你善于google你就会知道,这种反回是因为Keep-Live的存在 服务器使用chunked方式分多次返回数据的结果;既然服务端还是会返回数据,只是分了多次!(我们知道浏览器能够正常解析哦,所以别小看浏览器内核哦,很强大滴~~~~)
我们首要先来解决对于反回数据的解析:接着上篇文章中的HttpHelper,大家找到如下代码段:看看修改后的代码;
static byte[] ReadResponse(Stream sm){DateTime now = DateTime.Now;ArrayList array = new ArrayList();int length = 0;while (true){byte[] buff = new byte[1024];try{int len = sm.Read(buff, 0, buff.Length);if (len > 0){length += len;byte[] reads = new byte[len];Array.Copy(buff, 0, reads, 0, len);array.Add(reads);if (len < buff.Length){break; //fix bug}}else{break; //fix bug}}catch (Exception){break;}finally{Thread.Sleep(200);}}byte[] bytes = new byte[length];int index = 0;for (int i = 0; i < array.Count; i++){byte[] temp = (byte[])array[i];Array.Copy(temp, 0, bytes,index, temp.Length);index += temp.Length;}return bytes;}
很简单,其实就是多次请求,直到请求异常或者实际请求数据小于缓冲区大小就OK啦(注意两处 fix bug)
这样,我们首先解决了解析服务端反回数据的问题,紧接着我们娶到了登录页面的验证码图片, 如果你现在抓包看下,你又会发现,抓包的数据就是图片数据,
而我们使用Socket请求来的数据,多出了一段数据头,呵呵,明眼的你注意,就会发现多出的这段数据头还包含真实数据的长度!
如图中红色区域;
我这里呢没有通过解析这个长度去提取真实数据,而是通过遍历出JPEG图片数据头来截取数据的,代码如下:
1 /// <summary> 2 /// JFIF数据头 3 /// </summary> 4 static readonly byte[] JFIF = new byte[] { 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46 }; 5 6 public static Stream GetVerifyImage() 7 { 8 IPEndPoint endpoint = GetHost(); 9 RequestArgs request=new RequestArgs()10 {11 Url = "/otsweb/passCodeAction.do?rand=lrand",12 Host="dynamic.12306.cn",13 Accept="image/*",14 Referer="https://dynamic.12306.cn/otsweb/loginAction.do?method=init",15 };16 m_Response=HttpHelper.Get(endpoint,request,m_X509);17 if(m_Response!=null)18 {19 if(m_Response.Header.StartsWith("HTTP/1.1 200"))20 {21 //先校正保存cookies22 AdjustCookies();23 24 //由于HTTP返回协议使用了 chunked,所以要手动去分析实际需要的内容25 if (m_Response.Body != null && m_Response.Body.Length > 10)26 {27 int index = 0;28 byte[] head = new byte[10];29 while (index < m_Response.Body.Length - 10)30 {31 Array.Copy(m_Response.Body,index,head,0,head.Length);32 if (head.SequenceEqual(JFIF))33 {34 byte[] buff = new byte[m_Response.Body.Length-index];35 Array.Copy(m_Response.Body,36 index,37 buff,0,buff.Length);38 return new MemoryStream(buff);39 }40 index++;41 }42 }43 }44 }45 return null;46 }
上面这段代码也是我们购票程序登录页面的第一个服务代码; 注意看对Repose的解析部分,根据JPEG图片头进行数据解析,
读到这里,各位看官应该了解了如果使用上次文章中的HttpHelper,做一个简单的HTTPS请求,当然这个请求是通过Socket完成的,这里我就不再解释了!
OK,说了一些关于第一步请求的东西,接着我们先看下MVVM模式的简单应用,
我们的Login页面是一个UserCOntrol,整个购票程序是一个Window 当中是一个Frame,通过页面跳转来指导运行的,
Login页面绑定LoginViewModel,注意看,很多朋友不知道MVVM模式中ViewModel怎么和View交互,这里你可看清楚了哦!
1 <UserControl.DataContext>2 <vm:LoginViewModel Loaded="LoginViewModel_Loaded"3 Loading="LoginViewModel_Loading"4 Logined="LoginViewModel_Logined"5 VerifyImageCompleted="LoginViewModel_VerifyImageCompleted" />6 </UserControl.DataContext>
没错,事件!!! 就是痛过事件,我们简单的让ViewModel与View进行交互。 当然这并不是唯一的方法,方法很多,这里我就不多讲了,有兴趣的自己看下Prsim以及Service共享和,ICO相关知识!
至于您看到的这里,为什么会有三个事件,我需要解释下,Logined事件无疑是登录成功事件,其余两个看名字判断应该是一个登录过程提示的事件,
我这样做的目地是希望后台全部采用异步请求,避免程序体验效果不好,在MVVM模式中,该VM干的事就给VM干,该View干的那就让View去干吧!
OK,请求到了验证码图片,紧接着填写帐号信息,提交登录请求,判断返回结果,一步带过:通过ViewModel来看吧!
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using SimpleMvvmToolkit; 6 using System.Windows.Input; 7 using System.Windows.Media.Imaging; 8 using BuyTicket.Service; 9 using System.IO; 10 using System.Diagnostics; 11 using System.ComponentModel; 12 using System.Windows; 13 14 namespace BuyTicket.ViewModels 15 { 16 class LoginViewModel : ViewModelBase<LoginViewModel> 17 { 18 public LoginViewModel() 19 { 20 21 } 22 23 event EventHandler<NotificationEventArgs> m_Loading; 24 public event EventHandler<NotificationEventArgs> Loading 25 { 26 add 27 { 28 m_Loading += value; 29 } 30 remove 31 { 32 m_Loading -= value; 33 } 34 } 35 36 event EventHandler<NotificationEventArgs> m_Loaded; 37 public event EventHandler<NotificationEventArgs> Loaded 38 { 39 add 40 { 41 m_Loaded += value; 42 } 43 remove 44 { 45 m_Loaded -= value; 46 } 47 } 48 49 50 event EventHandler<NotificationEventArgs> m_Logined; 51 public event EventHandler<NotificationEventArgs> Logined 52 { 53 add 54 { 55 m_Logined += value; 56 } 57 remove 58 { 59 m_Logined -= value; 60 } 61 } 62 63 event EventHandler<NotificationEventArgs<BitmapImage>> m_VerifyImageCompleted; 64 public event EventHandler<NotificationEventArgs<BitmapImage>> VerifyImageCompleted 65 { 66 add 67 { 68 m_VerifyImageCompleted += value; 69 } 70 remove 71 { 72 m_VerifyImageCompleted -= value; 73 } 74 } 75 76 ICommand m_LoginCommand = null; 77 public ICommand LoginCommand 78 { 79 get 80 { 81 return m_LoginCommand ?? (m_LoginCommand = new DelegateCommand(Login)); 82 } 83 } 84 85 bool m_NotLogined = false; 86 87 void Login() 88 { 89 if(string.IsNullOrEmpty(m_UserName) 90 || string.IsNullOrEmpty(m_PassWord) 91 || string.IsNullOrEmpty(m_VerifyCode)) 92 { 93 MessageBox.Show("请填写完整的登录数据!", "错误提示", 94 MessageBoxButton.OK, MessageBoxImage.Error); 95 } 96 else 97 { 98 this.Async(new Func<object>(() => 99 {100 string error;101 bool logined = BuyTicketService.Login(m_UserName, m_PassWord, m_VerifyCode, out error);102 if (!logined)103 {104 m_NotLogined = true;105 if (!string.IsNullOrEmpty(error))106 {107 MessageBox.Show(error, "错误提示",108 MessageBoxButton.OK, MessageBoxImage.Error);109 }110 }111 return logined;112 }), new Action<object>(args =>113 {114 bool logined = (bool)args;115 if (logined)116 {117 if (m_Logined != null)118 {119 m_Logined(this, new NotificationEventArgs());120 }121 }122 else123 {124 RefreshVerifyCode(); //注意:此过程中可能造成View中Loading状态不一致125 }126 }));127 }128 }129 130 ICommand m_RefreshVerifyCodeCommand = null;131 public ICommand RefreshVerifyCodeCommand132 {133 get134 {135 return m_RefreshVerifyCodeCommand ?? (m_RefreshVerifyCodeCommand = new DelegateCommand(RefreshVerifyCode));136 }137 }138 139 void RefreshVerifyCode()140 {141 this.Async(new Func<object>(() =>142 {143 return BuyTicketService.GetVerifyImage();144 145 /* 过时146 var sm = BuyTicketService.GetVerifyImage();147 if (sm != null)148 {149 try150 {151 using (FileStream fs = new FileStream(@"d:\\temp.png", FileMode.Create))152 {153 sm.CopyTo(fs);154 fs.Close();155 }156 157 System.Drawing.Bitmap.FromStream(sm, true)158 .Save(@"d:\\temp.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);159 160 BitmapImage image = new BitmapImage();161 image.BeginInit();162 image.StreamSource = sm; //sm引用来自BackgroundWorker线程163 image.EndInit();164 return image;165 }166 catch (Exception ex)167 {168 Trace.WriteLine(ex);169 }170 }171 return null;172 */ 173 }), new Action<object>((args) =>174 {175 BitmapImage image = null;176 Stream sm = args as Stream;177 if (sm != null)178 {179 try180 {181 image = new BitmapImage();182 image.BeginInit();183 image.StreamSource = sm; //sm引用来自BackgroundWorker线程184 image.EndInit();185 }186 catch (Exception ex)187 {188 Trace.WriteLine(ex);189 }190 }191 //通知到View192 if (m_VerifyImageCompleted != null)193 {194 m_VerifyImageCompleted(this,195 new NotificationEventArgs<BitmapImage>("", image));196 }197 this.VerifyCode = "";198 /*199 BitmapImage image = args as BitmapImage;200 if (image != null)201 {202 if (m_VerifyImageCompleted != null)203 {204 m_VerifyImageCompleted(this, 205 new NotificationEventArgs<BitmapImage>("", image));206 }207 }*/208 }));209 210 }211 212 void Async(Func<object> func, Action<object> completed)213 {214 using (BackgroundWorker worker = new BackgroundWorker())215 {216 if (m_Loading != null)217 {218 m_Loading(this, new NotificationEventArgs());219 }220 worker.DoWork += (sender, e) =>221 {222 e.Result = func();223 };224 worker.RunWorkerCompleted += (sender, e) =>225 {226 completed(e.Result);227 if (m_NotLogined==false) //登录失败时不通知Loaded事件228 {229 if (m_Loaded != null)230 {231 m_Loaded(this, new NotificationEventArgs());232 }233 }else234 m_NotLogined = false; //重新标记235 };236 worker.RunWorkerAsync();237 }238 }239 240 string m_UserName;241 public string UserName242 {243 get244 {245 return m_UserName;246 }247 set248 {249 m_UserName = value;250 this.NotifyPropertyChanged(m => m.UserName);251 }252 }253 254 string m_PassWord;255 public string PassWord256 {257 get258 {259 return m_PassWord;260 }261 set262 {263 m_PassWord = value;264 this.NotifyPropertyChanged(m => m.PassWord);265 }266 }267 268 string m_VerifyCode;269 public string VerifyCode270 {271 get { return m_VerifyCode; }272 set273 {274 m_VerifyCode = value;275 this.NotifyPropertyChanged(m => m.VerifyCode);276 }277 }278 }279 }
相应的View就不展示出来了,需要的自己在文章末下载代码回去看吧!对于登录的过程,这里我不想细说,这里是演示上篇文章的通过Socket进行HTTP操作,
并不是延时如何抓包,如何模拟HTTP请求,有兴趣的可自己去查阅相关资料!
登录成功后,我们需要先填写购票信息,界面设计为:
请原谅我的美工水平哦,俺可不是专业的, 在这个页面中,起始站点信息是我从12306的js上搞下来的,说实话,最近这站被人诟病很多,
我也不乏诟病一下,但也不是非常差,我想没有点墨水可不是谁都能搞的了的。
这里还提供了一项功能就是跳转至浏览器, 其实登录成功后,Cookies信息我们也就娶到了,这时候通过浏览器打开12306再编辑其cookies也可以完成直接登录,
至于我后来添加“跳转至浏览器”是因为这款软件并没有完全完成,给需要使用的普通用户提供一个购票入口而已!
从当前页面进行下一步,就会根据参数提交到服务器查询余票信息,具体的提交服务展示一下,看看如果您抓取到了数据包您会是如何请求呢,
1 public static bool Login(string name, string pass, string verifyCode, out string error) 2 { 3 error = string.Empty; 4 IPEndPoint endpoint = GetHost(); 5 RequestArgs request=new RequestArgs() 6 { 7 Url = "/otsweb/loginAction.do?method=login", 8 Host="dynamic.12306.cn", 9 Accept="text/*",10 Referer="https://dynamic.12306.cn/otsweb/loginAction.do?method=init",11 Cookie = m_Cookies,12 Body = string.Format("loginUser.user_name={0}&nameErrorFocus=&user.password={1}&passwordErrorFocus=&randCode={2}&randErrorFocus=",13 name,pass,verifyCode)14 };15 m_Response=HttpHelper.Post(endpoint,request,m_X509);16 if (m_Response != null)17 {18 if (m_Response.Header.StartsWith("HTTP/1.1 200"))19 {20 if (m_Response.Body != null)21 {22 string strHtml = Encoding.UTF8.GetString(m_Response.Body);23 if (strHtml.Contains("我的信息") && strHtml.Contains("用户注销"))24 {25 return true;26 }27 else if (strHtml.Contains("当前访问用户过多,请稍后重试!"))28 {29 error = "当前访问用户过多,请稍后重试!";30 }31 else if (strHtml.Contains("请输入正确的验证码!</span>"))32 {33 error = "请输入正确的验证码!";34 }35 else36 {37 error = "各种登录失败,兄弟,理解万岁吧!";38 }39 }40 }41 else42 {43 error = "HTTP响应错误:"+Utility.SplitString(m_Response.Header,44 "HTTP/1.1 ",45 " ");46 } 47 }48 else49 {50 error = "超时,主机没有反映!";51 }52 return false;53 }54 55 static void AdjustCookies()56 {57 m_Cookies = HttpHelper.ParseCookies(m_Response.Header);58 }59 60 public static bool GetTickets(out string strHtml)61 {62 strHtml = string.Empty;63 IPEndPoint endpoint = GetHost();64 RequestArgs request=new RequestArgs()65 {66 Url = string.Format("/otsweb/order/querySingleAction.do?{0}",67 Global.MyTicketArgs.ToString()),68 Host="dynamic.12306.cn",69 Accept="text/*",70 Referer = "https://dynamic.12306.cn/otsweb/order/querySingleAction.do?method=init",71 Cookie = m_Cookies,72 };73 m_Response=HttpHelper.Get(endpoint,request,m_X509);74 if (m_Response != null)75 {76 if (m_Response.Header.StartsWith("HTTP/1.1 200"))77 {78 if (m_Response.Body != null)79 {80 strHtml = Encoding.UTF8.GetString(m_Response.Body);81 return true;82 }83 }84 }85 return false;86 }
忘记补充一点,购票信息我是存放在全局中了,服务是静态服务;从上面2个服务中您也能够看出对上篇文章中HttpHelper的使用,当时文章中的这个辅助类并不完善,有些BUG存在,目前我也只是简单修复了下BUG,有需要的还是需要自己去试试调试修改哦!
OK ,订票参数提交后,就是订票信息板的展示,这里只展示有余票信息的列车,抓包发现查询参数提交后服务器返回如下类型数据:
1 0,<span id='id_240000T1090C' class='base_txtdiv' οnmοuseοver=javascript:onStopHover('240000T1090C#BJP#SHH') οnmοuseοut='onStopOut()'>T109</span>,<img src='/otsweb/images/tips/first.gif'> 北京 <br> 19:28,<img src='/otsweb/images/tips/last.gif'> 上海 <br> 10:25,14:57,--,--,--,--,9,4,5,--,<font color='darkgray'>无</font>,<font color='darkgray'>无</font>,--,<input type='button' class='yuding_u' οnmοusemοve=this.className='yuding_u_over' οnmοusedοwn=this.className='yuding_u_down' οnmοuseοut=this.className='yuding_u' οnclick=javascript:getSelected('T109#14:57#19:28#240000T1090C#BJP#SHH#10:25#北京#上海#10179000003031700005404990000460921000091017903000') value='预订'></input>\n2 1,<span id='id_240000D31100' class='base_txtdiv' οnmοuseοver=javascript:onStopHover('240000D31100#BJP#SHH') οnmοuseοut='onStopOut()'>D311</span>,<img src='/otsweb/images/tips/first.gif'> 北京 <br> 20:52,<img src='/otsweb/images/tips/last.gif'> 上海 <br> 08:40,11:48,--,--,--,<font color='darkgray'>无</font>,--,<font color='#008800'>有</font>,--,--,--,--,--,<input type='button' class='yuding_u' οnmοusemοve=this.className='yuding_u_over' οnmοusedοwn=this.className='yuding_u_down' οnmοuseοut=this.className='yuding_u' οnclick=javascript:getSelected('D311#11:48#20:52#240000D31100#BJP#SHH#08:40#北京#上海#4069800047O031100000') value='预订'></input>\n3 2,<span id='id_240000D32110' class='base_txtdiv' οnmοuseοver=javascript:onStopHover('240000D32110#BJP#SHH') οnmοuseοut='onStopOut()'>D321</span>,<img src='/otsweb/images/tips/first.gif'> 北京 <br> 20:58,<img src='/otsweb/images/tips/last.gif'> 上海 <br> 08:46,11:48,--,--,--,<font color='#008800'>有</font>,--,<font color='#008800'>有</font>,--,--,--,--,--,<input type='button' class='yuding_u' οnmοusemοve=this.className='yuding_u_over' οnmοusedοwn=this.className='yuding_u_down' οnmοuseοut=this.className='yuding_u' οnclick=javascript:getSelected('D321#11:48#20:58#240000D32110#BJP#SHH#08:46#北京#上海#4069800096O031100046') value='预订'></input>\n4 3,<span id='id_240000D31310' class='base_txtdiv' οnmοuseοver=javascript:onStopHover('240000D31310#VNP#SHH') οnmοuseοut='onStopOut()'>D313</span>,<img src='/otsweb/images/tips/first.gif'> 北京南 <br> 21:11,<img src='/otsweb/images/tips/last.gif'> 上海 <br> 08:52,11:41,--,--,--,<font color='darkgray'>无</font>,16,<font color='#008800'>有</font>,--,--,--,--,--,<input type='button' class='yuding_u' οnmοusemοve=this.className='yuding_u_over' οnmοusedοwn=this.className='yuding_u_down' οnmοuseοut=this.className='yuding_u' οnclick=javascript:getSelected('D313#11:41#21:11#240000D31310#VNP#SHH#08:52#北京南#上海#40698002416139200016O031100000') value='预订'></input>\n5 4,<span id='id_330000T28407' class='base_txtdiv' οnmοuseοver=javascript:onStopHover('330000T28407#BXP#SNH') οnmοuseοut='onStopOut()'>T281</span>, 北京西 <br> 22:30, 上海南 <br> 13:24,14:54,--,--,--,--,--,<font color='darkgray'>无</font>,<font color='darkgray'>无</font>,--,<font color='darkgray'>无</font>,<font color='darkgray'>无</font>,--,<input type='button' class='yuding_x' value='预订'></input>
对于这种Html格式数据的分析,有过这方面的经验的同学可能会想到HtmlAgilityPack ,没错,这是一款非常不错并且强大的分析组件,如果您还没用过,不妨去codeplex上看看哦!
有了HtmlAgilityPack,那上面的数据分析就很简单啦:我们看下信息板View的ViewModel中的相关代码就很清楚了:
1 void Refresh() 2 { 3 this.Async(new Func<object>(() => 4 { 5 List<Ticket> result = new List<Ticket>(); 6 string strHtml; 7 bool geted = BuyTicketService.GetTickets(out strHtml); 8 if (geted) 9 {10 //通过strHtml解析出Ticket11 string[] sArry = Regex.Split(strHtml, @"\\n");12 foreach (var strLine in sArry)13 {14 HtmlDocument document = new HtmlDocument();15 document.LoadHtml(strLine);16 var node = document.DocumentNode.SelectSingleNode("//input[@type='button']");17 if (node != null)18 {19 var attribute = node.Attributes["onclick"];20 if (attribute != null)21 {22 string temp = Utility.SplitString(attribute.Value,23 "'",24 "'");25 sArry = Regex.Split(temp, "#");26 27 result.Add(new Ticket(sArry[0],28 string.Format("{0}/{1}",sArry[7],sArry[2]),29 string.Format("{0}/{1}",sArry[8],sArry[6]),30 temp));31 }32 }33 }34 }35 return result;36 }), new Action<object>(args =>37 {38 List<Ticket> tickets = args as List<Ticket>;39 if (tickets != null)40 {41 m_Tickets = tickets;42 this.NotifyPropertyChanged(m => m.Tickets);43 }44 }));45 }
当然,这个页面的数据请求显示也必须要异步哦, 而且最好向我做的这样,有个定时器间隔时间刷新,咱就算买不到票,咱看看这票源信息也是个满足么,呵呵!
开玩笑了,主要哥哥我今年不回家,要去丈母娘加过年咯,所以俺也不担心买票问题, 各位回家的朋友还是要上点心哦,
我写这文章 放出程序不是为了给您回家买票,俺想 您回家不需要了,回来总还要买票吧,看看文章,学点东西,自己再去完善下,你也就没白当程序员哦!
最后一点,点击预定后的界面、:
做到这里我就没做下去了,一来我没什么时间,都是早上早点去公司挤点,晚上下班搞搞,上个周末2天还把U盘丢公司浪费了2天。
做到这里也主要是破12306太卡, 抓不到最后的数据,我也就不着急去完善了, 在前面2页添加了个“跳转至浏览器” 通过浏览器打开登录成功的页面解燃眉之需!
最后附上完整项目:有时间有兴趣的可继续完善,我有时间也会写完,也算是个做个这东西的一个交代; 最好希望咱天朝火车有朝一日不用抢票!
代码:
/Files/cxwx/BuyTicket.zip 可能引用的组件有点大,我只包含了项目,用到的组件都是nuget来的,缺少第三方组件的自己nuget下
最后补充下:SimpleMVVM这框架Command上有个BUG,它NND
转载于:https://www.cnblogs.com/cxwx/archive/2012/01/10/2318507.html
Socket模拟HTTP协议之火车票购票软件相关推荐
- my12306 火车票购票软件使用方法
开发该软件的目的源于用IE抢票的时候,经常出现IE卡死或者崩溃现象,联想到自己也是搞开发的,于是就用VC开发这么个小软件,原理基本上完全模仿IE购票过程,只是去掉了广告和小图片等资源的下载,相对比IE ...
- C# socket 解析http 协议
c# 通过socket模拟HTTP协议,解析HTTP包头 2007-03-10 11:48 找到很好的文章,可以应用到我正在开发的彩信MM7协议中 C#C#C#C#C#C#C#C#C#C#C#C#C# ...
- 12306火车票最新购票软件
12306火车票购票 V1.02 1.车票预订功能,只需设定好订票信息,程序将间隔自动监测是否有票, 全自动预订,自动排队,直到订票成功. 2.飞信短信,声音等多种方式提醒功能,再不需一直坐在电脑前干 ...
- day34 异常处理、断言、socket之ftp协议
Python之路,Day20 = 异常处理.断言.socket之ftp协议 参考博客:http://www.cnblogs.com/metianzing/articles/7148191.html 1 ...
- Java socket调用Http协议Get请求
2019独角兽企业重金招聘Python工程师标准>>> HTTP是基于Socket之上的协议.HTTP Get是从服务器上获取数据. 服务地址: http://127.0.0.1:8 ...
- php socket 用户名密码,PHP用socket模拟post之fsocketopen
地听说这个功能需求的时候,立即就蒙了,不骗大家!首先PHP的socket几乎都没使用过,其次没有听说过post还能用PHP的socket来模拟的呢!后来找了个案例看了看,才明白其实没有那么高深,只不过 ...
- 2022年元旦火车票开售时间公布,元旦火车票购票用便签提醒
2022年的元旦马上就要到来了,很多人会选择趁着元旦假期回家或外出游玩,如果选择的出行方式是乘坐火车或高铁,这时候就要提前进行元旦火车票购票了,以免到时候抢不到票,而影响出行计划.那么2022年元旦火 ...
- 网络编程 socket模块 tcp协议 udp协议
网络基础相关知识 , socket模块 , tcp协议通信代码 , tcp和udp编码流程 网络基础相关知识(1) 架构 C / S 架构: client 客户端 和 server 服务器端 优势:能 ...
- Http、Socket和WebService协议之间的区别
1 数据传输方式 1.1 socket传输的定义和其特点 所谓socket通常也称作"套接字",实现服务器和客户端之间的物理连接,并进行数据传输,主要有udp和tcp两个协 ...
最新文章
- 上海大学建了一个“突发事件语料库”,包括地震、恐怖袭击等5大类
- MySQL查看、创建和删除索引的方法
- pytorch中arange()函数用法
- PostgreSQL 10.1 手册_部分 III. 服务器管理_第 30 章 可靠性和预写式日志_30.5. WAL内部...
- idea2021如何开启RunDashboard
- CTF Geek Challenge——第十一届极客大挑战Crypto Write Up
- mysql连接报错:The server time zone value ‘Öйú±ê׼ʱ¼ä‘ is unrecognized or represents more than one time
- P4345-[SHOI2015]超能粒子炮·改【Lucas定理,类欧】
- 如何使用Python的Flask和Google App Engine构建网络应用
- springmvc05 传值
- 大型基金电商多点开花 中小公司望洋兴叹
- 15 个提高 Google 搜索效率的小技巧
- JAVA山地车deca_越野怪兽 JAVA摆渡人 (ANIMA)山地车
- 软件工程实践寒假作业
- 通俗解释Docker是什么
- java抽象类存在的意义
- 截图热键冲突:关闭360截图功能建议使用QQ截图
- 聊聊电商交易平台的架构设计(干货)
- mysql查询学生表的总人数_mysql数据库查询练习
- java quartz 是干什么的_Quartz可以用来做什么