隨着區塊鏈的越來越火,去中心化的網絡設計再次被拿到技術人員面前。在這裏我使用非常通俗的語言,幫大家來理解去中心化的網絡設計的基礎—網絡穿透。再使用代碼來實現穿透。如果闡述不到位的地方,歡迎大家拋磚。代碼在此: https://github.com/wangbojing/P2PServer
在有中心化服務器的網絡中,客戶端,服務器,網關構成網絡拓撲圖。如下圖1所示:由於後續出現的名詞概念很多,先約法三章,在這裏統一一下稱呼:所有的終端機器成爲客戶端,不同客戶端使用大寫字母區分(A,B,C,…);客戶端上面運行的應用程序統一稱爲客戶程序,不同的應用程序使用不數字區分(1,2,3,…)。作爲服務器的物理機稱爲服務器,而服務器上運行的程序稱爲服務程序,後文中每一個拓撲組件都只有一個IP地址。爲客戶端提供公網IP服務的組件稱爲網關。
圖1 中心化服務器的網絡拓撲圖
從網關映射到客戶端中的網絡結構,這裏需要引入一個NAT的概念。什麼NAT呢?中文名叫網絡地址轉換,習慣稱爲網絡地址映射。爲什麼需要網絡地址映射呢?:需要說到IPV4網絡地址已經用完,全部使用IPV6又會造成很多隻支持IPV4的終端設備無法正常使用,所以網絡地址映射應運而生,忍辱負重。纔會有我們現在所謂的網絡穿透的出現。到底怎麼映射的?如圖2網絡地址映射所示。客戶程序使用192.168.0.234:7890發送數據,通過網關的網絡地址映射在公網被轉換爲112.93.116.102:6834,被互聯網上的大家所認知。此時在公網上使用客戶程序的ip與端口被112.93.116.102:6834代替。在這裏大家應該明白了NAT是何許物種了。
圖2 網絡地址映射
爲了保持新手福音,業界良心的態度。什麼是穿透?因爲NAT是客戶程序發起的,網絡爲了保持通訊新建的一個臨時牌照,隨時可能被收回,而且重新發起後的牌照不一樣。從而外界及時知道了這個臨時牌照也沒有用。所以需要通過穿透在網關上面打個洞,來爲外界進行服務。那NAT與穿透有什麼關係呢?正因爲有了NAT才需要穿透,如果是IPV6每個客戶端一個IP地址,那就不需要直接可以找到客戶端了。
網絡地址映射
由於網關的安全性要求不一致,就出現四種不同的NAT方式。分別進行闡述:
第一種完全錐形NAT,英文名叫Full Cone NAT。如圖3完全錐形NAT所示,客戶程序(192.168.0.234:7890)與服務器A(13.44.178.98:9800)通信,通過網關的地址轉換產生的臨時牌照的公網地址(112.93.116.102:6834),服務器B(157.78.13.156:23456)發送數據到公網地址(112.93.116.102:6834),如果客戶程序(192.168.0.234:7890)能夠收到服務器B(157.78.13.156:23456)發送的數據,這種NAT映射關係爲完全錐形NAT;
圖3 完全錐形NAT
第二種限制錐形NAT,英文名叫RestrictedCone NAT。在圖3 完全錐形NAT中,如果客戶程序(192.168.0.234:7890)不能收到服務器B(157.78.13.156:23456)發送的數據,這種NAT映射關係爲限制型錐形NAT。
第三種端口限制錐形NAT,英文名叫Port RestrictedCone NAT。客戶程序(192.168.0.234:7890)發送數據給服務程序(13.44.178.98:9800),網關通過網絡地址轉換產生的地址(112.93.116.102:6834),同樣的服務器內的另一個服務程序(13.44.178.178:9801)發送數據給網關(112.93.116.102:6834)地址,如果客戶程序(192.168.0.234:7890)能夠收到,則爲限制錐形NAT,如果客戶程序(192.168.0.234:7890)不能收到,則爲端口限制錐形NAT。
對於所有的錐型NAT,客戶程序(192.168.0.234:7890)對外發送的數據時,網關地址轉換的地址都是一樣的爲(112.93.116.102:6834),那爲什麼在圖4 限制型錐形NAT中,客戶程序不能收到服務程序B(13.44.178.98:9801)的數據呢?因爲在網關中沒有發生過客戶程序(192.168.0.234:7890)給服務程序B(13.44.178.98:9801),故服務程序(13.44.178.98:9801)直接發送給網關(112.93.116.102:6834),則被網關所丟棄。
圖4 限制型錐形NAT
第四種對稱NAT,英文,名叫Symmetric NAT。如圖5對稱NAT所示,客戶程序(192.168.0.234:7890)發送數據給兩個不同服務器(13.44.178.98:9800)和(157.78.13.156:23456)時,網關會進行不同的網絡地址映射產生(112.93.116.102:6834)和(112.93.116.102:6835)。這是對於整個NAT網絡發送數據出去的過程,而接收數據與端口限制錐形NAT一致。
圖5 對稱NAT
本節介紹三種錐形NAT和對稱NAT的概念,相信到此你還是不知道NAT類型與怎麼穿透網關友什麼關係。
穿透剖析
怎麼穿透網關來實現去中心化,如圖6穿透網絡NAT拓撲圖所示
在理想的情況下,在NAT 1中客戶程序(192.168.0.234:7890)知道NAT 2中客戶程序(192.168.2.168:2786)的網絡映射地址(157.123.80.165:6954),並給網絡映射地址(157.123.80.165:6954)發送數據,並且客戶程序(192.168.2.168:2786)能夠收到數據;而NAT 2中客戶程序(192.168.2.168:2786)也知道NAT 1中客戶程序的網絡映射地址,並給其網絡映射地址(112.93.116.102:6834)發送數據,並且也能收到數據。此時對於服務器而言,就已經沒有起到數據中轉的作用,此時客戶程序(192.168.0.234:7890)與客戶程序(192.168.2.168:2786)能夠互相收發數據,服務程序(13.44.178.98:9800)已經沒有作用,對於客戶端程序來說,已經實現了去中心化。
這只是在理論情況,現在具體實現步驟以及結合四種NAT類型來分析一下。
第一種:NAT 1爲完全錐形NAT,NAT 2爲任何一種NAT模式,如圖7 完全錐形NAT的穿透,綠色字體的順序。
客戶程序(192.168.0.234:7890)先發送一個連接請求給服務程序,通知服務程序,需要連接客戶程序(192.168.2.168:2786)。
服務程序收到連接請求後,發送給notify消息給客戶程序(192.168.2.168:2786),通知客戶程序(192.168.2.168:2786),發送p2p連接請求給網關(112.93.116.102:6834)。
客戶程序(192.168.2.168:2786)發送p2p連接請求給網關(112.93.116.102:6834),由於NAT1爲完全錐形NAT,所以客戶程序(192.168.0.234:7890)能夠收到客戶程序(192.168.2.168:2786)的請求。
客戶程序(192.168.0.234:7890)收到p2p連接請求後,從請求數據中解析出請求發送者客戶程序(192.168.2.168:2786)的IP地址與端口,並立即返回確認消息。此時雙方進入P2P的穿透模式。
然而在這裏有一點需要注意:NAT2爲對稱NAT的時候,在3步驟的時候,網關會新生成另一個端口,IP地址不變,用來與NAT1中的網絡進行通信;在4步驟的時候,客戶程序(192.168.0.234:7890)返回數據的地址,就是新生成的端口。
圖7 完全錐形NAT的穿透
第二種:NAT 1爲限制錐形NAT或者端口限制錐形NAT(兩個錐形NAT模式是一樣的,就不分開解釋了),NAT 2爲錐形NAT。如圖8 限制錐形NAT的穿透所示
客戶程序(192.168.0.234:7890)發送連接請求給服務程序,通知服務程序,需要連接客戶程序(192.168.2.168:2786)。
服務程序收到連接請求後,發送給notify消息給客戶程序(192.168.2.168:2786),通知客戶程序(192.168.2.168:2786),發送p2p連接請求給網關(112.93.116.102:6834)。
客戶程序(192.168.2.168:2786)發送p2p連接請求給網關(112.93.116.102:6834),由於NAT1爲限制錐形NAT,所以客戶程序(192.168.0.234:7890)收不到發送的p2p連接請求,此步驟最終的是在NAT2的網關(157.123.80.165:6954)新生成一條NAT目的地址的記錄。與後續6步驟作爲配合。
客戶程序(192.168.2.168:2786)提醒服務程序通知客戶程序(192.168.0.234:7890),
服務程序馬上通知客戶程序(192.168.0.234:7890)發送請求給NAT2的網關(157.123.80.165:6954)。
客戶程序(192.168.0.234:7890)發送p2p連接請求給網關(157.123.80.165:6954),由於剛剛3步驟發出了請求,此時網關會認爲是3步驟返回的響應,所以能夠p2p連接請求發送給客戶程序(192.168.2.168:2786)
客戶程序(192.168.2.168:2786)收到p2p連接請求後,立即返回確認消息給p2p連接請求包解析出來的IP地址與端口,此確認消息能夠順利到底客戶程序(192.168.0.234:7890),到此網關已經穿透,P2P已經建立。
圖8 限制錐形NAT的穿透
第三種:NAT1爲限制錐形NAT,NAT2爲對稱NAT。如圖8限制錐形NAT的穿透所示。
在步驟3和步驟6與NAT2爲限制錐形NAT有些差異,其餘步驟流程一致。
步驟3:客戶程序(192.168.2.168:2786)發送p2p連接請求給網關(112.93.116.102:6834),由於NAT2爲對稱網絡,此時會重新生成一個端口用於對網關(112.93.116.102:6834)通信。新生成的端口沒有辦法能夠準確的知道。只能進行猜測。
步驟6:發送數據給網關(157.123.80.165:猜測端口)。
在這裏提供一種思路來提高測猜的準確度,把服務程序使用兩個端口(之前9800,新加一個9801),由於網關NAT分配端口是順序的,在步驟4發送請求給服務程序(9801端口),因爲步驟3與步驟4相隔時間短,步驟3在網關(157.123.80.165)所生成的新端口比步驟4的端口小。從而來提高猜測的準確度。
相信已經對穿透的具體步驟有明確的概念,怎麼準確的判斷當前NAT的類型?
NAT分類
其實在網絡地址映射概念已經有介紹分類,在這裏使用更加計算機化語言描述。
第一種,檢測當前客戶程序的網關是否爲完全錐形NAT,如圖9檢測完全錐形NAT所示
圖9 檢測完全錐形NAT
首先檢測Udp的可用性,客戶程序(192.168.0.234:7890)使用一個300ms定時器發送Udp請求數據包給服務器A。等待服務器A返回確認數據。如果多次發送請求並未得到服務器的確認數據,則認爲Udp不能信息,則推出整個檢測過程。如果收到確認數據,同樣使用定時器再發送另一種請求數據要求服務器B發送數據給網關(112.93.116.102:6834),如果收到服務器B的數據,則認爲是完全錐形網絡。如果沒有收到則進行限制錐形NAT。
第二種,檢測限制錐形網絡,如圖10所示。
圖10 檢測限制錐形NAT
客戶程序(192.168.0.234:7890)定時發送數據包給服務程序A,並要求服務程序從另一個端口發送數據包給網關(112.93.116.102:6834)。若客戶程序(192.168.0.234:7890)收到迴應,則該NAT爲限制錐形NAT。若多次操作沒有迴應,則進行對稱NAT檢測。
第三種,檢測當前客戶程序的網關是否爲對稱NAT,如圖9所示
客戶程序(192.168.0.234:7890)給服務器A(13.44.178.98:9800)與服務器B(157.78.13.156:23456)發送數據包,對比兩個服務器收到客戶程序的()IP地址與端口是否一致。如果不一致則是對稱網絡。如果一致則該網絡爲端口限制錐形NAT。
以下爲實現了完全錐形網絡的穿透代碼
udp.h
/* * Author: WangBoJing * email: [email protected] * github: https://github.com/wangbojing */ #ifndef __UDP_H__ #define __UDP_H__ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <time.h> typedef unsigned int U32; typedef unsigned short U16; typedef unsigned char U8; typedef volatile long UATOMIC; typedef void* (*KING_CALLBACK)(void *arg); typedef enum { KING_RESULT_FAILED = -1, KING_RESULT_SUCCESS = 0, } KING_RESULT; typedef enum { KING_STATUS_NULL, KING_STATUS_LOGIN, KING_STATUS_HEARTBEAT, KING_STATUS_CONNECT, KING_STATUS_MESSAGE, KING_STATUS_NOTIFY, KING_STATUS_P2P_CONNECT, KING_STATUS_P2P_MESSAGE, } KING_STATUS_SET; #define KING_CLIENT_MAX 1024 #define KING_CLIENT_ADDR_LENGTH 6 #define KING_BUFFER_LENGTH 512 #define KING_NUMBER_ID_LENGTH 4 typedef struct _CLIENT_TABLE { U8 addr[KING_CLIENT_ADDR_LENGTH]; U32 client_id; long stamp; } client_table; /**************************** status define ****************************/ #define KING_PROTO_LOGIN_REQ 0x01 #define KING_PROTO_LOGIN_ACK 0x81 #define KING_PROTO_HEARTBEAT_REQ 0x02 #define KING_PROTO_HEARTBEAT_ACK 0x82 #define KING_PROTO_CONNECT_REQ 0x11 #define KING_PROTO_CONNECT_ACK 0x91 #define NTY_PROTO_NOTIFY_REQ 0x12 #define NTY_PROTO_NOTIFY_ACK 0x92 #define NTY_PROTO_P2P_CONNECT_REQ 0x13 #define NTY_PROTO_P2P_CONNECT_ACK 0x93 #define NTY_RPORO_MESSAGE_REQ 0x21 #define NTY_RPORO_MESSAGE_ACK 0xA1 /**************************** context define ****************************/ #define KING_PROTO_BUFFER_VERSION_IDX 0 #define KING_PROTO_BUFFER_STATUS_IDX 1 #define KING_PROTO_BUFFER_LENGTH_IDX (KING_PROTO_BUFFER_STATUS_IDX+1) #define KING_PROTO_BUFFER_SELFID_IDX (KING_PROTO_BUFFER_LENGTH_IDX+2) //login #define KING_PROTO_LOGIN_SELFID_IDX KING_PROTO_BUFFER_SELFID_IDX //heartbeat #define KING_PROTO_HEARTBEAT_SELFID_IDX KING_PROTO_BUFFER_SELFID_IDX //connect #define KING_PROTO_CONNECT_SELFID_IDX KING_PROTO_BUFFER_SELFID_IDX #define KING_PROTO_CONNECT_OTHERID_IDX (KING_PROTO_BUFFER_SELFID_IDX+KING_NUMBER_ID_LENGTH) //notify #define KING_PROTO_NOTIFY_SELFID_IDX KING_PROTO_BUFFER_SELFID_IDX #define KING_PROTO_NOTIFY_ADDR_IDX (KING_PROTO_BUFFER_SELFID_IDX+KING_NUMBER_ID_LENGTH) //p2p connect #define KING_PROTO_P2P_CONNECT_SELFID_IDX KING_PROTO_BUFFER_SELFID_IDX //p2p connect ack #define KING_PROTO_P2P_CONNECT_ACK_SELFID_IDX KING_PROTO_BUFFER_SELFID_IDX //message #define KING_RPORO_MESSAGE_SELFID_IDX KING_PROTO_BUFFER_SELFID_IDX #define KING_PROTO_MESSAGE_OTHERID_IDX (KING_RPORO_MESSAGE_SELFID_IDX+KING_NUMBER_ID_LENGTH) #define KING_RPORO_MESSAGE_CONTENT_IDX (KING_PROTO_MESSAGE_OTHERID_IDX+KING_NUMBER_ID_LENGTH) //message ack #define KING_RPORO_MESSAGE_ACK_SELFID_IDX KING_PROTO_BUFFER_SELFID_IDX static unsigned long cmpxchg(UATOMIC *addr, unsigned long _old, unsigned long _new) { U8 res; __asm__ volatile ( "lock; cmpxchg %3, %1;sete %0;" : "=a" (res) : "m" (*addr), "a" (_old), "r" (_new) : "cc", "memory"); return res; } static long time_genrator(void) { static long lTimeStamp = 0; static long timeStampMutex = 0; if(cmpxchg(&timeStampMutex, 0, 1)) { lTimeStamp = time(NULL); timeStampMutex = 0; } return lTimeStamp; } static int addr_to_array(U8 *array, struct sockaddr_in *p_addr) { int i = 0; for (i = 0;i < 4;i ++) { array[i] = *((unsigned char*)(&p_addr->sin_addr.s_addr) + i); } for (i = 0;i < 2;i ++) { array[4+i] = *((unsigned char*)(&p_addr->sin_port)+i); } } static int array_to_addr(U8 *array, struct sockaddr_in *p_addr) { int i = 0; for (i = 0;i < 4;i ++) { *((unsigned char*)(&p_addr->sin_addr.s_addr) + i) = array[i]; } for (i = 0;i < 2;i ++) { *((unsigned char*)(&p_addr->sin_port)+i) = array[4+i]; } } static int king_send_login(int sockfd, int self_id, struct sockaddr_in *paddr) { U8 buffer[KING_BUFFER_LENGTH] = {0}; buffer[KING_PROTO_BUFFER_STATUS_IDX] = KING_PROTO_LOGIN_REQ; *(int *)(buffer+KING_PROTO_LOGIN_SELFID_IDX) = self_id; int n = KING_PROTO_LOGIN_SELFID_IDX + KING_NUMBER_ID_LENGTH; n = sendto(sockfd, buffer, n, 0, (struct sockaddr*)paddr, sizeof(struct sockaddr_in)); if (n < 0) { perror("sendto"); } return n; } static int king_send_heartbeat(int sockfd, int self_id, struct sockaddr_in *paddr) { U8 buffer[KING_BUFFER_LENGTH] = {0}; buffer[KING_PROTO_BUFFER_STATUS_IDX] = KING_PROTO_HEARTBEAT_REQ; *(int *)(buffer+KING_PROTO_HEARTBEAT_SELFID_IDX) = self_id; int n = KING_PROTO_HEARTBEAT_SELFID_IDX + KING_NUMBER_ID_LENGTH; n = sendto(sockfd, buffer, n, 0, (struct sockaddr*)paddr, sizeof(struct sockaddr_in)); if (n < 0) { perror("sendto"); } return n; } static int king_send_connect(int sockfd, int self_id, int other_id, struct sockaddr_in *paddr) { U8 buffer[KING_BUFFER_LENGTH] = {0}; buffer[KING_PROTO_BUFFER_STATUS_IDX] = KING_PROTO_CONNECT_REQ; *(int *)(buffer+KING_PROTO_CONNECT_SELFID_IDX) = self_id; *(int *)(buffer+KING_PROTO_CONNECT_OTHERID_IDX) = other_id; int n = KING_PROTO_CONNECT_OTHERID_IDX + KING_NUMBER_ID_LENGTH; n = sendto(sockfd, buffer, n, 0, (struct sockaddr*)paddr, sizeof(struct sockaddr_in)); if (n < 0) { perror("sendto"); } return n; } static int king_send_p2pconnect(int sockfd, int self_id, struct sockaddr_in *paddr) { U8 buffer[KING_BUFFER_LENGTH] = {0}; buffer[KING_PROTO_BUFFER_STATUS_IDX] = NTY_PROTO_P2P_CONNECT_REQ; *(int *)(buffer+KING_PROTO_P2P_CONNECT_SELFID_IDX) = self_id; int n = KING_PROTO_P2P_CONNECT_SELFID_IDX + KING_NUMBER_ID_LENGTH; n = sendto(sockfd, buffer, n, 0, (struct sockaddr*)paddr, sizeof(struct sockaddr_in)); if (n < 0) { perror("sendto"); } return n; } static int king_send_p2pconnectack(int sockfd, int self_id, struct sockaddr_in *paddr) { U8 buffer[KING_BUFFER_LENGTH] = {0}; buffer[KING_PROTO_BUFFER_STATUS_IDX] = NTY_PROTO_P2P_CONNECT_ACK; *(int *)(buffer+KING_PROTO_P2P_CONNECT_ACK_SELFID_IDX) = self_id; int n = KING_PROTO_P2P_CONNECT_ACK_SELFID_IDX + KING_NUMBER_ID_LENGTH; n = sendto(sockfd, buffer, n, 0, (struct sockaddr*)paddr, sizeof(struct sockaddr_in)); if (n < 0) { perror("sendto"); } return n; } static int king_client_send_message(int sockfd, int self_id, int other_id, struct sockaddr_in *paddr, U8 *msg, int length) { U8 buffer[KING_BUFFER_LENGTH] = {0}; buffer[KING_PROTO_BUFFER_STATUS_IDX] = NTY_RPORO_MESSAGE_REQ; *(int *)(buffer+KING_RPORO_MESSAGE_SELFID_IDX) = self_id; *(int *)(buffer+KING_PROTO_MESSAGE_OTHERID_IDX) = other_id; memcpy(buffer+KING_RPORO_MESSAGE_CONTENT_IDX, msg, length); int n = KING_RPORO_MESSAGE_CONTENT_IDX + length; *(U16*)(buffer+KING_PROTO_BUFFER_LENGTH_IDX) = (U16) n; n = sendto(sockfd, buffer, n, 0, (struct sockaddr*)paddr, sizeof(struct sockaddr_in)); if (n < 0) { perror("sendto"); } return n; } static int king_send_messageack(int sockfd, int self_id, struct sockaddr_in *paddr) { U8 buffer[KING_BUFFER_LENGTH] = {0}; buffer[KING_PROTO_BUFFER_STATUS_IDX] = NTY_RPORO_MESSAGE_ACK; *(int *)(buffer+KING_RPORO_MESSAGE_ACK_SELFID_IDX) = self_id; int n = KING_RPORO_MESSAGE_ACK_SELFID_IDX + KING_NUMBER_ID_LENGTH; n = sendto(sockfd, buffer, n, 0, (struct sockaddr*)paddr, sizeof(struct sockaddr_in)); if (n < 0) { perror("sendto"); } return n; } client_table table[KING_CLIENT_MAX] = {0}; int client_count = 0; static int get_index_by_clientid(int client_id) { int i = 0; int now_count = client_count; for (i = 0;i < now_count;i ++) { if (table[i].client_id == client_id) return i; } } static int king_send_message(int sockfd, int client_id, U8 *buffer, int length) { int index = get_index_by_clientid(client_id); struct sockaddr_in c_addr; c_addr.sin_family = AF_INET; array_to_addr(table[index].addr, &c_addr); int n = sendto(sockfd, buffer, length, 0, (struct sockaddr*)&c_addr, sizeof(c_addr)); if (n < 0) { perror("sendto"); } return n; } static int king_send_notify(int sockfd, int client_id, int self_id) { U8 buffer[KING_BUFFER_LENGTH] = {0}; int index = get_index_by_clientid(self_id); buffer[KING_PROTO_BUFFER_STATUS_IDX] = NTY_PROTO_NOTIFY_REQ; *(int*)(buffer+KING_PROTO_NOTIFY_SELFID_IDX) = self_id; memcpy(buffer+KING_PROTO_NOTIFY_ADDR_IDX, table[index].addr, KING_CLIENT_ADDR_LENGTH); index = get_index_by_clientid(client_id); struct sockaddr_in c_addr; c_addr.sin_family = AF_INET; array_to_addr(table[index].addr, &c_addr); int n = KING_PROTO_NOTIFY_ADDR_IDX + KING_CLIENT_ADDR_LENGTH; n = sendto(sockfd, buffer, n, 0, (struct sockaddr*)&c_addr, sizeof(c_addr)); if (n < 0) { perror("sendto"); } return n; } #endif
udp_client.c
/* * Author: WangBoJing * email: [email protected] * github: https://github.com/wangbojing */ #include "udp.h" #include <pthread.h> static int status_machine = KING_STATUS_LOGIN; static int client_selfid = 0x0; struct sockaddr_in server_addr; client_table p2p_clients[KING_CLIENT_MAX] = {0}; static int p2p_count = 0; static int king_client_buffer_parser(int sockfd, U8 *buffer, U32 length, struct sockaddr_in *addr) { U8 status = buffer[KING_PROTO_BUFFER_STATUS_IDX]; switch (status) { case NTY_PROTO_NOTIFY_REQ: { struct sockaddr_in other_addr; other_addr.sin_family = AF_INET; array_to_addr(buffer+KING_PROTO_NOTIFY_ADDR_IDX, &other_addr); king_send_p2pconnect(sockfd, client_selfid, &other_addr); break; } case NTY_PROTO_P2P_CONNECT_REQ: { int now_count = p2p_count++; p2p_clients[now_count].stamp = time_genrator(); p2p_clients[now_count].client_id = *(int*)(buffer+KING_PROTO_P2P_CONNECT_SELFID_IDX); addr_to_array(p2p_clients[now_count].addr, addr); king_send_p2pconnectack(sockfd, client_selfid, addr); printf("Enter P2P Model\n"); status_machine = KING_STATUS_P2P_MESSAGE; break; } case NTY_PROTO_P2P_CONNECT_ACK: { int now_count = p2p_count++; p2p_clients[now_count].stamp = time_genrator(); p2p_clients[now_count].client_id = *(int*)(buffer+KING_PROTO_P2P_CONNECT_SELFID_IDX); addr_to_array(p2p_clients[now_count].addr, addr); printf("Enter P2P Model\n"); status_machine = KING_STATUS_P2P_MESSAGE; break; } case NTY_RPORO_MESSAGE_REQ: { U8 *msg = buffer+KING_RPORO_MESSAGE_CONTENT_IDX; U32 other_id = *(U32*)(buffer+KING_RPORO_MESSAGE_SELFID_IDX); printf(" from client:%d --> %s\n", other_id, msg); king_send_messageack(sockfd, client_selfid, addr); //status_machine = KING_STATUS_P2P_MESSAGE; break; } case KING_PROTO_LOGIN_ACK: { printf(" Connect Server Success\nPlease Enter Message : "); status_machine = KING_STATUS_MESSAGE; break; } case KING_PROTO_HEARTBEAT_ACK: case KING_PROTO_CONNECT_ACK: case NTY_PROTO_NOTIFY_ACK: break; case NTY_RPORO_MESSAGE_ACK: break; } } void* king_recv_callback(void *arg) { int sockfd = *(int *)arg; struct sockaddr_in addr; int length = sizeof(struct sockaddr_in); U8 buffer[KING_BUFFER_LENGTH] = {0}; //printf("king_recv_callback --> enter\n"); while (1) { int n = recvfrom(sockfd, buffer, KING_BUFFER_LENGTH, 0, (struct sockaddr*)&addr, &length); if (n > 0) { buffer[n] = 0; king_client_buffer_parser(sockfd, buffer, n, &addr); } else if (n == 0) { printf("server closed\n"); close(sockfd); break; } else if (n == -1) { perror("recvfrom"); close(sockfd); break; } } } void *king_send_callback(void *arg) { int sockfd = *(int *)arg; char buffer[KING_BUFFER_LENGTH] = {0}; //printf("king_send_callback --> enter\n"); while (1) { bzero(buffer, KING_BUFFER_LENGTH); scanf("%s", buffer); //getchar(); if (status_machine == KING_STATUS_MESSAGE) { printf(" --> please enter bt : "); int other_id = buffer[1]-0x30; if (buffer[0] == 'C') { king_send_connect(sockfd, client_selfid, other_id, &server_addr); } else { int length = strlen(buffer); king_client_send_message(sockfd, client_selfid, other_id, &server_addr, buffer, length); } } else if (status_machine == KING_STATUS_P2P_MESSAGE) { printf(" --> please enter message to send : "); int now_count = p2p_count; struct sockaddr_in c_addr; c_addr.sin_family = AF_INET; array_to_addr(p2p_clients[now_count-1].addr, &c_addr); int length = strlen(buffer); king_client_send_message(sockfd, client_selfid, 0, &c_addr, buffer, length); } } } int main(int argc, char *argv[]) { printf(" This is a UDP Client\n"); if (argc != 4) { printf("Usage: %s ip port\n", argv[0]); exit(1); } int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket"); exit(1); } pthread_t thread_id[2] = {0}; KING_CALLBACK cb[2] = {king_send_callback, king_recv_callback}; int i = 0; for (i = 0;i < 2;i ++) { int ret = pthread_create(&thread_id[i], NULL, cb[i], &sockfd); if (ret) { perror("pthread_create"); exit(1); } sleep(1); } server_addr.sin_family = AF_INET; server_addr.sin_port = htons(atoi(argv[2])); server_addr.sin_addr.s_addr = inet_addr(argv[1]); client_selfid = atoi(argv[3]); king_send_login(sockfd, client_selfid, &server_addr); for (i = 0;i < 2;i ++) { pthread_join(thread_id[i], NULL); } return 0; }
udp_server.c
/* * Author: WangBoJing * email: [email protected] * github: https://github.com/wangbojing */ #include "udp.h" int king_buffer_parser(int sockfd, U8 *buffer, U32 length, struct sockaddr_in *addr) { U8 status = buffer[KING_PROTO_BUFFER_STATUS_IDX]; printf("king_buffer_parser --> %x\n", status); switch (status) { case KING_PROTO_LOGIN_REQ: { #if 1 int old = client_count; int now = old+1; if(0 == cmpxchg((UATOMIC*)&client_count, old, now)) { printf("client_count --> %d, old:%d, now:%d\n", client_count, old, now); return KING_RESULT_FAILED; } #else client_count = client_count+1; int now = client_count; #endif U8 array[KING_CLIENT_ADDR_LENGTH] = {0}; addr_to_array(array, addr); printf("login --> %d.%d.%d.%d:%d\n", *(unsigned char*)(&addr->sin_addr.s_addr), *((unsigned char*)(&addr->sin_addr.s_addr)+1), *((unsigned char*)(&addr->sin_addr.s_addr)+2), *((unsigned char*)(&addr->sin_addr.s_addr)+3), addr->sin_port); table[now].client_id = *(U32*)(buffer+KING_PROTO_LOGIN_SELFID_IDX); memcpy(table[now].addr, array, KING_CLIENT_ADDR_LENGTH); break; } case KING_PROTO_HEARTBEAT_REQ: { int client_id = *(unsigned int*)(buffer+KING_PROTO_HEARTBEAT_SELFID_IDX); int index = get_index_by_clientid(client_id); table[index].stamp = time_genrator(); break; } case KING_PROTO_CONNECT_REQ: { int client_id = *(unsigned int*)(buffer+KING_PROTO_CONNECT_SELFID_IDX); int other_id = *(unsigned int*)(buffer+KING_PROTO_CONNECT_OTHERID_IDX); king_send_notify(sockfd, other_id, client_id); break; } case NTY_RPORO_MESSAGE_REQ: { U8 *msg = buffer+KING_RPORO_MESSAGE_CONTENT_IDX; int client_id = *(unsigned int*)(buffer+KING_RPORO_MESSAGE_SELFID_IDX); int other_id = *(unsigned int*)(buffer+KING_PROTO_MESSAGE_OTHERID_IDX); printf(" from client:%d --> %s\n", client_id, msg); #if 0 king_send_message(sockfd, other_id, buffer, length); #endif break; } } return KING_RESULT_SUCCESS; } int main(int argc, char *argv[]) { printf(" This is a UDP Server\n"); int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket"); exit(0); } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(atoi(argv[1])); addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); exit(1); } char buffer[KING_BUFFER_LENGTH] = {0}; struct sockaddr_in c_addr; int n; int length = sizeof(struct sockaddr_in); while(1) { n = recvfrom(sockfd, buffer, KING_BUFFER_LENGTH, 0, (struct sockaddr*)&c_addr, &length); if (n > 0) { buffer[n] = 0x0; printf("%d.%d.%d.%d:%d say: %s\n", *(unsigned char*)(&c_addr.sin_addr.s_addr), *((unsigned char*)(&c_addr.sin_addr.s_addr)+1), *((unsigned char*)(&c_addr.sin_addr.s_addr)+2), *((unsigned char*)(&c_addr.sin_addr.s_addr)+3), c_addr.sin_port, buffer); int ret = king_buffer_parser(sockfd, buffer, n, &c_addr); if (ret == KING_RESULT_FAILED) continue; buffer[KING_PROTO_BUFFER_STATUS_IDX] += 0x80; n = sendto(sockfd, buffer, n, 0, (struct sockaddr*)&c_addr, sizeof(c_addr)); if (n < 0) { perror("sendto"); break; } } else if (n == 0) { printf("server closed\n"); } else { perror("recv"); break; } } return 0; }