摘要: 轉貼自:http://blog.csdn.net/dylgsy/查找了很多資料都找不到select模型的詳細用法,《Windows網絡編程》這本書上也只是寫了一個簡單的回應服務器,就連writefds的用法都沒講,也不知道什么時候利用“可寫”來發文件。這些都是我的疑問,相信很多研究網絡編程的同路人也碰到了我的這些問題。這些疑問在這篇文章中都解決了!耗費了偶很多的精力去猜測去思考!?
感覺一些...
閱讀全文
posted @
2007-01-19 16:44 CPP&&設計模式小屋 閱讀(2073) |
評論 (1) |
編輯 收藏
先講#和#@字符:這兩個比較類似:前者是將所代表的東西字符串化,后者是將代表的東西字符化。
而##可以說是個符號粘合劑。(Modern C++中用來做編譯時期內存檢查--2.1節 就是用了這個符號)
#的用法The number-sign or "stringizing" operator (#) converts macro parameters (after expansion) to string constants. It is used only with macros that take arguments. If it precedes a formal parameter in the macro definition, the actual argument passed by the macro invocation is enclosed in quotation marks and treated as a string literal. The string literal then replaces each occurrence of a combination of the stringizing operator and formal parameter within the macro definition.
White space preceding the first token of the actual argument and following the last token of the actual argument is ignored. Any white space between the tokens in the actual argument is reduced to a single white space in the resulting string literal. Thus, if a comment occurs between two tokens in the actual argument, it is reduced to a single white space. The resulting string literal is automatically concatenated with any adjacent string literals from which it is separated only by white space.
Further, if a character contained in the argument usually requires an escape sequence when used in a string literal (for example, the quotation mark (") or backslash (\) character), the necessary escape backslash is automatically inserted before the character. The following example shows a macro definition that includes the stringizing operator and a main function that invokes the macro:
#define stringer( x ) printf( #x "\n" )
int main()
{
stringer( In quotes in the printf function call\n );
stringer( "In quotes when printed to the screen"\n );
stringer( "This: \" prints an escaped double quote" );
}
Such invocations would be expanded during preprocessing, producing the following code:
int main()
{
printf( "In quotes in the printf function call\n" "\n" );
printf( "\"In quotes when printed to the screen\"\n" "\n" );
printf( "\"This: \\\" prints an escaped double quote\"" "\n" );
}
When the program is run, screen output for each line is as follows:
In quotes in the printf function call
"In quotes when printed to the screen"
"This: \" prints an escaped double quotation mark"
Microsoft Specific
The Microsoft C (versions 6.0 and earlier) extension to the ANSI C standard that previously expanded macro formal arguments appearing inside string literals and character constants is no longer supported. Code that relied on this extension should be rewritten using the stringizing (#) operator.
END Microsoft Specific
#@的用法
Microsoft Specific
The charizing operator can be used only with arguments of macros. If #@ precedes a formal parameter in the definition of the macro, the actual argument is enclosed in single quotation marks and treated as a character when the macro is expanded. For example:
#define makechar(x) #@x
causes the statement
a = makechar(b);
to be expanded to
a = 'b';
The single-quotation character cannot be used with the charizing operator.
END Microsoft Specific
##的用法
The double-number-sign or "token-pasting" operator (##), which is sometimes called the "merging" operator, is used in both object-like and function-like macros. It permits separate tokens to be joined into a single token and therefore cannot be the first or last token in the macro definition.
If a formal parameter in a macro definition is preceded or followed by the token-pasting operator, the formal parameter is immediately replaced by the unexpanded actual argument. Macro expansion is not performed on the argument prior to replacement.
Then, each occurrence of the token-pasting operator in token-string is removed, and the tokens preceding and following it are concatenated. The resulting token must be a valid token. If it is, the token is scanned for possible replacement if it represents a macro name. The identifier represents the name by which the concatenated tokens will be known in the program before replacement. Each token represents a token defined elsewhere, either within the program or on the compiler command line. White space preceding or following the operator is optional.
This example illustrates use of both the stringizing and token-pasting operators in specifying program output:
#define paster( n ) printf( "token" #n " = %d", token##n )
int token9 = 9;
If a macro is called with a numeric argument like
paster( 9 );
the macro yields
printf( "token" "9" " = %d", token9 );
which becomes
printf( "token9 = %d", token9 );
posted @
2007-01-19 14:03 CPP&&設計模式小屋 閱讀(1612) |
評論 (1) |
編輯 收藏
幫朋友發個招聘公告:
招聘C/C++開發人員
要求:??熟悉STL,各類常用算法
工作地點:?上海
公司性質:?公司為某美國Nasdaq上市公司,員工福利與待遇從優,薪資高于行業平均水平,福利待遇頗佳。
主要工作內容:?網絡IM類服務器端、客戶端開發。
來信必復 mailto:sunhuiNO1@hotmail.com?
有興趣的趕快Mail。
posted @
2007-01-15 13:24 CPP&&設計模式小屋 閱讀(1448) |
評論 (10) |
編輯 收藏
作者:侯志江
編寫自己的一個ping程序,可以說是許多人邁出網絡編程的第一步吧!!這個ping程序的源代碼經過我的修改和調試,基本上可以取代windows中自帶的ping程序. 各個模塊后都有我的詳細注釋和修改日志,希望能夠對大家的學習有所幫助!!
/*? 本程序的主要源代碼來自MSDN網站, 筆者只是做了一些改進和注釋! 另外需要注意的是在Build之前,必須加入ws2_32.lib庫文件,否則會提示"error LNK2001:"的錯誤!*/
/******************************************************************************\
| Version 1.1 修改記錄:????????????????????????????????????????????????????????????????????????????????????????????????????????????? |
|??? <1> 解決了socket阻塞的問題,從而能夠正確地處理超時的請求!??????????????????????????????????????????????????????? |
|----------------------------------------------------------------------------------------------------|
| Version 1.2 修改記錄:????????????????????????????????????????????????????????????????????????????????????????????????????????????? |
|??? <1> 增加了由用戶控制發送ICMP包的數目的功能(即命令的第二個參數).????????????????????????????????????????????? |???
|??? <2> 增加了對ping結果的統計功能.?????????????????????????????????????????????????????????????????????????????????????????? |
\******************************************************************************/
#pragma pack(4)
#include
#include
#include
#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0
#define ICMP_MIN 8 // minimum 8 byte icmp packet (just header)
/* The IP header */
typedef struct iphdr {
unsigned int h_len:4; // length of the header
unsigned int version:4; // Version of IP
unsigned char tos; // Type of service
unsigned short total_len; // total length of the packet
unsigned short ident; // unique identifier
unsigned short frag_and_flags; // flags
unsigned char ttl;
unsigned char proto; // protocol (TCP, UDP etc)
unsigned short checksum; // IP checksum
unsigned int sourceIP;
unsigned int destIP;
}IpHeader;
//
// ICMP header
//
typedef struct icmphdr {
BYTE i_type;
BYTE i_code; /* type sub code */
USHORT i_cksum;
USHORT i_id;
USHORT i_seq;
/* This is not the std header, but we reserve space for time */
ULONG timestamp;
}IcmpHeader;
#define STATUS_FAILED 0xFFFF
#define DEF_PACKET_SIZE??? 32
#define DEF_PACKET_NUMBER? 4??? /* 發送數據報的個數 */
#define MAX_PACKET 1024
#define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s))
#define xfree(p) HeapFree (GetProcessHeap(),0,(p))
void fill_icmp_data(char *, int);
USHORT checksum(USHORT *, int);
int decode_resp(char *,int ,struct sockaddr_in *);
void Usage(char *progname){
fprintf(stderr,"Usage:\n");
fprintf(stderr,"%s [number of packets] [data_size]\n",progname);
fprintf(stderr,"datasize can be up to 1Kb\n");
ExitProcess(STATUS_FAILED);
}
int main(int argc, char **argv){
WSADATA wsaData;
SOCKET sockRaw;
struct sockaddr_in dest,from;
struct hostent * hp;
int bread,datasize,times;
int fromlen = sizeof(from);
int timeout = 1000;
int statistic = 0;? /* 用于統計結果 */?
char *dest_ip;
char *icmp_data;
char *recvbuf;
unsigned int addr=0;
USHORT seq_no = 0;
if (WSAStartup(MAKEWORD(2,1),&wsaData) != 0){
fprintf(stderr,"WSAStartup failed: %d\n",GetLastError());
ExitProcess(STATUS_FAILED);
}
if (argc <2 ) {
Usage(argv[0]);
}
sockRaw = WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL, 0,WSA_FLAG_OVERLAPPED);
//
//注:為了使用發送接收超時設置(即設置SO_RCVTIMEO, SO_SNDTIMEO),
//??? 必須將標志位設為WSA_FLAG_OVERLAPPED !
//
if (sockRaw == INVALID_SOCKET) {
fprintf(stderr,"WSASocket() failed: %d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
bread = setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,
sizeof(timeout));
if(bread == SOCKET_ERROR) {
fprintf(stderr,"failed to set recv timeout: %d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
timeout = 1000;
bread = setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,
sizeof(timeout));
if(bread == SOCKET_ERROR) {
fprintf(stderr,"failed to set send timeout: %d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
memset(&dest,0,sizeof(dest));
hp = gethostbyname(argv[1]);
if (!hp){
addr = inet_addr(argv[1]);
}
if ((!hp) && (addr == INADDR_NONE) ) {
fprintf(stderr,"Unable to resolve %s\n",argv[1]);
ExitProcess(STATUS_FAILED);
}
if (hp != NULL)
memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length);
else
dest.sin_addr.s_addr = addr;
if (hp)
dest.sin_family = hp->h_addrtype;
else
dest.sin_family = AF_INET;
dest_ip = inet_ntoa(dest.sin_addr);
//
//? atoi函數原型是: int atoi( const char *string );
//? The return value is 0 if the input cannot be converted to an integer !
//
if(argc>2)
{
?times=atoi(argv[2]);
?if(times == 0)
? times=DEF_PACKET_NUMBER;
}
else
??? times=DEF_PACKET_NUMBER;
if (argc >3)
{
?datasize = atoi(argv[3]);
??? if (datasize == 0)
??????? datasize = DEF_PACKET_SIZE;
?if (datasize >1024)?? /* 用戶給出的數據包大小太大 */
?{
? fprintf(stderr,"WARNING : data_size is too large !\n");
? datasize = DEF_PACKET_SIZE;
?}
}
else
??? datasize = DEF_PACKET_SIZE;
datasize += sizeof(IcmpHeader);
icmp_data = (char*)xmalloc(MAX_PACKET);
recvbuf = (char*)xmalloc(MAX_PACKET);
if (!icmp_data) {
fprintf(stderr,"HeapAlloc failed %d\n",GetLastError());
ExitProcess(STATUS_FAILED);
}
memset(icmp_data,0,MAX_PACKET);
fill_icmp_data(icmp_data,datasize);
//
//顯示提示信息
//
fprintf(stdout,"\nPinging %s ....\n\n",dest_ip);
for(int i=0;i{
int bwrote;
((IcmpHeader*)icmp_data)->i_cksum = 0;
((IcmpHeader*)icmp_data)->timestamp = GetTickCount();
((IcmpHeader*)icmp_data)->i_seq = seq_no++;
((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data,datasize);
bwrote = sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest));
if (bwrote == SOCKET_ERROR){
if (WSAGetLastError() == WSAETIMEDOUT) {
printf("Request timed out.\n");
continue;
}
fprintf(stderr,"sendto failed: %d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
if (bwrote < datasize ) {
fprintf(stdout,"Wrote %d bytes\n",bwrote);
}
bread = recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,&fromlen);
if (bread == SOCKET_ERROR){
if (WSAGetLastError() == WSAETIMEDOUT) {
printf("Request timed out.\n");
continue;
}
fprintf(stderr,"recvfrom failed: %d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
if(!decode_resp(recvbuf,bread,&from))
?statistic++; /* 成功接收的數目++ */
Sleep(1000);
}
?
/*
Display the statistic result
*/
fprintf(stdout,"\nPing statistics for %s \n",dest_ip);
fprintf(stdout,"??? Packets: Sent = %d,Received = %d, Lost = %d (%2.0f%% loss)\n",times,
???? statistic,(times-statistic),(float)(times-statistic)/times*100);
WSACleanup();
return 0;
}
/*
The response is an IP packet. We must decode the IP header to locate
the ICMP data
*/
int decode_resp(char *buf, int bytes,struct sockaddr_in *from) {
IpHeader *iphdr;
IcmpHeader *icmphdr;
unsigned short iphdrlen;
iphdr = (IpHeader *)buf;
iphdrlen = (iphdr->h_len) * 4 ; // number of 32-bit words *4 = bytes
if (bytes < iphdrlen + ICMP_MIN) {
printf("Too few bytes from %s\n",inet_ntoa(from->sin_addr));
}
icmphdr = (IcmpHeader*)(buf + iphdrlen);
if (icmphdr->i_type != ICMP_ECHOREPLY) {
fprintf(stderr,"non-echo type %d recvd\n",icmphdr->i_type);
return 1;
}
if (icmphdr->i_id != (USHORT)GetCurrentProcessId()) {
fprintf(stderr,"someone else's packet!\n");
return 1;
}
printf("%d bytes from %s:",bytes, inet_ntoa(from->sin_addr));
printf(" icmp_seq = %d. ",icmphdr->i_seq);
printf(" time: %d ms ",GetTickCount()-icmphdr->timestamp);
printf("\n");
return 0;
}
USHORT checksum(USHORT *buffer, int size) {
unsigned long cksum=0;
while(size >1) {
cksum+=*buffer++;
size -=sizeof(USHORT);
}
if(size) {
cksum += *(UCHAR*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}
/*
Helper function to fill in various stuff in our ICMP request.
*/
void fill_icmp_data(char * icmp_data, int datasize){
IcmpHeader *icmp_hdr;
char *datapart;
icmp_hdr = (IcmpHeader*)icmp_data;
icmp_hdr->i_type = ICMP_ECHO;
icmp_hdr->i_code = 0;
icmp_hdr->i_id = (USHORT)GetCurrentProcessId();
icmp_hdr->i_cksum = 0;
icmp_hdr->i_seq = 0;
datapart = icmp_data + sizeof(IcmpHeader);
//
// Place some junk in the buffer.
//
memset(datapart,'E', datasize - sizeof(IcmpHeader));
}
/******************* 附: ping命令執行時顯示的畫面 ***************\
*? C:\Documents and Settings\houzhijiang>ping 236.56.54.12?????????????? *
*????????????????????????????????????????????????????????????????????????????????????????????????????? *
*? Pinging 236.56.54.12 with 32 bytes of data:????????????????????????????????????? *
*????????????????????????????????????????????????????????????????????????????????????????????????????? *
*? Request timed out.???????????????????????????????????????????????????????????????????????? *
*? Request timed out.???????????????????????????????????????????????????????????????????????? *
*? Request timed out.???????????????????????????????????????????????????????????????????????? *
*? Request timed out.???????????????????????????????????????????????????????????????????????? *
*????????????????????????????????????????????????????????????????????????????????????????????????????? *
*? Ping statistics for 236.56.54.12:????????????????????????????????????????????????????? *
*???? Packets: Sent = 4, Received = 0, Lost = 4 (100% loss),????????????????? *
*????????????????????????????????????????????????????????????????????????????????????????????????????? *
\*********************************************************/
/*********************************************************\
*? C:\Documents and Settings\houzhijiang>ping 127.0.0.1??????????????????? *
*???????????????????????????????????????????????????????????????????????????????????????????????????? *
*? Pinging 127.0.0.1 with 32 bytes of data:????????????????????????????????????????? *
*???????????????????????????????????????????????????????????????????????????????????????????????????? *
*? Reply from 127.0.0.1: bytes=32 time<1ms TTL=128??????????????????????? *
*? Reply from 127.0.0.1: bytes=32 time<1ms TTL=128??????????????????????? *
*? Reply from 127.0.0.1: bytes=32 time<1ms TTL=128??????????????????????? *
*? Reply from 127.0.0.1: bytes=32 time<1ms TTL=128??????????????????????? *
*???????????????????????????????????????????????????????????????????????????????????????????????????? *
*? Ping statistics for 127.0.0.1:????????????????????????????????????????????????????????? *
*???? Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),??????????????????? *
*? Approximate round trip times in milli-seconds:???????????????????????????????? *
*???? Minimum = 0ms, Maximum = 0ms, Average = 0ms??????????????????????? *
*??????????????????????????????????????????????????????????????????????????????????????????????????? *
\********************************************************/
posted @
2007-01-05 13:46 CPP&&設計模式小屋 閱讀(1252) |
評論 (1) |
編輯 收藏
P2P之UDP穿透NAT的原理與實現(附源代碼)
作者:shootingstars | 日期:2004-05-25 | 字體:大中小 |
P2P 之 UDP穿透NAT的原理與實現(附源代碼)
原創:shootingstars
參考:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
論壇上經常有對P2P原理的討論,但是討論歸討論,很少有實質的東西產生(源代碼)。呵呵,在這里我就用自己實現的一個源代碼來說明UDP穿越NAT的原理。
首先先介紹一些基本概念:
??? NAT(Network Address Translators),網絡地址轉換:網絡地址轉換是在IP地址日益缺乏的情況下產生的,它的主要目的就是為了能夠地址重用。NAT分為兩大類,基本的NAT和NAPT(Network Address/Port Translator)。
??? 最開始NAT是運行在路由器上的一個功能模塊。
???
??? 最先提出的是基本的NAT,它的產生基于如下事實:一個私有網絡(域)中的節點中只有很少的節點需要與外網連接(呵呵,這是在上世紀90年代中期提出的)。那么這個子網中其實只有少數的節點需要全球唯一的IP地址,其他的節點的IP地址應該是可以重用的。
??? 因此,基本的NAT實現的功能很簡單,在子網內使用一個保留的IP子網段,這些IP對外是不可見的。子網內只有少數一些IP地址可以對應到真正全球唯一的IP地址。如果這些節點需要訪問外部網絡,那么基本NAT就負責將這個節點的子網內IP轉化為一個全球唯一的IP然后發送出去。(基本的NAT會改變IP包中的原IP地址,但是不會改變IP包中的端口)
??? 關于基本的NAT可以參看RFC 1631
???
??? 另外一種NAT叫做NAPT,從名稱上我們也可以看得出,NAPT不但會改變經過這個NAT設備的IP數據報的IP地址,還會改變IP數據報的TCP/UDP端口。基本NAT的設備可能我們見的不多(呵呵,我沒有見到過),NAPT才是我們真正討論的主角。看下圖:
??????????????????????????????? Server S1????????????????????????
???????????????????????? 18.181.0.31:1235?????????????????????????
????????????????????????????????????? |
????????? ^? Session 1 (A-S1)? ^????? |?
????????? |? 18.181.0.31:1235? |????? |??
????????? v 155.99.25.11:62000 v????? |???
????????????????????????????????????? |
???????????????????????????????????? NAT
???????????????????????????????? 155.99.25.11
????????????????????????????????????? |
????????? ^? Session 1 (A-S1)? ^????? |?
????????? |? 18.181.0.31:1235? |????? |?
????????? v?? 10.0.0.1:1234??? v????? |?
????????????????????????????????????? |
?????????????????????????????????? Client A
??????????????????????????????? 10.0.0.1:1234
??? 有一個私有網絡10.*.*.*,Client A是其中的一臺計算機,這個網絡的網關(一個NAT設備)的外網IP是155.99.25.11(應該還有一個內網的IP地址,比如10.0.0.10)。如果Client A中的某個進程(這個進程創建了一個UDP Socket,這個Socket綁定1234端口)想訪問外網主機18.181.0.31的1235端口,那么當數據包通過NAT時會發生什么事情呢?
??? 首先NAT會改變這個數據包的原IP地址,改為155.99.25.11。接著NAT會為這個傳輸創建一個Session(Session是一個抽象的概念,如果是TCP,也許Session是由一個SYN包開始,以一個FIN包結束。而UDP呢,以這個IP的這個端口的第一個UDP開始,結束呢,呵呵,也許是幾分鐘,也許是幾小時,這要看具體的實現了)并且給這個Session分配一個端口,比如62000,然后改變這個數據包的源端口為62000。所以本來是(10.0.0.1:1234->18.181.0.31:1235)的數據包到了互聯網上變為了(155.99.25.11:62000->18.181.0.31:1235)。
??? 一旦NAT創建了一個Session后,NAT會記住62000端口對應的是10.0.0.1的1234端口,以后從18.181.0.31發送到62000端口的數據會被NAT自動的轉發到10.0.0.1上。(注意:這里是說18.181.0.31發送到62000端口的數據會被轉發,其他的IP發送到這個端口的數據將被NAT拋棄)這樣Client A就與Server S1建立以了一個連接。
??? 呵呵,上面的基礎知識可能很多人都知道了,那么下面是關鍵的部分了。
??? 看看下面的情況:
??? Server S1???????????????????????????????????? Server S2
?18.181.0.31:1235????????????????????????????? 138.76.29.7:1235
??????? |???????????????????????????????????????????? |
??????? |???????????????????????????????????????????? |
??????? +----------------------+----------------------+
?????????????????????????????? |
?? ^? Session 1 (A-S1)? ^????? |????? ^? Session 2 (A-S2)? ^
?? |? 18.181.0.31:1235? |????? |????? |? 138.76.29.7:1235? |
?? v 155.99.25.11:62000 v????? |????? v 155.99.25.11:62000 v
?????????????????????????????? |
??????????????????????????? Cone NAT
????????????????????????? 155.99.25.11
?????????????????????????????? |
?? ^? Session 1 (A-S1)? ^????? |????? ^? Session 2 (A-S2)? ^
?? |? 18.181.0.31:1235? |????? |????? |? 138.76.29.7:1235? |
?? v?? 10.0.0.1:1234??? v????? |????? v?? 10.0.0.1:1234??? v
?????????????????????????????? |
??????????????????????????? Client A
???????????????????????? 10.0.0.1:1234
??? 接上面的例子,如果Client A的原來那個Socket(綁定了1234端口的那個UDP Socket)又接著向另外一個Server S2發送了一個UDP包,那么這個UDP包在通過NAT時會怎么樣呢?
??? 這時可能會有兩種情況發生,一種是NAT再次創建一個Session,并且再次為這個Session分配一個端口號(比如:62001)。另外一種是NAT再次創建一個Session,但是不會新分配一個端口號,而是用原來分配的端口號62000。前一種NAT叫做Symmetric NAT,后一種叫做Cone NAT。我們期望我們的NAT是第二種,呵呵,如果你的NAT剛好是第一種,那么很可能會有很多P2P軟件失靈。(可以慶幸的是,現在絕大多數的NAT屬于后者,即Cone NAT)
??
??? 好了,我們看到,通過NAT,子網內的計算機向外連結是很容易的(NAT相當于透明的,子網內的和外網的計算機不用知道NAT的情況)。
??? 但是如果外部的計算機想訪問子網內的計算機就比較困難了(而這正是P2P所需要的)。
??? 那么我們如果想從外部發送一個數據報給內網的計算機有什么辦法呢?首先,我們必須在內網的NAT上打上一個“洞”(也就是前面我們說的在NAT上建立一個Session),這個洞不能由外部來打,只能由內網內的主機來打。而且這個洞是有方向的,比如從內部某臺主機(比如:192.168.0.10)向外部的某個IP(比如:219.237.60.1)發送一個UDP包,那么就在這個內網的NAT設備上打了一個方向為219.237.60.1的“洞”,(這就是稱為UDP Hole Punching的技術)以后219.237.60.1就可以通過這個洞與內網的192.168.0.10聯系了。(但是其他的IP不能利用這個洞)。
呵呵,現在該輪到我們的正題P2P了。有了上面的理論,實現兩個內網的主機通訊就差最后一步了:那就是雞生蛋還是蛋生雞的問題了,兩邊都無法主動發出連接請求,誰也不知道誰的公網地址,那我們如何來打這個洞呢?我們需要一個中間人來聯系這兩個內網主機。
??? 現在我們來看看一個P2P軟件的流程,以下圖為例:
?????????????????????? Server S (219.237.60.1)
????????????????????????? |
????????????????????????? |
?? +----------------------+----------------------+
?? |???????????????????????????????????????????? |
?NAT A (外網IP:202.187.45.3)???????????????? NAT B (外網IP:187.34.1.56)
?? |?? (內網IP:192.168.0.1)????????????????????? | (內網IP:192.168.0.1)
?? |???????????????????????????????????????????? |
Client A? (192.168.0.20:4000)???????????? Client B (192.168.0.10:40000)
??? 首先,Client A登錄服務器,NAT A為這次的Session分配了一個端口60000,那么Server S收到的Client A的地址是202.187.45.3:60000,這就是Client A的外網地址了。同樣,Client B登錄Server S,NAT B給此次Session分配的端口是40000,那么Server S收到的B的地址是187.34.1.56:40000。
??? 此時,Client A與Client B都可以與Server S通信了。如果Client A此時想直接發送信息給Client B,那么他可以從Server S那兒獲得B的公網地址187.34.1.56:40000,是不是Client A向這個地址發送信息Client B就能收到了呢?答案是不行,因為如果這樣發送信息,NAT B會將這個信息丟棄(因為這樣的信息是不請自來的,為了安全,大多數NAT都會執行丟棄動作)。現在我們需要的是在NAT B上打一個方向為202.187.45.3(即Client A的外網地址)的洞,那么Client A發送到187.34.1.56:40000的信息,Client B就能收到了。這個打洞命令由誰來發呢,呵呵,當然是Server S。
??? 總結一下這個過程:如果Client A想向Client B發送信息,那么Client A發送命令給Server S,請求Server S命令Client B向Client A方向打洞。呵呵,是不是很繞口,不過沒關系,想一想就很清楚了,何況還有源代碼呢(侯老師說過:在源代碼面前沒有秘密 8)),然后Client A就可以通過Client B的外網地址與Client B通信了。
???
??? 注意:以上過程只適合于Cone NAT的情況,如果是Symmetric NAT,那么當Client B向Client A打洞的端口已經重新分配了,Client B將無法知道這個端口(如果Symmetric NAT的端口是順序分配的,那么我們或許可以猜測這個端口號,可是由于可能導致失敗的因素太多,我們不推薦這種猜測端口的方法)。
???
??? 下面是一個模擬P2P聊天的過程的源代碼,過程很簡單,P2PServer運行在一個擁有公網IP的計算機上,P2PClient運行在兩個不同的NAT后(注意,如果兩個客戶端運行在一個NAT后,本程序很可能不能運行正常,這取決于你的NAT是否支持loopback translation,詳見http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt,當然,此問題可以通過雙方先嘗試連接對方的內網IP來解決,但是這個代碼只是為了驗證原理,并沒有處理這些問題),后登錄的計算機可以獲得先登錄計算機的用戶名,后登錄的計算機通過send username message的格式來發送消息。如果發送成功,說明你已取得了直接與對方連接的成功。
??? 程序現在支持三個命令:send , getu , exit
???
??? send格式:send username message
??? 功能:發送信息給username
???
??? getu格式:getu
??? 功能:獲得當前服務器用戶列表
???
??? exit格式:exit
??? 功能:注銷與服務器的連接(服務器不會自動監測客戶是否吊線)
???????
??? 代碼很短,相信很容易懂,如果有什么問題,可以給我發郵件zhouhuis22@sina.com? 或者在CSDN上發送短消息。同時,歡迎轉發此文,但希望保留作者版權8-)。
???
??? 最后感謝CSDN網友 PiggyXP 和 Seilfer的測試幫助
P2PServer.c
/* P2P 程序服務端
?*
?* 文件名:P2PServer.c
?*
?* 日期:2004-5-21
?*
?* 作者:shootingstars(zhouhuis22@sina.com)
?*
?*/
#pragma comment(lib, "ws2_32.lib")
#include "windows.h"
#include "..\proto.h"
#include "..\Exception.h"
UserList ClientList;
void InitWinSock()
{
?WSADATA wsaData;
?if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
?{
??printf("Windows sockets 2.2 startup");
??throw Exception("");
?}
?else{
??printf("Using %s (Status: %s)\n",
???wsaData.szDescription, wsaData.szSystemStatus);
??printf("with API versions %d.%d to %d.%d\n\n",
???LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
???LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
??
?}
}
SOCKET mksock(int type)
{
?SOCKET sock = socket(AF_INET, type, 0);
?if (sock < 0)
?{
??????? printf("create socket error");
??throw Exception("");
?}
?return sock;
}
stUserListNode GetUser(char *username)
{
?for(UserList::iterator UserIterator=ClientList.begin();
??????UserIterator!=ClientList.end();
???????++UserIterator)
?{
??if( strcmp( ((*UserIterator)->userName), username) == 0 )
???return *(*UserIterator);
?}
?throw Exception("not find this user");
}
int main(int argc, char* argv[])
{
?try{
??InitWinSock();
??
??SOCKET PrimaryUDP;
??PrimaryUDP = mksock(SOCK_DGRAM);
??sockaddr_in local;
??local.sin_family=AF_INET;
??local.sin_port= htons(SERVER_PORT);
??local.sin_addr.s_addr = htonl(INADDR_ANY);
??int nResult=bind(PrimaryUDP,(sockaddr*)&local,sizeof(sockaddr));
??if(nResult==SOCKET_ERROR)
???throw Exception("bind error");
??sockaddr_in sender;
??stMessage recvbuf;
??memset(&recvbuf,0,sizeof(stMessage));
??// 開始主循環.
??// 主循環負責下面幾件事情:
??// 一:讀取客戶端登陸和登出消息,記錄客戶列表
??// 二:轉發客戶p2p請求
??for(;;)
??{
???int dwSender = sizeof(sender);
???int ret = recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(stMessage), 0, (sockaddr *)&sender, &dwSender);
???if(ret <= 0)
???{
????printf("recv error");
????continue;
???}
???else
???{
????int messageType = recvbuf.iMessageType;
????switch(messageType){
????case LOGIN:
?????{
??????//? 將這個用戶的信息記錄到用戶列表中
??????printf("has a user login : %s\n", recvbuf.message.loginmember.userName);
??????stUserListNode *currentuser = new stUserListNode();
??????strcpy(currentuser->userName, recvbuf.message.loginmember.userName);
??????currentuser->ip = ntohl(sender.sin_addr.S_un.S_addr);
??????currentuser->port = ntohs(sender.sin_port);
??????
??????ClientList.push_back(currentuser);
??????// 發送已經登陸的客戶信息
??????int nodecount = (int)ClientList.size();
??????sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
??????for(UserList::iterator UserIterator=ClientList.begin();
????????UserIterator!=ClientList.end();
????????++UserIterator)
??????{
???????sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender));
??????}
??????break;
?????}
????case LOGOUT:
?????{
??????// 將此客戶信息刪除
??????printf("has a user logout : %s\n", recvbuf.message.logoutmember.userName);
??????UserList::iterator removeiterator = NULL;
??????for(UserList::iterator UserIterator=ClientList.begin();
???????UserIterator!=ClientList.end();
???????++UserIterator)
??????{
???????if( strcmp( ((*UserIterator)->userName), recvbuf.message.logoutmember.userName) == 0 )
???????{
????????removeiterator = UserIterator;
????????break;
???????}
??????}
??????if(removeiterator != NULL)
???????ClientList.remove(*removeiterator);
??????break;
?????}
????case P2PTRANS:
?????{
??????// 某個客戶希望服務端向另外一個客戶發送一個打洞消息
??????printf("%s wants to p2p %s\n",inet_ntoa(sender.sin_addr),recvbuf.message.translatemessage.userName);
??????stUserListNode node = GetUser(recvbuf.message.translatemessage.userName);
??????sockaddr_in remote;
??????remote.sin_family=AF_INET;
??????remote.sin_port= htons(node.port);
??????remote.sin_addr.s_addr = htonl(node.ip);
??????in_addr tmp;
??????tmp.S_un.S_addr = htonl(node.ip);
??????printf("the address is %s,and port is %d\n",inet_ntoa(tmp), node.port);
??????stP2PMessage transMessage;
??????transMessage.iMessageType = P2PSOMEONEWANTTOCALLYOU;
??????transMessage.iStringLen = ntohl(sender.sin_addr.S_un.S_addr);
??????transMessage.Port = ntohs(sender.sin_port);
???????????????????????
??????sendto(PrimaryUDP,(const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr *)&remote, sizeof(remote));
??????break;
?????}
????
????case GETALLUSER:
?????{
??????int command = GETALLUSER;
??????sendto(PrimaryUDP, (const char*)&command, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
??????int nodecount = (int)ClientList.size();
??????sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
??????for(UserList::iterator UserIterator=ClientList.begin();
????????UserIterator!=ClientList.end();
????????++UserIterator)
??????{
???????sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender));
??????}
??????break;
?????}
????}
???}
??}
?}
?catch(Exception &e)
?{
??printf(e.GetMessage());
??return 1;
?}
?return 0;
}
/* P2P 程序客戶端
?*
?* 文件名:P2PClient.c
?*
?* 日期:2004-5-21
?*
?* 作者:shootingstars(zhouhuis22@sina.com)
?*
?*/
#pragma comment(lib,"ws2_32.lib")
#include "windows.h"
#include "..\proto.h"
#include "..\Exception.h"
#include <iostream>
using namespace std;
UserList ClientList;
?
#define COMMANDMAXC 256
#define MAXRETRY??? 5
SOCKET PrimaryUDP;
char UserName[10];
char ServerIP[20];
bool RecvedACK;
void InitWinSock()
{
?WSADATA wsaData;
?if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
?{
??printf("Windows sockets 2.2 startup");
??throw Exception("");
?}
?else{
??printf("Using %s (Status: %s)\n",
???wsaData.szDescription, wsaData.szSystemStatus);
??printf("with API versions %d.%d to %d.%d\n\n",
???LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
???LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
?}
}
SOCKET mksock(int type)
{
?SOCKET sock = socket(AF_INET, type, 0);
?if (sock < 0)
?{
??????? printf("create socket error");
??throw Exception("");
?}
?return sock;
}
stUserListNode GetUser(char *username)
{
?for(UserList::iterator UserIterator=ClientList.begin();
??????UserIterator!=ClientList.end();
???????++UserIterator)
?{
??if( strcmp( ((*UserIterator)->userName), username) == 0 )
???return *(*UserIterator);
?}
?throw Exception("not find this user");
}
void BindSock(SOCKET sock)
{
?sockaddr_in sin;
?sin.sin_addr.S_un.S_addr = INADDR_ANY;
?sin.sin_family = AF_INET;
?sin.sin_port = 0;
?
?if (bind(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0)
??throw Exception("bind error");
}
void ConnectToServer(SOCKET sock,char *username, char *serverip)
{
?sockaddr_in remote;
?remote.sin_addr.S_un.S_addr = inet_addr(serverip);
?remote.sin_family = AF_INET;
?remote.sin_port = htons(SERVER_PORT);
?
?stMessage sendbuf;
?sendbuf.iMessageType = LOGIN;
?strncpy(sendbuf.message.loginmember.userName, username, 10);
?sendto(sock, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote,sizeof(remote));
?int usercount;
?int fromlen = sizeof(remote);
?int iread = recvfrom(sock, (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote, &fromlen);
?if(iread<=0)
?{
??throw Exception("Login error\n");
?}
?// 登錄到服務端后,接收服務端發來的已經登錄的用戶的信息
?cout<<"Have "<<usercount<<" users logined server:"<<endl;
?for(int i = 0;i<usercount;i++)
?{
??stUserListNode *node = new stUserListNode;
??recvfrom(sock, (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote, &fromlen);
??ClientList.push_back(node);
??cout<<"Username:"<<node->userName<<endl;
??in_addr tmp;
??tmp.S_un.S_addr = htonl(node->ip);
??cout<<"UserIP:"<<inet_ntoa(tmp)<<endl;
??cout<<"UserPort:"<<node->port<<endl;
??cout<<""<<endl;
?}
}
void OutputUsage()
{
?cout<<"You can input you command:\n"
??<<"Command Type:\"send\",\"exit\",\"getu\"\n"
??<<"Example : send Username Message\n"
??<<"????????? exit\n"
??<<"????????? getu\n"
??<<endl;
}
/* 這是主要的函數:發送一個消息給某個用戶(C)
?*流程:直接向某個用戶的外網IP發送消息,如果此前沒有聯系過
?*????? 那么此消息將無法發送,發送端等待超時。
?*????? 超時后,發送端將發送一個請求信息到服務端,
?*????? 要求服務端發送給客戶C一個請求,請求C給本機發送打洞消息
?*????? 以上流程將重復MAXRETRY次
?*/
bool SendMessageTo(char *UserName, char *Message)
{
?char realmessage[256];
?unsigned int UserIP;
?unsigned short UserPort;
?bool FindUser = false;
?for(UserList::iterator UserIterator=ClientList.begin();
??????UserIterator!=ClientList.end();
??????++UserIterator)
?{
??if( strcmp( ((*UserIterator)->userName), UserName) == 0 )
??{
???UserIP = (*UserIterator)->ip;
???UserPort = (*UserIterator)->port;
???FindUser = true;
??}
?}
?if(!FindUser)
??return false;
?strcpy(realmessage, Message);
?for(int i=0;i<MAXRETRY;i++)
?{
??RecvedACK = false;
??sockaddr_in remote;
??remote.sin_addr.S_un.S_addr = htonl(UserIP);
??remote.sin_family = AF_INET;
??remote.sin_port = htons(UserPort);
??stP2PMessage MessageHead;
??MessageHead.iMessageType = P2PMESSAGE;
??MessageHead.iStringLen = (int)strlen(realmessage)+1;
??int isend = sendto(PrimaryUDP, (const char *)&MessageHead, sizeof(MessageHead), 0, (const sockaddr*)&remote, sizeof(remote));
??isend = sendto(PrimaryUDP, (const char *)&realmessage, MessageHead.iStringLen, 0, (const sockaddr*)&remote, sizeof(remote));
??
??// 等待接收線程將此標記修改
??for(int j=0;j<10;j++)
??{
???if(RecvedACK)
????return true;
???else
????Sleep(300);
??}
??// 沒有接收到目標主機的回應,認為目標主機的端口映射沒有
??// 打開,那么發送請求信息給服務器,要服務器告訴目標主機
??// 打開映射端口(UDP打洞)
??sockaddr_in server;
??server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
??server.sin_family = AF_INET;
??server.sin_port = htons(SERVER_PORT);
?
??stMessage transMessage;
??transMessage.iMessageType = P2PTRANS;
??strcpy(transMessage.message.translatemessage.userName, UserName);
??sendto(PrimaryUDP, (const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr*)&server, sizeof(server));
??Sleep(100);// 等待對方先發送信息。
?}
?return false;
}
// 解析命令,暫時只有exit和send命令
// 新增getu命令,獲取當前服務器的所有用戶
void ParseCommand(char * CommandLine)
{
?if(strlen(CommandLine)<4)
??return;
?char Command[10];
?strncpy(Command, CommandLine, 4);
?Command[4]='\0';
?if(strcmp(Command,"exit")==0)
?{
??stMessage sendbuf;
??sendbuf.iMessageType = LOGOUT;
??strncpy(sendbuf.message.logoutmember.userName, UserName, 10);
??sockaddr_in server;
??server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
??server.sin_family = AF_INET;
??server.sin_port = htons(SERVER_PORT);
??sendto(PrimaryUDP,(const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr *)&server, sizeof(server));
??shutdown(PrimaryUDP, 2);
??closesocket(PrimaryUDP);
??exit(0);
?}
?else if(strcmp(Command,"send")==0)
?{
??char sendname[20];
??char message[COMMANDMAXC];
??int i;
??for(i=5;;i++)
??{
???if(CommandLine[i]!=' ')
????sendname[i-5]=CommandLine[i];
???else
???{
????sendname[i-5]='\0';
????break;
???}
??}
??strcpy(message, &(CommandLine[i+1]));
??if(SendMessageTo(sendname, message))
???printf("Send OK!\n");
??else
???printf("Send Failure!\n");
?}
?else if(strcmp(Command,"getu")==0)
?{
??int command = GETALLUSER;
??sockaddr_in server;
??server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
??server.sin_family = AF_INET;
??server.sin_port = htons(SERVER_PORT);
??sendto(PrimaryUDP,(const char*)&command, sizeof(command), 0, (const sockaddr *)&server, sizeof(server));
?}
}
// 接受消息線程
DWORD WINAPI RecvThreadProc(LPVOID lpParameter)
{
?sockaddr_in remote;
?int sinlen = sizeof(remote);
?stP2PMessage recvbuf;
?for(;;)
?{
??int iread = recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(recvbuf), 0, (sockaddr *)&remote, &sinlen);
??if(iread<=0)
??{
???printf("recv error\n");
???continue;
??}
??switch(recvbuf.iMessageType)
??{
??case P2PMESSAGE:
???{
????// 接收到P2P的消息
????char *comemessage= new char[recvbuf.iStringLen];
????int iread1 = recvfrom(PrimaryUDP, comemessage, 256, 0, (sockaddr *)&remote, &sinlen);
????comemessage[iread1-1] = '\0';
????if(iread1<=0)
?????throw Exception("Recv Message Error\n");
????else
????{
?????printf("Recv a Message:%s\n",comemessage);
?????
?????stP2PMessage sendbuf;
?????sendbuf.iMessageType = P2PMESSAGEACK;
?????sendto(PrimaryUDP, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote, sizeof(remote));
????}
????delete []comemessage;
????break;
???}
??case P2PSOMEONEWANTTOCALLYOU:
???{
????// 接收到打洞命令,向指定的IP地址打洞
????printf("Recv p2someonewanttocallyou data\n");
????sockaddr_in remote;
????remote.sin_addr.S_un.S_addr = htonl(recvbuf.iStringLen);
????remote.sin_family = AF_INET;
????remote.sin_port = htons(recvbuf.Port);
????// UDP hole punching
????stP2PMessage message;
????message.iMessageType = P2PTRASH;
????sendto(PrimaryUDP, (const char *)&message, sizeof(message), 0, (const sockaddr*)&remote, sizeof(remote));
???????????????
????break;
???}
??case P2PMESSAGEACK:
???{
????// 發送消息的應答
????RecvedACK = true;
????break;
???}
??case P2PTRASH:
???{
????// 對方發送的打洞消息,忽略掉。
????//do nothing ...
????printf("Recv p2ptrash data\n");
????break;
???}
??case GETALLUSER:
???{
????int usercount;
????int fromlen = sizeof(remote);
????int iread = recvfrom(PrimaryUDP, (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote, &fromlen);
????if(iread<=0)
????{
?????throw Exception("Login error\n");
????}
????
????ClientList.clear();
????cout<<"Have "<<usercount<<" users logined server:"<<endl;
????for(int i = 0;i<usercount;i++)
????{
?????stUserListNode *node = new stUserListNode;
?????recvfrom(PrimaryUDP, (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote, &fromlen);
?????ClientList.push_back(node);
?????cout<<"Username:"<<node->userName<<endl;
?????in_addr tmp;
?????tmp.S_un.S_addr = htonl(node->ip);
?????cout<<"UserIP:"<<inet_ntoa(tmp)<<endl;
?????cout<<"UserPort:"<<node->port<<endl;
?????cout<<""<<endl;
????}
????break;
???}
??}
?}
}
int main(int argc, char* argv[])
{
?try
?{
??InitWinSock();
??
??PrimaryUDP = mksock(SOCK_DGRAM);
??BindSock(PrimaryUDP);
??cout<<"Please input server ip:";
??cin>>ServerIP;
??cout<<"Please input your name:";
??cin>>UserName;
??ConnectToServer(PrimaryUDP, UserName, ServerIP);
??HANDLE threadhandle = CreateThread(NULL, 0, RecvThreadProc, NULL, NULL, NULL);
??CloseHandle(threadhandle);
??OutputUsage();
??for(;;)
??{
???char Command[COMMANDMAXC];
???gets(Command);
???ParseCommand(Command);
??}
?}
?catch(Exception &e)
?{
??printf(e.GetMessage());
??return 1;
?}
?return 0;
}
/* 異常類
?*
?* 文件名:Exception.h
?*
?* 日期:2004.5.5
?*
?* 作者:shootingstars(zhouhuis22@sina.com)
?*/
#ifndef __HZH_Exception__
#define __HZH_Exception__
#define EXCEPTION_MESSAGE_MAXLEN 256
#include "string.h"
class Exception
{
private:
?char m_ExceptionMessage[EXCEPTION_MESSAGE_MAXLEN];
public:
?Exception(char *msg)
?{
??strncpy(m_ExceptionMessage, msg, EXCEPTION_MESSAGE_MAXLEN);
?}
?char *GetMessage()
?{
??return m_ExceptionMessage;
?}
};
#endif
/* P2P 程序傳輸協議
?*
?* 日期:2004-5-21
?*
?* 作者:shootingstars(zhouhuis22@sina.com)
?*
?*/
#pragma once
#include <list>
// 定義iMessageType的值
#define LOGIN 1
#define LOGOUT 2
#define P2PTRANS 3
#define GETALLUSER? 4
// 服務器端口
#define SERVER_PORT 2280
// Client登錄時向服務器發送的消息
struct stLoginMessage
{
?char userName[10];
?char password[10];
};
// Client注銷時發送的消息
struct stLogoutMessage
{
?char userName[10];
};
// Client向服務器請求另外一個Client(userName)向自己方向發送UDP打洞消息
struct stP2PTranslate
{
?char userName[10];
};
// Client向服務器發送的消息格式
struct stMessage
{
?int iMessageType;
?union _message
?{
??stLoginMessage loginmember;
??stLogoutMessage logoutmember;
??stP2PTranslate translatemessage;
?}message;
};
// 客戶節點信息
struct stUserListNode
{
?char userName[10];
?unsigned int ip;
?unsigned short port;
};
// Server向Client發送的消息
struct stServerToClient
{
?int iMessageType;
?union _message
?{
??stUserListNode user;
?}message;
};
//======================================
// 下面的協議用于客戶端之間的通信
//======================================
#define P2PMESSAGE 100?????????????? // 發送消息
#define P2PMESSAGEACK 101??????????? // 收到消息的應答
#define P2PSOMEONEWANTTOCALLYOU 102? // 服務器向客戶端發送的消息
???????????????????????????????????? // 希望此客戶端發送一個UDP打洞包
#define P2PTRASH??????? 103????????? // 客戶端發送的打洞包,接收端應該忽略此消息
// 客戶端之間發送消息格式
struct stP2PMessage
{
?int iMessageType;
?int iStringLen;???????? // or IP address
?unsigned short Port;
};
using namespace std;
typedef list<stUserListNode *> UserList;
posted @
2007-01-04 13:37 CPP&&設計模式小屋 閱讀(1800) |
評論 (1) |
編輯 收藏
摘要: Internet Draft?????????????????????????????????????????????????? B. FordDocument: draft-ford-midcom-p2p-01.txt??????????????????????????? M.I.T.Expires: April 27, 2004?????????????????????????????????...
閱讀全文
posted @
2007-01-04 13:35 CPP&&設計模式小屋 閱讀(1356) |
評論 (0) |
編輯 收藏
摘要: Partial Template Specialization顧名思義,模版偏特化就是對模版進行特化的意思。舉個例子:namespace?SHFTest{????template<????????????class?PLA,????????????class?PLB????????????>????class?PLClass????{????????//????????//?一般實現?...
閱讀全文
posted @
2006-12-30 17:30 CPP&&設計模式小屋 閱讀(4042) |
評論 (3) |
編輯 收藏
最近已經完成閱讀的書:《STL源碼剖析》--侯捷
????????????????????????????????????????????《TCP/IP詳解》第一卷
最近準備閱讀的書:《Moden C++ Design》英文版--
????????????????????????????????????電子書(最好有Template基礎和看過《設計模式》)
???????????????????????????????????《代碼大全》--上次看了5章
???????????????????????????????????《深入探索C++對象模型》
將要看的書:《TCP/IP詳解》第二卷和第三卷。
???????????????????????《應用密碼學》
很希望和大家做交流,我把《Moden C++ Design》英文版上傳到我的文件里面了,需要的朋友自取。
posted @
2006-12-30 16:14 CPP&&設計模式小屋 閱讀(1868) |
評論 (3) |
編輯 收藏
posted @
2006-12-26 19:14 CPP&&設計模式小屋 閱讀(1384) |
評論 (3) |
編輯 收藏
最近對P2P直播技術進行了一些研究,談談個人對Tvkoo軟件的優缺點的看法,一方面希望Tvkoo能做的更好,另一方面也拋磚引玉一下:
P2P直播的技術難點有:
一、防火墻的穿透:超過90%的電腦都在防火墻后面,如果讓2臺在防火墻后面的電腦能夠實現P2P互聯,這是一個技術的難點。有2種方式:
1. 要求用戶配置TCP端口:BT和電驢采用的方式,配置需要網絡專業知識。一般做法是通過在防火墻上開啟TCP端口來實現,如果開啟了端口或者本身有Internet IP地址的,為高聯通性電腦;在防火墻內并且沒有開啟TCP端口的電腦,為低聯通性電腦。高聯通性電腦可以和其它的高聯通性電腦已經低聯通性電腦進行P2P;而低聯通電腦只能和高聯通性電腦進行P2P。因此在BT、電驢中,有Internet IP或者已經在防火墻上開啟端口的電腦速度很快,而在防火墻后面的電腦(一般為局域網上網方式)就比較慢了。而通過局域網方式上網的電腦超過70%,如果沒有網絡基礎,或者沒有網管特殊配置,只能處于低聯通性,速度很慢。
2. 防火墻自動穿透。無需用戶配置,自動讓2臺在防火墻后面的電腦能P2P互聯。P2P連接可以使用TCP和UDP 2種方式。由于WinXP SP2限制了一個應用程序的TCP連接數,因此采用TCP方式進行P2P的話,要安裝TCP限制的破解軟件。而Tvkoo是使用UDP進行數據傳送的,因此不會有TCP限制。這是為什么有些用戶說:通過Netstat看不到很多的TCP連接而懷疑Tvkoo是不是P2P軟件的原因。
Tvkoo的P2P穿透力是我見到最強的軟件了,當然由于沒有使用TCP,使得在僅允許HTTP訪問的防火墻后面的計算機沒有辦法訪問。Skype在P2P穿透時有一個技巧,讓一些有Internet IP的電腦使用TCP的80端口,在僅允許HTTP訪問的防火墻后面的電腦通過類似HTTP的訪問方式連接這些80端口的電腦進行P2P連接。Tvkoo也可以考慮一下這個方式。
二、WMV格式的分析
目前P2P直播都是使用WMV格式,通過模擬HTTP服務器的方式,把WMV數據流傳送給Media Player。這是P2P直播的關鍵點。需要將Media Encoder發出的HTTP數據進行拆包,然后組成30秒-1分鐘的P2P數據塊,然后通過P2P方式將數據塊發送給Tvkoo客戶端,Tvkoo模擬成HTTP服務器將傳輸完成的數據塊發送給Media Player。這部分Tvkoo做的也不錯。
三、如何有效的選擇P2P的節點
這是Tvkoo的弱項。因為一臺電腦在P2P傳輸時,最多連接幾十臺其它的電腦。當幾萬臺電腦同時傳送一個P2P數據塊時,要有一個優化算法。比如:美國有5000個用戶,中國電信有5000個用戶,中國網通有5000個用戶,而每個用戶最多只能連30-50個節點,如果不湊巧,一個電信的用戶連接了20個網通的用戶和30個美國的用戶,就不斷的出現斷斷續續的情況了。這就是為什么人一多,Tvkoo就卡,并且Tvkoo要把國外IP封掉的原因了。
有什么好的方式解決這個問題呢?我先拋磚引玉一下:
(1)????作為P2P直播的營運商,可以多設幾臺P2P種子服務器,分布在不同的網段中。比如:北方網通設一臺(組),南方電信設一臺(組),種子的內容是一樣的。種子服務器多了,可以降低優化算法的難度。
(2)????種子服務器和普通節點的優先級:種子服務器的優先級總數低于普通節點的,如果普通節點的速度快了,就減少從種子服務器獲取的數據量。
(3)????全球IP地址表。P2P節點仲裁服務器中,應該有一個全球IP地址表,分中國大陸、香港、臺灣、北美、歐洲、澳洲、其它。中國大陸先按照營運商分:電信、網通、鐵通、聯通、教育網等,再按照省份分類。(網上有下載,可以整理)
(4)????高速網段表。在P2P訪問中,節點動態地將速度快的其它節點IP地址傳回服務器,服務器根據全球IP地址表算出網段,以網段-網段的方式記錄在數據庫中。
(5)????當一個新用戶連入節點時,在全球IP地址表中找到最近的節點,按照比例依次分配最快網段的節點;最近的節點;差一個級別的稍近的節點;隨機節點以及種子服務器。
(6)????P2P在數據傳送中,可以將30秒視頻作為1塊數據包;數據包中按照每16KB作為一個數據塊。每個時間段(如2秒),本節點向其它節點交換一下數據塊的傳送情況,然后計算一下數據包中每個數據塊的擁有率,優先傳送擁有率低的數據塊。在擁有率相當的情況下,隨機選擇。
(7)????在數據交換中,對于傳送慢的節點,定期剔除,然后問節點仲裁服務器要新的節點。
(8)????如果數據包中小于10%的數據塊沒有傳送完畢,在時間充足的情況下,對于余下的數據塊,可以同一個數據塊向多個節點請求。
(9)????節點仲裁服務器也會將新的P2P節點強行加載到另一個節點上,但不能超過節點最大連接數。
posted @
2006-12-19 17:17 CPP&&設計模式小屋 閱讀(970) |
評論 (3) |
編輯 收藏