<<网络游戏服务器端编程>> 电子工业出版社
1.Client/Server(客户端/服务器端)是现在网络游戏的最基本的框架,而从开发的角度看,又可以分为以下几种类型。
(1)对等的客户端与服务器端
(2)基于大厅的网络游戏
(3)纯C/S结构的网络游戏
2.网络协议
(1)协议的关键部分包括:语法(数据格式、编程、信号电平等)、语义(包括用于协调和进行差错处理的控制信息)和定时(包括速度匹配和排序等)。
(2)最常提到的网络协议就是TCP/IP(Transmission Contrlo Protocol/Internet Protocol),事实上,TCP/IP是一个协议的集合(称为协议族),而另一些系统则可能使用IPX(InterPacket Exchange)协议。
3.OSI通信协议模型(Open System Interconnection)
(1)OSI(开放系统互连)通信协议模型是一个多层的通信协议。
(2)所谓的开放系统是指允许任意两个具有不同基本体系结构的系统进行通信的一套协议集。理论上,OSI允许任意两台 计算机实现通信。
(3)OSI模型将网络划分为7层模型,分别在各层上实现不同的功能,这7层从上至下依次是:
(I)应用层(Application):
应用层是最接近用户的一层。一般是构建在各种通信协议上的网络应用软件,与用户直接交互。其作用是在实现多个系统应用进程相互通信的同时,完成一系列业务处理所需的服务。
(II)表示层(Presentation)
表示层的作用之一是为异种机通信提供异种公共语言,以便能进行互操作。另外,表示层还完成其他一些对数据的处理加工,如为了减少传输的数据量对数据进行压缩,为保证安全性进行的加密等。
(III)会话层(session)
会话层提供的服务可使应用建立和维护会话,并能使会话获得同步。主要的功能是对话管理、数据流同步和重新同步。
(IV)传输层(Transport)
传输层是两台计算机经过网络进行数据通信时,第一个端到端的层次,具有缓冲作用。传输层还可进行复用,即在一个网络连接上创建多个逻辑连接。传输层采用分流/合流、复用/介复用技术来调节上述通信子网络的差异,使会话层感受不到。传输层还还具备差错恢复、流量控制等功能。上述功能的最终目的是为会话提供可靠的,无误的数据传输。
(V)网络层(Network)
网络层提供跨越多个网络的选路功能,为端点提供无连接的数据报访问,并定义端到端通过整个互联网网络的寻址功能。
(VI)数据链路层(Data Link)
数据链路层也被称为数据链路控制层(DLC层,Data Link Control Layer),它主要负责管理数据格式。
(VII)物理层(Physical)
物理层为设备之间的数据通信提供传输媒体及互连设备,为数据传输提供可靠的环境。物理层的主要功能是为数据端设备提供传送数据的通路。
(4)OSI模型中每一层只和紧密的上下层两层通信,高层协议偏重于处理用户服务器和各种应用请求,底层协议偏重于处理实际实际的信息传输。分层的目的在于把各种特定的功能分离开来,各个层次相对独立。
4.TCP/IP协议
(1)TCP/IP对应于一个4层的概念模型,称为DARPA模型,这4层从上到下依次是:
(I)应用层(Application)
应用层对应于OSI模型的应用层,给应用程序提供访问其他层服务的能力并定义应用程序用于交换数据的协议。
(II)传输层(Transport)
传输层对应于OSI模型的传输层和部分会话层功能。它负责给应用层提供会话和数据通报通信功能。传输层的核心协议是传输控制协议(Transmission Control Protocol,TCP)和用户数据报协议(User Datagram Protocol,UDP)
(III)网络层(Network)
网络层也称互联网层,它对应于OSI模型的网络层,负责寻址、打包以及从一台计算机通过一个或多个路由器到最终目标的包转发机制。
(IV)网络访问层(Network Access)
网络访问层对应于OSI模型的物理层和数据链路层,并负责把ICP/IP包放到网络传输介质和从网络传输介质上接收TCP/IP包。
5.TCP/IP协议族
(1)IP
网间协议(Internet Protocol),负责主机间数据的路由和网络上数据的存储,同时为ICMP、TCP、UDP提供分组发送服务。用户进程通常不涉及这一层
(2)ARP
地址解析协议(Address Resolution Protocol),此协议将网络地址映射到硬件地址。
(3)RARP
反向地址解析协议(Reverse Address Resolution protocol),此协议将硬件地址映射到网络地址。
(4)ICMP
网络控制报文协议(Internet Control Message Protocol),此协议处理信息和主机的差错和传送控制。
(5)TCP
传送控制协议(Transmission Control Protocol),这是一种提供给用户进程的可靠的面向连接的全双工字节流协议。它要为用户进程提供虚拟电路服务,并为数据可靠传输建立检查。大多数网络用户程序使用TCP。
(6)UDP
用户数据报协议(User Datagram Protocol),这是提供给用户进程的无连接协议。用于传输数据而不执行正确性检查。
(7)FTP
文件传输协议(File Transfer Protocol),允许用户以文件操作的方式(文件的增、删、改、查、传送等)与另一主机相互通信。
(8)SMTP
简易邮件传送协议(Simple Mail Transfer Protocol),SMTP协议为系统之间传递电子邮件。
(9)TTP
终端协议(Telnet Terminal Protocol),允许用户以虚拟终端方式访问远程主机。
(10)HTTP
超文本传输协议(Hypertext Transfer Protocol)。用于传输超文本标记语言(HTML,Hyer Text Markup Language )写的文件,也就是人们常说的网页。
(11)TFTP
简单文件传输协议(Trivial File Transfer Protocol)。用户传输"简单"的文件,与FTP不同的是,它使用的是UDP的69端口,因此它可以穿越许多防火墙。
6.套接字(Socket)
(1)Socket实际上网络通信端点的一种抽象,它提供了一种发送和接收数据的机制。
(2)通过网络地址和端口就能唯一地确定整个Internet中的一个通信进程,也可以按这个等式来理解:
套接字(Socket)=网络地址(IP)+端口号(port)
虽然实际上Socket的数据结构中还有其他数据,但最核心的就是网络地址和端口号。
(3)Socket有两种:一种标准Socket(Linux/UNIx),另一种Winsock(Windows)
7.Socket通信流程
(1)Socket接口位于网络通信协议和应用程序之间,使用Socket通信需要建立连接。
(2)网络通信则依赖IP地址标志主机,端口号标志进程,IP加上端口号才能最终确定连接目标。连接建立后就可以进行双向的通信;最后,通信结束后需要关闭连接。
(3)一个网络连接需要以下5中信息
(I)本地协议端口:指出接收报文或数据的进程。
(II)本地主机地址:指出接收数据包的主机
(III)远程协议端口:指出目的的进程或程序。
(IV)远程主机地址:指出目的的知己
(V)协议:指出程序在网络上传输数据时使用的协议。
(4)Socket的数据结构包含这五种信息,也就是说Socket是网络通信中一个端点的抽象,Socket数据结构包含端点需要的所有数据元素。Socket结构大大简化了网络通信。
8.Socket函数
(1)Socket
(I)Socket函数用于创建一个Socket套接字,函数原型如下:
SOCKET socket(
int af,//使用的协议族
int type,//Socket类型
int protocol//使用的协议地址
);
(II)Socket协议族在计算机里表示为一个整数,可以取值AF_INET。
(III)Socket类型有两种:SOCK_STREAM和SOCK_DGRAM,代表流Socket和数据报Socket。
(IV)函数如果成功,返回一个Socket描述字,否则,返回INVALID_SOCKET。在建立Socket连接之前,必须首先创建所需的Socket连接字。
SOCKET s;
s=socket(AF_INET,SOCK_SYREAM,0);
(2)Connect
(I)Connect函数用于尝试与远端建立一个Socket连接,函数原型如下:
int connect( SOCKET s;//Socket描述字 const struct sockaddr* name,//远端的地址 int namelen//远端地址的长度 );
(II)在进行连接时,远端地址是一个SOCKADDR的结构,其定义为:
struct sockaddr_in{ short sin_family;//Socket族 u_short sin_port;//端口 struct in_addr sin_addr;//IP地址 char sin_zero[8];//结构的长度 };
(III)函数如果连接成功,返回0,否则返回SOCKET_ERROR。对于非阻塞模式的Socket连接,返回结果通常都是SOCKET_ERROR,并且错误代码为WSAEWOULDBLOCK,表示连接正在进行,而不是一个真正的错误。
(IV)建立连接通常是由客户端发起连接请求:
SOCKET s; SOCADDR_IN ServetAddr; ServerAddr.sin_family=AF_INET; ServerAddr.sin_port=htons(Port); ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1"); connect(s,(SOCKADDR*)&ServerAddr,sizeof(ServerAddr))
(3)Send
(I)Send函数用于在某个Socket上向远端发送数据,函数原型为:
int send( SOCKET s,//Socket套接字 const char*buf,//存放发送数据的缓冲区 int len,//将要发送的数据长度 int flags//发送时使用的附加参数 );
(II)如果发送成功,返回值为成功发送的字节数,否则返回SOCKET_ERROR
(4)Recv
(I)与发送数据相对应的就是接收数据,函数Recv用于接收远端发送的数据,函数原型为:
int recv( SOCKET s,//Socket 套接字 char*buf,//存放接收数据的缓冲区 int len,接收缓冲区的大小 int flags//接收时使用的附加信息 );
(II)接收数据成功,函数返回接收的字节数,否则,返回SOCKET_ERROR。
(5)Closesocket
(I)Closesocket函数用于关闭不再需要的Socket,函数原型为:
int closesocket( SOKCET s//Socket套接字 );
(II)关闭成功,返回0,否则返回SOCKET_ERROR
(6)Listen
(I)Listen函数用于在某个Socket上建立监听,函数原型为:
int listen( SOCKET s.//Socket套接字 int backlog //缓存队列的长度 );
(II)如果建立监听成功,返回0;否则返回SOCKET_ERROR.
(III)backlog值设为SOMAXCONN,表示取系统最大值。
(7)Accept
(I)Accept函数用于接收一条新的连接。注意:是接收连接而不是接收数据,函数原型为:
SOCKET accept( SOCKET s,//监听中的Socket struct sockaddr* addr,//表示地址结构体的指针 int *addrlen//地址结构体的长度 );
(II)接收连接成功,函数返回一个新的Socket套接字;否则返回SOCKET_ERROR
(8)Bind
(I)Bind函数用于给一个Socket套接字分配一个本地协议地址,函数原型为:
int bind( SOCKET s,//Socket套接字 const struct sockaddr*name,//表示地址结构体的指针 int namelen//地址结构体的长度 );
(II)函数执行成功,返回0;否则,返回SOCKET_ERROR
(9)Select
(I)Select函数用于检测Socket状态,主要用于高级的网络通信模型,函数原型为:
int bind( SOCKET s,//Socket套接字 const struct sockaddr*name,//表示地址结构体的指针 int namelen//地址结构体的长度 );
(II)函数执行成功,返回0,否则,返回SOCKET_ERROR。
9.IP地址转换
(1)域名服务DNS(Domain Name Service)将ASCII地址翻译成域名地址,或将域名地址翻译成ASCII地址,并在整个Internet上发布域名/ASCII的数据库。
(2)现在每个节点已经有三种IP地址:无符号整数、ASCII地址、域名地址。
(3)整数地址到ASCII地址的相互转换
(I)inet_aton是将ASCII地址转换到整数地址
#include<arpa/inet.h>
int inet_aton(const char *strddr,struct in_addr* adrp);
返回:0表示转换不成功;1表示转换成功。
(II)inet_ntoa是将整数地址转换到ASCII地址
char *inet_ntoa(struct in_addr inaddr);
返回:NULL表示转换不成功;其他返回值表示转换成功。
(III)Sin.sin_addr.s_addr=inet_addr("162.105.11.145");
Printf("%s",inet_ntoa(ina.sin_addr));
(4)域名地址与整数地址的相互转换
#include<netdb.h> struct hostnet*gethostbyname(const char *name); struct hostnet*gethostbyaddr(const char*addr,int len,int family)
gethostbyname是将域名地址转换到整数地址,gethostbyaddr是将整数地址转换到域名地址
10.字节转换
(1)计算机中有两种不同的存储字节的格式,一种是高字节在前(Big Endian),一种是低字节在前(Little Endian)。
(2)网络上输出的数据统一采用Big Endian格式,称之为“网络字节顺序”(network byte order)。
(3)计算机中使用的数据格式为"本地机字节顺序"(host byte order)。
(4)系统提供了一些相应的函数,用于字节顺序的转换,(h代表字节顺序host),你代表网络顺序network
(I)u_long PASCAL FAR htonl(TN u_long hostlong);//本地字节转换为网络字节顺序(长整数)
(II)u_ shoirt PASCAL FAR htons(TN u_short hostshort);//本地字节转换为网络字节顺序(短整数)
(III)u_long PASCAL FAR ntohl(TN u_long netlong);//网络字节转化为本地字节顺序(长整数)
(IV)u_ short PASCAL FAR ntohs(TN u_short netshort);//网络字节转化为本地字节顺序(短整数)
11.为什么在数据结构sockaddr_in中的sin_addr和sin_port要转换成网络字节顺序,而sin_family就不需要呢?
答:sin_addr和sin_port分别封装在包的IP和UDP层,因此它们必须是网络字节顺序;但是sin_family只是被本机使用来决定在数据结构中包含什么类型的地址,即sin_family没有被发送到网络上,只在本机使用,所以可以使用本机字节顺序。
12.基本Socket通信(服务器常用模型)
(1)初始化监听Socket
(I)作为Socket通信的服务器端,需要时时检测是否有新的连接请求,因此,需要先初始化一个Socket,并为这个Socket绑定本地协议和端口。
(II)初始化Socket
Socket s;
s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
//创建一个Socket作为监听端口
//Socket种类为流Socket
//使用的网络协议为TCP协议
//Socket家族协议为AF_INET
(III)绑定Socket
SOCKADDR_IN addr; addr.sin_family=AF_INET; addr.sin_addr.s_addr=inet_addr("127.0.0.1"); addr.sin_port=htons(9000); bind(s,(LPSOCKADDR)&addr,sizeof(addr)); //Socket家族取值为AF_INET //服务器IP地址为“127.0.0.1”; //服务器使用的端口为9000
(IV)开始监听
listen(s,SOMAXCONN);//连接最大数取系统设置最大值
(2)建立连接
(I)初始化Socket之后,该Socket处于监听状态,一旦有新的连接请求发入,就可以建立新的Socket连接,准备通信。
(II)检测Socket状态
函数Solete可以检测相应的Socket状态,从而决定是否需要建立新的连接。
fd_set readset; timeval timeout; timeout.ti_sec=0; timeout.tv_usec=0; FD_ZERO(&readset) FD_SET(s,&readset); int ret=select(FD_SETSIZE,&readset,NULL,NULL,&timeout); if(ret>0&&FD_ISSET(s,&readset)) { //有新的数据 }
(III)接收数据
char buf[1024]; int ret; ret=recv(s,buf,1024,0);
(IV)检测发送数据
fd_set writeset; timeval timeout; timeout.tv_usec=0; timeout.tv_usec=0; FD_ZERO(&,writeset); FD_SET(s,& writeset); int ret=select(FD_SETSIZE,&writeset,NULL,NULL,&timeout); if(ret>0&&FD_ISSET(s,&writeset)) { //可发送数据 }
(V)发送数据
char buf[1024]; int ret; ret=send(s,buf,1024,0);
(4)关闭连接
数据收发处理完成后,需要关闭Socket连接,避免浪费资源
closesocket(s);
13.TCP/IP通信实现
(1)TCP协议利用网络层IP协议提供的不可靠的通信服务,解决分组的重传和排序问题,为应用程序提供可靠的、端到端的、面向连接的基于字节流的服务。
14.进程
(1)一般来,进程有3中状态:就绪、执行和等待。
(2)进程被创建后是在内存中处于就绪状态,所谓就绪状态就是具备除了CPU之外的所有资源,一旦占有了CPU,就变成了执行状态;执行中如果需要等待外援设备输入数据,则进程就变成为等待状态,操作系统又会从就绪状态队列中调度一个进程占有CPU;等到数据到来后,等待状态的进程又被唤醒称为就绪状态。
(3)PCB(进程控制块).PCB是进程的唯一标识。
(4)在Window下,可以用CreteProcess函数创建一个进程,其函数原型如下:
BOOL CreateProcess( LPCTSTR lpApplicationName,//使用的可执行文件的名称 LPTSTR lpCommandLine,//传递给新进程的命令行参数 LPSECURITY_aTTRIBUTES lpProcessAttributes,//进程对象属性 LPSECURITY_aTTRIBUTES lpThreadAttributes,//线程对象属性 BOOL bInheritHandles, //进程能否继承 BWORD dwCreationFlags,//创建进程的附加标识 LPVOID lpEvironment,//指向包含新进程将要使用的环境字符串的内存块 LPCTSTR lpCurrentDirectory,//允许父进程设置子进程的当前驱动器和目录 LPSTARTUPINFO lpStartupInfo,//指向一个STARTUPINFO结构 LPPOCESS_INFORMATION lpProcessInformation//指向必须制定的PROCESS_INFORMATION结构 );
(I)当一个线程调用CreteProcess时,系统就会创建一个进程内核对象,其初始使用计算是1。
15.进程的终止<4种方法>
(1)主线程的进入点函数返回
任何应用程序在其主线程的入口函数终止之后,其进程会被自动终止,这是保证所有的线程资源能够得到正确清除的唯一办法。
(2)进程中的一个线程调用ExitProcess函数
当进程中的一个线程调用ExitProcess函数时,进程便终止运行:
VOID ExitProcess(UINT uExitCode);
(I)该函数用于终止进程的运行,并将进程的退出代码设置为uExitCode。
(II)ExitProcess函数并不返回任何值,因为进程已经终止运行。
(III)如果在调用ExitProcess之后又增加了一些代码,那么该代码将永远不会运行。
(3)另一个进程中的线程调用TerminateProcess函数
调用TerminateProcess函数也能够终止进程的运行:
BOOL TerminateProcess( HANDLE hProcess,//要终止的进程句柄 UINT uExitCode//进程返回码 );
(I)该函数与ExitProcess有一个很大的差别,那就是任何线程都可以调用TerminateProcess来终止另一个进程或它自己的进程的运行。
(4)进程中的所有线程自行终止运行。
16.线程
(1)创建线程
(I)调用CreateProcess函数可以创建进程的主线程。如果想要创建一个或多个辅助线程函数,只需要让一个已经在运行的线程来调用CreateThread函数,如下所示:
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes,//线程对象的安全属性 SIZE_T dwStackSize,//线程堆栈的大小 LPTHREAD_START_ROUTINE lpStartAddress,//对应线程函数的地址 LPVOID lpParameter,//线程对应参数 DWRD dwCreationFlags,//创建线程的标志 LPDWORD lpThreadId//新线程的ID );
(II)当CreateThread被调用时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作线程用来管理线程的较小的数据结构。
17.线程的终止<4种方法>
(1)线程函数返回
始终都应该将线程设计成这样的形式,即当想要线程终止运行时,它们就能够返回。这是确保所有线程资源被正确地清除的唯一办法。
(2)调用ExitThread函数
通过调用ExitThread函数,线程将自行撤销。
void ExitThread(
DWORD dwExitCode
);
(3)同一个进程或另一个进程中的线程调用TerminateThread函数
调用TerminateThread函数也能够终止线程的运行:
BOOL TerminateThread(
HANDLE hThread,//要终止的线程
BWORD dwExitCode//线程退出码
);
与ExitThread不同,ExitThread总是撤销调用的线程,而TerminateThread能够撤销任何线程。
(4)在进程终止运行时撤销线程。
18.线程的挂起与恢复
(1)在创建线程时,如果给其传入CREATE_SUSPENDED标志,则该线程创建后被挂起,可以使用ResumeThread恢复它,如下所示:
DWORD ResumeThrad( HANDLE hThread //恢复的线程句柄 );
(2)如果ResumeThread函数运行成功,它将返回线程的前一个暂停计数,否则返回0xFFFFFFFF。
(3)对于没有被挂起的线程,程序员可以调用SuspendThread函数强行挂起它:
DWORD SuspendThread( HANDLE hThread//线程句柄 );
19.线程优先级
(1)线程优先级决定了每个线程在运行时的优先级。
BOOL SetThreadPriority( HANDLE hThread,//线程句柄 int nPriority//优先级 );
(2)线程与包含它的进程的优先级关系如下:
线程优先级=进程累基本优先级+线程相对优先级
(3)进程类的基本优先级如下所示:
(I)实时:REALTIME_PRIORITY_CLASS
(II)高:HIGH_PRIORITY_CLASS
(III)高于正常:ABOVE_NORMAL_PRIORITY_CLASS
(IV)正常:NORMAL_PRIORITY_CLASS
(V)低于正常:BELOW_NORMAL_PRIPRITY_CLASS
(VI)空闲:IDLE_PRIORITY_CLASS
(4)线程的相对优先级:
(I)空闲:THREAD_PRIORITY_IDLE
(II)最低线程:THREAD_PRIORITY_LOWSET
(III)低于正常线程:THREAD_PRIORITY_BELOW_NORMAL
(IV)正常线程:THREAD_PRIORITY_NORMAL(默认)
(V)高于正常线程:THREAD_PRIORITY_ABOVE_NORMAL
(VI)最高线程:THREAD_PRIORITY_HIGHEST
(VII)关键时间:THREAD_PRIORTITY_CRITICAL
(5)可以用下面的形式给某个线程设置优先级
HANDLE hCurrentThread=GetCurrentThread();//获得该线程的句柄 SetThreadPriority(hCurrentThread,THREAD_PRIORITY_LOWESt);
(6)线程的休眠
HANDLE hCurrentThread=GetCurrentThread();//获得该线程的句柄 SetThreadPriority(hCurrentThread,THREAD_PRIORITY_LOWESt);