第一部分
先分别介绍IP/TCP协议族:
IP协议:
对于TCP/IP网络来说,网络层是其核心所在。该层的IP协议负责生成发往目的地的数据报以实现逻辑寻址,完成数据从网络上一个节点向另一个节点的传输。
IP的主要目的是通过一个互联的网络传输数据报,涉及两个最基本的功能。
●寻址(Addressing):IP协议根据数据报首部中包括的目的地址将数据报传送到目的节点,这就要涉及传送路径的选择,即路由功能。IP协议使用IP地址来实现路由。
●分片(Fragmentation): IP协议还提供对数据大小的分片和重组,以适应不同网络对数据包大小的限制。如果网络只能传送小数据包,IP协议将对数据报进行分段并重新组成小块再进行传送。
IP是一个无连接的、不可靠的、点对点的协议,只能尽力(BestEffort)传送数据,不能保证数据的到达。具体地讲,主要有以下特性。
●IP协议提供无连接数据报服务,各个数据报独立传输,可能沿着不同的路径到达目的地,也可能不会按序到达目的地。
●IP协议不含错误检测或错误恢复的编码,属于不可靠的协议。所谓不可靠,是从数据传输的可靠性不能保证的角度而言的,查询的延误及其他网络通信故障都有可能导致所传数据的丢失。对这种情况,IP协议本身不处理。它的不可靠并不能说明整个TCP/IP协议不可靠。如果要求数据传输具有可靠性,则要在IP的上面使用TCP协议加以保证。位于上一层的TCP协议则提供了错误检测和恢复机制。
●作为一种点对点协议,虽然IP数据报携带源IP地址和目的IP地址,但进行数据传输时的对等实体一定是相邻设备(同一网络)中的对等实体。
IP协议的效率非常高,实现起来也较简单。这是因为IP协议采用了尽力传输的思想,随着底层网络质量的日益提高,IP协议的尽力传输的优势体现得更加明显。
下图是IP数据包的格式:
TCP/UDP协议:
传输层是TCP/IP协议中的非常重要的层次,提供了面向连接的传输控制协议(Transmission Control Protocol,TCP)和无连接的用户数据报协议(User Datagram Protocol,UDP),负责提供端到端的数据传输服务,将任意数据通过网络从发送方传输到接收方。TCP提供的是可靠的、可控制的传输服务,适用于各种网络环境;UDP提供的服务轻便但不可靠,适用于可靠性较高的网络环境。大部分Internet应用都使用TCP,因为它能够确保数据不会丢失和被破坏。本章将对这两种协议进行详细分析。
在OSI模型中,传输层是介于网络层和会话层之间的一个中间层次,弥补高层服务和网络层服务之间的差距,并向高层用户((应用程序)屏蔽通信子网的细节,使高层用户看到的只是在两个传输实体间的一条端到端的、用户可控的、可靠的数据通路。在TCP/IP模型中,由于3个高层简化为1个应用层,传输层是介于网络层与应用层之间的一个层次。
网络层协议提供网络地址、路由、交付功能,而传输层协议提供了端到端数据传输的必要机制。传输层协议通常要负责以下几项基本功能。
●创建进程到进程的通信,进程即正在运行的应用程序。进程之间通过传输层进行通信,发送进程向传输层发送数据,接收进程从传输层接收数据。
●提供控制机制,如流量控制、差错控制。数据链路层定义相邻节点的流量控制,而传输层定义端到端用户之间的流量控制。
●提供连接机制。在数据传输开始时,通信双方需要建立连接。在传输过程中,双方还需要继续通过协议来通信以验证数据是否被正确接收。数据传输完成后,任一方都可关闭连接。
第二部分
下面就以王者荣耀为例说明TCP/IP协议族在网络游戏中的应用。
2.1启动、登录游戏
首先我们启动王者荣耀游戏APP。进人APP资页,第一步会进行版本更新检测。若检查到资源包有更新则进行下载,若未检查到更新的资源包,则本地加载游戏资源包及解压资源包。这期间会跟图片服务器(image.smoba.qq.com),用户信息(game.eve.mdt.qq.comgame.str.mdt.qq.com)、数据服务器(down.qq.com,dliedl.qq.com)进行交互,游戏界面加载过程主要是TCP传输。
通过对启动阶段进行抓包分析,游戏启动过程中,客户端通过DNS域名解析获得王者荣耀游戏服务器的IP地址,建立TCP链接,进行数据交互来启动游戏。
2.2游戏对战阶段
在实时对战的过程中,客户端与服务器间主要有两个交互连接,一个为TCP连接,一个为UDP连接。开战(玩家选择自己的英雄角色及技能)及游戏分出胜负(水晶被毁)时,会触发大量UDP包,包数量大于150;正式游戏过程中,终端与主服务器保持UDP和TCP连接。
(1)TCP长连接
游戏客户端与服务器之间建立一个TCP长连接,由终端发起,通过这个TCP长连接进行心跳和其它信息交互,用以确认服务器状态正常,心跳间隔3s,消息大小固定,流程如图1所示:
(2)UDP报文
客户端和服务器之间交互的报文,除了TCP连接报文以外,还有大量的UDP报文,分为两类:
上行UDP报文:客户端通过上行UDP报文将玩家所做的操作上报给服务器。
下行UDP报文:服务器汇总参加对战的所有玩家的操作,通过下行UDP报文广播给参加对战的所有玩家的客户端。
据腾讯消息,王者荣耀游戏采用的同步机制为帧同步(非状态同步),主要流程如下:
1、玩家上报操作。
2、服务器收集各玩家上报的各自操作,进行汇总,以固定的时间间隔(例如60 ms)向参加对方的各玩家广播所有玩家的操作。
令各客户端接收到广播,知道了所有玩家的操作,按照相同的游戏逻辑进行运算,得到相同的结果,呈现在游戏界面上。
通过上述帧同步机制,基本可以保证参加对战的各玩家游戏步调是一致的,即游戏玩家间的显示基本是相同的(因为网络时延的不同会略有差异)。图2王者荣耀游戏帧同步流程:
根据以上分析,总结如下:
一、王者荣耀游戏的启动和登录采用TCP连接,并且游戏交互过程中保持TcP连接通过心跳包(3s间隔)来检测用户是否在线。
二、王者荣耀游戏的客户端操作与界面显示是通过UDP数据流与服务器进行交互的,下行速率最小需80 kbps,上行速率需64 kbps。因此王者荣耀游戏对速率要求较小。
第三部分
推荐一个实现TCP/IP的简单代码。摘自(73条消息) C++:实现socket通信(TCP/IP)实例_Cche的博客-CSDN博客_c++ socket
(1)server端代码
#include \"pch.h\"#include<iostream>#include<winsock.h>#pragma comment(lib,\"ws2_32.lib\")using namespace std;void initialization();int main() {//定义长度变量int send_len = 0;int recv_len = 0;int len = 0;//定义发送缓冲区和接受缓冲区char send_buf[100];char recv_buf[100];//定义服务端套接字,接受请求套接字SOCKET s_server;SOCKET s_accept;//服务端地址客户端地址SOCKADDR_IN server_addr;SOCKADDR_IN accept_addr;initialization();//填充服务端信息server_addr.sin_family = AF_INET;server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);server_addr.sin_port = htons(5010);//创建套接字s_server = socket(AF_INET, SOCK_STREAM, 0);if (bind(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {cout << \"套接字绑定失败!\" << endl;WSACleanup();}else {cout << \"套接字绑定成功!\" << endl;}//设置套接字为监听状态if (listen(s_server, SOMAXCONN) < 0) {cout << \"设置监听状态失败!\" << endl;WSACleanup();}else {cout << \"设置监听状态成功!\" << endl;}cout << \"服务端正在监听连接,请稍候....\" << endl;//接受连接请求len = sizeof(SOCKADDR);s_accept = accept(s_server, (SOCKADDR *)&accept_addr, &len);if (s_accept == SOCKET_ERROR) {cout << \"连接失败!\" << endl;WSACleanup();return 0;}cout << \"连接建立,准备接受数据\" << endl;//接收数据while (1) {recv_len = recv(s_accept, recv_buf, 100, 0);if (recv_len < 0) {cout << \"接受失败!\" << endl;break;}else {cout << \"客户端信息:\" << recv_buf << endl;}cout << \"请输入回复信息:\";cin >> send_buf;send_len = send(s_accept, send_buf, 100, 0);if (send_len < 0) {cout << \"发送失败!\" << endl;break;}}//关闭套接字closesocket(s_server);closesocket(s_accept);//释放DLL资源WSACleanup();return 0;}void initialization() {//初始化套接字库WORD w_req = MAKEWORD(2, 2);//版本号WSADATA wsadata;int err;err = WSAStartup(w_req, &wsadata);if (err != 0) {cout << \"初始化套接字库失败!\" << endl;}else {cout << \"初始化套接字库成功!\" << endl;}//检测版本号if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {cout << \"套接字库版本号不符!\" << endl;WSACleanup();}else {cout << \"套接字库版本正确!\" << endl;}//填充服务端地址信息}
(2)client端:
#include \"pch.h\"#include<iostream>#include<winsock.h>#pragma comment(lib,\"ws2_32.lib\")using namespace std;void initialization();int main() {//定义长度变量int send_len = 0;int recv_len = 0;//定义发送缓冲区和接受缓冲区char send_buf[100];char recv_buf[100];//定义服务端套接字,接受请求套接字SOCKET s_server;//服务端地址客户端地址SOCKADDR_IN server_addr;initialization();//填充服务端信息server_addr.sin_family = AF_INET;server_addr.sin_addr.S_un.S_addr = inet_addr(\"127.0.0.1\");server_addr.sin_port = htons(1234);//创建套接字s_server = socket(AF_INET, SOCK_STREAM, 0);if (connect(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {cout << \"服务器连接失败!\" << endl;WSACleanup();}else {cout << \"服务器连接成功!\" << endl;}//发送,接收数据while (1) {cout << \"请输入发送信息:\";cin >> send_buf;send_len = send(s_server, send_buf, 100, 0);if (send_len < 0) {cout << \"发送失败!\" << endl;break;}recv_len = recv(s_server, recv_buf, 100, 0);if (recv_len < 0) {cout << \"接受失败!\" << endl;break;}else {cout << \"服务端信息:\" << recv_buf << endl;}}//关闭套接字closesocket(s_server);//释放DLL资源WSACleanup();return 0;}void initialization() {//初始化套接字库WORD w_req = MAKEWORD(2, 2);//版本号WSADATA wsadata;int err;err = WSAStartup(w_req, &wsadata);if (err != 0) {cout << \"初始化套接字库失败!\" << endl;}else {cout << \"初始化套接字库成功!\" << endl;}//检测版本号if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {cout << \"套接字库版本号不符!\" << endl;WSACleanup();}else {cout << \"套接字库版本正确!\" << endl;}//填充服务端地址信息}