Я пытаюсь запустить консольное приложение, где сервер и клиент могут отправлять сообщения друг другу.
Раньше у меня это работало, но было с блокировкой или синхронными вызовами, типа Send()
или Receive()
. Я хочу переключиться на неблокирующие вызовы, потому что либо клиенту, либо серверу приходится ждать получения сообщения для его отправки, а это непрактично.
Итак, я начал с BeginAccept()
, чтобы понять, как они работают, но мой BeginAccept()
не перезванивает на мой AcceptCallback()
, у которого есть EndAccept()
, для правильного соединения.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class Server
{
public static void Main()
{
//Set ipv4 address and socket to variables
IPAddress ServerAddress = IPAddress.Parse("127.0.0.1");
int ServerPort = 1234;
bool connected = false;
//Setup the new socket with the parameters of addressFamily.interNetwork (tells it to use a ipv4 address)
//socketType.stream and protocolType.tcp (tells it to have a connection tye of tcp)
Socket ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ServerSocket.Bind(new IPEndPoint(ServerAddress, ServerPort));
//telling the socket to start listening and to only let 5 connections at one time
ServerSocket.Listen(5);
Console.WriteLine("Server is listening for connections");
while (!connected)
{
ServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), ServerSocket);
if (ServerSocket.Connected == true)
{
connected = true;
Console.WriteLine("Client connected!");
}
}
while (connected == true)
{
if (ServerSocket.Connected == true)
{
connected = true;
}
else
{
connected = false;
}
//setup a array called temp to store the clients data, set what you recieve from clientSocket to be put into an int called ClientBytes
//convert the bytes from clientbytes to string to read it
byte[] temp = new byte[1024];
int ClientBytes = ServerSocket.Receive(temp);
if (ClientBytes <= 0 || temp == null)
{
return;
}
else
{
string ClientMessage = Encoding.ASCII.GetString(temp, 0, ClientBytes);
Console.WriteLine("Recieved data from client: " + ClientMessage);
}
//Convert the message to bytes, store it in a array then send it down the client socket
string ServerMessage = Console.ReadLine();
if (ServerMessage == null || ServerMessage.Length == 0)
{
return;
}
else
{
byte[] temp2 = Encoding.ASCII.GetBytes(ServerMessage);
ServerSocket.Send(temp2);
}
/*ClientSocket.Shutdown(SocketShutdown.Both);
ClientSocket.Close();*/
}
if (Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.F10)
{
ServerSocket.Close();
}
}
private static void AcceptCallback(IAsyncResult ar)
{
Socket ServerSocket = (Socket)ar.AsyncState;
Socket Handler = ServerSocket.EndAccept(ar);
}
}
Я ожидал, что он просто подключится, но этого не произошло. Честно говоря, я особо не пробовал, потому что думал, что хорошо в этом разбираюсь, но, похоже, нет.
Почему бы вам просто не использовать обычные async
методы, а не старый Begin
End
способ?
@Charlieface Я даже не осознавал, что это была вещь, которую я пытался реализовать, асинхронный прием работал, но для асинхронного приема не требуется временная температура для хранения данных "int ClientBytes = ServerSocket.ReceiveAsync(temp<byte>); " эта строка продолжает выдавать мне ошибку «переменная temp не может использоваться с аргументами типа». Есть идеи, как это исправить?
@MarcGravell Как мне зафиксировать результат? когда я делаю ServerSocket.CompletedSynchronous, он не отображается в списке так же, как .IsCompleted
@Doblob83 var whatever = ServerSocket.BeginAccept(...)
?
Вам необходимо использовать await
для функций, отмеченных Async
, которые возвращают Task
.
@MarcGravell, когда я получил результат, я сделал console.writeline, чтобы отобразить его, и он просто выдал кучу этого «System.Threading.Tasks.TaskToAsyncResult+TaskAsyncResult»
@Charlieface, извини, но я новичок в разблокировке, где бы я поместил ожидание в любом месте, где я его поставил, это дало бы мне новую ошибку
int ClientBytes = await ServerSocket.ReceiveAsync(temp<byte>);
но на самом деле вам следует использовать TcpClient, как я показываю ниже.
В вашем коде много ошибок:
BeginAccept
и т. д. — это устарело. Используйте новые функции Async с await
.TcpListener
и TcpClient
вместо необработанных сокетов.127.0.0.1
, если вы не хотите только слушать запросы локального хоста (а затем просто используйте IPAddress.Loopback
), в противном случае просто используйте IPAddress.Any
.public static async Task Main()
{
int ServerPort = 1234;
var listener = new TcpListener(IPAddress.Any, ServerPort);
listener.Start(5);
Console.WriteLine("Server is listening for connections");
while (true)
{
var client = await listener.AcceptTcpClientAsync();
Console.WriteLine("Client connected!");
// hand off client so we can carry on listening
Task.Run(() => HandleClient(client));
}
}
public static async Task HandleClient(TcpClient client)
{
using var _ = client;
using var stream = client.GetStream();
//setup a array called temp to store the clients data, set what you recieve from clientSocket to be put into an int called ClientBytes
//convert the bytes from clientbytes to string to read it
byte[] temp = new byte[1024];
do
{
int bytesRead = await stream.ReadAsync(temp);
if (bytesRead == 0)
return;
string ClientMessage = Encoding.ASCII.GetString(temp, 0, ClientBytes);
Console.WriteLine("Recieved data from client: " + ClientMessage);
//Convert the message to bytes, store it in a array then send it down the client socket
string ServerMessage = Console.ReadLine();
if (ServerMessage is not { Length : > 0 })
return;
byte[] temp2 = Encoding.ASCII.GetBytes(ServerMessage);
await stream.WriteAsync(temp2);
} while (Console.ReadKey(true).Key != ConsoleKey.F10); // how to end this loop??
}
Дальнейшие примечания:
Encoding.ASCII
довольно ограничивает.CancellationTokenSource
, возможно, в сочетании с Console.CancelKeyPress
, чтобы отключить прослушиватель и сокеты.Вашему AcceptCallback()
скорее всего звонят, просто вы ничего с него не сообщаете. Ваш основной код ведет журналирование, но он никогда не сообщит о подключении клиента, поскольку ищет для этого неправильное условие. Вы запускаете цикл занятости, который просто вызывает ServerSocket.BeginAccept()
снова и снова бесконечно, чего ServerSocket.Connected
никогда не будет true
- прослушивающий TCP-сокет никогда не сможет войти в состояние Connected
, не говоря уже о выполнении операций чтения/отправки ввода-вывода. Принятие TCP-клиента создает новый Socket
, для которого вы должны выполнить ввод-вывод, но вы его игнорируете.
Как описывает ответ @Charlieface, есть лучшие способы добиться того, чего вы хотите. Но если вы хотите, чтобы ваш существующий код работал, попробуйте реструктурировать его примерно так:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Collections.Generic;
public class Server
{
private class ClientInfo
{
Socket clientSocket;
byte[] readBuffer;
List<byte[]> sendBuffers;
byte[] currSendBuffer;
int sendTotal;
int sentSoFar;
public ClientInfo(Socket ClientSocket)
{
clientSocket = ClientSocket;
readBuffer = new byte[102];
sendBuffers = new List<byte[]>();
currSendBuffer = null;
sendTotal = 0;
sentSoFar = 0;
}
public void BeginReceive()
{
clientSocket.BeginReceive(readBuffer, 0, readBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), this);
}
private void ReceiveCallback(IAsyncResult ar)
{
int numRead = clientSocket.EndReceive(ar);
if (numRead > 0)
{
string ClientMessage = Encoding.ASCII.GetString(readBuffer, 0, numRead);
Console.WriteLine("Received data from client: " + ClientMessage);
BeginSendMsg("I GOT IT!");
BeginReceive();
}
else
{
clientSocket.Close();
Console.WriteLine("Client disconnected!");
}
}
private BeginSendMsg(string Message)
{
byte[] buffer = Encoding.ASCII.GetBytes(Message);
sendBuffers.Add(buffer);
if (sendBuffers.Count == 1)
BeginSendFirstBuffer();
}
private void BeginSendFirstBuffer()
{
currSendBuffer = sendBuffers[0];
sendTotal = currSendBuffer.Length;
sentSoFar = 0;
BeginSendNextChunk();
}
private void BeginSendNextChunk()
{
clientSocket.BeginSend(currSendBuffer, sentSoFar, sendTotal - sentSoFar, SocketFlags.None, new AsyncCallback(SendCallback), this);
}
private void SendCallback(IAsyncResult ar)
{
int numSent = clientSocket.EndSend(ar);
if (numSent <= 0)
{
clientSocket.Close();
return;
}
sentSoFar += numSent;
if (sentSoFar < sendTotal)
{
BeginSendNextChunk();
return;
}
sendBuffers.RemoveAt(0);
if (sendBuffers.Count == 0)
{
currSendBuffer = null;
sendTotal = 0;
sentSoFar = 0;
return;
}
BeginSendFirstBuffer();
}
}
public static void Main()
{
IPAddress ServerAddress = IPAddress.Loopback; // or IPAddress.Any
int ServerPort = 1234;
Socket ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ServerSocket.Bind(new IPEndPoint(ServerAddress, ServerPort));
ServerSocket.Listen(5);
Console.WriteLine("Server is listening for connections");
ServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), ServerSocket);
while (Console.ReadKey().Key != ConsoleKey.F10) {}
ServerSocket.Close();
}
private static void AcceptCallback(IAsyncResult ar)
{
Socket ServerSocket = (Socket)ar.AsyncState;
Socket ClientSocket = ServerSocket.EndAccept(ar);
Console.WriteLine("Client connected!");
ClientInfo client = new ClientInfo(ClientSocket);
client.BeginReceive();
ServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), ServerSocket);
}
}
Похоже, это должно работать, большое спасибо, но может показаться, что он имеет доступ к любому из "частных классов ClientInfo { Socket Socket; byte[] readBuffer; List<byte[]> sendBuffers; byte[] currSendBuffer; int sendTotal; int sendSoFar ; public ClientInfo (Socket ClientSocket) {socket = ClientSocket; readBuffer = новый байт [102]; sendBuffers = новый список <byte []> (); currSendBuffer = null; sendTotal = 0; }» уровень защиты;
@Doblob83 Doblob83 Я обновил свой пример.
Непроверено, но: попробуйте захватить результат
BeginAccept
и проверьте.CompletedSynchronously
/.IsCompleted
— возможно, в этом случае асинхронный обратный вызов не запускается, если он выполняется синхронно.