連接埠映射 及UDP內網和外網串連通訊的問題

來源:互聯網
上載者:User

什麼是連接埠映射:

連接埠映射(NAT或NAPT)裡所指的連接埠不是指物理意義上的連接埠,而是特指TCP/IP協議中的連接埠,是邏輯意義上的連接埠。連接埠映射:內網的一台電腦要上網際網路,就需要連接埠映射。連接埠映射分為動態和靜態.動態連接埠映射:內網中的一台電腦要訪問新浪網,會向NAT Gateway發送資料包,包頭中包括對方(就是新浪網)IP、連接埠和本機IP、連接埠,NAT Gateway會把本機IP、連接埠替換成自己的公網IP、一個未使用的連接埠,並且會記下這個映射關係,為以後轉寄資料包使用。然後再把資料發給新浪網,新浪網收到資料後做出反應,發送資料到NAT Gateway的那個未使用的連接埠,然後NAT Gateway將資料轉寄給內網中的那台電腦,實現內網和公網的通訊.當串連關閉時,NAT Gateway會釋放分配給這條串連的連接埠,以便以後的串連可以繼續使用。動態連接埠映射其實也就是NAT Gateway的工作方式。靜態連接埠映射: 就是在NAT Gateway上開放一個固定的連接埠,然後設定此連接埠收到的資料要轉寄給內網哪個IP和連接埠,不管有沒有串連,這個映射關係都會一直存在。就可以讓公網主動訪問內網的一個電腦。

 

UDP內網和外網串連通訊:

如我內網的IP為:192.168.0.2,連接埠為3200,此時我想和外網的IP:220.120.123.42,連接埠為23654通訊,從用戶端發起請求,可以根據外網的IP和連接埠順利找到伺服器,這是單項通訊,可是伺服器給內網的機器發就困難了,不以為然的同學請先仔細考慮下再來拍磚。

整個資料流的路途是這樣的:

我的內網IP和連接埠在經過我的網關之後,都會發生變化。可能會變為網關的外網如:124.253.124.12:62145,實際和伺服器通訊的是網關轉換後的地址和連接埠,也就是說你的內網IP和連接埠只有網關知道是哪台機器。好了,想清楚了這個就好辦多了,上代碼給大家看看吧。

UDP編程要留意用戶端的連接埠號碼,一定要注意,這個TCP不同,UDP是不會有長串連和穩定通訊渠道的

#include <WinSock2.h>
#pragma comment(lib, "ws2_32")

//socket版本號碼

WSADATA wsaData;
 WORD socketVersion = MAKEWORD(2, 2);
 if (::WSAStartup(socketVersion, &wsaData) != 0)
 {
  TRACE(L"Init socket dll error!");
 }

//ClientUDP.h

private:
 DWORD  mTargetIP;      //// 遠程端IP地址(使用主機位元組順序)
 WORD  mTargetPort;// 遠程連接埠號碼
 WORD  mLocalPort;     // 本地連接埠號碼
 BOOL  mIsReceiving;// 正在接收資料的標記
 HANDLE  mRcvThread;// 資料接收線程控制代碼
 SOCKET  mSckReceiver;// 用於接收的Socket
 SOCKET  mSckSender; // 用於發送的Socket
private:

//建立/銷毀用於發送的Socket
 BOOL CreateSender(void);
 void DeleteSender(void);
 // 建立/銷毀用於接收的Socket
 BOOL CreateReceiver(void);
 void DeleteReceiver(void);
 void ReceivingLoop(void);// 資料接收迴圈過程
 static DWORD WINAPI ReceivingThrd(void * pParam); // 接收線程執行體
 // 啟動/停止資料接收線程
 BOOL StartReceiving(void);
 void StopReceiving(void);
 void SendData(char* pchar,long length);//發送的資料(內容,長度)
 BOOL GetHostInfo(char * outIP, char * outName = NULL);擷取本機資訊

//ClientUDP.CPP

BOOL CreateSender(void)
{
 //DeleteSender();

 mSckSender = socket(AF_INET, SOCK_DGRAM, 0);
 if (mSckSender != INVALID_SOCKET)
 {
  BOOL flag = TRUE;
  int retr = setsockopt(mSckSender, SOL_SOCKET, SO_REUSEADDR,
   (char *) &flag, sizeof(flag));//設定socket為地址複用
  if (retr == SOCKET_ERROR)
  {
   DeleteReceiver();
   return FALSE;
  }
  int ret = 0;
  sockaddr_in addr;
  memset((char *) &addr, 0, sizeof(addr));
  char ip[20];
  char name[20];
  GetHostInfo(ip,name);//擷取原生IP和使用者名稱
  addr.sin_addr.S_un.S_addr = inet_addr(ip);
  addr.sin_family = AF_INET;
  addr.sin_port = htons(CLIENTPORT);//本機連接埠,注意該連接埠一定要和監聽的連接埠是同一連接埠(接聽下面會寫)
  ret = bind(mSckSender, (struct sockaddr*) &addr, sizeof(addr));//綁定要發送的socket
  if (ret == SOCKET_ERROR)
  {
  DeleteSender();
  return FALSE;
  }

   return TRUE;
 }
 return FALSE;
}

//發送的資料

void SendData(char* pchar,long length)
{
 char* tt = new char[length + 1];
 memset(tt,'/0',length + 1);
 memcpy(tt,pchar,length);
 sockaddr_in remote;
 memset((char *) &remote, 0, sizeof(remote));
 remote.sin_addr.S_un.S_addr = inet_addr(IP_SERVER);//要發送的伺服器IP
 remote.sin_family = AF_INET;
 remote.sin_port = htons(USRPORT_SERVER);//伺服器的連接埠

 sendto(mSckSender, tt, length, 0,
  (sockaddr *) &remote, sizeof(remote));
 DeleteSender();//每次發送要關閉發送socket,我測試過,要是注釋掉,下次就不會收到伺服器的反饋了
 delete[] tt;
}

BOOL GetHostInfo(char * outIP, char * outName)
{
 char   name[300];
 if (gethostname(name, 300) == 0)
 {
  if (outName)
  {
   strcpy(outName, name);
  }

  PHOSTENT  hostinfo;
  if ((hostinfo = gethostbyname(name)) != NULL)
  {
   LPCSTR pIP = inet_ntoa (*(struct in_addr *)*hostinfo->h_addr_list);
   strcpy(outIP, pIP);
   return TRUE;
  }
 }
 return FALSE;
}

void DeleteSender(void)
{
 if (mSckSender != INVALID_SOCKET)
 {
  closesocket(mSckSender);
  mSckSender = INVALID_SOCKET;
 }
}

//建立接收的socket

BOOL CreateReceiver(void)
{
 DeleteReceiver();
 // 建立一個UDP傳輸的Socket
 mSckReceiver = socket(AF_INET, SOCK_DGRAM, 0);
 if (mSckReceiver != INVALID_SOCKET)
 {
  // 在Socket上設定參數:允許地址複用
  BOOL flag = TRUE;
  int ret = setsockopt(mSckReceiver, SOL_SOCKET, SO_REUSEADDR,
   (char *) &flag, sizeof(flag));
  if (ret == SOCKET_ERROR)
  {
   DeleteReceiver();
   return FALSE;
  }
  // 將Socket綁定到本地連接埠號碼上
  SOCKADDR_IN  addr;
  addr.sin_family      = AF_INET;
  addr.sin_addr.s_addr = htonl(INADDR_ANY);
  addr.sin_port        = htons(CLIENTPORT);//一定要把這裡的監聽連接埠和發送的設定為同一個連接埠
  ret = bind(mSckReceiver, (struct sockaddr*) &addr, sizeof(addr));
  if (ret == SOCKET_ERROR)
  {
   DeleteReceiver();
   return FALSE;
  }
  return TRUE;
 }

 return FALSE;
}

//銷毀接收socket
void DeleteReceiver(void)
{
 if (mSckReceiver != INVALID_SOCKET)
 {
  closesocket(mSckReceiver);
  mSckReceiver = INVALID_SOCKET;
 }
}

//開啟接收線程
BOOL StartReceiving(void)
{
 // Create socket if necessary
 if (mSckReceiver == INVALID_SOCKET)
 {
  CreateReceiver();
 }

 if (mSckReceiver != INVALID_SOCKET)
 {
  if (mIsReceiving)
  {
   return TRUE;
  }

  DWORD threadID = 0;
  mRcvThread = CreateThread(NULL, 0, ReceivingThrd,
   this, 0, &threadID);
  return (mRcvThread != NULL);
 }
 return FALSE;
}
// 線程函數執行體:調用本類的ReceivingLoop函數
DWORD WINAPI CUDPClient_oneDlg::ReceivingThrd(void * pParam)
{
 ASSERT(pParam);
 CUDPClient_oneDlg * pController = (CUDPClient_oneDlg*) pParam;
 pController->ReceivingLoop();
 return 0;
}
// 資料接收過程
void CUDPClient_oneDlg::ReceivingLoop(void)
{
 struct sockaddr_in  addr_cli;
 int  addr_cli_len = sizeof(addr_cli);
 char buffer[MAX_PATH] = {'/0'};
 long bytes = 0;
 mIsReceiving = TRUE;
 CString tnote = L"";
 // 等待接收資料
 while (mIsReceiving)
 {   
  int addr_cli_len = sizeof(addr_cli);
  bytes = recvfrom(mSckReceiver, (char *)buffer, MAX_PATH,0, (LPSOCKADDR) &addr_cli, (int *) &addr_cli_len);
  if (bytes == SOCKET_ERROR || bytes == 0)
  {
   // 如果Socket發送錯誤或者Socket斷開,則跳出迴圈
   mIsReceiving = FALSE;
  }
  else
  {
    CEdit* client1 = (CEdit*)this->GetDlgItem(IDC_EDIT1);
    char * pStr = inet_ntoa(addr_cli.sin_addr);
   
    PTCHAR pszOP = new TCHAR[strlen(pStr)*2 + 1];
    memset(pszOP,'/0',strlen(pStr)*2 + 1);
    MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pStr, (int)strlen(pStr)*2, pszOP, (int)strlen(pStr)*2);
   
    PTCHAR pszContent = new TCHAR[bytes*2 + 1];
    memset(pszContent,'/0',bytes*2 + 1);
    MultiByteToWideChar(CP_ACP, 0, (LPCSTR)buffer, bytes*2, pszContent, bytes*2);

    CString text1;
    text1.Format(L"用戶端1的IP:%s,連接埠:%d,回複的內容:%s",pszOP,addr_cli.sin_port,pszContent);
    tnote += text1 + L"/r/n";
    client1->SetWindowText(tnote);

    delete[] pszOP;
    delete[] pszContent;
    memset(buffer,'/0',strlen(buffer));

  }
 }
}
void CUDPClient_oneDlg::StopReceiving(void)
{
 if (mIsReceiving)
 {
  DeleteReceiver();
  // Make sure the receiving thread has been terminated
  if (mRcvThread != NULL)
  {
   WaitForSingleObject(mRcvThread, INFINITE);
   mRcvThread = NULL;
  }
 }
}

發送調用的例子為:

{

         CreateSender();
 char p[] = "connect";
 SendData(p,strlen(p));

}

//UDPServer.h,方法和用戶端基本相同

private:
 // 建立/銷毀用於接收的Socket
 BOOL CreateReceiver(void);
 void DeleteReceiver(void);
 void ReceivingLoop(void);// 資料接收迴圈過程
 static DWORD WINAPI ReceivingThrd(void * pParam); // 接收線程執行體
 // 啟動/停止資料接收線程
 BOOL StartReceiving(void);
 void StopReceiving(void);

關鍵在這裡

struct sockaddr_in  addr_cli;

bytes = recvfrom(mSckReceiver, (char *)buffer, MAX_PATH,0, (LPSOCKADDR) &addr_cli, (int *) &addr_cli_len);

收到用戶端發來的內容後,反饋一定要:

sendto(mSckReceiver,(char*)buffer,strlen(buffer),0,(sockaddr*)&addr_cli,sizeof(addr_cli));

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/napolun007/archive/2010/12/02/6050241.aspx

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.