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