0

这实际上不是一个问题。我正在努力整理我今年在这个主题上学到的东西。因为我是 C# 的初学者,所以我在做这件事时遇到了很多困难。但是感谢 Stack Overflow 和课堂上关于数据包的讲座,我能够获得足够的信息来编写使用多种类型的数据包和连接的程序。该脚本适用于不知道如何处理套接字的初学者。

发送数据包的概念似乎是通过连接发送整个类。不将数据直接写入流。所以,我必须制作一个 DLL(类库)文件来定义数据包类,以及数据包类的派生类。

我将为 Packet.dll 编写一个简单的代码,它将包含一种类型的数据包,即 Login 类。

*要制作 DLL 文件,只需在 VS 上制作 C# 类库文件。要编译它,请按 F7。

“项目包,Packet.cs”

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary; 

namespace Packet

{
    public enum PacketType
    {

    initType = 0, //it's nothing on this code.
    login
}

[Serializable]
public class Packet
{
    public int Length;
    public int Type;

    public Packet() {

        this.Length = 0;
        this.Type = 0;


    }

    public static byte[] Serialize(Object o) {

        MemoryStream ms = new MemoryStream(1024 * 4); //packet size will be maximum of 4KB.
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(ms, o);
        return ms.ToArray();

    }

    public static Object Desirialize(byte[] bt) {

        MemoryStream ms = new MemoryStream(1024 * 4);//packet size will be maximum of 4KB.

        foreach( byte b in bt){

            ms.WriteByte(b);

        }

        ms.Position = 0;
        BinaryFormatter bf = new BinaryFormatter();
        object obj = bf.Deserialize(ms);
        ms.Close();
        return obj;

    }
}
}//end of Packet.cs

为此项目包添加一个新类“Login.cs”。

“项目包,Login.cs”

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Packet
{

    [Serializable]  //to serialize this class, this statement is essential.
   public class Login :Packet  //derived class of Packet.cs. Must be public.
    {
        public string id_str; //id
        public string pw_str; //pw

        public Login(string id, string pw) { //constructer to make the code more shorter.

            this.id_str = id;
            this.pw_str = pw;

        }
    }
}//end of Login.cs

完成此操作后,按 F7 进行编译,您将在 Packet 项目文件的 Debug 文件夹中获得 Packet.dll。这都是关于 Packet 类的。如果要添加更多类进行序列化,只需添加一个新类,并在 PacketType 上添加一个枚举值。

接下来,我将编写一个使用 Packet 类的简短示例源代码。虽然它是一个只使用一个连接的简单源,并且只使用一种类型的数据包,但它会在多个线程中编写。

我写的这个源码的原件有很多类型的数据包,并且预计会从多个用户那里获得多个连接,所以我创建了“UserSocket”类来创建一个连接用户的实例。而且,它将在另一个类“MessageThread.cs”中具有接收线程函数(用于从客户端接收数据包的线程函数。)。

"Project Server, Form1.cs" //一个 Windows 窗体项目,它只有一个名为 textBox1 的文本框。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Packet; //to add this, right click your project viewer and reference add Packet.dll.
using System.IO;  

namespace Server
{
    public partial class Form1 : Form
    {

        private TcpListener server_Listener;

        public List<UserSocket> user_list = new List<UserSocket>(); //list to store user instances

        private Thread server_Thread; //thread for getting connections.

        public void setLog(string msg) //a function to write string on the Form1.textBox1.
        {
            this.BeginInvoke((MethodInvoker)(delegate()
            {
                textBox1.AppendText(msg + "\n");
            }));

        } 

private void Form1_Load(object sender, EventArgs e)
{

    server_Thread = new Thread(new ThreadStart(RUN)); //starts to wait for connections.
    server_Thread.Start();

}

        public void RUN() // Thread function to get connection from client. 
        {

            server_Listener = new TcpListener(7778);
            server_Listener.Start();


            while (true)
            {


                this.BeginInvoke((MethodInvoker)(delegate()
                {
                    textBox1.AppendText("Waiting for connection\n");

                }));

                UserSocket user = new UserSocket(); Make an instance of UserSocket
                user.UserName = " ";
                try
                {
                    user.client = server_Listener.AcceptSocket();

                }
                catch
                {
                    break;
                }

                if (user.client.Connected)
                {

                    user.server_isClientOnline = true;
                    this.BeginInvoke((MethodInvoker)(delegate()
                    {
                        textBox1.AppendText("Client Online\n");

                    }));
                    user.server_netStream = new NetworkStream(user.client); //connect stream.
                    user_list.Add(user);
                    MessageThread mThread = new MessageThread(user, this, user_list); //make an instance of the MessageThread. 



                    user.receiveP = new Thread(new ThreadStart(mThread.RPACKET)); //run the receiving thread for user. 
                    user.receiveP.Start();

                }

            }

        } //end of Form1.cs

“项目服务器,UserSocket.cs”

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using Packet;

namespace Server
{
    public class UserSocket //Just a Class to make an instance of the connected user. 
    {
        public NetworkStream server_netStream;

        public bool server_isClientOnline = false;
        public byte[] sendBuffer = new byte[1024 * 4];
        public byte[] readBuffer = new byte[1024 * 4];

        public string UserName = null; //Not in this code, but on the original, used to identify user.

        public Login server_LoginClass;

        public Socket client = null;

    }
}//end of UserSocket.cs

“项目服务器,MessageThread.cs”

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Packet;
using System.IO;


namespace Server
{
    public class MessageThread //A Class for threads for each users. 
    {
        UserSocket uzr; 
        Form1 f;
        List<UserSocket> user_list = new List<UserSocket>();

        public MessageThread(UserSocket u, Form1 formget, List<UserSocket> u_l) //Constructer. 
        {
            uzr = u;
            f = formget;
            this.user_list = u_l;
        }

        public void RPACKET() //Thread function for receiving packets. 
        {

            f.setLog("rpacket online");

            int read = 0;

            while (uzr.server_isClientOnline)
            {

                try
                {
                    read = 0;
                    read = uzr.server_netStream.Read(uzr.readBuffer, 0, 1024 * 4);
                    if (read == 0)
                    {
                        uzr.server_isClientOnline = false;
                        break;
                    }
                }
                catch
                {
                    uzr.server_isClientOnline = false;
                    uzr.server_netStream = null;
                }

                Packet.Packet packet = (Packet.Packet)Packet.Packet.Desirialize(uzr.readBuffer);
//Deserialize the packet to a Packet.cs Type. It's because the packet.Type is in the super class. 

                switch ((int)packet.Type)
                {
                    case (int)PacketType.login: //If the PacketType is "login"
                        {

                            uzr.server_LoginClass = (Login)Packet.Packet.Desirialize(uzr.readBuffer);


                                f.setLog("ID : " + uzr.server_LoginClass.id_str + " PW : " + uzr.server_LoginClass.pw_str); 
                                uzr.UserName=uzr.server_LoginClass.id_str;                             

                        }

                }
            }

        }


    }
}

这将全部用于服务器部分。它在 form_load 上启动一个监听线程来获取连接,如果它连接到一个客户端,它会创建一个 UserSocket 的实例,连接将由 UserSocket.client(Socket 客户端)进行。并且它将socket与UserSocket的NetworkStream绑定,并启动一个监听线程。监听线程将对客户端接收到的数据包进行反序列化,并将接收到的类分配给UserSocket的一个成员类。

接下来,它将是该脚本的发送部分。客户端部分。(在原始源上,它可以发送,也可以从服务器接收数据包,但是在这个脚本上,我只是让它发送一个数据包。要接收一个数据包,只需创建一个线程和一个线程功能类似于主页上的服务器。

“项目客户,Form1.cs”

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Packet;
using System.IO;

namespace Client
{
    public partial class Form1 : Form //A Simple Windows Form Project with Two TextBoxes, and a Button
    {
        private string myid;
        private NetworkStream client_Netstream;

        private byte[] sendBuffer = new byte[1024 * 4];
        private byte[] receiveBuffer = new byte[1024 * 4];
        private TcpClient client_tcpClient;

        private bool client_isOnline = false;

        public Login login;

        private void Form1_Load(object sender, EventArgs e) 
        {
            //On Form1 Load, It will connect to the server directly. So, the Server must be active before executing the client. 
            this.client_tcpClient = new TcpClient();

            try
            {
                this.client_tcpClient.Connect("localhost", 7778);

            }
            catch
            {

                MessageBox.Show("Connection Failure\n");
                return;
            }

            this.client_isOnline = true;
            this.client_Netstream = this.client_tcpClient.GetStream();

        }  

        private void button1_Click(object sender, EventArgs e)
        {

            if (!this.client_isOnline)
                return;

            login = new Login();
            login.Type = (int)PacketType.login; //Very essential. must be defined for the server to identify the packet. 
            login.id_str = this.textBox1.Text;
            login.pw_str = this.textBox2.Text;            
            Packet.Packet.Serialize(login).CopyTo(this.sendBuffer, 0);
        this.client_Netstream.Write(this.sendBuffer, 0, this.sendBuffer.Length);
        this.client_Netstream.Flush();

            for (int i = 0; i < 1024 * 4; i++)
                this.sendBuffer[i] = 0;

        }

}

}//End of Form1.cs

正如我上面所说,这个客户端不会有接收线程。所以这一切都是为了客户。它在表单加载时连接到服务器,如果按下按钮1,textbox1 和textbox2 的值将作为序列化数据包发送到服务器,PacketType 为'login'。在此示例中,客户端仅发送两个变量,但它可以发送更大的类,例如带有列表的类。

这就是我能解释的关于使用 Packets 在 C# 上进行套接字编程的全部内容。我试图让它简单,但我不能让它更短。对于像我这样的初学者,如果您有任何问题,请发表评论,对于更熟练的专家,如果此代码需要修改以更有效地编码,请通过回答此脚本告诉我。

4

1 回答 1

4

这是一个很长的问题,我不清楚关键点是什么,但是:

发送数据包的概念似乎是通过连接发送整个类。不将数据直接写入流。所以,我必须制作一个 DLL(类库)文件来定义数据包类,以及数据包类的派生类。

不。在 TCP 数据包中,很大程度上是一个实现细节。套接字公开数据,没有任何进一步的逻辑或物理拆分定义。您可以在其中以任何您喜欢的方式发明自己的分区/框架,并且它不需要映射到任何“整个类” - 如果您愿意,它可以完全是“数据”。然而,关键是“将数据写入流”通过序列化程序处理起来非常方便,并且序列化程序可以很好地与“整个类”一起工作。然而,在我的大多数套接字工作中,数据比这更微妙,并且是手动和显式处理的。

有关更一般的套接字指南,请考虑http://marcgravell.blogspot.com/2013/02/how-many-ways-can-you-mess-up-io.html

于 2013-06-21T20:29:08.823 回答