使用数据包的C#基本套接字编程

本文关键字:套接字 编程 数据包 | 更新日期: 2023-09-27 18:19:30

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

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

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

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

"项目数据包,数据包.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"。

"项目包,登录.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的textBox。

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

"Project Server,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

"Project Server,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客户端)进行。它将套接字与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"。在这个例子中,客户端只发送两个变量,但它可以发送更大的类,例如带有Lists的类。

这就是我所能解释的关于使用数据包在C#上进行套接字编程的全部内容。我试着把它做得简单,但我做不短。对于像我这样的程序员,如果你有问题,请留言评论;对于更熟练的专家,如果为了更高效的编码需要修改此代码,请回答此脚本告诉我。

使用数据包的C#基本套接字编程

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

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

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

有关更通用的插座指南,请考虑http://marcgravell.blogspot.com/2013/02/how-many-ways-can-you-mess-up-io.html