这实际上不是一个问题。我正在努力整理我今年在这个主题上学到的东西。因为我是 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# 上进行套接字编程的全部内容。我试图让它简单,但我不能让它更短。对于像我这样的初学者,如果您有任何问题,请发表评论,对于更熟练的专家,如果此代码需要修改以更有效地编码,请通过回答此脚本告诉我。