如题。局域网内互聊的可以实现了。
但不能像qq那样的,即通过路由器上网还是直连上网都能连通。
这两天搜了一下,有的说需要公网服务器什么的,有的说需要端口映射的不懂啊。谁能给个详细的解说吗?能给源码更好啊。ps:我有个小网站,有独立ip什么的。
但不能像qq那样的,即通过路由器上网还是直连上网都能连通。
这两天搜了一下,有的说需要公网服务器什么的,有的说需要端口映射的不懂啊。谁能给个详细的解说吗?能给源码更好啊。ps:我有个小网站,有独立ip什么的。
能给出代码吗?大师。我才刚会局域网内用udp啊
不明白。。怎么在网页上运行服务端?什么定时签到?
即使运行了服务端,并且服务器端可以得到发来的信息,但发出信息的时候怎么区分客户端?通过ip吗?客户端要是一样的ip呢?你们整的都太专业了,我才刚会复制别人的代码稍修改下粘贴给自己用
在网络上有很多 C 实现该技术的范例,在 Linux 论坛里这种已经被看为老掉牙的
技术了,UDP、TCP的P2P代码满天飞,其实原理也很简单,用VB其实也可以实现,
不过很少看见有VB这方面的代码亮相。我也写过这类程序,不过也是用VC,如果要用VB来写,感觉效率可能不行,因为
虽然VB可以开多线程来处理过程,但要调到稳定还真是个费事的事情,还不如用VC
来写还快一点。因为通常在VB中用Sock API来做网络通讯,都是用异步的方式处理
过程,而这个“异步”,就是通过窗口消息来得到要处理的事件。这样一来,侦听
和连接的东西多了,窗口也会创建得多了,感觉很怪,但用堵塞方式,如果不上线程,
整个程序都动不了,更别说用了。其实原理很简单,首先需要在公网上有一台服务器,作用是提供用户信息和“打洞”
用的。大致的服务器技术可以这样实现。
公网服务器先创建一个 IP 协议的套接字,注意,是IP协议,不是TCP/IP或UDP/IP
如:
LocalSocketHandle = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);然后设置好基本的参数绑定套接字就可以开启线程狂读数据了,如:
while(ServerObj->IsServer==TRUE){
Sleep(1);
rd = recvfrom(LocalHandle, (char *)&recvbuf, sizeof(stMessage), 0, (sockaddr *)&sender, &dwSender);
if(rd<=0){
// 错误
}else{
// 读到数据分析数据包和远程主机信息
//.......
}
}运行过程是这样。
服务器程序先启动,服务器程序在这称为 S
然后启动客户端程序,客户端程序在这称为 C,若有多台客户端以 C1,C2,C3...Cn 作为区别
C1 向服务器提交登录数据包(数据包格式可自己定义,
如:
0xB2 数据包头,固定为0xB2
0x00000000 用户ID,登录时为0
0x01 命令 1表示登录请求
2表示索取好友列表
3取得好友IP地址和端口
4打洞请求
0x00 参数长度
0x00,0x00... 参数(根据参数长度来定)
0x000000 校验码,可以用CRC/CRC16/CRC32/异或 等校验方式校验数据的完整性
0xB3 包尾,固定为0xB3
)
S 向 C1 返回登录成功结果数据包
C1 向 S 发送索取好友列表请求
S 判断用户已登陆后返回整个C1的好友列表给 C1
C1 选择一个好友(称C2)弹出进行聊天,这时 CI 发送取得好友IP地址和端口命令给 S
S 判断用户已登陆后返回C2记录在 S 上的最后一次 IP 记录给 C1
C1 收到 S 发送过来的IP和端口后直接通过sendto发送给 C2
C1 发送完成后等待 C2 的收到数据回应
若C1判断回应超时,则发送一个打洞命令给 S
然后C1 不断发送测试命令给 C2
S 收到 C1 的打洞命令后,向 C2 发送包含 C1 的IP地址和端口信息的联通数据包
C2 收到 S 的联通数据包请求后,C2 向 C1 发送测试请求
这时因为 C1 不断重复的向 C2 一直在发送测试请求,只要 C2 同时也向 C1 发出信息,
这样打洞过程就成功了。
然后C1当收到C2的测试请求数据后,发送要发送的内容给 C2,
C2就可以读到C1发过来的内容了以上是整个P2P基于IP包的形式的通讯过程。
在VB里用控件我没实验过,不过VC中直接用Sock API测试成功,我两边的C端都是在内网
通过路由上网的,运作很正常
再懒一点的话,直接用QQ更简单。因为这种程序说白了开发已经没多大意义了,练手倒是不错。
如果要源码你才能开始动手,你可以到网上搜索一下呀,VC p2p源代码
有了原理、操作过程、源代码你如果还弄不了,那就算别人教你也
只是别人写一遍程序给你看而已。不会有太大区别。因为如果不能
通过原理、操作过程、源代码这些资源自己弄懂,只能是基础差太
远,即使别人教也不可能一步登天的取得成果。只有自己把基础打
牢才是问题的解决方法。否则和直接用现成的工具没什么两样。
好人啊...不过你写那些我都看不懂..我也只是初学vb,不懂vc啊.
"然后设置好基本的参数绑定套接字就可以开启线程狂读数据了"..看得我挺激动的就是不知道啥意思.顺便描述一下:这个功能是要嵌进公司一个软件中去的..并且这个软件其它功能已差不多ok了,就这个在线沟通的还没做出来.所以这个时候要换用vc来做会头大的.而我现在更想直接得到个代码,或者控件.我对vb了解本不多哈,更别说用vb来实现什么通信协议,等我学会了公司就要扣我money了.因为当时我试了局域网沟通很成功后对boss说了没问题的.
出了一款P2P控件,支持多种语言调用,包括VB,不过要收点费用。好像
也不是很贵,你应该能接受的,你可以搜索一下 P2P控件
以下是服务器端的主要过程#pragma comment(lib, "ws2_32.lib")#include "windows.h"
#include "..\proto.h"
#include "..\Exception.h"UserList ClientList;void InitWinSock()
{
WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Windows sockets 2.2 startup");
throw Exception("");
}
else{
printf("Using %s (Status: %s)\n",
wsaData.szDescription, wsaData.szSystemStatus);
printf("with API versions %d.%d to %d.%d\n\n",
LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
}
}SOCKET mksock(int type)
{
SOCKET sock = socket(AF_INET, type, 0);
if (sock < 0)
{
printf("create socket error");
throw Exception("");
}
return sock;
}stUserListNode GetUser(char *username)
{
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp( ((*UserIterator)->userName), username) == 0 )
return *(*UserIterator);
}
throw Exception("not find this user");
}int main(int argc, char* argv[])
{
try{
InitWinSock();
SOCKET PrimaryUDP;
PrimaryUDP = mksock(SOCK_DGRAM); sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port= htons(SERVER_PORT);
local.sin_addr.s_addr = htonl(INADDR_ANY);
int nResult=bind(PrimaryUDP,(sockaddr*)&local,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
throw Exception("bind error"); sockaddr_in sender;
stMessage recvbuf;
memset(&recvbuf,0,sizeof(stMessage)); for(;;)
{
int dwSender = sizeof(sender);
int ret = recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(stMessage), 0, (sockaddr *)&sender, &dwSender);
if(ret <= 0)
{
printf("recv error");
continue;
}
else
{
int messageType = recvbuf.iMessageType;
switch(messageType){
case LOGIN:
{
// 将这个用户的信息记录到用户列表中
printf("has a user login : %s\n", recvbuf.message.loginmember.userName);
stUserListNode *currentuser = new stUserListNode();
strcpy(currentuser->userName, recvbuf.message.loginmember.userName);
currentuser->ip = ntohl(sender.sin_addr.S_un.S_addr);
currentuser->port = ntohs(sender.sin_port);
ClientList.push_back(currentuser); // 发送已经登陆的客户信息
int nodecount = (int)ClientList.size();
sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender));
} break;
}
case LOGOUT:
{
// 将此客户信息删除
printf("has a user logout : %s\n", recvbuf.message.logoutmember.userName);
UserList::iterator removeiterator = NULL;
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp( ((*UserIterator)->userName), recvbuf.message.logoutmember.userName) == 0 )
{
removeiterator = UserIterator;
break;
}
}
if(removeiterator != NULL)
ClientList.remove(*removeiterator);
break;
}
case P2PTRANS:
{
// 某个客户希望服务端向另外一个客户发送一个打洞消息
printf("%s wants to p2p %s\n",inet_ntoa(sender.sin_addr),recvbuf.message.translatemessage.userName);
stUserListNode node = GetUser(recvbuf.message.translatemessage.userName);
sockaddr_in remote;
remote.sin_family=AF_INET;
remote.sin_port= htons(node.port);
remote.sin_addr.s_addr = htonl(node.ip); in_addr tmp;
tmp.S_un.S_addr = htonl(node.ip);
printf("the address is %s,and port is %d\n",inet_ntoa(tmp), node.port); stP2PMessage transMessage;
transMessage.iMessageType = P2PSOMEONEWANTTOCALLYOU;
transMessage.iStringLen = ntohl(sender.sin_addr.S_un.S_addr);
transMessage.Port = ntohs(sender.sin_port);
sendto(PrimaryUDP,(const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr *)&remote, sizeof(remote)); break;
}
case GETALLUSER:
{
int command = GETALLUSER;
sendto(PrimaryUDP, (const char*)&command, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender)); int nodecount = (int)ClientList.size();
sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender)); for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender));
}
break;
}
}
}
} }
catch(Exception &e)
{
printf(e.GetMessage());
return 1;
} return 0;
}
#include "..\proto.h"
#include "..\Exception.h"
#include <iostream>
using namespace std;UserList ClientList;#define COMMANDMAXC 256
#define MAXRETRY 5SOCKET PrimaryUDP;
char UserName[10];
char ServerIP[20];bool RecvedACK;void InitWinSock()
{
WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Windows sockets 2.2 startup");
throw Exception("");
}
else{
printf("Using %s (Status: %s)\n",
wsaData.szDescription, wsaData.szSystemStatus);
printf("with API versions %d.%d to %d.%d\n\n",
LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
}
}SOCKET mksock(int type)
{
SOCKET sock = socket(AF_INET, type, 0);
if (sock < 0)
{
printf("create socket error");
throw Exception("");
}
return sock;
}stUserListNode GetUser(char *username)
{
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp( ((*UserIterator)->userName), username) == 0 )
return *(*UserIterator);
}
throw Exception("not find this user");
}void BindSock(SOCKET sock)
{
sockaddr_in sin;
sin.sin_addr.S_un.S_addr = INADDR_ANY;
sin.sin_family = AF_INET;
sin.sin_port = 0;
if (bind(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0)
throw Exception("bind error");
}void ConnectToServer(SOCKET sock,char *username, char *serverip)
{
sockaddr_in remote;
remote.sin_addr.S_un.S_addr = inet_addr(serverip);
remote.sin_family = AF_INET;
remote.sin_port = htons(SERVER_PORT);
stMessage sendbuf;
sendbuf.iMessageType = LOGIN;
strncpy(sendbuf.message.loginmember.userName, username, 10); sendto(sock, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote,sizeof(remote)); int usercount;
int fromlen = sizeof(remote);
int iread = recvfrom(sock, (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote, &fromlen);
if(iread<=0)
{
throw Exception("Login error\n");
} cout<<"Have "<<usercount<<" users logined server:"<<endl;
for(int i = 0;i<usercount;i++)
{
stUserListNode *node = new stUserListNode;
recvfrom(sock, (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote, &fromlen);
ClientList.push_back(node);
cout<<"Username:"<<node->userName<<endl;
in_addr tmp;
tmp.S_un.S_addr = htonl(node->ip);
cout<<"UserIP:"<<inet_ntoa(tmp)<<endl;
cout<<"UserPort:"<<node->port<<endl;
cout<<""<<endl;
}
}void OutputUsage()
{
cout<<"You can input you command:\n"
<<"Command Type:\"send\",\"exit\",\"getu\"\n"
<<"Example : send Username Message\n"
<<" exit\n"
<<" getu\n"
<<endl;
}bool SendMessageTo(char *UserName, char *Message)
{
char realmessage[256];
unsigned int UserIP;
unsigned short UserPort;
bool FindUser = false;
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp( ((*UserIterator)->userName), UserName) == 0 )
{
UserIP = (*UserIterator)->ip;
UserPort = (*UserIterator)->port;
FindUser = true;
}
} if(!FindUser)
return false; strcpy(realmessage, Message);
for(int i=0;i<MAXRETRY;i++)
{
RecvedACK = false; sockaddr_in remote;
remote.sin_addr.S_un.S_addr = htonl(UserIP);
remote.sin_family = AF_INET;
remote.sin_port = htons(UserPort);
stP2PMessage MessageHead;
MessageHead.iMessageType = P2PMESSAGE;
MessageHead.iStringLen = (int)strlen(realmessage)+1;
int isend = sendto(PrimaryUDP, (const char *)&MessageHead, sizeof(MessageHead), 0, (const sockaddr*)&remote, sizeof(remote));
isend = sendto(PrimaryUDP, (const char *)&realmessage, MessageHead.iStringLen, 0, (const sockaddr*)&remote, sizeof(remote));
for(int j=0;j<10;j++)
{
if(RecvedACK)
return true;
else
Sleep(300);
} sockaddr_in server;
server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
server.sin_family = AF_INET;
server.sin_port = htons(SERVER_PORT);
stMessage transMessage;
transMessage.iMessageType = P2PTRANS;
strcpy(transMessage.message.translatemessage.userName, UserName); sendto(PrimaryUDP, (const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr*)&server, sizeof(server));
Sleep(100);
}
return false;
}void ParseCommand(char * CommandLine)
{
if(strlen(CommandLine)<4)
return;
char Command[10];
strncpy(Command, CommandLine, 4);
Command[4]='\0'; if(strcmp(Command,"exit")==0)
{
stMessage sendbuf;
sendbuf.iMessageType = LOGOUT;
strncpy(sendbuf.message.logoutmember.userName, UserName, 10);
sockaddr_in server;
server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
server.sin_family = AF_INET;
server.sin_port = htons(SERVER_PORT); sendto(PrimaryUDP,(const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr *)&server, sizeof(server));
shutdown(PrimaryUDP, 2);
closesocket(PrimaryUDP);
exit(0);
}
else if(strcmp(Command,"send")==0)
{
char sendname[20];
char message[COMMANDMAXC];
int i;
for(i=5;;i++)
{
if(CommandLine[i]!=' ')
sendname[i-5]=CommandLine[i];
else
{
sendname[i-5]='\0';
break;
}
}
strcpy(message, &(CommandLine[i+1]));
if(SendMessageTo(sendname, message))
printf("Send OK!\n");
else
printf("Send Failure!\n");
}
else if(strcmp(Command,"getu")==0)
{
int command = GETALLUSER;
sockaddr_in server;
server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
server.sin_family = AF_INET;
server.sin_port = htons(SERVER_PORT); sendto(PrimaryUDP,(const char*)&command, sizeof(command), 0, (const sockaddr *)&server, sizeof(server));
}
}DWORD WINAPI RecvThreadProc(LPVOID lpParameter)
{
sockaddr_in remote;
int sinlen = sizeof(remote);
stP2PMessage recvbuf;
for(;;)
{
int iread = recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(recvbuf), 0, (sockaddr *)&remote, &sinlen);
if(iread<=0)
{
printf("recv error\n");
continue;
}
switch(recvbuf.iMessageType)
{
case P2PMESSAGE:
{
char *comemessage= new char[recvbuf.iStringLen];
int iread1 = recvfrom(PrimaryUDP, comemessage, 256, 0, (sockaddr *)&remote, &sinlen);
comemessage[iread1-1] = '\0';
if(iread1<=0)
throw Exception("Recv Message Error\n");
else
{
printf("Recv a Message:%s\n",comemessage);
stP2PMessage sendbuf;
sendbuf.iMessageType = P2PMESSAGEACK;
sendto(PrimaryUDP, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote, sizeof(remote));
} delete []comemessage;
break; }
case P2PSOMEONEWANTTOCALLYOU:
{
printf("Recv p2someonewanttocallyou data\n");
sockaddr_in remote;
remote.sin_addr.S_un.S_addr = htonl(recvbuf.iStringLen);
remote.sin_family = AF_INET;
remote.sin_port = htons(recvbuf.Port); stP2PMessage message;
message.iMessageType = P2PTRASH;
sendto(PrimaryUDP, (const char *)&message, sizeof(message), 0, (const sockaddr*)&remote, sizeof(remote));
break;
}
case P2PMESSAGEACK:
{
RecvedACK = true;
break;
}
case P2PTRASH:
{
printf("Recv p2ptrash data\n");
break;
}
case GETALLUSER:
{
int usercount;
int fromlen = sizeof(remote);
int iread = recvfrom(PrimaryUDP, (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote, &fromlen);
if(iread<=0)
{
throw Exception("Login error\n");
}
ClientList.clear(); cout<<"Have "<<usercount<<" users logined server:"<<endl;
for(int i = 0;i<usercount;i++)
{
stUserListNode *node = new stUserListNode;
recvfrom(PrimaryUDP, (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote, &fromlen);
ClientList.push_back(node);
cout<<"Username:"<<node->userName<<endl;
in_addr tmp;
tmp.S_un.S_addr = htonl(node->ip);
cout<<"UserIP:"<<inet_ntoa(tmp)<<endl;
cout<<"UserPort:"<<node->port<<endl;
cout<<""<<endl;
}
break;
}
}
}
}
int main(int argc, char* argv[])
{
try
{
InitWinSock();
PrimaryUDP = mksock(SOCK_DGRAM);
BindSock(PrimaryUDP); cout<<"Please input server ip:";
cin>>ServerIP; cout<<"Please input your name:";
cin>>UserName; ConnectToServer(PrimaryUDP, UserName, ServerIP); HANDLE threadhandle = CreateThread(NULL, 0, RecvThreadProc, NULL, NULL, NULL);
CloseHandle(threadhandle);
OutputUsage(); for(;;)
{
char Command[COMMANDMAXC];
gets(Command);
ParseCommand(Command);
}
}
catch(Exception &e)
{
printf(e.GetMessage());
return 1;
}
return 0;
}
看楼主的样子好像是要在企业即将使用的软件中添加一个聊天功能,当然有可能是异地有分公司的企业吧。
我有一个小小的想法,没有时间认证的,哪位大哥证明一下?
我个人感觉局域网和公网好像是一样的运行结构吧,如果A、B两台异地电脑相互通信,A、B两台都有公网IP,那么这两台电脑之间不是就好像局域网的两台电脑通讯了吗?这样A、B能不能直接通讯呢?
交换机制,就由IP地址开始说起吧,IP地址现在通常使用的是4字节的IP地址,即最多支持256的4次方
个地址,就是所最多支持40多亿个有用自己独立IP的终端在这个大网络环境运作,而世界人口总数在
60多亿人口,单每人一个IP都分配不过来,何况还有无人看管的机房设备需要分配的IP,每个人可能
拥有办公室、家庭、手机上网这类需求,而且介于分配管理上的需求,每个管理部门只有一定配额的
IP段可分配,而还有可能因为地区,网络供应商和使用时间的问题浪费掉部分IP地址某些时段不在
Internet上,所以,在Internet单靠真实的IP量是不能满足与目前的需求的,所以出现了伪IP的概念
和IP6的概念,但IP6目前还没有很普及,我们通常也是在IP4的环境下上网,所以伪IP就和我们接触
得比较多,只是很多人不知道而已。其实如果你注意一下会发现,每次用PPPOE拨号的时候,如果有心
去某个网站看一下,会发现,网站上显示你的IP地址和你拨号显示的IP地址不一样,其实就是电信部门
帮你分配了个伪IP,如果一样,恭喜你,你拥有了真实IP。
伪IP其实是利用 TCP/IP 或 UDP/IP 通讯中的另一个参数配合实现的过程,这就是端口,通常一个内网
在同一个路由环境下能运作是是在同一个网段下的,也就是所最多255个终端,而这255个终端只要配合
路由协议中的端口,基本还是能满足常用的通讯需求的,因为基本上没人会用完所有的端口在一台终端
机来跑,因为这么个弄法,可能没到路由交换层,你的终端机已经出问题了,呵呵。
所以,Internet 的网络环境与内网环境是不一样的,当然,也有别的方法可以将网络环境设置得更为
简单,就比如说VPN,当你连入Internet后,可以通过VPN拨号到你的VPN服务器,通过这个服务器将
所有拨入VPN服务器的Internet终端模拟成一个局域网,这类似于驱动层的过滤驱动,他会把复杂的
网络信息转换成内网方式进行通讯。这样一来,在程序设计上就简单很多。楼主也可以参考这种方式
使用你的程序的,不用改动你原来的程序,只需要架设一台VPN服务器,然后在你的系统中建立个VPN
拨号连接,每次都自动拨号就可以了。这些都是Windows Server 2003系统自带的设置。使用其实很
简单的,你可以考虑考虑。