游戏服务器IPTCP协议(王者荣耀为例)
第⼀部分
先分别介绍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),⽤户信息
(game.eve.mdt.qq game.str.mdt.qq)、数据服务器(down.qq,dliedl.qq)进⾏交互,游戏界⾯加载过程主要是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的简单代码。摘⾃
(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();
return0;
}
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 << "请输⼊回复信息:";
socket通信报文格式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();
return0;
}
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();
return0;
}
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;
}
//填充服务端地址信息
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。