Клиент StreamSocket C# может писать только один раз на сервер
Я пытался настроить клиент-серверное приложение, используя StreamSockets в C#. Я в состоянии первоначально подключиться к серверу (ConnectAsync) и следующий, чтобы написать и прочитать поток. Если клиент отправляет другой поток на сервер с помощью метода WriteToServer, событие на стороне сервера не инициируется (SocketListener_ConnectionReceived). Я отправляю xmlSerialized объект "Сообщение" на сервер.
При отладке я не получаю никаких ошибок. На стороне клиента, хотя после использования "WriteToServer" и перехода к "ReadFromServer" код явно застревает в строке ниже, так как сервер не отвечает
int bufferLength = inStream.ReadByte();
Я надеюсь, что кто-то вызовет помощь. Честно говоря, я не уверен, в чем проблема, потому что метод "Запись" используется одинаково, когда клиент пытается выполнить запись на сервер.
Клиент - это компьютер с Windows 10, а Сервер Raspberry Pi работает под управлением Windows 10 IoT.
Класс внутри клиентского приложения, которое обрабатывает соединение, выглядит следующим образом.
StreamSocket socket;
HostName remoteHostName, localHostName;
string serviceAddress = "1337";
EndpointPair endPointPair;
Boolean isConnected;
Socket test;
public Connection()
{
remoteHostName = new HostName("172.16.20.202"); // might have to change based on pi's ip address
//remoteHostName = new HostName("172.16.20.4");
localHostName = new HostName(getLocalIpAddress());
endPointPair = new EndpointPair(localHostName, serviceAddress, remoteHostName, serviceAddress);
socket = new StreamSocket();
socket.Control.NoDelay = true;
socket.Control.QualityOfService = SocketQualityOfService.LowLatency;
}
private string getLocalIpAddress()
{
var icp = NetworkInformation.GetInternetConnectionProfile();
if (icp?.NetworkAdapter == null) return null;
var hostname =
NetworkInformation.GetHostNames()
.SingleOrDefault(
hn =>
hn.IPInformation?.NetworkAdapter != null && hn.IPInformation.NetworkAdapter.NetworkAdapterId
== icp.NetworkAdapter.NetworkAdapterId);
// the ip address
return hostname?.CanonicalName;
}
public async Task StartConnection()
{
try
{
if (isConnected)
{
await socket.CancelIOAsync();
socket.Dispose();
socket = null;
isConnected = false;
}
await socket.ConnectAsync(endPointPair);
isConnected = true;
}
catch (Exception exc)
{
if (Windows.Networking.Sockets.SocketError.GetStatus(exc.HResult) == SocketErrorStatus.Unknown)
{
throw;
}
Debug.WriteLine("Connect failed with error: " + exc.Message);
socket.Dispose();
isConnected = false;
socket = null;
//return null;
}
}
public async Task WriteToServer(Message msg)
{
try
{
using (DataWriter writer = new DataWriter(socket.OutputStream))
{
writer.WriteBytes(serialize(msg));
await writer.StoreAsync();
writer.DetachStream();
writer.Dispose();
}
}
catch (Exception exc)
{
Debug.WriteLine("Write failed with error: " + exc.Message);
}
}
public async Task<Library.Message> ReadFromServer()
{
try
{
Stream inStream = socket.InputStream.AsStreamForRead();
int bufferLength = inStream.ReadByte();
byte[] serializedMessage = new byte[bufferLength];
await inStream.ReadAsync(serializedMessage, 0, bufferLength);
await inStream.FlushAsync();
Library.Message incomingMessage;
using (var stream = new MemoryStream(serializedMessage))
{
var serializer = new XmlSerializer(typeof(Library.Message));
incomingMessage = (Library.Message)serializer.Deserialize(stream);
}
return incomingMessage;
}
catch (Exception exc)
{
Debug.WriteLine("Read failed with error: " + exc.Message);
return null;
}
}
private byte[] serialize(Message msg)
{
byte[] serializedMessage, returnArray;
var serializer = new XmlSerializer(typeof(Library.Message));
using (var stream = new MemoryStream())
{
serializer.Serialize(stream, msg);
serializedMessage = stream.ToArray();
stream.Dispose();
}
int bufferLength = serializedMessage.Length;
returnArray = new byte[serializedMessage.Length + 1];
serializedMessage.CopyTo(returnArray, 1);
returnArray[0] = (byte)bufferLength;
Debug.WriteLine("ClientReturnArrayLength: " + returnArray.Length);
Debug.WriteLine("ClientFirstByte: " + returnArray[0]);
return returnArray;
}
Серверная сторона выглядит так
public Task DispatcherPriority { get; private set; }
public MainPage()
{
this.InitializeComponent();
dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
hostName = new HostName(getLocalIpAddress());
clients = new List<Client>();
}
/// <summary>
/// Gets the ip address of the host
/// </summary>
/// <returns></returns>
private string getLocalIpAddress()
{
var icp = NetworkInformation.GetInternetConnectionProfile();
if (icp?.NetworkAdapter == null) return null;
var hostname =
NetworkInformation.GetHostNames()
.SingleOrDefault(
hn =>
hn.IPInformation?.NetworkAdapter != null && hn.IPInformation.NetworkAdapter.NetworkAdapterId
== icp.NetworkAdapter.NetworkAdapterId);
// the ip address
return hostname?.CanonicalName;
}
async void setupSocketListener()
{
if (socketListener != null)
{
await socketListener.CancelIOAsync();
socketListener.Dispose();
socketListener = null;
}
socketListener = new StreamSocketListener();
socketListener.Control.QualityOfService = SocketQualityOfService.LowLatency;
socketListener.ConnectionReceived += SocketListener_ConnectionReceived;
await socketListener.BindServiceNameAsync("1337");
listBox.Items.Add("server started.");
clients.Clear();
}
private async void SocketListener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
{
HostName ip = args.Socket.Information.RemoteAddress;
string port = args.Socket.Information.RemotePort;
try
{
Stream inStream = args.Socket.InputStream.AsStreamForRead();
int bufferLength = inStream.ReadByte();
byte[] serializedMessage = new byte[bufferLength];
await inStream.ReadAsync(serializedMessage, 0, bufferLength);
await inStream.FlushAsync();
Message incomingMessage;
using (var stream = new MemoryStream(serializedMessage))
{
var serializer = new XmlSerializer(typeof(Message));
incomingMessage = (Message)serializer.Deserialize(stream);
}
/// <summary>
/// 1 = Connected
/// 2 = SentNote
/// 3 = Login
/// </summary>
switch (incomingMessage.Mode)
{
case 1:
onClientConnect(ip, port, incomingMessage.Username, args.Socket);
break;
case 2:
onNoteReceived(incomingMessage);
break;
case 3:
//handle login
break;
}
}
catch (Exception msg)
{
Debug.WriteLine(msg);
}
}
private async void onNoteReceived(Message msg)
{
foreach (var client in clients)
{
//if (client.Username != msg.Username)
//{
using (DataWriter writer = new DataWriter(client.Socket.OutputStream))
{
writer.WriteBytes(serialize(msg));
await writer.StoreAsync();
writer.DetachStream();
writer.Dispose();
}
//}
}
}
private void buttonStartServer_Click(object sender, RoutedEventArgs e)
{
setupSocketListener();
}
private async void notifyClients(string username)
{
Message msg = new Message();
msg.Username = username;
foreach (var client in clients)
{
//if (client.Username != msg.Username)
//{
using (DataWriter writer = new DataWriter(client.Socket.OutputStream))
{
writer.WriteBytes(serialize(msg));
await writer.StoreAsync();
writer.DetachStream();
writer.Dispose();
}
//}
}
}
private async void onClientConnect(HostName ip, string port, string username, StreamSocket socket)
{
clients.Add(new Client(ip, port, username, socket));
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
listBox.Items.Add("User: " + username + " on IP " + ip + " is connected.");
});
notifyClients(username);
}
private byte[] serialize(Message msg)
{
byte[] serializedMessage, returnArray;
var serializer = new XmlSerializer(typeof(Message));
using (var stream = new MemoryStream())
{
serializer.Serialize(stream, msg);
serializedMessage = stream.ToArray();
stream.Dispose();
}
int bufferLength = serializedMessage.Length;
returnArray = new byte[serializedMessage.Length + 1];
serializedMessage.CopyTo(returnArray, 1);
returnArray[0] = (byte)bufferLength;
Debug.WriteLine("ClientReturnArrayLength: " + returnArray.Length);
Debug.WriteLine("ClientFirstByte: " + returnArray[0]);
return returnArray;
}
}
Это класс сообщений, который я использую для отправки по сети.
public string Username { get; set; }
/// <summary>
/// 1 = startConnection
/// 2 = SendNote
/// 3 = Login
/// </summary>
public int Mode { get; set; }
public Piano PianoNote { get; set; }
public string Instrument { get; set; }
public Message()
{
}
}
public enum Piano { a1, a1s, b1, c1 };
**Редактировать: **
Обрамление сообщения:
byte[] prefix = BitConverter.GetBytes(serializedMessage.Length);
returnArray = new byte[serializedMessage.Length + prefix.Length];
prefix.CopyTo(returnArray, 0);
serializedMessage.CopyTo(returnArray, prefix.Length);
Читая сообщение:
byte[] prefix = new byte[4];
await inStream.ReadAsync(prefix, 0, 4);
int bufferLength = BitConverter.ToInt32(prefix, 0);
Полуоткрытый: вместо синхронного чтения я переключился на асинхронное чтение первых 4 байтов, как показано выше.
1 ответ
Я в состоянии первоначально подключиться к серверу (ConnectAsync) и следующий, чтобы написать и прочитать поток. Если клиент отправляет другой поток на сервер с помощью метода WriteToServer, событие на стороне сервера не инициируется (SocketListener_ConnectionReceived).
Внимательно посмотрите на эти имена. Ты звонишь ConnectAsync
один раз и потом WriteToServer
дважды, и только видя SocketListener_ConnectionReceived
один раз. Есть только одна связь, так что да, ConnectionReceived
будет срабатывать только один раз.
Это просто царапает поверхность, хотя. Есть несколько других очень тонких проблем с этим кодом.
То, что торчит, - это отсутствие надлежащей структуры сообщений, как я описываю в своем блоге. Вы используете префикс длиной в один байт, поэтому в сети все в порядке (хотя ограничено 256 байтами, что далеко не идет с XML). Но чтение сообщений неверно; особенно, Stream.ReadAsync
может читать между 1
а также bufferLength
байтов, или он может вернуться 0
,
Еще одна проблема заключается в том, что это проблема полуоткрытого типа, как я описываю в своем блоге. Особенно, int bufferLength = inStream.ReadByte();
будет блокироваться на неопределенный срок в полуоткрытой ситуации. Вы должны использовать только асинхронные методы для всех сетевых потоков и периодически выполнять запись в ожидании поступления данных.
Таким образом, я настоятельно рекомендую вам использовать собственный хост SignalR вместо необработанных сокетов. Программирование сокетов в необработанном виде чрезвычайно сложно сделать правильно, особенно потому, что неправильный код часто работает правильно в локальной среде.