这几天忙着搞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));
这几个值不要修改,照原样反馈给客户端,就没问题了,本来是打算把工程都发上来的,可是没找到那里可以添加附件,所以填了代码,如果大家不清楚,可以来问我,我及时给解答