使用java Socket编写简单的联机井字棋游戏

使用java Socket编写简单的联机井字棋游戏

Socket套接字简介

Socket套接字是在应用层和运输层之间的一个假象层(可以这莫理解),它向下包装了其他四层的操作,对程序员来说是透明的,也就是说,实际上C/S是在socket这一层上进行通讯的。

1.Socket建立连接

java中Socket把对象分为两种ServerSocket和Socket分别对应着服务端和客户端,我们不探究socket的具体原理有兴趣的可以看以下文章
链接: C++知识分享: Socket 编程详解,万字长文
接下来我们来建立简单的连接:
客户端代码:

//8080表明我们将服务器放在了8080端口
ServerSocket serverSocket=new ServerSocket(8080);
//这里阻塞的接收一个连接
Socket socket=serverSocket.accept();

服务端代码:

//向服务端发送连接请求返回一个socket对象
Socket socket = new Socket("localhost", 8080);

建立连接代码很简单,但是单纯建立一个连接又没有什么,我们建立连接就是为了交互,所以我们需要一点逻辑代码:最简单的输入输出

//输入流从另一端的输入读入
 BufferedReader bufferedReader =  new BufferedReader(new InputStreamReader(socket.getInputStream()));
 //输出流输出到另一端
 PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
 
 //读入使用bufferedReader.readline();
 //输出则使用printWriter.println();
 //这其中还有很多细节,譬如bufferedReader.readline();是阻塞函数等等,这是需要根据实现场景改变的不进行讨论

此时我们已经可以简单的使用Socket了如果你尝试了通讯,那你会发现只能输入一次,然后连接就断开了,这是因为如果socket连接不处于活跃状态,那么连接就会断开,所以我们需要让这个连接一直保持活跃,“心跳包”什么的不需要,我们需要添加一个while循环。

2.主要逻辑


//Server中的主要逻辑
 while (true) {
    
    
 				//为线程添加锁
                synchronized (lock) {
    
    
                    if(win.getWin()) {
    
    
                        printWriter.println("win");
                        printWriter.println("游戏结束");
                        printWriter.println(toString.ArrayToString(map));

                        //如果在此直接break,那么输出流就会关闭,连接断开,此时客户端就收不到信息
                        //所以在此接收一个bufferedReader
                        message=bufferedReader.readLine();
                        System.out.println(message);
                        break;
                    }
                    //如果已经赢了那么就退出


                    printWriter.println(toString.ArrayToString(map));




                    message = bufferedReader.readLine();
//                System.out.println(message);
                    //只有输入合法位置时才能往下进行
                    while (!go(message, model)) {
    
    
                        printWriter.println("error");
                        message = bufferedReader.readLine();
                    }
                    //下棋之后进行判断
                    if (judge())
                        win.setWin(true);
                    printWriter.println(toString.ArrayToString(map));

                }


                    //在释放锁后让线程睡眠100毫秒,确保下一次拿到锁的是另一个线程
                Thread.sleep(100);


            }
       bufferedReader.close();
        printWriter.close();
        socket.close();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
//服务端主要逻辑
while ((message=bufferedReader.readLine())!=null) {
    
    


            // 从服务器接收消息
            //如果收到的是错误,说明下的位置不对
            //在此处继续输入
            if(message.equals("error")) {
    
    
                System.out.println("该位置已有棋子,请换位置");
                String send = input.readLine();
                printWriter.println(send);
            }

            //收到胜利信息后
            else if(message.equals("win")) {
    
    
                message=bufferedReader.readLine();
                System.out.println(message);
                message=bufferedReader.readLine();
                client.map = toString.StringToArray(message);
                client.picture();


                printWriter.println("确认收到");

                break;
            }
            //渲染画面
            else {
    
    
                System.out.flush();
                client.map = toString.StringToArray(message);
                client.picture();

                String send = input.readLine();
                printWriter.println(send);

                message= bufferedReader.readLine();
                client.map = toString.StringToArray(message);
                client.picture();
            }



        }

接下来就是运行App

public static void main(String[] args) throws IOException {
    
    

        Object lock=new Object();
        ServerSocket serverSocket=new ServerSocket(8080);
        Socket socket=serverSocket.accept();
//        System.out.println("客户端已连接,地址:" + socket.getRemoteSocketAddress());
//        Thread thread1=new Thread(new ServerThread(socket,lock),"1");
//        Socket socket1=serverSocket.accept();
//        System.out.println("客户端已连接,地址:" + socket1.getRemoteSocketAddress());
//        Thread thread2=new Thread(new ServerThread(socket1,lock),"2");

        //错误:这里的ServerThread不是同一个对象,所以里面的map是不共享的


        //共享map
        //将基本类型包装成对象进行共享
        Win win=new Win();
        ArrayList<Integer> map=new ArrayList<>();
        map=new ArrayList<>();
        for (int i=0;i<10;i++)
        {
    
    
            map.add(0);
        }
        System.out.println("客户端已连接,地址:" + socket.getRemoteSocketAddress());
        Thread thread1=new Thread(new ServerThread(socket,map,win,lock),"1");
        Socket socket1=serverSocket.accept();
        System.out.println("客户端已连接,地址:" + socket1.getRemoteSocketAddress());
        Thread thread2=new Thread(new ServerThread(socket1,map,win,lock),"2");


        thread1.start();
        thread2.start();

//

    }

注释为编写过程中遇到的问题及解决办法

2.1 运行流程

在这里插入图片描述

当GameServer启动后会且只接受两个客户端的连接请求,在接收两个请求以后,会为两个请求开启不同的线程ServerThread,为其传入相同的obj来作为线程的锁和同一个map对象以保证运行的顺序如我们期待的那样
然后就是普通的传输数据和数据处理
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
每下一步棋客户端会向服务器端发送信息,服务端将他解析然后更新map中的值,因为map是共享对象,所以更新的结果会同步到两个线程

在这里插入图片描述

2e6d8b942a784f3de9f4875fed5.png)
每次数据更新就会向客户端发送信息然后客户端重新渲染并输出,直到有一方胜利
在这里插入图片描述

3. 未解决的问题

(1)最开时想要使用直接传输map对象来实现C/S中map值的交互,但后来因为序列化(可能是)没有实现

(2)在客户端的逻辑代码中是有System.out.flush()来刷新控制台的,但不知为何没有用

(3)没有解决当一个线程阻塞时,其对应的客户端若还是输入,则其输入还是会在线程拿到锁的时候被读入,这就可能造成读入错误,或者读入不合法数据,可能会导致系统错乱所以系统是非常不强健的

Supongo que te gusta

Origin blog.csdn.net/BeiWoFW/article/details/130176248
Recomendado
Clasificación