记一次奇葩的计算机网络作业
前天考完了计算机网络,这学期只剩下数据库和组成原理两门考试了。开通博客以来也一直没有写什么东西,正好想起来之前计算机网络课老师流过一个比较奇葩的作业,就整理一下贴上来吧。以后慢慢将自己从大一以来的编程笔记贴到博客上来,笔记主要是内核驱动方面的内容。
当时计算机网络每两周有一节讨论课。老师会留一些题目让学生选做,看样子题目都是老师自己想出来的。有一次一组题目里有一条是QQ群聊中多播IP如何获取,我就选了这条,因为我想QQ群聊怎么可能用的是多播。一是因为使用组播的话好多功能无法实现,比如上传群文件。二是因为这不利于腾讯对群消息的监控。
在网上也有很多人对这个问题进行讨论。通过筛选我确定了三个最有可能的方法。一是在群里指定几个网速快的用户作为Server将消息转发,比如指定群中有100人,指定五个人作为Server,每个用户发送的消息会发给这五个人,在由他们向其他人转发。这个方法管理起来可能会比较麻烦。而是服务器为每个群维护一个用户列表,群消息发送给服务器,由服务器向每个人转发。这个方法对服务器开销很大。三是P2P和服务器转发相结合。
群聊具体使用的什么机制只要腾讯不公开我们就无法精确了解。但是否定某些可能的机制还是有戏的,接下来我要做的就是否定老师说的使用组播了。
组播的具体原理就不在详细的介绍了。我们使用setsockopts函数可以加入或离开某个组。加入某个组之后就可以接受到这个组内的消息。而调用sendto函数,将第三参数设置为组的地址,就可以向这个组中发送消息。向组内发送消息不用加入到这个组中。
我写了两套组播的小程序作为参照。每套包含两个组播发送方与组播接受方。这两套程序的发送方与接收方代码里除了组播地址与绑定的端口不同外,其他都一样。也就是每套程序就是一个组,一个组里有两个用户发送接收消息。两个组的地址分别为234.5.6.7和234.5.6.8。使用两台位于同一WIFI的计算机进行测试,计算机A打开两个接收程序,分别监听着两个组中的消息。计算机B用于发送。B中运行抓包软件进行抓包分析。
接受消息的代码为
1 #include <winsock2.h> 2 #pragma comment(lib, "WS2_32") 3 4 #include <stdio.h> 5 #include <windows.h> 6 #include <Ws2tcpip.h> 7 8 9 class CInitSock 10 { 11 public: 12 CInitSock(); 13 ~CInitSock(); 14 }; 15 16 inline CInitSock::CInitSock() 17 { 18 // 初始化WS2_32.dll 19 WSADATA wsaData; 20 WORD sockVersion = MAKEWORD(2, 2); 21 if(::WSAStartup(sockVersion, &wsaData) != 0) 22 { 23 exit(0); 24 } 25 } 26 27 inline CInitSock::~CInitSock() 28 { 29 ::WSACleanup(); 30 } 31 32 33 34 35 36 // 初始化Winsock库 37 CInitSock theSock; 38 39 void main() 40 { 41 SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0); 42 43 // 允许其它进程使用绑定的地址 44 BOOL bReuse = TRUE; 45 ::setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&bReuse, sizeof(BOOL)); 46 47 48 // 绑定到4567端口 49 sockaddr_in si; 50 si.sin_family = AF_INET; 51 si.sin_port = ::ntohs(4567); 52 si.sin_addr.S_un.S_addr = INADDR_ANY; 53 ::bind(s, (sockaddr*)&si, sizeof(si)); 54 55 // 加入多播组 56 ip_mreq mcast; 57 mcast.imr_interface.S_un.S_addr = INADDR_ANY; 58 mcast.imr_multiaddr.S_un.S_addr = ::inet_addr("234.5.6.7"); // 多播地址为234.5.6.7 59 ::setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcast, sizeof(mcast)); 60 61 62 // 接收多播组数据 63 printf(" 开始接收多播组234.5.6.7上的数据... \n"); 64 char buf[1280]; 65 int nAddrLen = sizeof(si); 66 while(TRUE) 67 { 68 int nRet = ::recvfrom(s, buf, strlen(buf), 0, (sockaddr*)&si, &nAddrLen); 69 if(nRet != SOCKET_ERROR) 70 { 71 buf[nRet] = '\0'; 72 printf(buf); 73 } 74 else 75 { 76 int n = ::WSAGetLastError(); 77 break; 78 } 79 } 80 }
发送消息的代码为
1 #include <winsock2.h> 2 #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib 3 #include <stdio.h> 4 #include <Ws2tcpip.h> 5 6 7 class CInitSock 8 { 9 public: 10 CInitSock(BYTE minorVer = 2, BYTE majorVer = 2) 11 { 12 // 初始化WS2_32.dll 13 WSADATA wsaData; 14 WORD sockVersion = MAKEWORD(minorVer, majorVer); 15 if(::WSAStartup(sockVersion, &wsaData) != 0) 16 { 17 exit(0); 18 } 19 } 20 ~CInitSock() 21 { 22 ::WSACleanup(); 23 } 24 }; 25 26 27 CInitSock initSock; // 初始化Winsock库 28 int main() 29 { 30 // 创建套节字 31 SOCKET sSend = ::socket(AF_INET, SOCK_DGRAM, 0); 32 if(sSend == INVALID_SOCKET) 33 { 34 printf("Failed socket() \n"); 35 return 0; 36 } 37 38 // 允许其它进程使用绑定的地址 39 BOOL bReuse = TRUE; 40 ::setsockopt(sSend, SOL_SOCKET, SO_REUSEADDR, (char*)&bReuse, sizeof(BOOL)); 41 42 int nTTL = 64; 43 // 设置多播封包的TTL值 44 ::setsockopt(sSend, 45 IPPROTO_IP, IP_MULTICAST_TTL, (char*)&nTTL, sizeof(int)); 46 47 DWORD dwLocalAddr; 48 // 设置要使用的发送接口 49 setsockopt(sSend, 50 IPPROTO_IP, IP_MULTICAST_IF, (char*)&dwLocalAddr, sizeof(dwLocalAddr)); 51 52 53 char szText[] = "Multicasting Test 1"; 54 55 sockaddr_in dest; 56 dest.sin_family = AF_INET; 57 dest.sin_addr.S_un.S_addr = inet_addr("234.5.6.7"); 58 dest.sin_port = ::ntohs(4567); 59 60 sendto(sSend,szText,strlen(szText),0, (sockaddr*)&dest, sizeof(dest)); 61 62 printf("Send Success No.1"); 63 closesocket(sSend); 64 65 Sleep(INFINITE); 66 67 return 0; 68 69 }
先运行A中接收程序,在B中运行发送程序。A收到消息,证明发送成功。查看抓包软件。


从抓包软件可以看出,发到不同组的包,目的IP就是对应的组地址。然后我们来测试向QQ群中发送消息。


由于QQ消息是加密的,我们不能确定哪两个是我们发送出去的包。但是,所有包的目的IP都是183.232.127.242,并不像我们刚才的组播的包那样不同组播使用不同组播地址。这个地址应该就是腾讯服务器的地址。然后我们又测试了发文件,私聊消息等。发现除了发送文件是两台计算机经过路由器直接发送外,其他消息都是发往这个地址。现在已经可以排除群聊用的是组播,最有可能是服务器转发。
最后成功的说服了老师否定了题目。之前老师认为是组播而不是服务器转发是因为他感觉这样服务器压力太大。不过按说凭腾讯的实力做到这点应该不会有什么问题。
在网上也有很多人对这个问题进行讨论。通过筛选我确定了三个最有可能的方法。一是在群里指定几个网速快的用户作为Server将消息转发,比如指定群中有100人,指定五个人作为Server,每个用户发送的消息会发给这五个人,在由他们向其他人转发。这个方法管理起来可能会比较麻烦。而是服务器为每个群维护一个用户列表,群消息发送给服务器,由服务器向每个人转发。这个方法对服务器开销很大。三是P2P和服务器转发相结合。
浙公网安备 33010602011771号