UDP內網和外網串連通訊的問題

來源:互聯網
上載者:User

這幾天忙著搞UDP的socket通訊,忙乎了幾天終於有點成就了,竊喜下。。。。

如果你不懂內網和外網的區別,不懂區域網路和廣域網路就先熟悉下,再來看程式。我目前的情況是用戶端在一個內網上,要串連外網的伺服器,外網伺服器在收到用戶端的請求後,反饋資訊給用戶端。

請注意是UDP,不是TCP。

先引入內網和外網的一些些小知識:

如我內網的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));

這幾個值不要修改,照原樣反饋給用戶端,就沒問題了,本來是打算把工程都發上來的,可是沒找到那裡可以添加附件,所以填了代碼,如果大家不清楚,可以來問我,我及時給解答

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.