使用Delphi编写棋牌类游戏 -- 基础篇(2)
2020-02-16 00:31
369 查看
对于网络游戏来说,它和单机版游戏最大的区别就在于网络通信部分。可以说,网络通信是构成网游的最基本元素。在这里我不想详细的论述如何使用DELPHI在WINDOWS下进行网络编程,因为这是一个非常复杂的话题。这里我只是想说明在我设计的棋牌类游戏中如何实现游戏网络部分的。
对于WINDOWS来说,它的通信模型大概分为5种。分别是:
1:SELECT模型。
2:WSAAsynSelect模型。
3:WSAEventSelect模型。
4:IO重叠模型
5:完成端口模型。
对于性能来说,完成端口可以管理上万连接(理论连接数量可以达到65535个),所以我选择它来作为我游戏服务器的通信模型。
由于游戏客户端和游戏服务器的连接只需要一条就可以,所以我选择了Select模型作为客户端通信模型。
关于如何编写完成端口以及在完成端口中如何加入心跳,如何避免网络通信中的粘包现象在我以前的BLOG中已经有了详细的讲解,由于篇幅太多,我这里就不在将其贴出。详细的可以参看我的BLOG 《DELPHI中完成端口(IOCP)的简单分析(1)》-《 DELPHI中完成端口(IOCP)的简单分析(4)》和《网络通信中的心跳机制的实现!》
但是在我最近重新检查代码的时候发现我以前关于IOCP的一些处理方式不合适或者有些地方是错误的,我已经在以前的BLOG帖子中做了相关的修改。
在这里我们讨论一下如何实现客户端通信模型(Select模型)。对于熟悉网络编程的程序员来说,编写一个SELECT通信模型是一件很简单的事,因为它是5中通信模型中最简单的一种。
编写select模型的代码如下:
Var
Fsocket:Tsokcet;
procedure Star;
var
CliAddrIn:TSockAddrIn;
hThread:THandle;
ThreadID,outByte:DWORD;
keep_alive,out_keep_alive:TTimeVal;
iAddrSize,opt,I:Integer;
begin
//加载SOCKET,我使用的是2.2版为了后面方便加入心跳
if WSAStartUp($202, wsData) <> 0 then
begin
WSACleanup();
end; //创建一个套接字 Fsocket:=socket(AF_INET,SOCK_STREAM,0); if Fsocket =SOCKET_ERROR then begin closesocket(Fsocket); end else begin //这里填入实际的服务器IP地址和服务器监听端口 CliAddrIn.sin_addr.s_addr:=inet_addr(Pchar(‘127.0.0.1’)); CliAddrIn.sin_family:=AF_INET; CliAddrIn.sin_port:=htons(5500); //连接服务器 if (connect(Fsocket,CliAddrIn,sizeof(CliAddrIn))<>SOCKET_ERROR) then begin //设置套接字的心跳属性 opt:=1; if setsockopt(Fsocket,SOL_SOCKET,SO_KEEPALIVE,@opt,sizeof(opt))=SOCKET_ERROR then begin closesocket(Fsocket); end; FinKeepAlive.onoff:=1; FinKeepAlive.keepalivetime:=FKeepTime; FinKeepAlive.keepaliveinterval:=1; Finsize:=sizeof(TTCP_KEEPALIVE); Foutsize:=sizeof(TTCP_KEEPALIVE); if WSAIoctl(Fsocket,SIO_KEEPALIVE_VALS,@FinKeepAlive,Finsize,@FoutKeepAlive,Foutsize,@outByte,nil,nil)=SOCKET_ERROR then begin closesocket(Fsocket); end; //启动接收线程 hThread:=CreateThread(Nil,0,@RevData,Pointer(Fsocket),0,ThreadID); CloseHandle(hThread); end else begin closesocket(Fsocket); end; end; end; //接收线程 function RevData(sc:Pointer):Integer;stdcall; var szBuf:array[0..DATA_BUFSIZE-1] of char; sRevSize:Integer; DeBuf:array[0..DATA_BUFSIZE-1]of char; Delen:Integer; SpBuf:array[0..DATA_BUFSIZE-1]of char; SpLen:Integer; IsEnd:Boolean; AnalyzeData:array [0..DATA_BUFSIZE-1]of char; AnalyzeDataLen:Integer; t_sc:TSocket; InterlockedCrit:TRTLCriticalSection; begin //初始化临界区 InitializeCriticalSection(InterlockedCrit); FillChar(FBuffer,SizeOf(FBuffer),#0); FBufferLen:=0; t_sc:=TSocket(sc); //接收数据死循环 while TRUE do begin FillChar(szBuf,sizeof(szBuf),#0); //接收数据 sRevSize:=recv(t_sc,szBuf,sizeof(szBuf),0); //根据接收函数的返回值得到接收到的数据长度 if sRevSize = -1 then begin //如果接收长度为-1便表示已经和服务器断开了连接,这样可以进入断开连接处理函数中 EnterCriticalSection(InterlockedCrit); ……. closesocket(t_sc); LeaveCriticalSection(InterlockedCrit); Exit; end else if (Integer(t_sc)=-1) or (sRevSize=0) then begin //如果套接字变为-1或者接收长度变为0也便是和服务器断开了连接,这样可以进入断开连接处理函数中 EnterCriticalSection(InterlockedCrit); …….. closesocket(t_sc); LeaveCriticalSection(InterlockedCrit); Exit; end else if (sRevSize>0) and (sRevSize<=DATA_BUFSIZE) then begin IsEnd:=false; while not IsEnd do begin Delen:=0; SpLen:=0; if sRevSize>0 then begin //进入数据正确处理函数中 …… end else begin IsEnd:=true; end; end; end; end; DeleteCriticalSection(InterlockedCrit); end; //数据发送函数的处理 function SendBuffer(Data: array of char; DataLen: Integer;sc:TSocket):Boolean; var SendData:array[0..DATA_BUFSIZE-1] of char; SendDataLen:Integer; RealLen:Integer; LenStr:String; begin Result:=false; //在发送数据的前面加入4位的发送长度。 SetArrayLength(DataLen,LenStr); //初始化发送数组 Fillchar(SendData,sizeof(SendData),#0); //填充发送数组 strmove(SendData,Pointer(LenStr),4); strmove(SendData+4,Data,DataLen); SendDataLen:=DataLen+4; //发送数据 RealLen:=Send(FSocket,SendData,SendDataLen,0); if RealLen = DataLen + 4 then begin Result:=true; end; 以上的代码就是使用SELECT通信模型编写的一个客户端通信部分所需要的代码。如果要和服务器正确的通信,客户端自然少不了编写粘包处理的函数。其处理的过程类似于服务器中粘包的处理,我这里就不再重述。 通过对客户端和服务器端的编写,一个完整的网络通信部分已经建立。有了稳定的网络通信作为基础,我们就可以顺利的进行后续开发。下一篇我会和大家探讨如何实现棋牌类游戏界面效果。好的界面可以使游戏效果锦上添花,是不可忽视的。
begin
WSACleanup();
end; //创建一个套接字 Fsocket:=socket(AF_INET,SOCK_STREAM,0); if Fsocket =SOCKET_ERROR then begin closesocket(Fsocket); end else begin //这里填入实际的服务器IP地址和服务器监听端口 CliAddrIn.sin_addr.s_addr:=inet_addr(Pchar(‘127.0.0.1’)); CliAddrIn.sin_family:=AF_INET; CliAddrIn.sin_port:=htons(5500); //连接服务器 if (connect(Fsocket,CliAddrIn,sizeof(CliAddrIn))<>SOCKET_ERROR) then begin //设置套接字的心跳属性 opt:=1; if setsockopt(Fsocket,SOL_SOCKET,SO_KEEPALIVE,@opt,sizeof(opt))=SOCKET_ERROR then begin closesocket(Fsocket); end; FinKeepAlive.onoff:=1; FinKeepAlive.keepalivetime:=FKeepTime; FinKeepAlive.keepaliveinterval:=1; Finsize:=sizeof(TTCP_KEEPALIVE); Foutsize:=sizeof(TTCP_KEEPALIVE); if WSAIoctl(Fsocket,SIO_KEEPALIVE_VALS,@FinKeepAlive,Finsize,@FoutKeepAlive,Foutsize,@outByte,nil,nil)=SOCKET_ERROR then begin closesocket(Fsocket); end; //启动接收线程 hThread:=CreateThread(Nil,0,@RevData,Pointer(Fsocket),0,ThreadID); CloseHandle(hThread); end else begin closesocket(Fsocket); end; end; end; //接收线程 function RevData(sc:Pointer):Integer;stdcall; var szBuf:array[0..DATA_BUFSIZE-1] of char; sRevSize:Integer; DeBuf:array[0..DATA_BUFSIZE-1]of char; Delen:Integer; SpBuf:array[0..DATA_BUFSIZE-1]of char; SpLen:Integer; IsEnd:Boolean; AnalyzeData:array [0..DATA_BUFSIZE-1]of char; AnalyzeDataLen:Integer; t_sc:TSocket; InterlockedCrit:TRTLCriticalSection; begin //初始化临界区 InitializeCriticalSection(InterlockedCrit); FillChar(FBuffer,SizeOf(FBuffer),#0); FBufferLen:=0; t_sc:=TSocket(sc); //接收数据死循环 while TRUE do begin FillChar(szBuf,sizeof(szBuf),#0); //接收数据 sRevSize:=recv(t_sc,szBuf,sizeof(szBuf),0); //根据接收函数的返回值得到接收到的数据长度 if sRevSize = -1 then begin //如果接收长度为-1便表示已经和服务器断开了连接,这样可以进入断开连接处理函数中 EnterCriticalSection(InterlockedCrit); ……. closesocket(t_sc); LeaveCriticalSection(InterlockedCrit); Exit; end else if (Integer(t_sc)=-1) or (sRevSize=0) then begin //如果套接字变为-1或者接收长度变为0也便是和服务器断开了连接,这样可以进入断开连接处理函数中 EnterCriticalSection(InterlockedCrit); …….. closesocket(t_sc); LeaveCriticalSection(InterlockedCrit); Exit; end else if (sRevSize>0) and (sRevSize<=DATA_BUFSIZE) then begin IsEnd:=false; while not IsEnd do begin Delen:=0; SpLen:=0; if sRevSize>0 then begin //进入数据正确处理函数中 …… end else begin IsEnd:=true; end; end; end; end; DeleteCriticalSection(InterlockedCrit); end; //数据发送函数的处理 function SendBuffer(Data: array of char; DataLen: Integer;sc:TSocket):Boolean; var SendData:array[0..DATA_BUFSIZE-1] of char; SendDataLen:Integer; RealLen:Integer; LenStr:String; begin Result:=false; //在发送数据的前面加入4位的发送长度。 SetArrayLength(DataLen,LenStr); //初始化发送数组 Fillchar(SendData,sizeof(SendData),#0); //填充发送数组 strmove(SendData,Pointer(LenStr),4); strmove(SendData+4,Data,DataLen); SendDataLen:=DataLen+4; //发送数据 RealLen:=Send(FSocket,SendData,SendDataLen,0); if RealLen = DataLen + 4 then begin Result:=true; end; 以上的代码就是使用SELECT通信模型编写的一个客户端通信部分所需要的代码。如果要和服务器正确的通信,客户端自然少不了编写粘包处理的函数。其处理的过程类似于服务器中粘包的处理,我这里就不再重述。 通过对客户端和服务器端的编写,一个完整的网络通信部分已经建立。有了稳定的网络通信作为基础,我们就可以顺利的进行后续开发。下一篇我会和大家探讨如何实现棋牌类游戏界面效果。好的界面可以使游戏效果锦上添花,是不可忽视的。
- 点赞
- 收藏
- 分享
- 文章举报
相关文章推荐
- Delphi 2009 gif动画方法
- Delphi调用外部程序详解(转)
- Delphi 2010手动安装indy10.5.7
- delphi调用js脚本
- 让UltraEdit-32成为Delphi 7编译器的工具设置
- Delphi7 安装ICS,与简单使用
- Delphi 拦截滚轮事件不响应滚轮的上下滚动
- Delphi Live Bindings 初探
- 重装Delphi10.2的IDE必要设置
- Delphi7 GDI+学习
- delphi7 TRichView 安装
- DELPHI常用函数集及简要范例
- Delphi: 如何减小应用程序(EXE)的大小?
- Delphi中建议使用的语句
- delphi使用SQL的教程8
- Delphi中本年、本月、本周第一天和最后一天
- delphi xml 添加节点
- delphi xml 查找节点
- delphi 中 Record ,packet Record
- Delphi的MessageBox的用法