使用数据包的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#上进行套接字编程的全部内容。我试着把它做得简单,但我做不短。对于像我这样的程序员,如果你有问题,请留言评论;对于更熟练的专家,如果为了更高效的编码需要修改此代码,请回答此脚本告诉我。
这是一个很长的问题,我不清楚关键点是什么,但是:
发送数据包的概念似乎是通过连接发送整个类。不将数据直接写入流。因此,我必须制作一个DLL(类库)文件,它定义了数据包类和数据包类的派生类。
没有。在TCP中,数据包在很大程度上是一个实现细节。套接字公开数据流,没有任何逻辑或物理拆分的进一步定义。你可以在其中以任何你喜欢的方式发明你自己的分区/框架,它不需要映射到任何"整个类"——如果你喜欢的话,它完全可以是"数据"。然而,关键是"将数据写入流"通过序列化程序处理非常方便,而且序列化程序可以很好地与"整个类"配合使用。然而,在我的大多数套接字工作中,数据都比这更微妙,并且是手动显式处理的。
有关更通用的插座指南,请考虑http://marcgravell.blogspot.com/2013/02/how-many-ways-can-you-mess-up-io.html