默然说话

一个异想天开的人做着异想天开的梦

用户操作
[即时聊天] [发私信] [加为好友]
默然ID:mouyong
66204次访问,排名1571好友5人,关注者10
我快乐,我存在
mouyong的文章
原创 108 篇
翻译 4 篇
转载 31 篇
评论 13 篇
默然的公告
如果要联系我,希望能说明来意,谢谢.

点击这里给我发消息

Google

最近评论
sap99:www.sap99.com/,SAP99资料多多

SAP免费资料下载
http://www.sap99.com

有很多的学习资料,推荐一下,
peigen:又~~~~为什么是又呢???
dcopperfield:顶下
gaoyunpeng:无意中进入到这个博客,很快就被里面的内容所吸引,感觉很有意思,不知道为什么会有这样的感觉,或许只是一种直觉上的吸引吧,一直在看博客里的文章,觉得很不错,天天等更新,哈哈,终于看到新的文章啦~
我会一直关注的~
mouyong:谢谢你的鼓励,我会更加努力。
了祝愿你实现自己的理想,达成自己的目标
文章分类
收藏
    相册
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 Java2游戏编程读书笔记(13-2)收藏

    新一篇: 游戏编程中的人工智能技术(4) | 旧一篇: 成为一个合格程序员的十三条原则

     
    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对象:
    InetAddress groupAddress=InetAddress.getByName("229.13.77.21");
    前面已经提过,MulticastSocket类以IP包或者说以数据报的方式传送或者接收数据。DatagramPacket可以容纳IP传送消息所需要的一切信息。记住,IP工作的方式类似于邮政系统,包中包含数据和数据应该发送的目的地址,因此,数据包可又像下面这样发送:
    String msg="Icn bin maroon";
    DatagramPacket packet
    =new DatagramPacket(msg.getBytes(),msg.length(),groupAddress,port);
    MulticastSocket.send(packet);
    如果大家理解加密消息,那么将发现这其实很简单。然而,大家可以看到,上面的包包含组成消息的原始字节和消息长度,又及目标IP地址及端口号。
    数据报的接收也是一样简单。首先,必须建立一个有足够大的字节数组的DatagramPacket来容纳消息。由于通常的目的而言,1KB(1024byte)的缓冲区应该足够了。一旦接收到数据包,就可以以数据报中的字节数组为参数创建一个新的String对象,并打印出消息,就像下面这样:
    byte[] data=new byte[1024];
    dataPacket
    =new DatagramPacket(data,data.length);
    multicastSocket.receive(dataPacket);
    System.out.println(
    new String(dataPacket.getDate()).trim());
    了解了这些后,看看下面的一个类的代码,这个类封装了加入和离开一个多点传输组的方法,以及发送和接收消息的方法。我们将使用MulticastConnection类来和一定数量的客户端建立多点传输组连接。
     
    import java.io.*;
    import java.net.*;
     
    public class MulticastConnection{
           
    public static final int DEFAULT_PORT=1234;
           
           
    //连接,发送和接收消息用的多点传送
           protected MulticastSocket mcSocket;
           
           
    //属于多点传送群中的一个IP地址
           protected InetAddress groupAddress;
           
           
    //连接用的端口号
           protected int port;
           
           
    //发送和接收消息的IP报
           protected DatagramPacket dataPacket;
           
           
    //接收和发送数据报用的byte数组
           protected byte[] data;
           
           
    //一个数据报可以容纳的字节的最大数目
           protected final int PACKET_SIZE=1024;
           
           
    //用给定的地址和端口创建一个新的MulticastConnection对象
           public MulticastConnection(String address,int portNo)throws Exception{
                  
    //将给定的地址解析为有效的IP地址
                  groupAddress=InetAddress.getByName(address);
                  
                  port
    =portNo;
                  
                  
    //确保端口号有效
                  mcSocket=(port>0)?new MulticastSocket(port):new MulticastSocket(DEFAULT_PORT);
                  
                  
    //将socket连接到IP地址群
                  mcSocket.joinGroup(groupAddress);
                  
                  data
    =null;
           }

           
           
    //试图从群中断开
           public void disconnect(){
                  
    if(mcSocket==null)return;
                  
                  
    try{
                         
    if(mcSocket==null){
                                mcSocket.leaveGroup(groupAddress);
                         }

                  }
    catch(IOException e){
                  }

           }

           
           
    //尝试从群中接收一个数据报
           public String recv(){
                  data
    =new byte[PACKET_SIZE];
                  dataPacket
    =new DatagramPacket(data,data.length);
                  
                  
    try{
                         mcSocket.receive(dataPacket);
                  }
    catch(IOException e){
                         
    return "";
                  }

                  
                  
    //将数据报中的原始字节转化为一个有效的String对象
                  return new String(dataPacket.getData()).trim();
           }

           
           
    public boolean send(String msg){
                  dataPacket
    =new DatagramPacket(msg.getBytes(),msg.length(),groupAddress,port);
                  
                  
    try{
                         mcSocket.send(dataPacket);
                  }
    catch(IOException e){
                         
    return false;
                  }

                  
                  
    return true;
           }

    }
    //MulticastConnection
    3.创建一个可视化的排五点游戏
    在因特网服务器上,同一个时间可能会有成百上千的人在玩各种版本的排五点游戏。因特网上排五点游戏赢得的奖品各不相同,可又是金钱,可又是荣誉标志,可又完全什么都没有。大多数人玩排五点并不是为了赢——他们只是因为它有趣才玩它。重要的是,互联网让开发者使生活倒退到过去的时代。
    实现自己的排五点游戏框架的微妙之处在于:在服务器端,可以让程序加入某个多点传送组并开始广播叫牌。和其他的排五点游戏一样,在每一盘中,每一个数字只能叫一次。所有的数字叫完后,服务器对所有监听客户端发送一个消息来启动它们,这个过程不断执行。和聊天程序一样,我们的BingoServer applet也在一个Frame中运行。代码如下:
    import java.applet.*;
    import java.awt.*;
    import java.awt.event.*;
    import java.net.*;
    import java.util.*;
    import com.speakmore.game.net.*;
     
    public class BingoServer extends Applet{
     
    //用户广播数字的多点传送连接
     protected MulticastConnection service;
     
     
    //放置内部服务器消息的区域
     protected TextArea textArea;
     
     
    //容纳可用的bingo叫号
     protected int[] numbers;
     
     
    //这局游戏中bingo叫号数目
     protected int numbersCalled;
     
     
    //产生bingo叫号
     protected Random random;
     
     
    //在叫号之间等待的时间
     protected final int CALL_PAUSE=3000;
     
     
    public void init(){
        textArea
    =new TextArea("",15,60,TextArea.SCROLLBARS_VERTICAL_ONLY);
        textArea.setEditable(
    false);
        add(textArea);
     
        random
    =new Random();
        reset();
     
        
    //连接到bingo组
        String address="224.0.0.21";
        
    int port=1234;
     
        
    try {
          service
    =new MulticastConnection(address,port);
          textArea.append(
    "系统消息:Java BINGO 在线服务 ");
        }

        
    catch (Exception ex) {
          ex.printStackTrace();
        }

     }

     
     
    //填充有效bingo叫号的数组(1~75)
     public void reset(){
        numbers
    =new int[75];
     
        
    for(int i=0;i<75;i++){
          numbers[i]
    =i+1;
        }

        numbersCalled
    =0;
     
        
    //清除文本域
        textArea.setText("");
     }

     
     
    public void callNumber(){
        
    //检查是否所有的数字都被叫过
        if(numbersCalled==75){
          reset();
          textArea.append(
    "所有数字都被叫过!重新开始游戏...... ");
     
          
    //向整个组广播reset动作
          service.send("Reset");
     
          
    //在开始一局新游戏前等待10秒
          try {
            Thread.sleep(
    10000);
          }

          
    catch (Exception ex) {
            ex.printStackTrace();
          }

        }

     
        
    //产生下一个叫号数字
        int i=random.nextInt(75);
        
    while(numbers[i]==-1){
          i
    =random.nextInt(75);
        }

     
        
    //保存下一个数字并从数组中清除它
        int n=numbers[i];
        numbers[i]
    =-1;
     
        
    //叫一个数字
        textArea.append("Calling"+n+" ");
        service.send(
    ""+n);
        
    ++numbersCalled;
     }

     
     
    //启动服务器,不断叫号
     public void start(){
        
    while(true){
          callNumber();
     
          
    //在下一次叫号前暂停
          try {
            Thread.sleep(CALL_PAUSE);
          }

          
    catch (Exception ex) {
            ex.printStackTrace();
          }

        }

     }

     
     
    //创建一个BingoServer applet并把它加载到一个Frame中
     public static void main(String[] args){
        Applet a
    =new BingoServer();
     
        a.init();
        Frame f
    =new Frame("Java BINGO Server");
        f.setSize(
    500,320);
        f.addWindowListener(
    new WindowAdapter(){
          
    public void windowClosing(WindowEvent e){
            System.exit(
    0);
          }

        }
    );
     
        f.add(a);
        f.show();
        a.start();
     }

    }
    //BingoServer
    客户端要复杂很多,不过只是复杂在它要比服务器程序做更多的绘制工作。毕竟,我们的用户看到的是客户端程序。这里没有完全列出编写客户端的过程,读者已经可以自己写出来了。在实现客户端时,应注意以下几点:
    服务器可以广播两种消息:一种是重启的消息,它告诉客户端清除所有的叫牌,重新开始游戏:另一种是一个1~75之间的数字。
    在每一盘中,应该打印服务器所叫的每一个数字。记住数字1~15归入“B”,16~30归入“I”,依此类推。给定一个数字,我们可以用算法给出它归属的类。
    只要一个用户调用“Bingo!”,客户端程序就应该在内部对所叫的数字进行验证。如果用户确实赢了,那么客户端程序将告诉服务器应该重新开始。
    下面给出一个Bingo客户端,把它留给读者只是作为参考来完成自己的客户端。它决不是一个完整的Bingo客户端!
         import java.io.*;
         
    import java.applet.*;
         
    import java.awt.*;
         
    import java.awt.geom.*;
         
    import java.awt.font.*;
         
    import java.awt.event.*
         
    import java.net.*;
         
    import java.util.*;
     
         
    class BingoTile extends Object 
         
    {
              
    protected Rectangle2D bounds;
              
    protected Point2D textOrigin; 
              
    protected int     value;
              
    protected boolean filled;
              
    protected Font font;
              
    public static Color fillColor;
     
              
    public BingoTile(Rectangle2D r, Font f)
              
    {
                    bounds 
    = r;
                    value 
    = 0;
                    font 
    = f;
                    filled 
    = false;
     
                    textOrigin 
    = null;
              }

     
              
    public void paint(Graphics2D g2d)
              
    {
                   
    if(textOrigin == null)
                   
    {
                        
    // 获得Graphics2D 容器的FontRenderContext
                        FontRenderContext frc = g2d.getFontRenderContext();
     
                        
    // 使用上面的FontRenderContext,获取消息和字体的布局
                        
                        TextLayout layout 
    = new TextLayout(""+value, font, frc);
     
                        
    // 得到布局的边界
                        Rectangle2D fontBounds = layout.getBounds();
     
                        textOrigin 
    = new Point2D.Double(bounds.getX() + bounds.getWidth()/2 - fontBounds.getWidth()/2,
                                             bounds.getY() 
    + bounds.getHeight()/2 + fontBounds.getHeight()/2);
                   }

     
                   
    if(filled)