C# version of network game backgammon and Socket communication

foreword

    This online backgammon game was written in early April this year. At that time, I felt that I should learn something about network programming. And the topic of my course design has been set-do an Everything. Then help me, Brother Fei, to make an online version of backgammon.

Source code: https://pan.baidu.com/s/1oLYgg-PykBkCtT0MtKI_xQ

    The interface is WinForm, using GDI drawing to complete the drawing of the chessboard and chess pieces, and the coordinates of the chess pieces are calculated by the defined formula. I used to do the human-computer version of backgammon, so the most important part of the game logic did not spend a lot of time. This program has been done for more than a week.
insert image description here
insert image description here
But now it seems that the code at that time was too young. First, the course design is about to have a mid-term inspection and there is not much time. Second, the level and vision are really not high.
for example:

  • Serialization of message objects. At that time, I didn’t know that there was JSON serialization, so I wrote a ToString() method. After receiving it, the other party parsed the string, split it, and reconstructed the entity.
  • message processing. Write a lot of logic code in Switch to handle different types of messages.
  • And a lot of bugs etc.

Source code: https://pan.baidu.com/s/1oLYgg-PykBkCtT0MtKI_xQ

design

    The difference between player vs. man-machine vs. man-machine vs. player A is to send the operation of player A to player B, and the interface of player B is rendered. I encapsulate the operation instructions in the game as an enumeration type.

public enum MsgType
{
    LuoZi=0,//玩家落子
    Connect=1,//玩家上线
    Quit=2,//玩家退出房间
    IsWin=3,//是否胜利
    CreateRoom=4,//创建房间
    JoinRoom=5,//加入房间
    UserList=6,//请求|发送玩家列表
    RoomList,//请求|发送房间列表
    Other,//其他
    Start,//开始游戏
    Exit,//玩家连接断开
    OtherName,//忘了干嘛的了
    Restart,//重新开始游戏
    Msg//聊天
}

message object:

public class MessagePackage
{
    public MsgType msgType;
    public string data;
    public string senderIP = "";
    public string senderName = "";
    public string sendTime;

    public MessagePackage()
    {

    }

    public MessagePackage(string msg)
    {
        string[] msgs = msg.Split('|');
        msgType = (MsgType)int.Parse(msgs[0]);
        data = msgs[1];
        senderIP = msgs[2];
        senderName = msgs[3];
        sendTime = msgs[4];
    }

    public MessagePackage(MsgType msg, string data, string senderIP, string senderName, string sendTime)
    {
        this.msgType = msg;
        this.data = data;
        this.senderIP = senderIP;
        this.senderName = senderName;
        this.sendTime = sendTime;
    }

    public string ConvertToString()
    {
        string msg = ((int)msgType).ToString() + "|" + data + "|" + senderIP + "|" + senderName + "|" + sendTime;
        return msg;
    }


}

client logic

  • GDI drawing
  • game logic
  • Login to build a house
  • join start
  • end all over again
  • chat information
  • exit-
    _insert image description here
    insert image description here
    insert image description here

    I decided to give one of the most basic chestnuts --- the player's move in the game logic.

Lazi

    After entering the game room, I will use GDI to draw a 15*15 board. Friends who have used GDI know that it is based on pixels, and it is not easy to do so.

    For example, if you want to place a chess piece on the point (7, 7) on the chessboard, you need to use GDI to draw a white chess piece at that position. What is the circle drawing method provided by GDI? FillEllipse, you need to specify a rectangle, including the horizontal and vertical coordinates of the upper left corner of the rectangle, its length and width, and the color of the fill. This method can draw the largest circle or ellipse in the rectangle for you.

    private bool GraphicsPiece(Point upleft, Color c)
    {
        Graphics g = this.panel1.CreateGraphics();
        if (upleft.X != -1 || upleft.Y != -1)
        {
            g.FillEllipse(new SolidBrush(c), upleft.X, upleft.Y, CheckerBoard.chessPiecesSize, CheckerBoard.chessPiecesSize);
            return true;
        }
        return false;
    }

    The point is how to get the coordinates of the upper left corner of this rectangle? We know that in the mouse click event, the parameter Args brings us a position in pixels relative to the drawing area. And you can't expect the user to click on exactly that point on the board, he may click on a point above (7, 7), or a point below. Therefore, we need to process the coordinate value of the mouse click and convert it into a relative form of expression (7, 7).

Convert pixel coordinates to relative coordinates:

    public static Piece ConvertPointToCoordinates(Point p,int flag)
    {
        int x, y;
        Piece qi;
        if (p.X<leftBorder||p.Y<topBorder||p.X>(lineNumber-1)*distance+leftBorder|| p.Y > (lineNumber - 1) * distance + topBorder)
        {
             qi= new Piece(-1,-1,flag);
        }
        else
        {
            float i = ((float)p.X - leftBorder) / distance;
            float j= ((float)p.Y - topBorder) / distance;
            x = Convert.ToInt32(i);
            y = Convert.ToInt32(j);
            if (GameControl.ChessPieces[x, y] != 0)
            {
                qi = new Piece(-1, -1, flag);
            }
            else
            {
                qi = new Piece(x, y,flag);                  
            }              
        }
        return qi;
    }

Convert relative coordinates to pixel coordinates:

    public static Point ConvertCoordinatesToPoint(Piece p)
    {
        int x, y;
        x = p.X * distance + leftBorder - chessPiecesSize / 2;
        y = p.Y * distance + topBorder - chessPiecesSize / 2;
        return new Point(x, y);
    }

Locating pieces: Draw the local pieces and send the relative coordinates to the server; if you win, send a victory message to the server, the server finds the opponent player based on the room information, and sends a message to the opponent player.

    Piece p = CheckerBoard.ConvertPointToCoordinates(new Point(e.X, e.Y), 1);
            if (p.X != -1)
            {
                Point point = CheckerBoard.ConvertCoordinatesToPoint(p);
                if (Program.gc.AddPiece(p))
                {
                    GraphicsPiece(point, myColor);
                    MessageBox.Show("黑棋获胜");
                    return;
                }
                else
                {
                    GraphicsPiece(point, myColor);
                    p = Program.gc.MachineChoose();
                    point = CheckerBoard.ConvertCoordinatesToPoint(p);
                    if (Program.gc.AddPiece(p))
                    {
                        GraphicsPiece(point, otherColor);
                        turnFlag = true;
                        MessageBox.Show("白棋获胜");
                        return;
                    }
                    GraphicsPiece(point, otherColor);
                    lbmyscore.Text = (0 - Program.gc.GetScore()).ToString();
                    lbhisscore.Text = Program.gc.GetScore().ToString();
                    turnFlag = true;
                }
            }

After the other party receives the drop message

    case MsgType.LuoZi:
                {
                    string[] qi = mp.data.Split(',');
                    int x = int.Parse(qi[0]);
                    int y = int.Parse(qi[1]);
                    Piece p = new Piece(x, y, 3 - flag);
                    Point point = CheckerBoard.ConvertCoordinatesToPoint(p);
                    if (Program.gc.AddPiece(p))
                    {                           
                        GraphicsPiece(point, otherColor);                            
                        start = false;
                        btnStart.Enabled = true;
                        MessageBox.Show("对方获胜");
                    }
                    else
                    {
                        GraphicsPiece(point, otherColor);
                        turnFlag = true;
                    }                        
                    break;
                }

    Convert the relative coordinates into local pixel coordinates, draw the chess pieces, and then place the pieces by yourself.

server design

    I didn't think much about it, just realize the "upload and release" function.

  • message forwarding
  • Control the number of users
  • Maintain room list information
  • Maintain user list information
  1. For example, when a player is disconnected: it should be cleared from the player list in time, update the list, and send it to online players.
  2. For example, the player exits the room: find the room, update the room information, and send it to online players
    insert image description here

give a chestnut

    Compared with the client side, the code amount of the server side is much less, except for general codes, about 400 lines.

A player exits a room

    case MsgType.Quit:
                {
                    GameRoom r = SearchRoomBySenderName(mp.senderName);
                    GamePlayer p = SearchUserByName(mp.senderName);
                    r.QuitRoom(p);
                    if (r.PlayerNumber == 0)
                        roomList.Remove(r);
                    else
                    {
                        mp = new MessagePackage(MsgType.Quit, "", "", "", "");
                        tcpServer.Send(r.RoomMaster.Session, mp.ConvertToString());                          
                    }
                    mp = new MessagePackage(MsgType.RoomList, GetRoomList(), "", "", DateTime.Now.ToString());
                    foreach (Session session in tcpServer.SessionTable.Values)
                    {
                        tcpServer.Send(session, mp.ConvertToString());
                    }
                    break;
                }
  1. Based on the player name, the room should be found from the room list.
  2. r.QuitRoom§: Determine whether the player is the owner of the room, yes: promote another player to be the owner of the room; finally: clear the player from the room.
  3. If all players in the room exit, delete the room
  4. Send new room list information to all players.

insert image description here

Socket communication

    Here comes the point, I said at the beginning that I want to learn network programming. Finally, a brief introduction to Socket programming in C#. Of course, C# also provides higher-level packages such as TcpClient and TcpListener. And higher performance asynchronous sockets: SocketAsyncEventArgs.

Server Socket

    mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    mainSocket.Bind(new IPEndPoint(IPAddress.Any, 4396));
    mainSocket.Listen(5);
    mainSocket.BeginAccept(new AsyncCallback(AcceptConn), mainSocket);
  1. Create a new Socket instance: specify to use IPv4, stream transmission, and TCP protocols.

  2. Bind to this machine, port 4396

  3. Start listening, the maximum connection queue is 5

  4. Register the AcceptConn function as a connection callback function. The callback function must receive a parameter of type IAsyncResult.

     mainSocket.BeginAccept(new AsyncCallback(AcceptConn), mainSocket);
    

BeginAccept will block the current thread. When a connection comes in, encapsulate the mainSocket as an IAsyncResult object and pass it to AcceptConn as a parameter.

Usage of connection callback function AcceptConn

    protected virtual void AcceptConn(IAsyncResult iar)
    {          
        Socket Server = (Socket)iar.AsyncState;
        Socket client = Server.EndAccept(iar);
        if (clientCount == maxClient)
        {
            ServerFull?.Invoke(this, new NetEventArgs(new Session(client)));
        }
        else
        {
            Session clientSession = new Session(client);
            sessionTable.Add(clientSession.SessionId, clientSession);
            clientCount++;
            clientSession.Socket.BeginReceive(receiveBuffer, 0, DefaultBufferSize, SocketFlags.None, new AsyncCallback(ReceiveData), clientSession.Socket);
            ClientConn?.Invoke(this, new NetEventArgs(clientSession));
            Server.BeginAccept(new AsyncCallback(AcceptConn), Server);
        }
    }
  1. Get the mainSocket from IAsyncResult and end the asynchronous operation. This is a more classic way of writing the asynchronous programming model.

  2. When the server is full, the ServerFull event is triggered to notify the client that it cannot enter.

  3. If the server is not full, encapsulate the incoming socket connection and add it to the player collection

  4. Start receiving messages from this Socket

     clientSession.Socket.BeginReceive(receiveBuffer, 0, DefaultBufferSize, SocketFlags.None, new AsyncCallback(ReceiveData), clientSession.Socket);
    
     BeginReceive函数有多种重载形式,看看说明不难理解。
    
  5. The server continues to listen for connections

     Server.BeginAccept(new AsyncCallback(AcceptConn), Server);
    

Client Socket

  1. connect

     Socket newSoc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
     IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse(ip), port);
     newSoc.BeginConnect(remoteEP, new AsyncCallback(Connected), newSoc);
    
  2. send

     public virtual void Send(string datagram)
     {
         if (datagram.Length == 0)
         {
             return;
         }
         if (!isConnected)
         {
             throw (new ApplicationException("没有连接服务器,不能发送数据"));
         }
         //获得报文的编码字节 
         byte[] data = coder.GetEncodingBytes(datagram);
         session.Socket.BeginSend(data, 0, data.Length, SocketFlags.None,new AsyncCallback(SendDataEnd), session.Socket);
     }
    
  3. take over

      session.Socket.BeginReceive(receiveBuffer, 0, DefaultBufferSize, SocketFlags.None, new AsyncCallback(RecvData), socket);
    

end

Of course, many problems will be encountered in actual programming, such as:

  1. The problem of normal disconnection and abnormal disconnection of Socket connection.
  2. In the event-driven model, the event listener is no longer directly referenced, and the publisher still has a reference, and the garbage collector cannot recycle it. Confusion occurs when there are event listeners for multiple screens. wait.

Guess you like

Origin blog.csdn.net/qq_40404477/article/details/100803543