Java2游戏编程读书笔记(13

13.3 使用UDP实现无连接网络前面已经阐述了在Java中如何以Socket类的形式使用TCP网络连接,下一步将学习如何使用Java的MulticastSocket类创建一个无连接网络客户端。1.MulticastConnection类我们已经看到,TCP连接对于客户端和服务器之间的一对一通信是很好的。虽然TCP服务器可以对到来的几乎任何数量的客户端启动服务,但是所有的通信都被限制在双向连接中。这对于像跳棋或者棋类游戏这样两个人连接玩的游戏是很好的。然而,很多游戏要求在一群人中间而不是两个人之间通信。大众化的多人在线游戏就是要求在一群人之间通信的一个例子。像这样成千上万的人可以在广泛的虚拟世界中同时聚集起来的游戏,推动着当今的技术不断突破限制。网络上的组间通信可以有很多方式。我们将专注于所谓的多点传送(Multicasting)来满足多用户应用程序的需要。不要把“多点传送”和“广播”这两个词搞混淆,广播客户端给网络上的每一个人发送消息,其中包括那些对接收这些消息不感兴趣的人,而多点传送客户端把传送的范围缩小为那些属于同一个组的客户端。多点传送环境理解起来很像教室,教室里坐满聚精会神渴望知识的学生,一个教室里的交流只是在听这堂课的人之间进行。每一个教室都可以看作是整个教室网络中的一个子节点,通过扩音器传到每一个教室的通知可以看作是一个广播消息,因为它传给了整个网络中的每一个子节点。由于我们只对特定网络组中的成员之间的通信感兴趣,所以采用了通过多点传送来传送数据包的方式。MulticastSocket类被归入所谓的D类IP地址。一个D类IP地址由其前4位来标志。D类地址必须在第一,第二和第三位上为“1”,而第四位必须为“0”.剩下的24位可以是任意的(默然:这里说的是二进制位哦,看不太明白可以忽略这一段,接着往下看)。最低的D类地址的第一个8位是11100000,即十进制的224。最高的D类地址的第一个8位是11101111,即十进制中的239。这样,D类地址的整个有效范围是224.0.0.0和239.255.255.255。然而,记住224.0.0.0是保留的,而224.0.0.1被用来发送给现存的所有多点传送主机,所以不能使用它们。其他的所有IP地址都可以使用。注意:记住,一个多点传送组的地址必须是在224.0.0.1到239.255.255.255。在创建多点传送套接字时,不在这个范围的地址将产生一个SocketException。前面已经提到多点传送客户端如何连接到一个普通IP地址和端口号。通常端口号又int变量的形式给出,关于IP地址的信息封装在InetAddress类中。InetAddress类包含一个静态的getByName方法来把一个String转换为IP地址,因此,可又用下面的方式创建一个InetAddress对象:InetAddressgroupAddress=InetAddress.getByName("229.13.77.21");前面已经提过,MulticastSocket类以IP包或者说以数据报的方式传送或者接收数据。DatagramPacket可以容纳IP传送消息所需要的一切信息。记住,IP工作的方式类似于邮政系统,包中包含数据和数据应该发送的目的地址,因此,数据包可又像下面这样发送:Stringmsg="Icnbinmaroon";DatagramPacketpacket=newDatagramPacket(msg.getBytes(),msg.length(),groupAddress,port);MulticastSocket.send(packet);如果大家理解加密消息,那么将发现这其实很简单。然而,大家可以看到,上面的包包含组成消息的原始字节和消息长度,又及目标IP地址及端口号。数据报的接收也是一样简单。首先,必须建立一个有足够大的字节数组的DatagramPacket来容纳消息。由于通常的目的而言,1KB(1024byte)的缓冲区应该足够了。一旦接收到数据包,就可以以数据报中的字节数组为参数创建一个新的String对象,并打印出消息,就像下面这样:byte[]data=newbyte[1024];dataPacket=newDatagramPacket(data,data.length);multicastSocket.receive(dataPacket);System.out.println(newString(dataPacket.getDate()).trim());了解了这些后,看看下面的一个类的代码,这个类封装了加入和离开一个多点传输组的方法,以及发送和接收消息的方法。我们将使用MulticastConnection类来和一定数量的客户端建立多点传输组连接。importjava.io.*;importjava.net.*;publicclassMulticastConnection…{publicstaticfinalintDEFAULT_PORT=1234;//连接,发送和接收消息用的多点传送protectedMulticastSocketmcSocket;//属于多点传送群中的一个IP地址protectedInetAddressgroupAddress;//连接用的端口号protectedintport;//发送和接收消息的IP报protectedDatagramPacketdataPacket;//接收和发送数据报用的byte数组protectedbyte[]data;//一个数据报可以容纳的字节的最大数目protectedfinalintPACKET_SIZE=1024;//用给定的地址和端口创建一个新的MulticastConnection对象publicMulticastConnection(Stringaddress,intportNo)throwsException…{//将给定的地址解析为有效的IP地址groupAddress=InetAddress.getByName(address);port=portNo;//确保端口号有效mcSocket=(port>0)?newMulticastSocket(port):newMulticastSocket(DEFAULT_PORT);//将socket连接到IP地址群mcSocket.joinGroup(groupAddress);data=null;}//试图从群中断开publicvoiddisconnect()…{if(mcSocket==null)return;try…{if(mcSocket==null)…{mcSocket.leaveGroup(groupAddress);}}catch(IOExceptione)…{}}//尝试从群中接收一个数据报publicStringrecv()…{data=newbyte[PACKET_SIZE];dataPacket=newDatagramPacket(data,data.length);try…{mcSocket.receive(dataPacket);}catch(IOExceptione)…{return"";}//将数据报中的原始字节转化为一个有效的String对象returnnewString(dataPacket.getData()).trim();}publicbooleansend(Stringmsg)…{dataPacket=newDatagramPacket(msg.getBytes(),msg.length(),groupAddress,port);try…{mcSocket.send(dataPacket);}catch(IOExceptione)…{returnfalse;}returntrue;}}//MulticastConnection3.创建一个可视化的排五点游戏在因特网服务器上,同一个时间可能会有成百上千的人在玩各种版本的排五点游戏。因特网上排五点游戏赢得的奖品各不相同,可又是金钱,可又是荣誉标志,可又完全什么都没有。大多数人玩排五点并不是为了赢——他们只是因为它有趣才玩它。重要的是,互联网让开发者使生活倒退到过去的时代。实现自己的排五点游戏框架的微妙之处在于:在服务器端,可以让程序加入某个多点传送组并开始广播叫牌。和其他的排五点游戏一样,在每一盘中,每一个数字只能叫一次。所有的数字叫完后,服务器对所有监听客户端发送一个消息来启动它们,这个过程不断执行。和聊天程序一样,我们的BingoServer applet也在一个Frame中运行。代码如下:importjava.applet.*;importjava.awt.*;importjava.awt.event.*;importjava.net.*;importjava.util.*;importcom.speakmore.game.net.*;publicclassBingoServerextendsApplet…{//用户广播数字的多点传送连接protectedMulticastConnectionservice;//放置内部服务器消息的区域protectedTextAreatextArea;//容纳可用的bingo叫号protectedint[]numbers;//这局游戏中bingo叫号数目protectedintnumbersCalled;//产生bingo叫号protectedRandomrandom;//在叫号之间等待的时间protectedfinalintCALL_PAUSE=3000;publicvoidinit()…{textArea=newTextArea("",15,60,TextArea.SCROLLBARS_VERTICAL_ONLY);textArea.setEditable(false);add(textArea);random=newRandom();reset();//连接到bingo组Stringaddress="224.0.0.21";intport=1234;try…{service=newMulticastConnection(address,port);textArea.append("系统消息:JavaBINGO在线服务 ");}catch(Exceptionex)…{ex.printStackTrace();}}//填充有效bingo叫号的数组(1~75)publicvoidreset()…{numbers=newint[75];for(inti=0;i<75;i++)…{numbers[i]=i+1;}numbersCalled=0;//清除文本域textArea.setText("");}publicvoidcallNumber()…{//检查是否所有的数字都被叫过if(numbersCalled==75)…{reset();textArea.append("所有数字都被叫过!重新开始游戏…… ");//向整个组广播reset动作service.send("Reset");//在开始一局新游戏前等待10秒try…{Thread.sleep(10000);}catch(Exceptionex)…{ex.printStackTrace();}}//产生下一个叫号数字inti=random.nextInt(75);while(numbers[i]==-1)…{i=random.nextInt(75);}//保存下一个数字并从数组中清除它intn=numbers[i];numbers[i]=-1;//叫一个数字textArea.append("Calling"+n+"");service.send(""+n);++numbersCalled;}//启动服务器,不断叫号publicvoidstart()…{while(true)…{callNumber();//在下一次叫号前暂停try…{Thread.sleep(CALL_PAUSE);}catch(Exceptionex)…{ex.printStackTrace();}}}//创建一个BingoServerapplet并把它加载到一个Frame中publicstaticvoidmain(String[]args)…{Appleta=newBingoServer();a.init();Framef=newFrame("JavaBINGOServer");f.setSize(500,320);f.addWindowListener(newWindowAdapter()…{publicvoidwindowClosing(WindowEvente)…{System.exit(0);}});f.add(a);f.show();a.start();}}//BingoServer客户端要复杂很多,不过只是复杂在它要比服务器程序做更多的绘制工作。毕竟,我们的用户看到的是客户端程序。这里没有完全列出编写客户端的过程,读者已经可以自己写出来了。在实现客户端时,应注意以下几点:q服务器可以广播两种消息:一种是重启的消息,它告诉客户端清除所有的叫牌,重新开始游戏:另一种是一个1~75之间的数字。q在每一盘中,应该打印服务器所叫的每一个数字。记住数字1~15归入“B”,16~30归入“I”,依此类推。给定一个数字,我们可以用算法给出它归属的类。q只要一个用户调用“Bingo!”,客户端程序就应该在内部对所叫的数字进行验证。如果用户确实赢了,那么客户端程序将告诉服务器应该重新开始。下面给出一个Bingo客户端,把它留给读者只是作为参考来完成自己的客户端。它决不是一个完整的Bingo客户端!importjava.io.*;importjava.applet.*;importjava.awt.*;importjava.awt.geom.*;importjava.awt.font.*;importjava.awt.event.*;importjava.net.*;importjava.util.*;classBingoTileextendsObject…{protectedRectangle2Dbounds;protectedPoint2DtextOrigin;protectedintvalue;protectedbooleanfilled;protectedFontfont;publicstaticColorfillColor;publicBingoTile(Rectangle2Dr,Fontf)…{bounds=r;value=0;font=f;filled=false;textOrigin=null;}publicvoidpaint(Graphics2Dg2d)…{if(textOrigin==null)…{//获得Graphics2D容器的FontRenderContextFontRenderContextfrc=g2d.getFontRenderContext();//使用上面的FontRenderContext,获取消息和字体的布局TextLayoutlayout=newTextLayout(""+value,font,frc);//得到布局的边界Rectangle2DfontBounds=layout.getBounds();textOrigin=newPoint2D.Double(bounds.getX()+bounds.getWidth()/2-fontBounds.getWidth()/2,bounds.getY()+bounds.getHeight()/2+fontBounds.getHeight()/2);}if(filled)…{g2d.setPaint(fillColor);}else…{g2d.setPaint(g2d.getBackground());}g2d.fill(bounds);g2d.setPaint(Color.BLACK);g2d.draw(bounds);g2d.drawString(""+value,(int)textOrigin.getX(),(int)textOrigin.getY());}publicvoidsetValue(intn)…{value=n;}publicintgetValue()…{returnvalue;}publicbooleancontains(Point2Dp)…{returnbounds.contains(p);}publicvoidreset()…{filled=false;textOrigin=null;}publicvoidtoggle()…{filled=!filled;}}classBingoGridextendsObject…{publicstaticfinaldoubleTILE_WIDTH=50;publicstaticfinaldoubleTILE_HEIGHT=50;protectedRectangle2Dbounds;protectedBingoTile[][]tiles;protectedBingoTiledirty;publicBingoGrid(Point2DupperLeft,Fontfont,ColorfillColor)…{bounds=newRectangle2D.Double(upperLeft.getX(),upperLeft.getY(),5*TILE_WIDTH,5*TILE_HEIGHT);BingoTile.fillColor=fillColor;dirty=null;tiles=newBingoTile[5][5];doublex=0;doubley=bounds.getY();Randomrandom=newRandom();for(inti=0;i<5;i++)…{x=bounds.getX();for(intj=0;j<5;j++)…{tiles[i][j]=newBingoTile(newRectangle2D.Double(x,y,TILE_WIDTH,TILE_HEIGHT),font);x+=TILE_WIDTH;}y+=TILE_HEIGHT;}intvalue=0;for(inti=0;i<5;i++)…{for(intj=0;j<5;j++)…{booleanunique=false;while(!unique)…{unique=true;value=1+i*15+random.nextInt(15);for(intk=0;k<5;k++)…{if(tiles[k][i].getValue()==value)…{unique=false;}}}//System.out.println("Setting"+j+""+i);tiles[j][i].setValue(value);}}}publicRectangle2DgetBounds()…{returnbounds;}publicbooleancontains(Point2Dp)…{if(!bounds.contains(p))returnfalse;for(inti=0;i<5;i++)…{for(intj=0;j<5;j++)…{if(tiles[i][j].contains(p))…{tiles[i][j].toggle();dirty=tiles[i][j];returntrue;}}}returnfalse;}publicvoidpaintAll(Graphics2Dg2d,Fontfont)…{for(inti=0;i<5;i++)…{for(intj=0;j<5;j++)…{tiles[i][j].paint(g2d);}}}publicvoidreset()…{for(inti=0;i<5;i++)…{for(intj=0;j<5;j++)…{tiles[i][j].reset();}}intvalue=0;Randomrandom=newRandom();for(inti=0;i<5;i++)…{for(intj=0;j<5;j++)…{booleanunique=false;while(!unique)…{unique=true;value=1+i*15+random.nextInt(15);for(intk=0;k<5;k++)…{if(tiles[k][i].getValue()==value)…{unique=false;}}}tiles[j][i].setValue(value);}}}publicvoidupdateDirty(Graphics2Dg2d)…{if(dirty!=null)…{dirty.paint(g2d);dirty=null;}}}publicclassBingoClientextendsAppletimplementsActionListener,MouseListener…{protectedMulticastConnectionclient;protectedFontfont;protectedFontcalledFont;protectedBingoGridgrid;protectedStringcurrCall;protectedString[]calledNumbers;protectedfinalStringBINGO="BINGO";protectedButtonbingo;publicvoidinit()…{font=newFont("Helvetica",Font.PLAIN,30);calledFont=newFont("Helvetica",Font.PLAIN,16);grid=newBingoGrid(newPoint2D.Double(50,50),font,Color.ORANGE);addMouseListener(this);calledNumbers=newString[5];for(inti=0;i<5;i++)…{calledNumbers[i]=BINGO.charAt(i)+"";}currCall="";setLayout(newBorderLayout());Panelp=newPanel();bingo=newButton("Bingo!");bingo.addActionListener(this);p.add(bingo);add(p,BorderLayout.SOUTH);Stringaddress="224.0.0.21";intport=1234;try…{client=newMulticastConnection(address,port);}catch(Exceptione)…{}}publicvoidstart()…{Stringmsg="";while(true)…{msg=client.recv();if(msg.equals("Reset"))…{for(inti=0;i<5;i++)…{calledNumbers[i]=BINGO.charAt(i)+"";}currCall="Servercallingnewgame…";grid.reset();firstPaint=true;updateCalls=true;repaint();}else…{intn=Integer.parseInt(msg);for(inti=0;i<5;i++)…{if(n<i*15+16)…{currCall=BINGO.charAt(i)+""+n;calledNumbers[i]=calledNumbers[i]+""+n;updateCalls=true;repaint();break;}}}try…{Thread.sleep(5);}catch(InterruptedExceptione)…{}}}publicvoidupdate(Graphicsg)…{paint(g);}protectedbooleanfirstPaint=true;protectedbooleanupdateCalls=true;publicvoidpaint(Graphicsg)…{Graphics2Dg2d=(Graphics2D)g;if(firstPaint)…{g2d.setPaint(Color.WHITE);g2d.fill(newRectangle2D.Double(0,0,getSize().width,getSize().height));g2d.setFont(font);grid.paintAll(g2d,font);g2d.setFont(font);for(inti=0;i<5;i++)…{FontRenderContextfrc=g2d.getFontRenderContext();TextLayoutlayout=newTextLayout(""+BINGO.charAt(i),font,frc);Rectangle2DfontBounds=layout.getBounds();doublex=grid.getBounds().getX()+(i*BingoGrid.TILE_WIDTH);doubley=grid.getBounds().getY()-fontBounds.getHeight()/2;g2d.drawString(""+BINGO.charAt(i),(int)(x+BingoGrid.TILE_WIDTH/2-fontBounds.getWidth()/2),(int)y);}firstPaint=false;}if(updateCalls)…{g2d.setFont(calledFont);updateCalls=false;intx=(int)grid.getBounds().getX()+6*(int)BingoGrid.TILE_WIDTH;inty=(int)grid.getBounds().getY()+(int)BingoGrid.TILE_HEIGHT;for(inti=0;i<5;i++)…{g2d.drawString(calledNumbers[i],x,y);y+=40;}FontRenderContextfrc=g2d.getFontRenderContext();TextLayoutlayout=newTextLayout(currCall,font,frc);Rectangle2DfontBounds=layout.getBounds();x=(int)(getSize().width/2-fontBounds.getWidth()/2);y=(int)grid.getBounds().getY()+6*(int)BingoGrid.TILE_HEIGHT;Rectangle2DclearRect=newRectangle2D.Double(0,315,getSize().width,50);g2d.setPaint(Color.WHITE);g2d.fill(clearRect);g2d.setPaint(Color.BLACK);g2d.setFont(font);g2d.drawString(currCall,x,y);}g2d.setFont(font);grid.updateDirty(g2d);}publicvoidactionPerformed(ActionEvente)…{//bingo按钮应该已经触发了这个事件;不过这个特性现在还没有实现System.out.println("’Bingo’buttonnotimplemented");}publicvoidmouseClicked(MouseEvente)…{if(grid.contains(e.getPoint()))…{repaint();}}publicvoidmouseEntered(MouseEvente)…{}publicvoidmouseExited(MouseEvente)…{}publicvoidmousePressed(MouseEvente)…{}publicvoidmouseReleased(MouseEvente)…{}publicstaticvoidmain(String[]args)…{Appleta=newBingoClient();a.init();Framef=newFrame("JavaBINGOClient");f.setSize(800,420);f.addWindowListener(newWindowAdapter()…{publicvoidwindowClosing(WindowEvente)…{System.exit(0);}});f.add(a);f.show();a.start();}}享受编写Bingo客户端的乐趣吧,这是检验技术并把本书中我们所讨论过的所有主题集中到一起的一个很好的途径。13.4 总结对我们而言,网络的底层实现细节是很有意思的。它们背后的思路和原则确实很简单,然而,实际实现一个网络系统可能令人头疼。幸运的是,Java提供了各种类,它们承担了通过Internet发送和接收数据的主要任务。本章所开发并展示的类,比如AbstractConnection和MulticastConnection类,封装了基于连接的网络和无连接网络的常用的特性来供读者使用。连接,断开连接,发送和接收消息都是经常使用的网络操作。虽然读者并不一定需要在本章使用这些类,但是在将来它们肯定可以节省时间。和往常一样,可以根据自己的需要添加和修改某些特性。最后,记住当前IP的版本——IPv4正在慢慢地变得过时。由于所提供的地址空间的限制,新用户可用的地址数量越来越少。一个新版本的IP——IPv6将成为替换当前标准的协议。Java1.4已经包含了一些对IPv6的支持,所以读者可以确信它会在IPv6变得更加流行时提供更全面的支持。像前文一样,将给读者留下一些练习。如果需要更多关于java.net包的信息,可以查看Java 2文档。13.5 练习13.1出于演示的目的,本章中有些例程简单地把客户端连接的服务器地址和端口硬编码到程序中了。如果可能,修改这些程序,让它以使用applet参数或者其他的方式让用户来指定连接的服务器地址和端口号。如果用户可以忽略这一步,确保程序可以连接到一个默认的地址和端口号。13.2修改GuessServer和GuessClient程序,让它们提供一个重新开始的选项,这样如果用户想再玩一局的话不需要重新连接。13.3在SimpleChatClient程序中,实现一个确保每一个客户端有一个惟一称呼的机制。例如,如果已经有一个名为Sally的用户加入组中,那么,每一次当另外一个名为Sally的用户想要加入这个组时,程序会提示她(他)输入另外一个名字。一种实现的方法是让每一个客户端和一个服务建立一个独立的TCP连接,而这个服务中包含所有已经使用的称谓列表。客户端只有接收到从服务传来的同意响应后才能加入该多点传送聊天室。同样地,如果用户离开组,必须创建另一个TCP连接从称谓列表中删除他(她)的称谓,这样,其他的人可以使用这个称谓。而且,如果称谓查询服务不可用,客户端窗口必须输出一个“服务不可用”的消息。13.4“家庭友好”游戏和实时聊天通常不能共享。不应该让敏感的玩家和孩子处理那些讨厌的词汇和言语。实现一种类似SimpleChatClient中回显选项的过滤机制,让它防止淫秽的言语到达客户窗口。有问题的词汇可以用像“#$%&”这样的字符串替换,或者完全忽略掉。当然,如果用户不希望审查接收到的消息,可以关掉这个选项。13.5在SimpleChatClient applet中,对不同的聊天室使用不同的端口号。比如,名为“动物”的聊天室可以在端口1000上,名为“游戏”的聊天室可以在端口1001上等。13.6实现一个BingoClient类来和我们已经看过的BingoServer类一起工作,如果大家喜欢,可以让它充满特色。我们将面对的一个最大问题是当一个客户端获胜时结束游戏。此外,创建一个计分系统,在赢了每一局时来给客户端计分,或者设标志。

您好: 当您在阅读和使用我所提供的各种内容的时候,我非常感谢,您的阅读已是对我最大的支持。 我更希望您能给予我更多的支持。 1.希望您帮助我宣传我的博客,让更多的人知道它,从中获益(别忘记了提醒他们帮我点点广告,嘿嘿)。 2.希望您能多提出宝贵意见,包括我所提供的内容中的错误,建设性的意见,更希望获得哪些方面的帮助,您的经验之谈等等。 3.更希望能得到您经济上的支持。 我博客上面的内容均属于个人的经验,所有的内容均为开源内容,允许您用于任何非商业用途,并不以付费为前提,如果您觉得在阅读和使用我所提供的各种内容的过程中,您得到了帮助,并能在经济上给予我支持,我将感激不尽。

您可以通过点击我网站上的广告表示对我的支持。

您可以通过银行转帐付款给我: 招商银行一卡通: 卡号:6225888712586894 姓名:牟勇 您也可以通过汇款的方式: 通讯地址:云南省昆明市女子(28)中学人民中路如意巷1号 收信人:陈谦转牟勇收 邮编:650021 无论您给予我怎么样的支持,我都衷心的再次感谢。 欢迎光临我的博客,欢迎宣传我的博客 http://blog.csdn.net/mouyong http://blog.sina.com.cn/mouyong EMail:mouyong@yeah.net QQ:11167603 MSN:mouyong1973@hotmail.com

null饶人不是痴汉,痴汉不会饶人。

Java2游戏编程读书笔记(13

相关文章:

你感兴趣的文章:

标签云: