0

これは実際には質問ではありません。このテーマについて今年学んだことを整理しようとしています。私は C# の初心者なので、これを行うには多くの困難がありました。しかし、Stack Overflow とクラスでのパケットに関する講義のおかげで、複数の種類のパケットと接続を使用するプログラムを作成するのに十分な情報を得ることができました。このスクリプトは、ソケットについて何をすべきかわからない初心者向けです。

パケットを送信するという概念は、接続を介してクラス全体を送信しているようです。ストリームに直接データを書き込まない。そのため、パケット クラスを定義する DLL(クラス ライブラリ) ファイルと、パケット クラスの派生クラスを作成する必要があります。

Packet.dll の簡単なコードを記述します。このコードは、Login クラスという 1 種類のパケットを持ちます。

※DLLファイルの作成は、VS上でC# Class Library Fileを作成するだけです。コンパイルするには、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 クラスを使用するための短いサンプル ソースを記述します。1 つの接続のみを使用し、1 種類のパケットのみを使用する単純なソースですが、複数のスレッドで記述されます。

私が書いたこのソースのオリジナルは、パケットの種類が多く、複数のユーザーから複数の接続を受けることが想定されるため、接続ユーザーのインスタンスを作成するクラス「UserSocket」を作成しました。また、別クラス「MessageThread.cs」に受信スレッド機能(クライアントからパケットを受信するためのスレッド機能)を持たせます。

"Project Server, Form1.cs" //textBox1 という名前のテキスト ボックスのみを持つ Windows フォーム プロジェクト。

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(ソケット クライアント) によって行われます。そして、ソケットを 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」のシリアル化されたパケットとしてサーバーに送信されます。この例では、クライアントは 2 つの変数を送信するだけですが、リストを持つクラスなどのより大きなクラスを送信できます。

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 に答える