//
// Packet.h
//
// Copyright (c) Shareaza Development Team, 2002-2005.
// This file is part of SHAREAZA (www.shareaza.com)
//
// Shareaza is free software; you can redistribute it
// and/or modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// Shareaza is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Shareaza; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// CPacket represents a packet on a peer-to-peer network, and CPacketPool keeps lists of them
// http://wiki.shareaza.com/static/Developers.Code.CPacket
// Make the compiler only include the lines here once, this is the same thing as pragma once
#if !defined(AFX_PACKET_H__3094C1CC_8AD2_49BD_BF10_EA639A9EAE6F__INCLUDED_)
#define AFX_PACKET_H__3094C1CC_8AD2_49BD_BF10_EA639A9EAE6F__INCLUDED_
// Only include the lines beneath this one once
#pragma once
// Reverses the order of 2 bytes, like "12" to "21" 将字节转化为逆序的,如0X12变成0X21
#define SWAP_SHORT(x) ( ( ( (x) & 0xFF00 ) >> 8 ) + ( ( (x) & 0x00FF ) << 8 ) )
// Reverses the order of 4 bytes, like "1234" to "4321"
#define SWAP_LONG(x) ( ( ( (x) & 0xFF000000 ) >> 24 ) + ( ( (x) & 0x00FF0000 ) >> 8 ) + ( ( (x) & 0x0000FF00 ) << 8 ) + ( ( (x) & 0x000000FF ) << 24 ) )
// Reverses the order of 8 bytes, like "12345678" to "87654321"
#define SWAP_64(x) ( ( SWAP_LONG( (x) & 0xFFFFFFFF ) << 32 ) | SWAP_LONG( (x) >> 32 ) )
// When the allocated block of memory needs to be bigger, make it 128 bytes bigger
#define PACKET_GROW 128
// Sizes of buffers that hold 128 ASCII and 128 wide characters so MultiByteToWideChar can convert short text quickly
#define PACKET_BUF_SCHAR 127
#define PACKET_BUF_WCHAR 127
// Shareaza's vendor code is "RAZA", here is that text in ASCII and wide characters
#define SHAREAZA_VENDOR_A VENDOR_CODE
#define SHAREAZA_VENDOR_T _T( VENDOR_CODE )
// Tell the compiler these classes exist, and it will find out more about them soon
class CBuffer;
class CNeighbour;
// A packet on a peer-to-peer network
class CPacket
{
protected:
// Create a new CPacket object, and delete this one
// Make a new CPacket object for the given protocol id, like Gnutella or eDonkey2000
CPacket(PROTOCOLID nProtocol);
// The destructor is virtual, meaning classes that inherit from CPacket
//may replace it with their own destructor
virtual ~CPacket();
public:
// The network this packet is on, like Gnutella or eDonkey2000
PROTOCOLID m_nProtocol;
// List pointer and reference count
// Unused packets in the packet pool are linked together from m_pFree,
//through each packet's m_pNext pointer
CPacket* m_pNext;
// The number of other objects that need this packet and point to it,
//0 if everyone is done with it
DWORD m_nReference;
public:
// Packet data
// A pointer to memory we allocated to hold the bytes of the payload
//of the packet, this is not a CBuffer object 注意这里的m_pBuffer仅仅是一个Char *
BYTE* m_pBuffer;
// The size of the allocated block of memory that holds the payload
//分配的长度,而m_nLength是有效数据的长度,肯定是小于m_nBuffer的
DWORD m_nBuffer;
// The number of bytes of data we've written into the allocated block of memory
//有效数据的长度
DWORD m_nLength;
//当前访问到的数据的位置
// What byte we are on, this position index is remembered
//by the packet between calls to methods
DWORD m_nPosition;
// True if the bytes of the packet are in big endian format, which is the default
//packet默认是big endian的,
BOOL m_bBigEndian;
// Set the position a given distance forwards from the start, or backwards from the end
enum { seekStart, seekEnd };
protected:
// Buffers that hold 128 ASCII and 128 wide characters,
//used so MultiByteToWideChar can convert short text quickly
//对小于127个字符的utf8格式的转化提供了一个缓冲数组,从而对短文本的处理更快
static CHAR m_szSCHAR[PACKET_BUF_SCHAR + 1];
// Static means these are separate from all the CPacket objects this class will make
static WCHAR m_szWCHAR[PACKET_BUF_WCHAR + 1];
public:
// Reset this packet object to make it like it was when it came from the constructor
virtual void Reset();
// What is const = 0 (do)
virtual void ToBuffer(CBuffer* pBuffer) const = 0;
public:
// Packet position and length
// Set the position the given distance from the given end
//将当前文件指针移动到相对于nRelative的nPosition位置
void Seek(DWORD nPosition, int nRelative = seekStart);
// Shorten the packet to the given number of bytes
void Shorten(DWORD nLength);
// Read and write ASCII text in the packet
//对packet的ascii内容的读写
// Read null terminated ASCII text at our position in the packet
virtual CString ReadString(DWORD nMaximum = 0xFFFFFFFF);
// Write ASCII text and a null terminator into the end of the packet
virtual void WriteString(LPCTSTR pszString, BOOL bNull = TRUE);
// String utility, not at all related to the packet
// Takes a string, and determines how long it would be as ASCII text
//返回一个string的ascii文本的长度
virtual int GetStringLen(LPCTSTR pszString) const;
// Read and write ASCII text in the packet, using the UTF8 code page
// Read null terminated ASCII text at our position in the packet
//将buffer转化为utf8编码widechar,返回结果CString
virtual CString ReadStringUTF8(DWORD nMaximum = 0xFFFFFFFF);
// Write ASCII text and a null terminator into the end of the packet
//将utf8编码的widechar字符串转化为ascii编码,写入到buffer中
virtual void WriteStringUTF8(LPCTSTR pszString, BOOL bNull = TRUE);
// String utility, not at all related to the packet
// Takes a string, and determines how long it would be as ASCII text converted UTF8
//计算出如果将参数字符串转化为utf8编码后的长度
virtual int GetStringLenUTF8(LPCTSTR pszString) const;
// Data compression
// Read compressed data from the packet, decompress it, and return it
//将当前buffer中的内容解压缩,返回解压缩的结果
LPBYTE ReadZLib(DWORD nLength, DWORD* pnOutput, DWORD nSuggest = 0);
// Compress the given data and write it into the packet
//将参数pData压缩,结果写入到当前buffer中
void WriteZLib(LPCVOID pData, DWORD nLength);
// Insert data into the packet
// Makes room at the given spot, and returns a pointer to it
//在nOffset位置空出DLength长度的位置,返回可写入nLength长度的数据开始位置
BYTE* WriteGetPointer(DWORD nLength, DWORD nOffset = 0xFFFFFFFF);
public:
// Inheriting classes will override this to return text describing what type of packet this is
virtual LPCTSTR GetType() const;
// Encode the bytes of the packet into text
// Express the bytes of the packet in base 13 with spaces, like "08 C0 12 AF"
CString ToHex() const;
// Express the bytes of the packet as ASCII characters, like "abc..fgh.i",
//spaces replace low characters
CString ToASCII() const;
// Inheriting classes will override this to (do)
virtual void Debug(LPCTSTR pszReason) const;
// Gives this packet and related objects to each window in the tab bar for them to process it
//将当前Packet传给所有的CPacketWnd窗口,调用Process方法处理
void SmartDump(CNeighbour* pNeighbour, IN_ADDR* pUDP, BOOL bOutgoing) const;
public:
// Compute the SHA hash of the bytes of the packet
//计算出当前packet的SHA1 hash 编码
virtual BOOL GetRazaHash(SHA1* pHash, DWORD nLength = 0xFFFFFFFF) const;
// Does nothing (do)
void RazaSign();
BOOL RazaVerify() const;
public:
// Get the length beyond our position in the packet
//返回剩余的还未处理的数据的长度
inline int GetRemaining()
{
// Return the number of bytes of packet data at and beyond our position in the packet
return m_nLength - m_nPosition;
}
// Takes a pointer to a buffer, and the number of bytes we want written there
// Copies this number of bytes from the packet, and moves the packet's position beyond them
//从m_nPosition位置开始,读取nLength个数据到pData中
inline void Read(LPVOID pData, int nLength)
{
// Make sure the requested length doesn't poke beyond the end of the packet
if ( m_nPosition + nLength > m_nLength ) // 不能超过有效数据的最大长度
{
AfxThrowUserException();
}
// Copy memory from the packet to the given buffer
CopyMemory(
pData, // Destination is the given pointer
m_pBuffer + m_nPosition, // Source is our position in the packet
nLength ); // Size is the requested length
// Move our position in the packet beyond the data we just copied out
m_nPosition += nLength;
}
// Read the next byte in the packet, moving the position beyond it
// Returns the byte
//读取 m_nPosition位置的一个字节,同时m_nPosition增加1指向下一个字节
inline BYTE ReadByte()
{
// Make sure the position isn't at the end or beyond it, where there is no byte to read
if ( m_nPosition >= m_nLength )
{
AfxThrowUserException();
}
// Read one byte, return it, and move our position in this packet beyond it
return m_pBuffer[ m_nPosition++ ];
}
// Get the next byte in the packet, not moving the position beyond it
// Returns the byte
//读取一个字节,m_nPosition不增加
inline BYTE PeekByte()
{
// Make sure the position isn't at the end or beyond it, where there is no byte to read
if ( m_nPosition >= m_nLength )
{
AfxThrowUserException();
}
// Read one byte, return it, but don't move our position in this packet beyond it
return m_pBuffer[ m_nPosition ];
}
// Read the next 2 bytes in the packet, moving the position beyond them
// Returns the bytes in a word, and assumes little endian order which we don't need to change
//读取小字节序的short
inline WORD ReadShortLE()
{
// Make sure there are at least 2 bytes of data at our position in the packet
if ( m_nPosition + 2 > m_nLength )
{
AfxThrowUserException();
}
// Read the 2 bytes at the position, and move the position past them
WORD nValue = *(WORD*)( m_pBuffer + m_nPosition ); // Look at the pointer as a 2 byte word
m_nPosition += 2; // Move the position beyond it
// Return the 2 bytes in a word
return nValue;
}
// Read the next 2 bytes in the packet, moving the position beyond them
// Returns the bytes in a word, switching their order if the packet thinks its contents are in big endian order
//读取一个4字节数据,考虑bigendian的情况
inline WORD ReadShortBE()
{
// Make sure there are at least 2 bytes of data at our position in the packet
if ( m_nPosition + 2 > m_nLength )
{
AfxThrowUserException();
}
// Read the 2 bytes at the position, move the position past them, and return them
WORD nValue = *(WORD*)( m_pBuffer + m_nPosition ); // Look at the pointer as a 2 byte word
m_nPosition += 2; // Move the position beyond it
// If the packet is in big endian, reverse the order of the 2 bytes before returning them in a word
return m_bBigEndian ? SWAP_SHORT( nValue ) : nValue;
}
// Read the next 4 bytes in the packet, moving the position beyond them
// Returns the bytes in a DWORD, and assumes little endian order which we don't need to change
//读取little endian的一个4字节数据
inline DWORD ReadLongLE()
{
// Make sure there are at least 4 bytes of data at our position in the packet
if ( m_nPosition + 4 > m_nLength )
{
AfxThrowUserException();
}
// Read the 4 bytes at the position, and move the position past them
DWORD nValue = *(DWORD*)( m_pBuffer + m_nPosition ); // Look at the pointer as a 4 byte DWORD
m_nPosition += 4; // Move the position beyond it
// Return the 4 bytes in a DWORD
return nValue;
}
// Read the next 4 bytes in the packet, moving the position beyond them
// Returns the bytes in a DWORD, reversing their order if the packet thinks its contents are in big endian order
//考虑bigendian的字节序
inline DWORD ReadLongBE()
{
// Make sure there are at least 4 bytes of data at our position in the packet
if ( m_nPosition + 4 > m_nLength )
{
AfxThrowUserException();
}
// Read the 4 bytes at the position, and move the position past them
DWORD nValue = *(DWORD*)( m_pBuffer + m_nPosition ); // Look at the pointer as a 4 byte DWORD
m_nPosition += 4; // Move the position beyond it
// If the packet is in big endian, reverse the order of the 4 bytes before returning them in a DWORD
return m_bBigEndian ? SWAP_LONG( nValue ) : nValue;
}
// Read the next 8 bytes in the packet, moving the position beyond them
// Returns the bytes in a QWORD, reversing their order if the packet thinks its contents are in big endian order
//读取8个字节,如果是bigedian则转化为小字节序,否则不变,m_nPosition 位置读取的
inline QWORD ReadInt64()
{
// Make sure there are at least 8 bytes of data at our position in the packet
if ( m_nPosition + 8 > m_nLength )
{
AfxThrowUserException();
}
// Read the 8 bytes at the position, and move the position past them
QWORD nValue = *(QWORD*)( m_pBuffer + m_nPosition ); // Look at the pointer as a 8 byte QWORD
m_nPosition += 8; // Move the position beyond it
// If the packet is in big endian, reverse the order of the 8 bytes before returning them in a QWORD
return m_bBigEndian ? SWAP_64( nValue ) : nValue;
}
// Takes a length of bytes we would like to add to the packet
// Ensures the allocated block of memory is big enough for them, making it bigger if necessary
//确保剩余的空间大于参数nLength,如果不够则创建足够的空间
inline void Ensure(int nLength)
{
// If the buffer isn't big enough to hold that many more new bytes
if ( m_nLength + nLength > m_nBuffer ) // 剩余的空间不够了
{
// Switch to a new bigger one,每次增加PACKET_GROW 个字节
m_nBuffer += max( nLength, PACKET_GROW ); // Size the buffer larger by the requested amount, or the packet grow size of 128 bytes
LPBYTE pNew = new BYTE[ m_nBuffer ]; // Allocate a new block of memory of that size
CopyMemory( pNew, m_pBuffer, m_nLength ); // Copy the packet data from the old buffer to the new one
if ( m_pBuffer )
{
delete [] m_pBuffer; // If there is an old buffer, free it
}
m_pBuffer = pNew; // Point the packet object's member variable pointer at the new buffer
}
}
// Takes a pointer to data and the number of bytes there
// Adds them to the end of the buffer
//在buffer中加入pData指向的数据,如果不够则分配新的空间
inline void Write(LPCVOID pData, int nLength)
{
// If the allocated block of memory doesn't have enough extra space to hold the new data
if ( m_nLength + nLength > m_nBuffer ) // 剩余的空间不够了
{
// Make it bigger 开拓足够的空间
m_nBuffer += max( nLength, PACKET_GROW ); // Calculate the new size to be nLength or 128 bytes bigger
LPBYTE pNew = new BYTE[ m_nBuffer ]; // Allocate a new buffer of that size
CopyMemory( pNew, m_pBuffer, m_nLength ); // Copy the data from the old buffer to the new one
if ( m_pBuffer )
{
delete [] m_pBuffer; // Delete the old buffer
}
m_pBuffer = pNew; // Point m_pBuffer at the new, bigger buffer
}
// Add the given data to the end of the packet将新的数据加入到buffer中
CopyMemory( m_pBuffer + m_nLength, pData, nLength ); // Copy the data into the end
m_nLength += nLength; // Record that the new bytes are stored here
}
// Takes a byte
// Writes it into the end of the packet
//将参数nValue加入到m_pBuffer数组的末尾
inline void WriteByte(BYTE nValue)
{
// Make sure there is room for the byte
if ( m_nLength + sizeof(nValue) > m_nBuffer )
{
Ensure( sizeof(nValue) );
}
// Write it at the end of the packet, and record that it is there
m_pBuffer[ m_nLength++ ] = nValue;
}
// Takes 2 bytes in a word
// Writes them into the end of the packet, keeping them in the same order
//在最后写入两个字节的数据nValue,这里是little-endian了
inline void WriteShortLE(WORD nValue)
{
// Make sure there is room for the 2 bytes
if ( m_nLength + sizeof(nValue) > m_nBuffer )
{
Ensure( sizeof(nValue) );
}
// Write the two bytes as a word at the end of the packet, and record that it is there
*(WORD*)( m_pBuffer + m_nLength ) = nValue;
m_nLength += sizeof(nValue);
}
// Takes 2 bytes in a word
// Writes them into the end of the packet, reversing their order if the packet is in big endian order
//BigEndian写入两个字节的数据nValue,m_bBigEndian为真则调整字节顺序
inline void WriteShortBE(WORD nValue)
{
// Make sure there is room for the 2 bytes
if ( m_nLength + sizeof(nValue) > m_nBuffer )
{
Ensure( sizeof(nValue) );
}
// Write the 2 bytes as a word at the end of the packet, and record that it is there
*(WORD*)( m_pBuffer + m_nLength ) =
m_bBigEndian ? SWAP_SHORT( nValue ) : nValue; // Reverse their order if necessary
m_nLength += sizeof(nValue);
}
// Takes 4 bytes in a DWORD
// Writes them into the end of the packet, keeping them in the same order
//小字节序写入4个字节的nValue
inline void WriteLongLE(DWORD nValue)
{
// Make sure there is room for the 4 bytes
if ( m_nLength + sizeof(nValue) > m_nBuffer )
{
Ensure( sizeof(nValue) );
}
// Write the 4 bytes as a DWORD at the end of the packet, and record that it is there
*(DWORD*)( m_pBuffer + m_nLength ) = nValue;
m_nLength += sizeof(nValue);
}
// Takes 4 bytes in a DWORD
// Writes them into the end of the packet, reversing their order if the packet is in big endian order
//根据m_bBigEndian是否是字节序来写入数据
inline void WriteLongBE(DWORD nValue)
{
// Make sure there is room for the 4 bytes
if ( m_nLength + sizeof(nValue) > m_nBuffer )
{
Ensure( sizeof(nValue) );
}
// Write the 4 bytes as a DWORD at the end of the packet, and record that it is there
*(DWORD*)( m_pBuffer + m_nLength ) =
m_bBigEndian ? SWAP_LONG( nValue ) : nValue; // Reverse their order if necessary
m_nLength += sizeof(nValue);
}
// Takes 8 bytes in a QWORD
// Writes them into the end of the packet, reversing their order if the packet is in big endian order
//写入8个字节的数据
inline void WriteInt64(QWORD nValue)
{
// Make sure there is room for the 8 bytes
if ( m_nLength + sizeof(nValue) > m_nBuffer )
{
Ensure( sizeof(nValue) );
}
// Write the 8 bytes as a QWORD at the end of the packet, and record that it is there
*(QWORD*)( m_pBuffer + m_nLength ) =
m_bBigEndian ? SWAP_64( nValue ) : nValue; // Reverse their order if necessary
m_nLength += sizeof(nValue);
}
public:
// Have this packet object remember that one more thing is referencing it
//使用当前packet的对象数目增加1
inline void AddRef()
{
// Incriment the reference count stored in the object
m_nReference++;
}
// Tell this packet object that one less thing needs it
//使用当前packet的对象释放当前packet的引用
inline void Release()
{
// Decrement the reference count, and if that makes it go to 0, delete the object
if ( this != NULL && ! --m_nReference )
{
Delete(); // 如果引用是0了,则直接删除内存空间,delete由子类实现
}
}
// Decrement the reference count of this packet, the packet it points to,
//and so on down the linked list from here
//将从当前packet开始的所有Packet,依次调用Release方法
inline void ReleaseChain()
{
// Make sure this object exists
if ( this == NULL )
{
return;
}
// Point pPacket at this packet, and loop until that pointer becomes null
//将当前packet后面的所有packet,调用release方法
for ( CPacket* pPacket = this ; pPacket ; )
{
// Decrement the reference count of this packet, and move to the next one
CPacket* pNext = pPacket->m_pNext; // Save a pointer to the next packet
pPacket->Release(); // Record that one fewer object needs this one, which might delete it
pPacket = pNext; // Move pPacket to the next one
}
}
// (do)
virtual inline void Delete() = 0;
// Let the CPacketPool class access the private members of this one
friend class CPacketPool;
};
// Allocates and holds array of 256 packets so we can grab a packet to use it quickly
class CPacketPool
{
public:
// Make a new packet pool, and delete one
CPacketPool();
// Virtual lets inheriting classes override this with their own custom destructor
virtual ~CPacketPool();
protected:
// Free packets ready to be used
// A linked list of packets that are free and ready to be removed from the linked list and used
//指向使用过的Packet 的链表
CPacket* m_pFree;
// The total number of free packets in the linked list
//m_pFree空闲packet链表中空闲packet的数目
DWORD m_nFree;
protected:
// Used to make sure only one thread can access this object at a time
CCriticalSection m_pSection;
// An array of pointers, each of which points to an array of 256 packets
//包含了很多CPacket*,每个CPacket*指向256个Packet的链表
/*
为什么要创建变量m_pPools呢?
m_pPools中的元素是这样的:管理了一大堆CPacket*,仅管理指向
256个Packet的头指针,也就是说它只负责申请和释放
CPacket * -> CPacket * ... 256 个
CPacket * -> CPacket * ... 256 个
CPacket * -> CPacket * ... 256 个
同时上面的这些CPacket又都在CPacket* m_pFree空闲链表中,一旦分配后
立即加入到m_pFree中。m_pFree是一个结果链表,取得时候直接从这个list
中取头结点,释放的时候直接将释放的packet加入到free list的开头。
最后释放的时候遍历m_pPools链表,依次FreePoolImpl每个pool就ok了
每次申请256个,从而避免每次都要申请,加快了申请速度。
如果仅仅使用m_pFree来管理的话,一旦CPacket被取走了,就无法释放了,
因为m_pFree头结点指向了后面的节点,无法找到被取走的节点了,而
不论被取走的CPacket节点在哪里,m_pPools可以随时监控,释放之
*/
CPtrArray m_pPools;
protected:
// Delete all the packet pools, and make a new one
void Clear(); // Delete all the packet pools in this CPacketPool object
// Create a new packet pool, which is an array that can hold 256 packets,
//and add it to the list here
void NewPool();
protected:
// Methods inheriting classes impliment to allocate and free arrays of 256 packets
// Allocate a new array of 256 packets
virtual void NewPoolImpl(int nSize, CPacket*& pPool, int& nPitch) = 0;
// Free an array of 256 packets
virtual void FreePoolImpl(CPacket* pPool) = 0;
public:
// Removes a packet from the linked list of free packets,
//resets it, and adds a reference count
// Returns a pointer to the new packet the caller can use
//返回一个可用的空闲packet指针
inline CPacket* New()
{
// Make sure this is the only thread accessing this CPacketPool object at this time
m_pSection.Lock();
// If there aren't any packet pools yet, make one
//没有空闲链表了,申请256个packet,使用CPacket*指针指向
//然后将这256个Packet加入到空闲packet链表中
if ( m_nFree == 0 )
{
// Now, m_pFree will point to the last packet in that pool,
//and the first packet will point to null
NewPool();
}
//有空闲packet可用
// Make sure this caused the count of packets to go up, it should go to 256
ASSERT( m_nFree > 0 );
// Remove the last linked packet in the most recently added packet pool
//from the linked list of free packets
//从Free Packet链表中卸下一个来,空闲packet数目减少1
CPacket* pPacket = m_pFree; // Point pPacket at the last packet in the newest pool
m_pFree = m_pFree->m_pNext; // Move m_pFree to the second to last packet in the newest pool
m_nFree--; // Record that there is one fewer packet linked together in all the packet pools here
// We're done, let other threads stuck on a Lock line like that one above inside
m_pSection.Unlock();
// Prepare the packet for use 将packet清空
pPacket->Reset(); // Clear the values of the packet we just unlinked from the list
//将当前packet的记数增加1
pPacket->AddRef(); // Record that an external object is referencing this packet
// Return a pointer to the packet we just
return pPacket;
}
// Takes a pointer to a packet
// Links it back into the list of free packets we can grab to use quickly
//将参数指定的释放的packet加入到空闲packet链表
inline void Delete(CPacket* pPacket)
{
// Make sure the pointer points to a packet, and that packet doesn't still have a reference count
ASSERT( pPacket != NULL );
ASSERT( pPacket->m_nReference == 0 );//确保这个packet没有被引用了
// Make sure this is the only thread accessing this CPacketPool object at this time
m_pSection.Lock();
// Link the given packet back into the list of free ones we can use later
//将参数pPacket加入到Freelist的开头
pPacket->m_pNext = m_pFree; // Link the given packet into the front of the free list
m_pFree = pPacket;
//Free packet的数目增加1
m_nFree++; // Record one more packet is in the free list
// We're done, let other threads stuck on a Lock line like that one above inside
m_pSection.Unlock();
}
};
// End the group of lines to only include once, pragma once doesn't require an endif at the bottom
#endif // !defined(AFX_PACKET_H__3094C1CC_8AD2_49BD_BF10_EA639A9EAE6F__INCLUDED_)