Принять асинхронный TCP-клиент

Я делал сервер. Я использую TcpListener.AcceptTcpClientAsync() в методе async, но понятия не имею, как заставить его работать. Мой код прямо сейчас:

private static async void StartServer()
{
    Console.WriteLine("S: Server started on port {0}", WebVars.ServerPort);
    var listener = new TcpListener(WebVars.LocalIp, WebVars.ServerPort);
    listener.Start();
    var client = await listener.AcceptTcpClientAsync();
}

Как обработать клиента? Я просто продолжаю кодить, и он автоматически создаст новые потоки того же метода, или мне нужно использовать какой-то волшебный метод, который сделает это за меня?

Изменить: текущий код:

private static Task HandleClientAsync(TcpClient client)
{
    var stream = client.GetStream();
    // do stuff
}
/// <summary>
/// Method to be used on seperate thread.
/// </summary>
private static async void RunServerAsync()
{
    while (true)
    {
        Console.WriteLine("S: Server started on port {0}", WebVars.ServerPort);
        var listener = new TcpListener(WebVars.LocalIp, WebVars.ServerPort);
        listener.Start();
        var client = await listener.AcceptTcpClientAsync();
        await HandleClientAsync(client);
    }
}

person Ilan    schedule 05.03.2014    source источник
comment
Пожалуйста, не включайте информацию о языке, используемом в заголовке вопроса, за исключением случаев, когда без этого он не имеет смысла. Теги служат этой цели.   -  person Ondrej Janacek    schedule 05.03.2014
comment
Это связано с: stackoverflow.com/questions/21013751/   -  person noseratio    schedule 05.03.2014


Ответы (2)


Ничто не будет волшебным образом создавать для вас выделенные потоки, хотя есть несколько потоков, используемых для завершения ввода-вывода, которые могут вступить в игру, особенно если у вас нет контекста синхронизации, который вы нужно вернуться к.

Вы должны решить, хотите ли вы, чтобы ваш метод StartServer действительно завершался, когда он принял одно соединение, или продолжал зацикливаться до тех пор, пока вам не велят завершить работу.

В любом случае, вам также необходимо решить, как вести себя с клиентом. Либо вы можете запустить новый поток и использовать синхронные методы, либо вы можете просто использовать асинхронный ввод-вывод для обработки всего в одном потоке. Например, чтобы сбросить входящие данные в файл:

private Task HandleClientAsync(TcpClient client)
{
    // Note: this uses a *synchronous* call to create the file; not ideal.
    using (var output = File.Create("client.data"))
    {
        using (var input = client.GetStream())
        {
            // Could use CopyToAsync... this is just demo code really.

            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = await input.ReadAsync(buffer, 0, buffer.Length)) > 0)
            {
                await output.WriteAsync(buffer, 0, bytesRead);
            }
        }
    }
}

(Это предполагает, что клиент просто разорвет соединение, когда закончит запись данных.) Помимо вызова File.Create, все это асинхронно, поэтому нет необходимости создавать для него отдельный поток.

Конечно, это всего лишь пример — реальная обработка соединения обычно была бы более сложной. Если ваша реальная обработка требует чего-то более ресурсоемкого, вы можете рассмотреть возможность использования Task.Run для использования пула потоков... таким образом, он не будет мешать принятию большего количества соединений.

person Jon Skeet    schedule 05.03.2014
comment
Я хочу, чтобы сервер продолжал зацикливаться. Он будет в цикле while и получит входящую строку, обработает ее, а затем вернет другую строку или файл. - person Ilan; 05.03.2014
comment
@ Ilan321: Правильно, поэтому вам нужно добавить этот цикл while к вашему методу StartServer (который я бы изменил на RunServerAsync и заставил его возвращать Task, чтобы все, что его вызывает, могло видеть, когда оно завершено). Вы также можете взглянуть на демонстрационный обработчик HTTP RPC, который я написал для введения в асинхронность: github.com/jskeet/DemoCode/blob/master/AsyncIntro/Code/ (не рабочий код, но может быть вам полезен.) - person Jon Skeet; 05.03.2014
comment
Я собираюсь отредактировать свой вопрос и поставить свой текущий код. Это будет работать? - person Ilan; 05.03.2014
comment
@ Ilan321: Вы не хотите создавать нового слушателя в цикле - вам нужен только вызов AcceptTcpClientAsync (и вызов, чтобы начать его обработку) в цикле. И вы не хотите ждать результата HandleClientAsync, иначе вы сможете обрабатывать только один запрос за раз. Вы вполне можете захотеть создать коллекцию выполняемых запросов (представленных задачами), чтобы вы могли сказать, когда все будет завершено. Опять же, посмотрите код, на который я ссылался. - person Jon Skeet; 05.03.2014
comment
Сделаю! Спасибо за помощь! - person Ilan; 05.03.2014
comment
Джон , глядя на await input.ReadAsync , ожидает ли он во время выборки информации из ввода (например, при выборке нет задействованного потока) ИЛИ он ожидает до тех пор, пока информация не станет доступной (ожидание, отсутствие блока)? - person Royi Namir; 30.10.2014
comment
@RoyiNamir: я не понимаю, что ты имеешь в виду. Предполагая, что данные не доступны немедленно, вызов вернет незавершенную задачу, когда встретится с выражением await, и поток сможет сделать что-то еще. Когда данные станут доступными, асинхронный метод продолжит работу с того места, где остановился, в соответствующем потоке. (В консольном приложении это будет поток пула потоков.) - person Jon Skeet; 30.10.2014

// вкратце, вся заслуга принадлежит C# 7.0 (Джозеф Альбахари и Бен Альбахари)

    async void RunServerAsync()
    {
        var listner = new TcpListener(IPAddress.Any, 9999);
        listner.Start();
        try
        {
            while (true)
                await Accept(await listner.AcceptTcpClientAsync());
        }
        finally { listner.Stop(); }
    }


    const int packet_length = 2;  // user defined packet length

    async Task Accept(TcpClient client)
    {
        await Task.Yield();
        try
        {
            using(client)
            using(NetworkStream n = client.GetStream())
            {
                byte[] data = new byte[packet_length];
                int bytesRead = 0;
                int chunkSize = 1;

                while (bytesRead < data.Length && chunkSize > 0)
                    bytesRead += chunkSize = 
                        await n.ReadAsync(data, bytesRead, data.Length - bytesRead);

                // get data
                string str = Encoding.Default.GetString(data);
                Console.WriteLine("[server] received : {0}", str);

                // To do
                // ...

                // send the result to client
                string send_str = "server_send_test";
                byte[] send_data = Encoding.ASCII.GetBytes(send_str);
                await n.WriteAsync(send_data, 0, send_data.Length);

            }
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
person sailfish009    schedule 21.11.2018