套接字,接收序列化异常
Sockets, recieving Serialization Exception
首先,我已经阅读了 Whosebug 和其他地方的许多指南和帖子,但仍然没有解决这个问题。我最近一直在尝试学习套接字,事实证明这是一个挑战。我已经了解如何使用带有字符串等的套接字,但我知道想要开始发送对象以便更好地组织我的程序。我正在制作一个聊天客户端;服务器将连接多个客户端,我猜它的工作方式有点像 IRC。我已经完成了服务器的基础知识;我有方法来检查收到的数据包类型(所有数据包都继承自 Packet
),并且有一个非常基本的 switch 语句,它根据收到的数据包采取行动。在此之前,我显然需要将套接字中接收到的 byte[]
转换为 Packet
。当我试图反序列化字节数组时,问题就来了。我在调用 .Deserialize(); 的那一行得到了一个 SerilizationException;正文如下:
System.Runtime.Serialization.SerializationException was unhandled by
user code HResult=-2146233076 Message=Unable to find assembly
'Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
我不明白为什么会这样;我尝试编写不同的反序列化数组的方法,添加随机 if 语句以确保我没有传递一个空数组(我不是)但无法让它在我的生活中工作。我希望有人能指出我做错了什么?我是否打算通过套接字发送对象全错了?我只是错过了代码中的一些基本内容吗?
我已经包括了我的客户端的准系统(它所做的只是发送一个 MessagePacket
和一个字符串,我的服务器也包括在内。现在,这个程序无论如何都不是最漂亮的所以我提前道歉;这对我来说都是一个学习曲线!
服务器:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Chat_Application.Packets;
namespace Chat_Application
{
class Server
{
private const int port = 1090;
private readonly ManualResetEvent allDone;
private readonly Dictionary<String, Socket> connections; // will hold all client sockets
private readonly IPAddress ipAddress;
private readonly IPEndPoint ipEndPoint;
private readonly Thread listenThread; // seperate thread to run the server
private readonly Socket serverSocket;
public Server()
{
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
connections = new Dictionary<string, Socket>();
ipAddress = IPAddress.Parse(GetLocalIPv4(NetworkInterfaceType.Ethernet));
ipEndPoint = new IPEndPoint(ipAddress, port);
listenThread = new Thread(StartListen);
allDone = new ManualResetEvent(false);
}
//called to start the new thread
public void Start()
{
listenThread.Start();
}
//TODO: implement this method
public void Stop()
{
throw new NotImplementedException();
}
//method that is run on the new server thread
public void StartListen()
{
serverSocket.Bind(ipEndPoint);
serverSocket.Listen(20);
String statusUpdate = "\n<INFO> Socket bound, listening for connections...";
Program.mainWin.AddConsoleText(statusUpdate);
while (true)
{
allDone.Reset();
serverSocket.BeginAccept(AcceptConnectionAsync, serverSocket);
allDone.WaitOne();
}
}
//Asynchronus method to recieve connections. The logic of the way things are done is not great, it is only temporary while I learn better ways to do things
public void AcceptConnectionAsync(IAsyncResult ar)
{
var bufferBytes = new byte[1024];
Packet packet = null;
allDone.Set();
var listener = (Socket) ar.AsyncState;
var client = listener.EndAccept(ar);
client.Receive(bufferBytes);
packet = ConvertPacket(bufferBytes);
switch (CheckPacketType(packet))
{
case PacketType.Message:
packet = ConvertPacket(bufferBytes);
var messagePacket = (MessagePacket) packet;
String userMessage = "(" + messagePacket.userName + ") " +
Encoding.UTF8.GetString(messagePacket.message);
Program.mainWin.AddConsoleText(userMessage);
break;
case PacketType.Connection:
packet = ConvertPacket(bufferBytes);
var connectionPacket = (Connect) packet;
String connectionMessage = "\n<CONNECT>New connection established to " +
connectionPacket.userName;
connections.Add(connectionPacket.userName, client);
Program.mainWin.AddConsoleText(connectionMessage);
break;
case PacketType.Command:
// TODO Implement this case, add new packet type
packet = ConvertPacket(bufferBytes);
var commandPacket = (MessagePacket) packet;
break;
case PacketType.Disconnect:
packet = ConvertPacket(bufferBytes);
var disconnectPacket = (Dissconnect) packet;
String disconnectMessage = "\n<DISCONNECT>User " + disconnectPacket.userName +
" Disconnected succesffuly";
connections.Remove(disconnectPacket.userName);
if (disconnectPacket.goodDissconnect)
{
Program.mainWin.AddConsoleText(disconnectMessage);
}
else
{
String message = "\n<DISCONNECT>User " + disconnectPacket.userName +
" Disconnected unexpectedly";
Program.mainWin.AddConsoleText(message);
}
break;
case PacketType.Request:
// TODO Implement this case, add new packet type
packet = ConvertPacket(bufferBytes);
var requestPacket = (MessagePacket) packet;
break;
case PacketType.Default:
MessageBox.Show("Packet Error", "Empty Packet Recieved");
break;
default:
break;
}
}
//used to check what type of packet has been recieved
private PacketType CheckPacketType(Packet packet)
{
switch (packet.type)
{
case PacketType.Message:
return PacketType.Message;
break;
case PacketType.Connection:
return PacketType.Connection;
break;
case PacketType.Command:
return PacketType.Command;
break;
case PacketType.Disconnect:
return PacketType.Disconnect;
break;
case PacketType.Request:
return PacketType.Request;
break;
default:
break;
}
return PacketType.Default;
}
//converts the byte array to a packet
private Packet ConvertPacket(byte[] bytes)
{
Packet packet = null;
//try to convert byte array to a Packet, the error occurs on the last line; when trying to de-serialize
/*try
{*/
var memStream = new MemoryStream();
var binForm = new BinaryFormatter();
memStream.Write(bytes, 0, bytes.Length);
memStream.Seek(0, SeekOrigin.Begin);
packet = (Packet) binForm.Deserialize(memStream);
//}
/*catch (Exception) // MAKE THIS THE APPROPRIATE EXCEPTION
{
MessageBox.Show("An unexpected error has occured when atempting to convert a packet!");
}*/
if (packet != null)
{
return packet;
}
else
{
MessageBox.Show("Error converting packet!");
return null;
}
}
//gets the local ip
public string GetLocalIPv4(NetworkInterfaceType _type)
{
var output = "";
foreach (var item in NetworkInterface.GetAllNetworkInterfaces())
{
if (item.NetworkInterfaceType == _type && item.OperationalStatus == OperationalStatus.Up)
{
foreach (var ip in item.GetIPProperties().UnicastAddresses)
{
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
{
output = ip.Address.ToString();
}
}
}
}
return output;
}
}
}
客户:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Chat_Application.Packets;
namespace Client
{
public partial class ClientWindow : Form
{
public ClientWindow()
{
InitializeComponent();
}
private void ClientWindow_Load(object sender, EventArgs e)
{
MessagePacket packet = new MessagePacket("Chris", "Hello Server!");
byte[] data;
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(new IPEndPoint(IPAddress.Parse(GetLocalIPv4(NetworkInterfaceType.Ethernet)), 1090));
packet.type = PacketType.Message;
data = ConvertPacket(packet);
//to check if the packet has been converted correctly
if (data != null)
{
socket.Send(data);
}
}
public string GetLocalIPv4(NetworkInterfaceType _type)
{
string output = "";
foreach (NetworkInterface item in NetworkInterface.GetAllNetworkInterfaces())
{
if (item.NetworkInterfaceType == _type && item.OperationalStatus == OperationalStatus.Up)
{
foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses)
{
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
{
output = ip.Address.ToString();
}
}
}
}
return output;
}
private byte[] ConvertPacket(Packet packet)
{
byte[] bytes = new byte[1024];
BinaryFormatter formatter = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
formatter.Serialize(ms, packet);
bytes = ms.ToArray();
}
return bytes;
}
}
}
这个例外对我来说似乎很清楚。当您尝试反序列化数据时,显然找不到序列化数据时引用的 Client
程序集。
不幸的是,您的代码示例不包含有关您在何处以及如何声明 Packet
class 的信息。但是根据您报告的行为,在我看来您可能已将同一个 .cs 文件链接到两个不同的项目,结果每个程序集最终都有自己的 Packet
类型的私有副本。
如果是这样,那么至少有几个好的解决方案:
- 仅在一个程序集中定义类型,并根据需要引用该程序集。例如,您可以在
Client.dll
程序集中定义它,然后将 Client.dll
程序集添加为 Server.dll
程序集的引用。或者您可以构建包含该类型的第三个程序集,并让您的 Client.dll
和 Server.dll
程序集都引用该第三个程序集。
- 改用
XmlSerializer
。 XML 不会包含需要特定程序集来定义相关类型的类型信息。当使用 XmlSerializer
映射到在 .NET 外部处理的 XML 或从 XML 映射时,这种方法更常用(即共享公共程序集来定义类型是不可能的),但这种方法可能也解决您的具体问题。
就个人而言,我会选择选项 #1,并将类型放入客户端和服务器共享的 DLL 中。
首先,我已经阅读了 Whosebug 和其他地方的许多指南和帖子,但仍然没有解决这个问题。我最近一直在尝试学习套接字,事实证明这是一个挑战。我已经了解如何使用带有字符串等的套接字,但我知道想要开始发送对象以便更好地组织我的程序。我正在制作一个聊天客户端;服务器将连接多个客户端,我猜它的工作方式有点像 IRC。我已经完成了服务器的基础知识;我有方法来检查收到的数据包类型(所有数据包都继承自 Packet
),并且有一个非常基本的 switch 语句,它根据收到的数据包采取行动。在此之前,我显然需要将套接字中接收到的 byte[]
转换为 Packet
。当我试图反序列化字节数组时,问题就来了。我在调用 .Deserialize(); 的那一行得到了一个 SerilizationException;正文如下:
System.Runtime.Serialization.SerializationException was unhandled by user code HResult=-2146233076 Message=Unable to find assembly 'Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
我不明白为什么会这样;我尝试编写不同的反序列化数组的方法,添加随机 if 语句以确保我没有传递一个空数组(我不是)但无法让它在我的生活中工作。我希望有人能指出我做错了什么?我是否打算通过套接字发送对象全错了?我只是错过了代码中的一些基本内容吗?
我已经包括了我的客户端的准系统(它所做的只是发送一个 MessagePacket
和一个字符串,我的服务器也包括在内。现在,这个程序无论如何都不是最漂亮的所以我提前道歉;这对我来说都是一个学习曲线!
服务器:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Chat_Application.Packets;
namespace Chat_Application
{
class Server
{
private const int port = 1090;
private readonly ManualResetEvent allDone;
private readonly Dictionary<String, Socket> connections; // will hold all client sockets
private readonly IPAddress ipAddress;
private readonly IPEndPoint ipEndPoint;
private readonly Thread listenThread; // seperate thread to run the server
private readonly Socket serverSocket;
public Server()
{
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
connections = new Dictionary<string, Socket>();
ipAddress = IPAddress.Parse(GetLocalIPv4(NetworkInterfaceType.Ethernet));
ipEndPoint = new IPEndPoint(ipAddress, port);
listenThread = new Thread(StartListen);
allDone = new ManualResetEvent(false);
}
//called to start the new thread
public void Start()
{
listenThread.Start();
}
//TODO: implement this method
public void Stop()
{
throw new NotImplementedException();
}
//method that is run on the new server thread
public void StartListen()
{
serverSocket.Bind(ipEndPoint);
serverSocket.Listen(20);
String statusUpdate = "\n<INFO> Socket bound, listening for connections...";
Program.mainWin.AddConsoleText(statusUpdate);
while (true)
{
allDone.Reset();
serverSocket.BeginAccept(AcceptConnectionAsync, serverSocket);
allDone.WaitOne();
}
}
//Asynchronus method to recieve connections. The logic of the way things are done is not great, it is only temporary while I learn better ways to do things
public void AcceptConnectionAsync(IAsyncResult ar)
{
var bufferBytes = new byte[1024];
Packet packet = null;
allDone.Set();
var listener = (Socket) ar.AsyncState;
var client = listener.EndAccept(ar);
client.Receive(bufferBytes);
packet = ConvertPacket(bufferBytes);
switch (CheckPacketType(packet))
{
case PacketType.Message:
packet = ConvertPacket(bufferBytes);
var messagePacket = (MessagePacket) packet;
String userMessage = "(" + messagePacket.userName + ") " +
Encoding.UTF8.GetString(messagePacket.message);
Program.mainWin.AddConsoleText(userMessage);
break;
case PacketType.Connection:
packet = ConvertPacket(bufferBytes);
var connectionPacket = (Connect) packet;
String connectionMessage = "\n<CONNECT>New connection established to " +
connectionPacket.userName;
connections.Add(connectionPacket.userName, client);
Program.mainWin.AddConsoleText(connectionMessage);
break;
case PacketType.Command:
// TODO Implement this case, add new packet type
packet = ConvertPacket(bufferBytes);
var commandPacket = (MessagePacket) packet;
break;
case PacketType.Disconnect:
packet = ConvertPacket(bufferBytes);
var disconnectPacket = (Dissconnect) packet;
String disconnectMessage = "\n<DISCONNECT>User " + disconnectPacket.userName +
" Disconnected succesffuly";
connections.Remove(disconnectPacket.userName);
if (disconnectPacket.goodDissconnect)
{
Program.mainWin.AddConsoleText(disconnectMessage);
}
else
{
String message = "\n<DISCONNECT>User " + disconnectPacket.userName +
" Disconnected unexpectedly";
Program.mainWin.AddConsoleText(message);
}
break;
case PacketType.Request:
// TODO Implement this case, add new packet type
packet = ConvertPacket(bufferBytes);
var requestPacket = (MessagePacket) packet;
break;
case PacketType.Default:
MessageBox.Show("Packet Error", "Empty Packet Recieved");
break;
default:
break;
}
}
//used to check what type of packet has been recieved
private PacketType CheckPacketType(Packet packet)
{
switch (packet.type)
{
case PacketType.Message:
return PacketType.Message;
break;
case PacketType.Connection:
return PacketType.Connection;
break;
case PacketType.Command:
return PacketType.Command;
break;
case PacketType.Disconnect:
return PacketType.Disconnect;
break;
case PacketType.Request:
return PacketType.Request;
break;
default:
break;
}
return PacketType.Default;
}
//converts the byte array to a packet
private Packet ConvertPacket(byte[] bytes)
{
Packet packet = null;
//try to convert byte array to a Packet, the error occurs on the last line; when trying to de-serialize
/*try
{*/
var memStream = new MemoryStream();
var binForm = new BinaryFormatter();
memStream.Write(bytes, 0, bytes.Length);
memStream.Seek(0, SeekOrigin.Begin);
packet = (Packet) binForm.Deserialize(memStream);
//}
/*catch (Exception) // MAKE THIS THE APPROPRIATE EXCEPTION
{
MessageBox.Show("An unexpected error has occured when atempting to convert a packet!");
}*/
if (packet != null)
{
return packet;
}
else
{
MessageBox.Show("Error converting packet!");
return null;
}
}
//gets the local ip
public string GetLocalIPv4(NetworkInterfaceType _type)
{
var output = "";
foreach (var item in NetworkInterface.GetAllNetworkInterfaces())
{
if (item.NetworkInterfaceType == _type && item.OperationalStatus == OperationalStatus.Up)
{
foreach (var ip in item.GetIPProperties().UnicastAddresses)
{
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
{
output = ip.Address.ToString();
}
}
}
}
return output;
}
}
}
客户:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Chat_Application.Packets;
namespace Client
{
public partial class ClientWindow : Form
{
public ClientWindow()
{
InitializeComponent();
}
private void ClientWindow_Load(object sender, EventArgs e)
{
MessagePacket packet = new MessagePacket("Chris", "Hello Server!");
byte[] data;
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(new IPEndPoint(IPAddress.Parse(GetLocalIPv4(NetworkInterfaceType.Ethernet)), 1090));
packet.type = PacketType.Message;
data = ConvertPacket(packet);
//to check if the packet has been converted correctly
if (data != null)
{
socket.Send(data);
}
}
public string GetLocalIPv4(NetworkInterfaceType _type)
{
string output = "";
foreach (NetworkInterface item in NetworkInterface.GetAllNetworkInterfaces())
{
if (item.NetworkInterfaceType == _type && item.OperationalStatus == OperationalStatus.Up)
{
foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses)
{
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
{
output = ip.Address.ToString();
}
}
}
}
return output;
}
private byte[] ConvertPacket(Packet packet)
{
byte[] bytes = new byte[1024];
BinaryFormatter formatter = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
formatter.Serialize(ms, packet);
bytes = ms.ToArray();
}
return bytes;
}
}
}
这个例外对我来说似乎很清楚。当您尝试反序列化数据时,显然找不到序列化数据时引用的 Client
程序集。
不幸的是,您的代码示例不包含有关您在何处以及如何声明 Packet
class 的信息。但是根据您报告的行为,在我看来您可能已将同一个 .cs 文件链接到两个不同的项目,结果每个程序集最终都有自己的 Packet
类型的私有副本。
如果是这样,那么至少有几个好的解决方案:
- 仅在一个程序集中定义类型,并根据需要引用该程序集。例如,您可以在
Client.dll
程序集中定义它,然后将Client.dll
程序集添加为Server.dll
程序集的引用。或者您可以构建包含该类型的第三个程序集,并让您的Client.dll
和Server.dll
程序集都引用该第三个程序集。 - 改用
XmlSerializer
。 XML 不会包含需要特定程序集来定义相关类型的类型信息。当使用XmlSerializer
映射到在 .NET 外部处理的 XML 或从 XML 映射时,这种方法更常用(即共享公共程序集来定义类型是不可能的),但这种方法可能也解决您的具体问题。
就个人而言,我会选择选项 #1,并将类型放入客户端和服务器共享的 DLL 中。