﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-elva-随笔分类-网络编程</title><link>http://www.cppblog.com/elva/category/8427.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 29 Nov 2013 05:53:30 GMT</lastBuildDate><pubDate>Fri, 29 Nov 2013 05:53:30 GMT</pubDate><ttl>60</ttl><item><title>5imx2s</title><link>http://www.cppblog.com/elva/archive/2013/11/29/204513.html</link><dc:creator>叶子</dc:creator><author>叶子</author><pubDate>Fri, 29 Nov 2013 05:34:00 GMT</pubDate><guid>http://www.cppblog.com/elva/archive/2013/11/29/204513.html</guid><wfw:comment>http://www.cppblog.com/elva/comments/204513.html</wfw:comment><comments>http://www.cppblog.com/elva/archive/2013/11/29/204513.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/elva/comments/commentRss/204513.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/elva/services/trackbacks/204513.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 让收东西更容易，这是一种进步。<br /><br /><br /><img alt="" src="http://www.cppblog.com/images/cppblog_com/elva/5imx2s/1.png" height="557" width="522" /><br /><br /><img alt="" src="http://www.cppblog.com/images/cppblog_com/elva/5imx2s/2.png" height="356" width="546" /><br /><br /><div><img alt="" src="http://www.cppblog.com/images/cppblog_com/elva/5imx2s/3.png" height="356" width="602" /><br /><br /><br /><br />下载：<div>http://www.easyice.cn/5imx2s/download.htm</div></div><img src ="http://www.cppblog.com/elva/aggbug/204513.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/elva/" target="_blank">叶子</a> 2013-11-29 13:34 <a href="http://www.cppblog.com/elva/archive/2013/11/29/204513.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Socket I/O模型全接触</title><link>http://www.cppblog.com/elva/archive/2010/10/29/131764.html</link><dc:creator>叶子</dc:creator><author>叶子</author><pubDate>Fri, 29 Oct 2010 09:12:00 GMT</pubDate><guid>http://www.cppblog.com/elva/archive/2010/10/29/131764.html</guid><wfw:comment>http://www.cppblog.com/elva/comments/131764.html</wfw:comment><comments>http://www.cppblog.com/elva/archive/2010/10/29/131764.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/elva/comments/commentRss/131764.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/elva/services/trackbacks/131764.html</trackback:ping><description><![CDATA[<div id=app-share-content>
<p>本文简单介绍了当前Windows支持的各种Socket I/O模型，如果你发现其中存在什么错误请务必赐教。</p>
<p>&nbsp;&nbsp;&nbsp; 一：select模型<br>&nbsp;&nbsp;&nbsp; 二：WSAAsyncSelect模型<br>&nbsp;&nbsp;&nbsp; 三：WSAEventSelect模型<br>&nbsp;&nbsp;&nbsp; 四：Overlapped I/O 事件通知模型<br>&nbsp;&nbsp;&nbsp; 五：Overlapped I/O 完成例程模型<br>&nbsp;&nbsp;&nbsp; 六：IOCP模型</p>
<p>&nbsp;&nbsp;&nbsp; 老陈有一个在外地工作的女儿，不能经常回来，老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。<br>&nbsp;&nbsp;&nbsp; 这和Socket模型非常类似。下面我就以老陈接收信件为例讲解Socket I/O模型~~~</p>
<p>一：select模型</p>
<p>老陈非常想看到女儿的信。以至于他每隔10分钟就下楼检查信箱，看是否有女儿的信~~~~~<br>在这种情况下，&#8220;下楼检查信箱&#8221;然后回到楼上耽误了老陈太多的时间，以至于老陈无法做其他工作。<br>select模型和老陈的这种情况非常相似：周而复始地去检查......如果有数据......接收/发送.......</p>
<p>使用线程来select应该是通用的做法：</p>
<p><br>procedure TListenThread.Execute;<br>var<br>addr&nbsp;&nbsp;&nbsp;&nbsp; : TSockAddrIn;<br>fd_read : TFDSet;<br>timeout : TTimeVal;<br>ASock,<br>MainSock : TSocket;<br>len, i&nbsp;&nbsp; : Integer;<br>begin<br>MainSock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );<br>addr.sin_family := AF_INET;<br>addr.sin_port := htons(5678);<br>addr.sin_addr.S_addr := htonl(INADDR_ANY);<br>bind( MainSock, @addr, sizeof(addr) );<br>listen( MainSock, 5 );</p>
<p>while (not Terminated) do<br>begin<br>&nbsp;&nbsp;&nbsp; FD_ZERO( fd_read );<br>&nbsp;&nbsp;&nbsp; FD_SET( MainSock, fd_read );<br>&nbsp;&nbsp;&nbsp; timeout.tv_sec := 0;<br>&nbsp;&nbsp;&nbsp; timeout.tv_usec := 500;<br>&nbsp;&nbsp;&nbsp; if select( 0, @fd_read, nil, nil, @timeout ) &gt; 0 then {//至少有1个等待Accept的connection}<br>&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if FD_ISSET( MainSock, fd_read ) then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for i:=0 to fd_read.fd_count-1 do {//注意，fd_count &lt;= 64，也就是说select只能同时管理最多64个连接}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; len := sizeof(addr);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ASock := accept( MainSock, addr, len );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ASock &lt;&gt; INVALID_SOCKET then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .{//为ASock创建一个新的线程，在新的线程中再不停地select}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end; <br>&nbsp;&nbsp;&nbsp; end; <br>end; {//while (not self.Terminated)}</p>
<p>shutdown( MainSock, SD_BOTH );<br>closesocket( MainSock );<br>end;</p>
<p>&#160;</p>
<p>二：WSAAsyncSelect模型</p>
<p>后来，老陈使用了微软公司的新式信箱。这种信箱非常先进，一旦信箱里有新的信件，盖茨就会给老陈打电话：喂，大爷，你有新的信件了！从此，老陈再也不必频繁上下楼检查信箱了，牙也不疼了，你瞅准了，蓝天......不是，微软~~~~~~~~<br>微软提供的WSAAsyncSelect模型就是这个意思。</p>
<p>WSAAsyncSelect模型是Windows下最简单易用的一种Socket I/O模型。使用这种模型时，Windows会把网络事件以消息的形势通知应用程序。<br>首先定义一个消息标示常量：<br>const WM_SOCKET = WM_USER + 55;<br>再在主Form的private域添加一个处理此消息的函数声明：</p>
<p><br>private<br>procedure WMSocket(var Msg: TMessage); message WM_SOCKET;<br>{然后就可以使用WSAAsyncSelect了：}</p>
<p><br>Code<br>var<br>addr : TSockAddr;<br>sock : TSocket;</p>
<p>sock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );<br>addr.sin_family := AF_INET;<br>addr.sin_port := htons(5678);<br>addr.sin_addr.S_addr := htonl(INADDR_ANY);<br>bind( m_sock, @addr, sizeof(SOCKADDR) );</p>
<p>WSAAsyncSelect( m_sock, Handle, WM_SOCKET, FD_ACCEPT or FD_CLOSE );</p>
<p>listen( m_sock, 5 );<br>&nbsp;&nbsp; <br>应用程序可以对收到WM_SOCKET消息进行分析，判断是哪一个socket产生了网络事件以及事件类型：</p>
<p><br>procedure TfmMain.WMSocket(var Msg: TMessage);<br>var<br>sock&nbsp;&nbsp;&nbsp; : TSocket;<br>addr&nbsp;&nbsp;&nbsp; : TSockAddrIn;<br>addrlen : Integer;<br>buf&nbsp;&nbsp;&nbsp;&nbsp; : Array [0..4095] of Char;<br>begin<br>{//Msg的WParam是产生了网络事件的socket句柄，LParam则包含了事件类型}<br>case WSAGetSelectEvent( Msg.LParam ) of<br>&nbsp;&nbsp;&nbsp; FD_ACCEPT :<br>&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; addrlen := sizeof(addr);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sock := accept( Msg.WParam, addr, addrlen );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if sock &lt;&gt; INVALID_SOCKET then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSAAsyncSelect( sock, Handle, WM_SOCKET, FD_READ or FD_WRITE or FD_CLOSE );<br>&nbsp;&nbsp;&nbsp; end;</p>
<p>&nbsp;&nbsp;&nbsp; FD_CLOSE : closesocket( Msg.WParam );<br>&nbsp;&nbsp;&nbsp; FD_READ : recv( Msg.WParam, buf[0], 4096, 0 );<br>&nbsp;&nbsp;&nbsp; FD_WRITE : ;<br>end; <br>end;</p>
<p>三：WSAEventSelect模型</p>
<p>后来，微软的信箱非常畅销，购买微软信箱的人以百万计数......以至于盖茨每天24小时给客户打电话，累得腰酸背痛，喝蚁力神都不好使~~~~~~<br>微软改进了他们的信箱：在客户的家中添加一个附加装置，这个装置会监视客户的信箱，每当新的信件来临，此装置会发出&#8220;新信件到达&#8221;声，提醒老陈去收信。盖茨终于可以睡觉了。</p>
<p>同样要使用线程：</p>
<p><br>procedure TListenThread.Execute;<br>var<br>hEvent : WSAEvent;<br>ret&nbsp;&nbsp;&nbsp; : Integer;<br>ne&nbsp;&nbsp;&nbsp;&nbsp; : TWSANetworkEvents;<br>sock&nbsp;&nbsp; : TSocket;<br>adr&nbsp;&nbsp;&nbsp; : TSockAddrIn;<br>sMsg&nbsp;&nbsp; : String;<br>Index,<br>EventTotal : DWORD;<br>EventArray : Array [0..WSA_MAXIMUM_WAIT_EVENTS-1] of WSAEVENT;<br>begin<br>&nbsp;&nbsp; socket bind <br>hEvent := WSACreateEvent();<br>WSAEventSelect( ListenSock, hEvent, FD_ACCEPT or FD_CLOSE );<br>&nbsp;&nbsp; listen</p>
<p>while ( not Terminated ) do<br>begin<br>&nbsp;&nbsp;&nbsp; Index := WSAWaitForMultipleEvents( EventTotal, @EventArray[0], FALSE, WSA_INFINITE, FALSE );<br>&nbsp;&nbsp;&nbsp; FillChar( ne, sizeof(ne), 0 );<br>&nbsp;&nbsp;&nbsp; WSAEnumNetworkEvents( SockArray[Index-WSA_WAIT_EVENT_0], EventArray[Index-WSA_WAIT_EVENT_0], @ne );</p>
<p>&nbsp;&nbsp;&nbsp; if ( ne.lNetworkEvents and FD_ACCEPT ) &gt; 0 then<br>&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ne.iErrorCode[FD_ACCEPT_BIT] &lt;&gt; 0 then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret := sizeof(adr);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sock := accept( SockArray[Index-WSA_WAIT_EVENT_0], adr, ret );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if EventTotal &gt; WSA_MAXIMUM_WAIT_EVENTS-1 then{//这里WSA_MAXIMUM_WAIT_EVENTS同样是64}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closesocket( sock );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hEvent := WSACreateEvent();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSAEventSelect( sock, hEvent, FD_READ or FD_WRITE or FD_CLOSE );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SockArray[EventTotal] := sock;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EventArray[EventTotal] := hEvent;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Inc( EventTotal );<br>&nbsp;&nbsp;&nbsp; end;</p>
<p>&nbsp;&nbsp;&nbsp; if ( ne.lNetworkEvents and FD_READ ) &gt; 0 then<br>&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ne.iErrorCode[FD_READ_BIT] &lt;&gt; 0 then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FillChar( RecvBuf[0], PACK_SIZE_RECEIVE, 0 );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret := recv( SockArray[Index-WSA_WAIT_EVENT_0], RecvBuf[0], PACK_SIZE_RECEIVE, 0 );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; end;<br>end;<br>end;</p>
<p>四：Overlapped I/O 事件通知模型</p>
<p>后来，微软通过调查发现，老陈不喜欢上下楼收发信件，因为上下楼其实很浪费时间。于是微软再次改进他们的信箱。新式的信箱采用了更为先进的技术，只要用户告诉微软自己的家在几楼几号，新式信箱会把信件直接传送到用户的家中，然后告诉用户，你的信件已经放到你的家中了！老陈很高兴，因为他不必再亲自收发信件了！</p>
<p>Overlapped I/O 事件通知模型和WSAEventSelect模型在实现上非常相似，主要区别在&#8220;Overlapped&#8221;，Overlapped模型是让应用程序使用重叠数据结构(WSAOVERLAPPED)，一次投递一个或多个Winsock I/O请求。这些提交的请求完成后，应用程序会收到通知。什么意思呢？就是说，如果你想从socket上接收数据，只需要告诉系统，由系统为你接收数据，而你需要做的只是为系统提供一个缓冲区~~~~~<br>Listen线程和WSAEventSelect模型一模一样，Recv/Send线程则完全不同：</p>
<p><br>Code<br>procedure TOverlapThread.Execute;<br>var<br>dwTemp : DWORD;<br>ret&nbsp;&nbsp;&nbsp; : Integer;<br>Index : DWORD;<br>begin<br></p>
<p>while ( not Terminated ) do<br>begin<br>&nbsp;&nbsp;&nbsp; Index := WSAWaitForMultipleEvents( FLinks.Count, @FLinks.Events[0], FALSE, RECV_TIME_OUT, FALSE );<br>&nbsp;&nbsp;&nbsp; Dec( Index, WSA_WAIT_EVENT_0 );<br>&nbsp;&nbsp;&nbsp; if Index &gt; WSA_MAXIMUM_WAIT_EVENTS-1 then {//超时或者其他错误}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;</p>
<p>&nbsp;&nbsp;&nbsp; WSAResetEvent( FLinks.Events[Index] );<br>&nbsp;&nbsp;&nbsp; WSAGetOverlappedResult( FLinks.Sockets[Index], FLinks.pOverlaps[Index], @dwTemp, FALSE, FLinks.pdwFlags[Index]^ );</p>
<p>&nbsp;&nbsp;&nbsp; if dwTemp = 0 then {//连接已经关闭}<br>&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp; end else<br>&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmMain.ListBox1.Items.Add( FLinks.pBufs[Index]^.buf );<br>&nbsp;&nbsp;&nbsp; end;</p>
<p>&nbsp;&nbsp;&nbsp; {//初始化缓冲区}<br>&nbsp;&nbsp;&nbsp; FLinks.pdwFlags[Index]^ := 0;<br>&nbsp;&nbsp;&nbsp; FillChar( FLinks.pOverlaps[Index]^, sizeof(WSAOVERLAPPED), 0 );<br>&nbsp;&nbsp;&nbsp; FLinks.pOverlaps[Index]^.hEvent := FLinks.Events[Index];<br>&nbsp;&nbsp;&nbsp; FillChar( FLinks.pBufs[Index]^.buf^, BUFFER_SIZE, 0 );</p>
<p>&nbsp;&nbsp;&nbsp; {//递一个接收数据请求}<br>&nbsp;&nbsp;&nbsp; WSARecv( FLinks.Sockets[Index], FLinks.pBufs[Index], 1, FLinks.pdwRecvd[Index]^, FLinks.pdwFlags[Index]^, FLinks.pOverlaps[Index], nil );<br>end;<br>end;</p>
<p>五：Overlapped I/O 完成例程模型</p>
<p>老陈接收到新的信件后，一般的程序是：打开信封----掏出信纸----阅读信件----回复信件......为了进一步减轻用户负担，微软又开发了一种新的技术：用户只要告诉微软对信件的操作步骤，微软信箱将按照这些步骤去处理信件，不再需要用户亲自拆信/阅读/回复了！老陈终于过上了小资生活！</p>
<p>Overlapped I/O 完成例程要求用户提供一个回调函数，发生新的网络事件的时候系统将执行这个函数：</p>
<p><br>procedure WorkerRoutine( const dwError, cbTransferred : DWORD; const<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpOverlapped : LPWSAOVERLAPPED; const dwFlags : DWORD ); stdcall;</p>
<p>然后告诉系统用WorkerRoutine函数处理接收到的数据：<br>WSARecv( m_socket, @FBuf, 1, dwTemp, dwFlag, @m_overlap, WorkerRoutine );<br>然后......没有什么然后了，系统什么都给你做了！微软真实体贴！</p>
<p><br>Code<br>while ( not Terminated ) do{//这就是一个Recv/Send线程要做的事情什么都不用做啊！！！}<br>begin<br>if SleepEx( RECV_TIME_OUT, True ) = WAIT_IO_COMPLETION then {//}<br>begin<br>&nbsp;&nbsp;&nbsp; ;<br>end else<br>begin<br>&nbsp;&nbsp;&nbsp; continue;<br>end;<br>end;</p>
<p>&#160;</p>
<p>六：IOCP模型</p>
<p>微软信箱似乎很完美，老陈也很满意。但是在一些大公司情况却完全不同！这些大公司有数以万计的信箱，每秒钟都有数以百计的信件需要处理，以至于微软信箱经常因超负荷运转而崩溃！需要重新启动！微软不得不使出杀手锏......<br>微软给每个大公司派了一名名叫&#8220;Completion Port&#8221;的超级机器人，让这个机器人去处理那些信件！</p>
<p>&#8220;Windows NT小组注意到这些应用程序的性能没有预料的那么高。特别的，处理很多同时的客户请求意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的[没有被挂起和等待发生什么事]，Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文[Context]，线程就没有得到很多CPU时间来做它们的工作。大家可能也都感觉到并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程。创建线程比起创建进程开销要小，但也远不是没有开销的。我们不妨设想一下：如果事先开好N个线程，让它们在那hold[堵塞]，然后可以将所有用户的请求都投递到一个消息队列中去。然后那N个线程逐一从消息队列中去取出消息并加以处理。就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源，也提高了线程的利用率。理论上很不错，你想我等泛泛之辈都能想出来的问题，Microsoft又怎会没有考虑到呢?&#8221;-----摘自nonocast的《理解I/O Completion Port》</p>
<p>先看一下IOCP模型的实现：</p>
<p><br>Code<br>{创建一个完成端口}<br>FCompletPort := CreateIoCompletionPort( INVALID_HANDLE_VALUE, 0,0,0 );</p>
<p>{接受远程连接，并把这个连接的socket句柄绑定到刚才创建的IOCP上}<br>AConnect := accept( FListenSock, addr, len);<br>CreateIoCompletionPort( AConnect, FCompletPort, nil, 0 );</p>
<p>{创建CPU数*2 + 2个线程}<br>for i:=1 to si.dwNumberOfProcessors*2+2 do<br>begin<br>AThread := TRecvSendThread.Create( false );<br>AThread.CompletPort := FCompletPort;{//告诉这个线程，你要去这个IOCP去访问数据}<br>end;</p>
<p>OK，就这么简单，我们要做的就是建立一个IOCP，把远程连接的socket句柄绑定到刚才创建的IOCP上，最后创建n个线程，并告诉这n个线程到这个IOCP上去访问数据就可以了。</p>
<p>再看一下TRecvSendThread线程都干些什么：</p>
<p><br>Code<br>procedure TRecvSendThread.Execute;<br>var<br><br>begin<br>while (not self.Terminated) do<br>begin<br>&nbsp;&nbsp;&nbsp; {查询IOCP状态（数据读写操作是否完成）}<br>&nbsp;&nbsp;&nbsp; GetQueuedCompletionStatus( CompletPort, BytesTransd, CompletKey, POVERLAPPED(pPerIoDat), TIME_OUT );</p>
<p>&nbsp;&nbsp;&nbsp; if BytesTransd &lt;&gt; 0 then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .;{//数据读写操作完成}</p>
<p>&nbsp;&nbsp;&nbsp; {//再投递一个读数据请求}<br>&nbsp;&nbsp;&nbsp; WSARecv( CompletKey, @(pPerIoDat^.BufData), 1, BytesRecv, Flags, @(pPerIoDat^.Overlap), nil );<br>end;<br>end;</p>
<p>读写线程只是简单地检查IOCP是否完成了我们投递的读写操作，如果完成了则再投递一个新的读写请求。<br>应该注意到，我们创建的所有TRecvSendThread都在访问同一个IOCP（因为我们只创建了一个IOCP），并且我们没有使用临界区！难道不会产生冲突吗？不用考虑同步问题吗？<br>呵呵，这正是IOCP的奥妙所在。IOCP不是一个普通的对象，不需要考虑线程安全问题。它会自动调配访问它的线程：如果某个socket上有一个线程A正在访问，那么线程B的访问请求会被分配到另外一个socket。这一切都是由系统自动调配的，我们无需过问。</p>
<p>呵呵，终于写完了，好累......以上所有的源代码可以从这里看到：<br><a href="http://community.csdn.net/Expert/topic/3844/3844679.xml?temp=5.836123E-02"><u><font color=#0066cc>http://community.csdn.net/Expert/topic/3844/3844679.xml?temp=5.836123E-02</font></u></a><br>不过代码写的很简陋，请多包涵！</p>
<p>转自：<a href="http://blog.csdn.net/flyinwuhan/"><u><font color=#0066cc>http://blog.csdn.net/flyinwuhan/</font></u></a></p>
<p><u><font color=#0066cc></font></u></p>
<p>声明：除CSDN外的任何媒体转载必须注明作者以及&#8220;转载自CSDN&#8221;。</p>
<p>&#160;</p>
</div>
<img src ="http://www.cppblog.com/elva/aggbug/131764.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/elva/" target="_blank">叶子</a> 2010-10-29 17:12 <a href="http://www.cppblog.com/elva/archive/2010/10/29/131764.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>wincap中文版翻译2&lt;转&gt;</title><link>http://www.cppblog.com/elva/archive/2008/10/07/63368.html</link><dc:creator>叶子</dc:creator><author>叶子</author><pubDate>Tue, 07 Oct 2008 02:11:00 GMT</pubDate><guid>http://www.cppblog.com/elva/archive/2008/10/07/63368.html</guid><wfw:comment>http://www.cppblog.com/elva/comments/63368.html</wfw:comment><comments>http://www.cppblog.com/elva/archive/2008/10/07/63368.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/elva/comments/commentRss/63368.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/elva/services/trackbacks/63368.html</trackback:ping><description><![CDATA[<p>不用loopback捕获数据报 <br>这节的例子很象先前的一章（获得网卡的高级信息）但是这一节中是用pcap_next_ex()来代替pcap_loop()来捕获数据包。基于回调包捕获机制</p>
<p>的pcap_loop()在某些情况下是不错的选择。但是在一些情况下处理回调并不特别好：这会使程序变的复杂并且在象多线程或C++类这些情况下</p>
<p>它看起来到象一块绊脚石。</p>
<p>在这些情况下pcap_next_ex()允许直接调用来接收包，它的参数和pcap_loop()相同：有一个网卡描述副，和两个指针，这两个指针会被初始化</p>
<p>并返回给用户，一个是pcap_pkthdr结构，另一个是接收数据的缓冲区。下面的程序我门将循环调用前一节的例子中的回掉部分，只是把它移到</p>
<p>了main里面了。</p>
<p>程序代码： [ 复制代码到剪贴板 ] <br>#include "pcap.h"</p>
<p><br>main()<br>{<br>pcap_if_t *alldevs;<br>pcap_if_t *d;<br>int inum;<br>int i=0;<br>pcap_t *adhandle;<br>int res;<br>char errbuf[PCAP_ERRBUF_SIZE];<br>struct tm *ltime;<br>char timestr[16];<br>struct pcap_pkthdr *header;<br>u_char *pkt_data;</p>
<p><br>/* Retrieve the device list */<br>if (pcap_findalldevs(&amp;alldevs, errbuf) == -1)<br>{<br>fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);<br>exit(1);<br>}</p>
<p>/* Print the list */<br>for(d=alldevs; d; d=d-&gt;next)<br>{<br>printf("%d. %s", ++i, d-&gt;name);<br>if (d-&gt;description)<br>printf(" (%s)\n", d-&gt;description);<br>else<br>printf(" (No description available)\n");<br>}</p>
<p>if(i==0)<br>{<br>printf("\nNo interfaces found! Make sure WinPcap is installed.\n");<br>return -1;<br>}</p>
<p>printf("Enter the interface number (1-%d):",i);<br>scanf("%d", &amp;inum);</p>
<p>if(inum &lt; 1 || inum &gt; i)<br>{<br>printf("\nInterface number out of range.\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}</p>
<p>/* Jump to the selected adapter */<br>for(d=alldevs, i=0; i&lt; inum-1 ;d=d-&gt;next, i++);</p>
<p>/* Open the adapter */<br>if ( (adhandle= pcap_open_live(d-&gt;name, // name of the device<br>65536, // portion of the packet to capture. <br>// 65536 grants that the whole packet will be captured on all the MACs.<br>1, // promiscuous mode<br>1000, // read timeout<br>errbuf // error buffer<br>) ) == NULL)<br>{<br>fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}</p>
<p>printf("\nlistening on %s...\n", d-&gt;description);</p>
<p>/* At this point, we don&#8216;t need any more the device list. Free it */<br>pcap_freealldevs(alldevs);</p>
<p>/* 此处循环调用 pcap_next_ex来接受数据报*/<br>while((res = pcap_next_ex( adhandle, &amp;header, &amp;pkt_data)) &gt;= 0){</p>
<p>if(res == 0)<br>/* Timeout elapsed */<br>continue;</p>
<p>/* convert the timestamp to readable format */<br>ltime=localtime(&amp;header-&gt;ts.tv_sec);<br>strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);</p>
<p>printf("%s,%.6d len:%d\n", timestr, header-&gt;ts.tv_usec, header-&gt;len);<br>}</p>
<p>if(res == -1){<br>printf("Error reading the packets: %s\n", pcap_geterr(adhandle));<br>return -1;<br>}</p>
<p>return 0;<br>}</p>
<p>注：pcap_next_ex()只在Win32环境下才行因为它不是原始libpcap API中的一部分，这就意味着依赖于这个函数的代码不会在UNIX下工作。那</p>
<p>么为什么我们用pcap_next_ex()而不用pcap_next()？因为pcap_next()有许多限制在很多情况下并不鼓励用它。首先它的效率很低因为它隐藏</p>
<p>了回掉方法并且还依赖于pcap_dispatch()这个函数。再次它不能够识别文件结束标志EOF所以对来自文件的数据流它几乎无能为力。<br>注意当pcap_next_ex()在成功，超时，出错和文件结束的情况下会返回不同的值<br></p>
<p>五）数据流的过滤</p>
<p>WinPcap或libpca最强大的特点之一就是数据流的过滤引擎。它提供一种高效的方法来只捕获网络数据流的某些数据而且常常和系统的捕获机制</p>
<p>相集成。过滤数据的函数是pcap_compile() 和 pcap_setfilter()来实现的。</p>
<p><br>pcap_compile()来编译一个过滤设备，它通过一个高层的boolean型变量和字串产生一系列的能够被底层驱动所解释的二进制编码。boolean表</p>
<p>示语法能够在这个文件的过滤表示语法中找到。</p>
<p>pcap_setfilter() 用来联系一个在内核驱动上过滤的过滤器，这时所有网络数据包都将流经过滤器，并拷贝到应用程序中。</p>
<p>下面的代码展示了如何编译并社定一个过滤设备。注意我们必须从pcap_if结构中获得掩码，因为一些过滤器的创建需要这个参数。</p>
<p>下面的代码段中的pcap_compile()的"ip and tcp"参数说明只有IPV4和TCP数据才会被内核保存并被传递到应用程序。</p>
<p>程序代码： [ 复制代码到剪贴板 ] <br>if(d-&gt;addresses != NULL)<br>/* 获得第一个接口地址的掩码 */<br>netmask=((struct sockaddr_in *)(d-&gt;addresses-&gt;netmask))-&gt;sin_addr.S_un.S_addr;<br>else<br>/* 如果这个接口没有地址那么我们假设他为C类地址 */<br>netmask=0xffffff;</p>
<p><br>//compile the filter<br>if(pcap_compile(adhandle, &amp;fcode, "ip and tcp", 1, netmask) &lt;0 ){<br>fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}</p>
<p>//set the filter<br>if(pcap_setfilter(adhandle, &amp;fcode)&lt;0){<br>fprintf(stderr,"\nError setting the filter.\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}<br></p>
<p>&nbsp;&nbsp;&nbsp;<br>六）解析数据包<br>现在经过上几节的学习能够进行数据报的捕获和过滤了，我们想用一个简单的"real world"程序将我们所学的</p>
<p>知识应用于实际。<br>这一节里我们将利用以前的代码并将其引申从而建立一个更实用的程序。该程序的主要目的是如何显示出所捕</p>
<p>获的数据报的内容，尤其是对它的协议头的分析和说明。这个程序名叫UDPdump它将在屏幕上显示出我们网络上</p>
<p>UDP数据的信息。<br>在此我们选择解析UDP而不用TCP因为他比TCP简单更加的直观明了。下面让我们来看看原代码。</p>
<p>程序代码： [ 复制代码到剪贴板 ] <br>/*<br>* Copyright (c) 1999 - 2002<br>* Politecnico di Torino. All rights reserved.<br>*<br>* Redistribution and use in source and binary forms, with or without<br>* modification, are permitted provided that: (1) source code distributions<br>* retain the above copyright notice and this paragraph in its entirety, (2)<br>* distributions including binary code include the above copyright notice and<br>* this paragraph in its entirety in the documentation or other materials<br>* provided with the distribution, and (3) all advertising materials mentioning<br>* features or use of this software display the following acknowledgement:<br>* ``This product includes software developed by the Politecnico<br>* di Torino, and its contributors.&#8216;&#8216; Neither the name of<br>* the University nor the names of its contributors may be used to endorse<br>* or promote products derived from this software without specific prior<br>* written permission.<br>* THIS SOFTWARE IS PROVIDED ``AS IS&#8216;&#8216; AND WITHOUT ANY EXPRESS OR IMPLIED<br>* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF<br>* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.<br>*/</p>
<p>#include "pcap.h"</p>
<p>/* 4 BIT的IP头定义 */<br>typedef struct ip_address{<br>u_char byte1;<br>u_char byte2;<br>u_char byte3;<br>u_char byte4;<br>}ip_address;</p>
<p>/* IPv4 头的定义 */<br>typedef struct ip_header{<br>u_char ver_ihl; // 4 bit的版本信息 + 4 bits的头长<br>u_char tos; // TOS类型 <br>u_short tlen; // 总长度<br>u_short identification; // Identification<br>u_short flags_fo; // Flags (3 bits) + Fragment offset (13 bits)<br>u_char ttl; // 生存期<br>u_char proto; // 后面的协议信息<br>u_short crc; // 校验和<br>ip_address saddr; // 源IP<br>ip_address daddr; // 目的IP<br>u_int op_pad; // Option + Padding<br>}ip_header;</p>
<p>/* UDP header*/<br>typedef struct udp_header{<br>u_short sport; // Source port<br>u_short dport; // Destination port<br>u_short len; // Datagram length<br>u_short crc; // Checksum<br>}udp_header;</p>
<p>/* 定义处理包的函数 */<br>void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);</p>
<p><br>main()<br>{<br>pcap_if_t *alldevs;<br>pcap_if_t *d;<br>int inum;<br>int i=0;<br>pcap_t *adhandle;<br>char errbuf[PCAP_ERRBUF_SIZE];<br>u_int netmask;<br>char packet_filter[] = "ip and udp";<br>struct bpf_program fcode;</p>
<p>/* Retrieve the device list */<br>if (pcap_findalldevs(&amp;alldevs, errbuf) == -1)<br>{<br>fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);<br>exit(1);<br>}</p>
<p>/* Print the list */<br>for(d=alldevs; d; d=d-&gt;next)<br>{<br>printf("%d. %s", ++i, d-&gt;name);<br>if (d-&gt;description)<br>printf(" (%s)\n", d-&gt;description);<br>else<br>printf(" (No description available)\n");<br>}</p>
<p>if(i==0)<br>{<br>printf("\nNo interfaces found! Make sure WinPcap is installed.\n");<br>return -1;<br>}</p>
<p>printf("Enter the interface number (1-%d):",i);<br>scanf("%d", &amp;inum);</p>
<p>if(inum &lt; 1 || inum &gt; i)<br>{<br>printf("\nInterface number out of range.\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}</p>
<p>/* Jump to the selected adapter */<br>for(d=alldevs, i=0; i&lt; inum-1 ;d=d-&gt;next, i++);</p>
<p>/* Open the adapter */<br>if ( (adhandle= pcap_open_live(d-&gt;name, // name of the device<br>65536, // portion of the packet to capture. <br>// 65536 grants that the whole packet will be captured on</p>
<p>all the MACs.<br>1, // promiscuous mode<br>1000, // read timeout<br>errbuf // error buffer<br>) ) == NULL)<br>{<br>fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}</p>
<p>/* Check the link layer. We support only Ethernet for simplicity. */<br>if(pcap_datalink(adhandle) != DLT_EN10MB)<br>{<br>fprintf(stderr,"\nThis program works only on Ethernet networks.\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}</p>
<p>if(d-&gt;addresses != NULL)<br>/* Retrieve the mask of the first address of the interface */<br>netmask=((struct sockaddr_in *)(d-&gt;addresses-&gt;netmask))-&gt;sin_addr.S_un.S_addr;<br>else<br>/* If the interface is without addresses we suppose to be in a C class network */<br>netmask=0xffffff;</p>
<p><br>//compile the filter<br>if(pcap_compile(adhandle, &amp;fcode, packet_filter, 1, netmask) &lt;0 ){<br>fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}</p>
<p>//set the filter<br>if(pcap_setfilter(adhandle, &amp;fcode)&lt;0){<br>fprintf(stderr,"\nError setting the filter.\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}</p>
<p>printf("\nlistening on %s...\n", d-&gt;description);</p>
<p>/* At this point, we don&#8216;t need any more the device list. Free it */<br>pcap_freealldevs(alldevs);</p>
<p>/* start the capture */<br>pcap_loop(adhandle, 0, packet_handler, NULL);</p>
<p>return 0;<br>}</p>
<p>/* Callback function invoked by libpcap for every incoming packet */<br>void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)<br>{<br>struct tm *ltime;<br>char timestr[16];<br>ip_header *ih;<br>udp_header *uh;<br>u_int ip_len;<br>u_short sport,dport;</p>
<p>/* convert the timestamp to readable format */<br>ltime=localtime(&amp;header-&gt;ts.tv_sec);<br>strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);</p>
<p>/* print timestamp and length of the packet */<br>printf("%s.%.6d len:%d ", timestr, header-&gt;ts.tv_usec, header-&gt;len);</p>
<p>/* 找到IP头的位置 */<br>ih = (ip_header *) (pkt_data +<br>14); //14为以太头的长度</p>
<p>/* 找到UDP的位置 */<br>ip_len = (ih-&gt;ver_ihl &amp; 0xf) * 4;<br>uh = (udp_header *) ((u_char*)ih + ip_len);</p>
<p>/* 将端口信息从网络型转变为主机顺序 */<br>sport = ntohs( uh-&gt;sport );<br>dport = ntohs( uh-&gt;dport );</p>
<p>/* print ip addresses and udp ports */<br>printf("%d.%d.%d.%d.%d -&gt; %d.%d.%d.%d.%d\n",<br>ih-&gt;saddr.byte1,<br>ih-&gt;saddr.byte2,<br>ih-&gt;saddr.byte3,<br>ih-&gt;saddr.byte4,<br>sport,<br>ih-&gt;daddr.byte1,<br>ih-&gt;daddr.byte2,<br>ih-&gt;daddr.byte3,<br>ih-&gt;daddr.byte4,<br>dport);<br>}</p>
<p>&#160;</p>
<p>首先我门设置UDP过滤，用这种方法我们确保packet_handler()只接受到基于IPV4的UDP数据。我们同样定义了</p>
<p>两个数据结构来描述IP 和UDP的头部信息，packet_handler()用这两个结构来定位头部的各种字段。<br>packet_handler()虽然只是限于处理一些UDP数据但却显示了复杂的嗅探器如tcpdump/WinDump的工作原理。<br>首先我们对MAC地址的头部并不感兴趣所以我们跳过它。不过在开始捕获之前我们用pcap_datalink()来检查MAC</p>
<p>层，所以以上的程序只能够工作在Ethernet networks上，再次我们确保MAC头为14 bytes。<br>MAC头之后是IP头，我们从中提取出了目的地址。IP之后是UDP，在确定UDP的位置时有点复杂，因为IP的长度以</p>
<p>为版本的不同而不同，所以我们用头长字段来定位UDP，一旦 我们确定了UDP的起始位置，我们就可以解析出原</p>
<p>和目的端口。<br>下面是我们打印出来的一些结果：</p>
<p>1. {A7FD048A-5D4B-478E-B3C1-34401AC3B72F} (Xircom t 10/100 Adapter) <br>Enter the interface number (1-2):1</p>
<p>listening on Xircom CardBus Ethernet 10/100 Adapter... <br>16:13:15.312784 len:87 130.192.31.67.2682 -&gt; 130.192.3.21.53 <br>16:13:15.314796 len:137 130.192.3.21.53 -&gt; 130.192.31.67.2682 <br>16:13:15.322101 len:78 130.192.31.67.2683 -&gt; 130.192.3.21.53</p>
<p>上面每一行都显示出不同的数据包的内容.<br></p>
<p>&nbsp;&nbsp; <br>（七）处理脱机的堆文件</p>
<p>通过以前的学习我门已经熟悉了从网卡上捕获数据包，现在我门将学习如何处理数据包。WINPCAP为我们提供了很多API来将流经网络的数据包</p>
<p>保存到一个堆文件并读取堆的内容。这一节将讲述如何使用所有的这些API。<br>这种文件的格式很简单，但包含了所捕获的数据报的二进制内容，这种文件格式也是很多网络工具的标准如WinDump, Ethereal 还有 Snort等.</p>
<p><br>关于如何将数据包保存到文件：</p>
<p>首先我们看看如何以LIBPCAP的格式写数据包。<br>下面的例子演示了如何从指定的接口上捕获数据包并将它们存储到一个指定的文件。</p>
<p>程序代码： [ 复制代码到剪贴板 ] <br>#include "pcap.h"</p>
<p>/* 定义处理数据的函数原形 */<br>void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);</p>
<p>main(int argc, char **argv)<br>{<br>pcap_if_t *alldevs;<br>pcap_if_t *d;<br>int inum;<br>int i=0;<br>pcap_t *adhandle;//定义文件句柄<br>char errbuf[PCAP_ERRBUF_SIZE];<br>pcap_dumper_t *dumpfile;</p>
<p>&#160;</p>
<p>/* 检查命令行参数 是否带有文件名*/<br>if(argc != 2){</p>
<p>printf("usage: %s filename", argv[0]);<br>return -1;</p>
<p>}</p>
<p>/* 获得驱动列表 */<br>if (pcap_findalldevs(&amp;alldevs, errbuf) == -1)<br>{<br>fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);<br>exit(1);<br>}</p>
<p>/* 打印 list */<br>for(d=alldevs; d; d=d-&gt;next)<br>{<br>printf("%d. %s", ++i, d-&gt;name);<br>if (d-&gt;description)<br>printf(" (%s)\n", d-&gt;description);<br>else<br>printf(" (No description available)\n");<br>}</p>
<p>if(i==0)<br>{<br>printf("\nNo interfaces found! Make sure WinPcap is installed.\n");<br>return -1;<br>}</p>
<p>printf("Enter the interface number (1-%d):",i);<br>scanf("%d", &amp;inum);</p>
<p>if(inum &lt; 1 || inum &gt; i)<br>{<br>printf("\nInterface number out of range.\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}</p>
<p>/* 跳转到指定的网卡 */<br>for(d=alldevs, i=0; i&lt; inum-1 ;d=d-&gt;next, i++);</p>
<p>/* Open the adapter */<br>if ( (adhandle = pcap_open_live(d-&gt;name, // name of the device<br>65536, // portion of the packet to capture. <br>// 65536 grants that the whole packet will be captured on all the MACs.<br>1, // promiscuous mode<br>1000, // read timeout<br>errbuf // error buffer<br>) ) == NULL)<br>{<br>fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}</p>
<p>/* 打开文件 */<br>dumpfile = pcap_dump_open(adhandle, argv[1]);<br>if(dumpfile==NULL){<br>fprintf(stderr,"\nError opening output file\n");<br>return -1;<br>}</p>
<p>printf("\nlistening on %s...\n", d-&gt;description);</p>
<p>/* At this point, we don&#8216;t need any more the device list. Free it */<br>pcap_freealldevs(alldevs);</p>
<p>/* 循环捕获数据并调用packet_handler函数把数据存储到堆文件 */<br>pcap_loop(adhandle, 0, packet_handler, (unsigned char *)dumpfile);</p>
<p>return 0;<br>}</p>
<p>/* Callback function invoked by libpcap for every incoming packet */</p>
<p>void packet_handler(u_char *dumpfile, const struct pcap_pkthdr *header, const u_char *pkt_data)<br>{<br>/* 此函数功能将数据报存储到堆文件 */<br>pcap_dump(dumpfile, header, pkt_data);<br>}</p>
<p>&#160;</p>
<p>正如你看到的那样该程序的结构非常类似与以前的例子，区别是：<br>一旦打开网卡就调用pcap_dump_open()来打开一个文件，该调用将文件和某个网卡相关联。<br>packet_handler()内部通过调用pcap_dump()来将捕获的数据报存储到文件。pcap_dump()的参数和 packet_handler()一样，所以用起来比较方</p>
<p>便。</p>
<p>从文件读数据包：</p>
<p>下面我们来看如何从文件读取数据内容。下面的代码打开了 一个堆文件并打印了其中的每个包内容。<br>pcap_open_offline()用来打开一个堆文件，之后用pcap_loop()来循环从文件中读取数据。你能发现读取脱机的数据几乎和实时的从网卡上读</p>
<p>取一摸一样。</p>
<p>程序代码： [ 复制代码到剪贴板 ] <br>#include &lt;stdio.h&gt;<br>#include &lt;pcap.h&gt;</p>
<p>#define LINE_LEN 16</p>
<p>void dispatcher_handler(u_char *, const struct pcap_pkthdr *, const u_char *);</p>
<p>main(int argc, char **argv) {</p>
<p>pcap_t *fp;<br>char errbuf[PCAP_ERRBUF_SIZE];</p>
<p><br>if(argc != 2){</p>
<p>printf("usage: %s filename", argv[0]);<br>return -1;</p>
<p>}</p>
<p>/* 打开一个存储有数据的堆文件 */<br>if ( (fp = pcap_open_offline(argv[1], errbuf) ) == NULL)<br>{<br>fprintf(stderr,"\nError opening dump file\n");<br>return -1;<br>}</p>
<p>// 读取数据直到遇到 EOF标志。 <br>pcap_loop(fp, 0, dispatcher_handler, NULL);</p>
<p>return 0;<br>}</p>
<p>&#160;</p>
<p>void dispatcher_handler(u_char *temp1, <br>const struct pcap_pkthdr *header, const u_char *pkt_data)<br>{<br>u_int i=0;</p>
<p>/* print pkt timestamp and pkt len */<br>printf("%ld:%ld (%ld)\n", header-&gt;ts.tv_sec, header-&gt;ts.tv_usec, header-&gt;len);</p>
<p>/* Print the packet */<br>for (i=1; (i &lt; header-&gt;caplen + 1 ) ; i++)<br>{<br>printf("%.2x ", pkt_data[i-1]);<br>if ( (i % LINE_LEN) == 0) printf("\n");<br>}</p>
<p>printf("\n\n");</p>
<p>}</p>
<p>&#160;</p>
<p>下面的代码具有一样的作用，只不过是用pcap_next_ex()来代替pcap_loop()循环读取数据而已。</p>
<p><br>程序代码： [ 复制代码到剪贴板 ] <br>#include &lt;stdio.h&gt;<br>#include &lt;pcap.h&gt;</p>
<p>#define LINE_LEN 16</p>
<p>main(int argc, char **argv) {</p>
<p>pcap_t *fp;<br>char errbuf[PCAP_ERRBUF_SIZE];<br>struct pcap_pkthdr *header;<br>u_char *pkt_data;<br>u_int i=0;<br>int res;</p>
<p>if(argc != 2){</p>
<p>printf("usage: %s filename", argv[0]);<br>return -1;</p>
<p>}</p>
<p>/* Open a capture file */<br>if ( (fp = pcap_open_offline(argv[1], errbuf) ) == NULL)<br>{<br>fprintf(stderr,"\nError opening dump file\n");<br>return -1;<br>}</p>
<p>/* Retrieve the packets from the file */<br>while((res = pcap_next_ex( fp, &amp;header, &amp;pkt_data)) &gt;= 0){<br>/* print pkt timestamp and pkt len */<br>printf("%ld:%ld (%ld)\n", header-&gt;ts.tv_sec, header-&gt;ts.tv_usec, header-&gt;len);</p>
<p>/* Print the packet */<br>for (i=1; (i &lt; header-&gt;caplen + 1 ) ; i++)<br>{<br>printf("%.2x ", pkt_data[i-1]);<br>if ( (i % LINE_LEN) == 0) printf("\n");<br>}</p>
<p>printf("\n\n"); <br>}</p>
<p><br>if(res == -1){<br>printf("Error reading the packets: %s\n", pcap_geterr(fp));<br>}</p>
<p>return 0;<br>}</p>
<p><br>用pcap_live_dump将数据写到文件：<br>WinPcap的最新版本提供了一个进一步的方法来将数据包存储到磁盘，就是使用pcap_live_dump()函数。他需要三个参数：一个文件名，和一个</p>
<p>该文件允许的最大长度还有一个参数是该文件所允许的最大包的数量。对这些参数来说 0 意味着没有最大限制。注：我们可以在调用</p>
<p>pcap_live_dump()前设置一个过滤器来定义哪些数据报需要存储。</p>
<p>pcap_live_dump() 是非阻塞的，所以他会立刻返回：数据的存储过程将会异步的进行，直到文件到达了指定的最大长度或最大数据报的数目为</p>
<p>止。<br>应用程序能够用pcap_live_dump_ended()来等检查是否数据存储完毕，如果你指定的最大长度参数和数据报数量为0，那么该操作将永远阻塞。</p>
<p>pcap_live_dump() 和 pcap_dump()的不同从设置的最大极限来说就是性能的问题。pcap_live_dump()采用WinPcap NPF驱动来从内核级的层次</p>
<p>上向文件中写数据，从而使内存拷贝最小化。<br>显然，这些特点当前在其他的操作系统下是不能够实现的，pcap_live_dump()是WinPcap所特有的，而且只能够应用于Win32环境.<br></p>
<p>&nbsp;&nbsp;&nbsp;<br>八）发送数据包</p>
<p>尽管WinPcap从名字上来看表明他的主要目的是捕获数据包，但是他还为原始网络提供了一些其他的功能，其中</p>
<p>之一就是用户可以发送数据包，这也就是本节的主要内容。需要指出的是原来的libpcap并不提供数据包 的发</p>
<p>送功能，这里所说的功能都是WinPcap的扩展功能，所以并不能够工作在UNIX下。</p>
<p>用pcap_sendpacket来发送一个数据包：</p>
<p>下面的代码是一个最简单的发送数据的方法。打开一个适配器后就可以用 pcap_sendpacket（）来手工发送一</p>
<p>个数据包了。这个函数需要的参数：一个装有要发送数据的缓冲区，要发送的长度，和一个适配器。注意缓冲</p>
<p>区中的数据将不被内核协议处理，只是作为最原始的数据流被发送，所以我门必须填充好正确的协议头以便正</p>
<p>确的将数据发送。</p>
<p>程序代码： [ 复制代码到剪贴板 ] <br>#include &lt;stdlib.h&gt;<br>#include &lt;stdio.h&gt;</p>
<p>#include &lt;pcap.h&gt;</p>
<p>void usage();</p>
<p>void main(int argc, char **argv) {<br>pcap_t *fp;<br>char error[PCAP_ERRBUF_SIZE];<br>u_char packet[100];<br>int i;</p>
<p>/* Check the validity of the command line */<br>if (argc != 2)<br>{<br>printf("usage: %s inerface", argv[0]);<br>return;<br>}</p>
<p>/* 打开指定网卡 */<br>if((fp = pcap_open_live(argv[1], 100, 1, 1000, error) ) == NULL)<br>{<br>fprintf(stderr,"\nError opening adapter: %s\n", error);<br>return;<br>}</p>
<p>/* 假设网络环境为ethernet，我门把目的MAC设为1:1:1:1:1:1*/<br>packet[0]=1;<br>packet[1]=1;<br>packet[2]=1;<br>packet[3]=1;<br>packet[4]=1;<br>packet[5]=1;</p>
<p>/* 假设源MAC为 2:2:2:2:2:2 */<br>packet[6]=2;<br>packet[7]=2;<br>packet[8]=2;<br>packet[9]=2;<br>packet[10]=2;<br>packet[11]=2;</p>
<p>/* 填充发送包的剩余部分 */<br>for(i=12;i&lt;100;i++){<br>packet[i]=i%256;<br>}</p>
<p>/* 发送包 */<br>pcap_sendpacket(fp,<br>packet,<br>100);</p>
<p>return;<br>}</p>
<p>&#160;</p>
<p>发送队列：<br>pcap_sendpacket()只是提供一个简单的直接的发送数据的方法，而发送队列提供一个高级的强大的和最优的机</p>
<p>制来发送一组数据包，队列实际上是一个装有要发送数据的一个容器，他有一个最大值来表明他所能够 容纳的</p>
<p>最大比特数。<br>pcap_sendqueue_alloc()用来创建一个队列，并指定该队列的大小。<br>一旦队列被创建就可以调用pcap_sendqueue_queue()来将数据存储到队列中，这个函数接受一个带有时间戳和</p>
<p>长度的pcap_pkthdr结构和一个装有数据报的缓冲区。这些参数同样也应用于pcap_next_ex() 和</p>
<p>pcap_handler()中，所以给要捕获的数据包或要从文件读取的数据包排队就是pcap_sendqueue_queue()的事情</p>
<p>了。<br>WinPcap调用pcap_sendqueue_transmit()来发送数据包，注意，第三个参数如果非零，那么发送将是同步的，</p>
<p>这将站用很大的CPU资源，因为发生在内核驱动的同步发送是通过"brute force"loops的，但是一般情况下能够</p>
<p>精确到微秒。<br>需要指出的是用pcap_sendqueue_transmit()来发送比用pcap_sendpacket()来发送一系列的数据要高效的多，</p>
<p>因为他的数据是在内核级上被缓冲。<br>当不再需要队列时可以用pcap_sendqueue_destroy()来释放掉所有的队列资源。</p>
<p>下面的代码演示了如何用发送队列来发送数据，该示例用pcap_open_offline()打开了一个文件，然后将数据</p>
<p>从文件移动到已分配的队列，这时就同步地传送队列（如果用户指定为同步的话）。</p>
<p>程序代码： [ 复制代码到剪贴板 ] <br>/*<br>* Copyright (c) 1999 - 2002<br>* Politecnico di Torino. All rights reserved.<br>*<br>* Redistribution and use in source and binary forms, with or without<br>* modification, are permitted provided that: (1) source code distributions<br>* retain the above copyright notice and this paragraph in its entirety, (2)<br>* distributions including binary code include the above copyright notice and<br>* this paragraph in its entirety in the documentation or other materials<br>* provided with the distribution, and (3) all advertising materials mentioning<br>* features or use of this software display the following acknowledgement:<br>* ``This product includes software developed by the Politecnico<br>* di Torino, and its contributors.&#8216;&#8216; Neither the name of<br>* the University nor the names of its contributors may be used to endorse<br>* or promote products derived from this software without specific prior<br>* written permission.<br>* THIS SOFTWARE IS PROVIDED ``AS IS&#8216;&#8216; AND WITHOUT ANY EXPRESS OR IMPLIED<br>* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF<br>* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.<br>*/</p>
<p>#include &lt;stdlib.h&gt;<br>#include &lt;stdio.h&gt;</p>
<p>#include &lt;pcap.h&gt;</p>
<p>void usage();</p>
<p>void main(int argc, char **argv) {<br>pcap_t *indesc,*outdesc;<br>char error[PCAP_ERRBUF_SIZE];<br>FILE *capfile;<br>int caplen,<br>sync;<br>u_int res;<br>pcap_send_queue *squeue;<br>struct pcap_pkthdr *pktheader;<br>u_char *pktdata;</p>
<p>/* Check the validity of the command line */<br>if (argc &lt;= 2 || argc &gt;= 5)<br>{<br>usage();<br>return;<br>}</p>
<p>/* 得到文件长度 */<br>capfile=fopen(argv[1],"rb");<br>if(!capfile){<br>printf("Capture file not found!\n");<br>return;<br>}</p>
<p>fseek(capfile , 0, SEEK_END);<br>caplen= ftell(capfile)- sizeof(struct pcap_file_header);<br>fclose(capfile);</p>
<p>/* 检查确保时间戳被忽略 */<br>if(argc == 4 &amp;&amp; argv[3][0] == &#8216;s&#8216;)<br>sync = TRUE;<br>else<br>sync = FALSE;</p>
<p>/* Open the capture */<br>if((indesc = pcap_open_offline(argv[1], error)) == NULL){<br>fprintf(stderr,"\nError opening the input file: %s\n", error);<br>return; <br>}</p>
<p>/* Open the output adapter */<br>if((outdesc = pcap_open_live(argv[2], 100, 1, 1000, error) ) == NULL)<br>{<br>fprintf(stderr,"\nError opening adapter: %s\n", error);<br>return;<br>}</p>
<p>/* 检测MAC类型 */<br>if(pcap_datalink(indesc) != pcap_datalink(outdesc)){<br>printf("Warning: the datalink of the capture differs from the one of the selected</p>
<p>interface.\n");<br>printf("Press a key to continue, or CTRL+C to stop.\n");<br>getchar();<br>}</p>
<p>/* 给对列分配空间 */<br>squeue = pcap_sendqueue_alloc(caplen);</p>
<p>/* 从文件获得包来填充队列 */<br>while((res = pcap_next_ex( indesc, &amp;pktheader, &amp;pktdata)) == 1){<br>if(pcap_sendqueue_queue(squeue, pktheader, pktdata) == -1){<br>printf("Warning: packet buffer too small, not all the packets will be sent.\n");<br>break;<br>}<br>}</p>
<p>if(res == -1){<br>printf("Corrupted input file.\n");<br>pcap_sendqueue_destroy(squeue);<br>return;<br>}</p>
<p>/* 传送队列数据 */</p>
<p>if((res = pcap_sendqueue_transmit(outdesc, squeue, sync)) &lt; squeue-&gt;len)<br>{<br>printf("An error occurred sending the packets: %s. Only %d bytes were sent\n", error,</p>
<p>res);<br>}</p>
<p>/* free the send queue */<br>pcap_sendqueue_destroy(squeue);</p>
<p>return;<br>}</p>
<p><br>void usage()<br>{</p>
<p>printf("\nSendcap, sends a libpcap/tcpdump capture file to the net. Copyright (C) 2002 Loris</p>
<p>Degioanni.\n");<br>printf("\nUsage:\n");<br>printf("\t sendcap file_name adapter [s]\n");<br>printf("\nParameters:\n");<br>printf("\nfile_name: the name of the dump file that will be sent to the network\n");<br>printf("\nadapter: the device to use. Use \"WinDump -D\" for a list of valid devices\n");<br>printf("\ns: if present, forces the packets to be sent synchronously, i.e. respecting the</p>
<p>timestamps in the dump file. This option will work only under Windows NTx.\n\n");</p>
<p>exit(0);<br>} <br></p>
<p>&nbsp;&nbsp;&nbsp;<br>九）收集并统计网络流量<br>这一节将展示WinPcap的另一高级功能：收集网络流量的统计信息。WinPcap的统计引擎在内核层次上对到来的数据进行分类。如果你想了解更</p>
<p>多的细节请查看NPF驱动指南。<br>为了利用这个功能来监视网络，我门的程序必须打开一个网卡并用pcap_setmode()将其设置为统计模式。注意pcap_setmode()要用 MODE_STAT</p>
<p>来将网卡设置为统计模式。<br>在统计模式下编写一个程序来监视TCP流量只是几行代码的事情，下面的例子说明了如何来实现该功能的。 <br>程序代码： [ 复制代码到剪贴板 ] <br>/*<br>* Copyright (c) 1999 - 2002<br>* Politecnico di Torino. All rights reserved.<br>*<br>* Redistribution and use in source and binary forms, with or without<br>* modification, are permitted provided that: (1) source code distributions<br>* retain the above copyright notice and this paragraph in its entirety, (2)<br>* distributions including binary code include the above copyright notice and<br>* this paragraph in its entirety in the documentation or other materials<br>* provided with the distribution, and (3) all advertising materials mentioning<br>* features or use of this software display the following acknowledgement:<br>* ``This product includes software developed by the Politecnico<br>* di Torino, and its contributors.&#8216;&#8216; Neither the name of<br>* the University nor the names of its contributors may be used to endorse<br>* or promote products derived from this software without specific prior<br>* written permission.<br>* THIS SOFTWARE IS PROVIDED ``AS IS&#8216;&#8216; AND WITHOUT ANY EXPRESS OR IMPLIED<br>* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF<br>* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.<br>*/</p>
<p>#include &lt;stdlib.h&gt;<br>#include &lt;stdio.h&gt;</p>
<p>#include &lt;pcap.h&gt;</p>
<p>void usage();</p>
<p>void dispatcher_handler(u_char *, <br>const struct pcap_pkthdr *, const u_char *);</p>
<p><br>void main(int argc, char **argv) {<br>pcap_t *fp;<br>char error[PCAP_ERRBUF_SIZE];<br>struct timeval st_ts;<br>u_int netmask;<br>struct bpf_program fcode;</p>
<p>/* Check the validity of the command line */<br>if (argc != 2)<br>{<br>usage();<br>return;<br>}</p>
<p>/* Open the output adapter */<br>if((fp = pcap_open_live(argv[1], 100, 1, 1000, error) ) == NULL)<br>{<br>fprintf(stderr,"\nError opening adapter: %s\n", error);<br>return;<br>}</p>
<p>/* Don&#8216;t care about netmask, it won&#8216;t be used for this filter */<br>netmask=0xffffff;</p>
<p>//compile the filter<br>if(pcap_compile(fp, &amp;fcode, "tcp", 1, netmask) &lt;0 ){<br>fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");<br>/* Free the device list */<br>return;<br>}</p>
<p>//set the filter<br>if(pcap_setfilter(fp, &amp;fcode)&lt;0){<br>fprintf(stderr,"\nError setting the filter.\n");<br>/* Free the device list */<br>return;<br>}</p>
<p>/* 将网卡设置为统计模式 */<br>pcap_setmode(fp, MODE_STAT);</p>
<p>printf("TCP traffic summary:\n");</p>
<p>/* Start the main loop */<br>pcap_loop(fp, 0, dispatcher_handler, (PUCHAR)&amp;st_ts);</p>
<p>return;<br>}</p>
<p>void dispatcher_handler(u_char *state, const struct pcap_pkthdr *header, const u_char *pkt_data)<br>{<br>struct timeval *old_ts = (struct timeval *)state;<br>u_int delay;<br>LARGE_INTEGER Bps,Pps;<br>struct tm *ltime;<br>char timestr[16];</p>
<p>/* 从最近一次的采样以微秒计算延迟时间 */<br>/* This value is obtained from the timestamp that the associated with the sample. */<br>delay=(header-&gt;ts.tv_sec - old_ts-&gt;tv_sec) * 1000000 - old_ts-&gt;tv_usec + header-&gt;ts.tv_usec;<br>/* 获得每秒的比特数 */<br>Bps.QuadPart=(((*(LONGLONG*)(pkt_data + 8)) * 8 * 1000000) / (delay));<br>/* ^ ^<br>| |<br>| | <br>| |<br>converts bytes in bits -- |<br>|<br>delay is expressed in microseconds --<br>*/</p>
<p>/* 获得每秒的数据包数 */<br>Pps.QuadPart=(((*(LONGLONG*)(pkt_data)) * 1000000) / (delay));</p>
<p>/* 将时间戳转变为可读的标准格式 */<br>ltime=localtime(&amp;header-&gt;ts.tv_sec);<br>strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);</p>
<p>/* Print timestamp*/<br>printf("%s ", timestr);</p>
<p>/* Print the samples */<br>printf("BPS=%I64u ", Bps.QuadPart);<br>printf("PPS=%I64u\n", Pps.QuadPart);</p>
<p>//store current timestamp<br>old_ts-&gt;tv_sec=header-&gt;ts.tv_sec;<br>old_ts-&gt;tv_usec=header-&gt;ts.tv_usec;<br>}</p>
<p><br>void usage()<br>{</p>
<p>printf("\nShows the TCP traffic load, in bits per second and packets per second.\nCopyright (C) 2002 Loris Degioanni.\n");<br>printf("\nUsage:\n");<br>printf("\t tcptop adapter\n");<br>printf("\t You can use \"WinDump -D\" if you don&#8216;t know the name of your adapters.\n");</p>
<p>exit(0);<br>}</p>
<p>&#160;</p>
<p>在设置为统计模式前可以设置一个过滤器来指定要捕获的协议包。如果没有设置过滤器那么整个网络数据都将被监视。一旦设置了 过滤器就可</p>
<p>以调用pcap_setmode()来设置为统计模式，之后网卡开始工作在统计模式下。<br>需要指出的是pcap_open_live()的第四个参数(to_ms)定义了采样的间隔，回调函数pcap_loop()每隔一定间隔就获取一次采样统计，这个采样</p>
<p>被装入pcap_loop()的第二和第三个参数，过程如下图所示：<br>______________<br>|struct timeval ts | <br>|_____________|<br>|bpf_u_int32 |<br>|caplen=16 | struct pcap_pkthdr*<br>|_____________| (参数2)<br>| bpf_u_int32 |<br>| len=16 |<br>|_____________|</p>
<p>__________________________<br>|large_integer Accepted packet |<br>|_________________________| uchar *<br>| large_integer Accepted bits | (参数3)<br>|_________________________|</p>
<p>&#160;</p>
<p>用两个64位的计数器分别记录最近一次间隔数据包数量和比特数量。<br>本例子中，网卡打开时设置超时为1000毫秒，也就是说dispatcher_handler()每隔1秒就被调用一次。过滤器也</p>
<p>设置为只监视TCP包，然后pcap_setmode() and pcap_loop()被调用，注意一个指向timeval的指针 作为参数传</p>
<p>送到pcap_loop()。这个timeval结构将用来存储个时间戳以计算两次采样的时间间隔。<br>dispatcher_handler()用该间隔来获取每秒的比特数和数据包数，并把着两个数显示在显示器上。<br>最后指出的是目前这个例子是比任何一个利用传统方法在用户层统计的包捕获程序都高效。因为统计模式需要</p>
<p>最小数量的数据拷贝和上下环境交换，同时还有最小的内存需求，所以CPU是最优的。</p>
<img src ="http://www.cppblog.com/elva/aggbug/63368.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/elva/" target="_blank">叶子</a> 2008-10-07 10:11 <a href="http://www.cppblog.com/elva/archive/2008/10/07/63368.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>wincap中文版翻译&lt;转&gt;</title><link>http://www.cppblog.com/elva/archive/2008/10/07/63367.html</link><dc:creator>叶子</dc:creator><author>叶子</author><pubDate>Tue, 07 Oct 2008 02:08:00 GMT</pubDate><guid>http://www.cppblog.com/elva/archive/2008/10/07/63367.html</guid><wfw:comment>http://www.cppblog.com/elva/comments/63367.html</wfw:comment><comments>http://www.cppblog.com/elva/archive/2008/10/07/63367.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/elva/comments/commentRss/63367.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/elva/services/trackbacks/63367.html</trackback:ping><description><![CDATA[<p>wincap中文版翻译<br>（一）得到网络驱动列表</p>
<p>用PCAP写应用程序的第一件事往往就是要获得本地的网卡列表。PCAP提供了pcap_findalldevs()这个函数来实现此功能，这个API返回一个</p>
<p>pcap_if结构的连表，连表的每项内容都含有全面的网卡信息：尤其是字段名字和含有名字的描述以及有关驱动器的易读信息。</p>
<p>得到网络驱动列表的程序如下：</p>
<p>程序代码： [ 复制代码到剪贴板 ] </p>
<p>&nbsp;</p>
<p><br>#include "pcap.h"</p>
<p>main()<br>{<br>pcap_if_t *alldevs;<br>pcap_if_t *d;<br>int i=0;<br>char errbuf[PCAP_ERRBUF_SIZE];</p>
<p>/* 这个API用来获得网卡 的列表 */<br>if (pcap_findalldevs(&amp;alldevs, errbuf) == -1)<br>{<br>fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);<br>exit(1);<br>}</p>
<p>/* 显示列表的响应字段的内容 */<br>for(d=alldevs;d;d=d-&gt;next)<br>{<br>printf("%d. %s", ++i, d-&gt;name);<br>if (d-&gt;description)<br>printf(" (%s)\n", d-&gt;description);<br>else printf(" (No description available)\n");<br>}</p>
<p>if(i==0)<br>{<br>printf("\nNo interfaces found! Make sure WinPcap is installed.\n");<br>return;<br>}</p>
<p>/* We don&#8216;t need any more the device list. Free it */<br>pcap_freealldevs(alldevs);<br>}<br>[code]有关这段程序的一些说明：<br>首先pcap_findalldevs()同其他的libpcap函数一样有一个errbuf参数，当有异常情况发生时，这个参数会被PCAP填充为某个特定的错误字串。</p>
<p>再次，UNIX也同样提供pcap_findalldevs()这个函数，但是请注意并非所有的系统都支持libpcap提供的网络程序接口。所以我门要想写出合适</p>
<p>的程序就必须考虑到这些情况（系统不能够返回一些字段的描述信息），在这种情况下我门应该给出类似"No description available"这样的</p>
<p>提示。</p>
<p>最后结束时别忘了用pcap_freealldevs()释放掉内存资源。</p>
<p>[code]#include "pcap.h"</p>
<p>main()<br>{<br>pcap_if_t *alldevs;<br>pcap_if_t *d;<br>int i=0;<br>char errbuf[PCAP_ERRBUF_SIZE];</p>
<p>/* Retrieve the device list */<br>if (pcap_findalldevs(&amp;alldevs, errbuf) == -1)<br>{<br>fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);<br>exit(1);<br>}</p>
<p>/* Print the list */<br>for(d=alldevs;d;d=d-&gt;next)<br>{<br>printf("%d. %s", ++i, d-&gt;name);<br>if (d-&gt;description)<br>printf(" (%s)\n", d-&gt;description);<br>else printf(" (No description available)\n");<br>}</p>
<p>if(i==0)<br>{<br>printf("\nNo interfaces found! Make sure WinPcap is installed.\n");<br>return;<br>}</p>
<p>/* We don&#8216;t need any more the device list. Free it */<br>pcap_freealldevs(alldevs);<br>}</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><br>二）获得已安装网络驱动器的高级信息</p>
<p>在第一章中演示了如何获得已存在适配器的静态信息。实际上WinPcap同样也提供其他的高级信息，特别是 pcap_findalldevs()这个函数返回</p>
<p>的每个 pcap_if结构体都同样包含一个pcap_addr结构的列表，他包含：<br>一个地址列表，一个掩码列表，一个广播地址列表和一个目的地址列表。<br>下面的例子通过一个ifprint()函数打印出了pcap_if结构的的所有字段信息，该程序对每一个pcap_findalldevs()所返回的pcap_if结构循环调</p>
<p>用ifprint()来显示详细的字段信息。</p>
<p>程序代码： [ 复制代码到剪贴板 ] <br>#include "pcap.h"<br>#ifndef WIN32<br>#include &lt;sys/socket.h&gt;<br>#include &lt;netinet/in.h&gt;<br>#else<br>#include &lt;winsock.h&gt;<br>#endif</p>
<p>void ifprint(pcap_if_t *d);<br>char *iptos(u_long in);</p>
<p>int main()<br>{<br>pcap_if_t *alldevs;<br>pcap_if_t *d;<br>char errbuf[PCAP_ERRBUF_SIZE+1];</p>
<p>/* 获得网卡的列表 */<br>if (pcap_findalldevs(&amp;alldevs, errbuf) == -1)<br>{<br>fprintf(stderr,"Error in pcap_findalldevs: %s\n",errbuf);<br>exit(1);<br>}</p>
<p>/* 循环调用ifprint() 来显示pcap_if结构的信息*/<br>for(d=alldevs;d;d=d-&gt;next)<br>{<br>ifprint(d);<br>}</p>
<p>return 1;<br>}</p>
<p>/* Print all the available information on the given interface */<br>void ifprint(pcap_if_t *d)<br>{<br>pcap_addr_t *a;</p>
<p>/* Name */<br>printf("%s\n",d-&gt;name);</p>
<p>/* Description */<br>if (d-&gt;description)<br>printf("\tDescription: %s\n",d-&gt;description);</p>
<p>/* Loopback Address*/<br>printf("\tLoopback: %s\n",(d-&gt;flags &amp; PCAP_IF_LOOPBACK)?"yes":"no");</p>
<p>/* IP addresses */<br>for(a=d-&gt;addresses;a;a=a-&gt;next) {<br>printf("\tAddress Family: #%d\n",a-&gt;addr-&gt;sa_family);</p>
<p>/*关于 sockaddr_in 结构请参考其他的网络编程书*/<br>switch(a-&gt;addr-&gt;sa_family)<br>{<br>case AF_INET:<br>printf("\tAddress Family Name: AF_INET\n");//打印网络地址类型<br>if (a-&gt;addr)//打印IP地址<br>printf("\tAddress: %s\n",iptos(((struct sockaddr_in *)a-&gt;addr)-&gt;sin_addr.s_addr));<br>if (a-&gt;netmask)//打印掩码<br>printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a-&gt;netmask)-&gt;sin_addr.s_addr));<br>if (a-&gt;broadaddr)//打印广播地址<br>printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a-&gt;broadaddr)-&gt;sin_addr.s_addr));<br>if (a-&gt;dstaddr)//目的地址<br>printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a-&gt;dstaddr)-&gt;sin_addr.s_addr));<br>break;<br>default:<br>printf("\tAddress Family Name: Unknown\n");<br>break;<br>}<br>}<br>printf("\n");<br>}</p>
<p>/* 将一个unsigned long 型的IP转换为字符串类型的IP */<br>#define IPTOSBUFFERS 12<br>char *iptos(u_long in)<br>{<br>static char output[IPTOSBUFFERS][3*4+3+1];<br>static short which;<br>u_char *p;</p>
<p>p = (u_char *)&amp;in;<br>which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);<br>sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);<br>return output[which];<br>} <br></p>
<p>&nbsp;&nbsp;&nbsp;</p>
<p>&nbsp;</p>
<p>三）打开网卡捕获数据包</p>
<p><br>现在我门已经知道了如何去获得网卡的信息现在就让我们开始真正的工作：打开网卡并捕获数据流。在这一节</p>
<p>里我们将写一个打印流经网络的每个数据包信息的程序。打开网卡的功能是通过pcap_open_live()来实现的它</p>
<p>有三个参数snaplen promisc to_ms。</p>
<p>snaplen用于指定所捕获包的特定部分，在一些系统上（象xBSD and Win32等）驱动只给出所捕获数据包的一部分而不是全部，这样就减少了拷</p>
<p>贝数据的数量从而提高了包捕获的效率。</p>
<p>promisc指明网卡处于混杂模式，在正常情况下网卡只接受去往它的包而去往其他主机的数据包则被忽略。相反当网卡处于混杂 模式时他将接</p>
<p>收所有的流经它的数据包：这就意味着在共享介质的情况下我门可以捕获</p>
<p>到其它主机的数据包。大部分的包捕获程序都将混杂模式设为默认，所有我们在下面的例子里也将网卡设为混杂模式。to_ms 参数指定读数据</p>
<p>的超时控制，超时以毫秒计算。当在超时时间内网卡上没有数据到来时对网卡的读操作将返回（如pcap_dispatch() or pcap_next_ex()等函数</p>
<p>）。还有，如果网卡处于统计模式下（请查看&#8220;统计和收集网络数据流一节&#8221;）to_ms还定义了统计的时间间隔。如果该参数为0那么意味着没</p>
<p>有超时控制，对网卡的读操作在没有数据到来是将永远堵塞。如果为-1那么对网卡的读操作将立即返回不管有没有数据可读。<br>程序代码： [ 复制代码到剪贴板 ] <br>#include "pcap.h"</p>
<p>/* prototype of the packet handler */<br>void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);</p>
<p>main()<br>{<br>pcap_if_t *alldevs;<br>pcap_if_t *d;<br>int inum;<br>int i=0;<br>pcap_t *adhandle;<br>char errbuf[PCAP_ERRBUF_SIZE];</p>
<p>/* 获得网卡的列表 */<br>if (pcap_findalldevs(&amp;alldevs, errbuf) == -1)<br>{<br>fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);<br>exit(1);<br>}</p>
<p>/* 打印网卡信息 */<br>for(d=alldevs; d; d=d-&gt;next)<br>{<br>printf("%d. %s", ++i, d-&gt;name);<br>if (d-&gt;description)<br>printf(" (%s)\n", d-&gt;description);<br>else<br>printf(" (No description available)\n");<br>}</p>
<p>if(i==0)<br>{<br>printf("\nNo interfaces found! Make sure WinPcap is installed.\n");<br>return -1;<br>}</p>
<p>printf("Enter the interface number (1-%d):",i);<br>scanf("%d", &amp;inum); //输入要选择打开的网卡号</p>
<p>if(inum &lt; 1 || inum &gt; i) //判断号的合法性<br>{<br>printf("\nInterface number out of range.\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}</p>
<p>/* 找到要选择的网卡结构 */<br>for(d=alldevs, i=0; i&lt; inum-1 ;d=d-&gt;next, i++);</p>
<p>/* 打开选择的网卡 */<br>if ( (adhandle= pcap_open_live(d-&gt;name, // 设备名称<br>65536, // portion of the packet to capture. <br>// 65536 grants that the whole packet will be captured on all the MACs.<br>1, // 混杂模式<br>1000, // 读超时为1秒<br>errbuf // error buffer<br>) ) == NULL)<br>{<br>fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n");<br>/* Free the device list */<br>pcap_freealldevs(alldevs);<br>return -1;<br>}</p>
<p>printf("\nlistening on %s...\n", d-&gt;description);</p>
<p>/* At this point, we don&#8216;t need any more the device list. Free it */<br>pcap_freealldevs(alldevs);</p>
<p>/* 开始捕获包 */<br>pcap_loop(adhandle, 0, packet_handler, NULL);</p>
<p>return 0;<br>}</p>
<p><br>/* 对每一个到来的数据包调用该函数 */<br>void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)<br>{<br>struct tm *ltime;<br>char timestr[16];</p>
<p>/* 将时间戳转变为易读的标准格式*/<br>ltime=localtime(&amp;header-&gt;ts.tv_sec);<br>strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);</p>
<p>printf("%s,%.6d len:%d\n", timestr, header-&gt;ts.tv_usec, header-&gt;len);</p>
<p>}</p>
<p>&nbsp;</p>
<p>一旦网卡被打开，旧可以调用pcap_dispatch() 或pcap_loop()进行数据的捕获，这两个函数的功能十分相似不同的是pcap_ dispatch()可以不</p>
<p>被阻塞，而pcap_loop()在没有数据流到达时将阻塞。在这个简单的例子里用pcap_loop()就足够了，而在一些复杂的程序里往往用</p>
<p>pcap_dispatch()。<br>Once the adapter is opened, the capture can be started with pcap_dispatch() or pcap_loop(). These</p>
<p>two functions are very similar, the difference is that pcap_ dispatch() is granted to return when</p>
<p>the expires while pcap_loop() doesn&#8216;t return until cnt packets have been captured, so it can</p>
<p>block for an arbitrary period on a few utilized network. pcap_loop() is enough for the purpose of</p>
<p>this sample, while pcap_dispatch() is normally used in more complex program.<br>这两个函数都有返回的参数，一个指向某个函数（该函数用来接受数据如该程序中的packet_handler）的指针，libpcap调用该函数对每个从网</p>
<p>上到来的数据包进行处理和接收数据包。另一个参数是带有时间戳和包长等信息的头部，最后一个是含有所有协议头部数据报的实际数据。注</p>
<p>意MAC的冗余校验码一般不出现，因为当一个桢到达并被确认后网卡就把它删除了，同样需要注意的是大多数网卡会丢掉冗余码出错的数据包，</p>
<p>所以WinPcap一般不能够捕获这些出错的数据报。</p>
<p>刚才的例子里从pcap_pkthdr中提取出了每个数据报的时间戳和长度并在显示器上打印出了他们。<br></p>
<img src ="http://www.cppblog.com/elva/aggbug/63367.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/elva/" target="_blank">叶子</a> 2008-10-07 10:08 <a href="http://www.cppblog.com/elva/archive/2008/10/07/63367.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>