как заставить TcpListener принимать несколько подключений и работать с каждым отдельно?

У меня есть прослушиватель SMTP, который работает хорошо, но может принимать только одно соединение. Мой код C # приведен ниже, и я использую его как службу. Моя цель - запустить его на сервере и проанализировать несколько отправленных ему SMTP-сообщений.

в настоящее время он разбирает первое сообщение и перестает работать. как я могу заставить его принимать 2-е, 3-е, 4-е ... SMTP-сообщение и обрабатывать его так же, как и первое?

вот мой код:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.IO;  

namespace SMTP_Listener
{
    class Program
    {
        static void Main(string[] args)
        {


            TcpListener listener = new TcpListener(IPAddress.Any , 8000);
            TcpClient client;
            NetworkStream ns;

            listener.Start();

            Console.WriteLine("Awaiting connection...");
            client = listener.AcceptTcpClient();
            Console.WriteLine("Connection accepted!");

            ns = client.GetStream();

            using (StreamWriter writer = new StreamWriter(ns))
            {
                writer.WriteLine("220 localhost SMTP server ready.");
                writer.Flush();

                using (StreamReader reader = new StreamReader(ns))
                {
                    string response = reader.ReadLine();

                    if (!response.StartsWith("HELO") && !response.StartsWith("EHLO"))
                    {
                        writer.WriteLine("500 UNKNOWN COMMAND");
                        writer.Flush();
                        ns.Close();
                        return;
                    }

                    string remote = response.Replace("HELO", string.Empty).Replace("EHLO", string.Empty).Trim();

                    writer.WriteLine("250 localhost Hello " + remote);
                    writer.Flush();

                    response = reader.ReadLine();

                    if (!response.StartsWith("MAIL FROM:"))
                    {
                        writer.WriteLine("500 UNKNOWN COMMAND");
                        writer.Flush();
                        ns.Close();
                        return;
                    }

                    remote = response.Replace("RCPT TO:", string.Empty).Trim();
                    writer.WriteLine("250 " + remote + " I like that guy too!");
                    writer.Flush();

                    response = reader.ReadLine();

                    if (!response.StartsWith("RCPT TO:"))
                    {
                        writer.WriteLine("500 UNKNOWN COMMAND");
                        writer.Flush();
                        ns.Close();
                        return;
                    }

                    remote = response.Replace("MAIL FROM:", string.Empty).Trim();
                    writer.WriteLine("250 " + remote + " I like that guy!");
                    writer.Flush();

                    response = reader.ReadLine();

                    if (response.Trim() != "DATA")
                    {
                        writer.WriteLine("500 UNKNOWN COMMAND");
                        writer.Flush();
                        ns.Close();
                        return;
                    }

                    writer.WriteLine("354 Enter message. When finished, enter \".\" on a line by itself");
                    writer.Flush();

                    int counter = 0;
                    StringBuilder message = new StringBuilder();

                    while ((response = reader.ReadLine().Trim()) != ".")
                    {
                        message.AppendLine(response);
                        counter++;

                        if (counter == 1000000)
                        {
                            ns.Close();
                            return;  // Seriously? 1 million lines in a message?
                        }
                    }

                    writer.WriteLine("250 OK");
                    writer.Flush();
                    ns.Close();
                    // Insert "message" into DB
                    Console.WriteLine("Received message:");
                    Console.WriteLine(message.ToString());
                }
            }

            Console.ReadKey();
        }
    }
}

person kacalapy    schedule 17.03.2011    source источник


Ответы (4)


Вы можете выделить большую часть кода в отдельный поток:

static void Main(string[] args)
{
    TcpListener listener = new TcpListener(IPAddress.Any , 8000);
    TcpClient client;
    listener.Start();

    while (true) // Add your exit flag here
    {
        client = listener.AcceptTcpClient();
        ThreadPool.QueueUserWorkItem(ThreadProc, client);
    }
}
private static void ThreadProc(object obj)
{
    var client = (TcpClient)obj;
    // Do your work here
}
person Oleg Tarasov    schedule 17.03.2011
comment
Почему не с BeginAcceptTcpClient? В очень простом примере, как этот, в этом нет необходимости, но если есть какой-либо графический интерфейс, async BeginAcceptTcpClient избежит зависания. - person i486; 18.09.2017
comment
Все это хорошо и все такое, но я имею дело с двоичными данными, а не с красивыми строками. Как сообщить объекту списка, что мне нужны необработанные данные в виде массива byte []? - person Bing Bang; 21.09.2018
comment
@BingBang Вы ничего не говорите объекту-слушателю. Вы получаете клиента от слушателя, а от клиента вы получаете свой поток, который по умолчанию является byte []. - person sLw; 13.06.2019

Вы почти наверняка захотите превратить каждое соединение в другой поток. Итак, у вас есть вызов accept в цикле:

while (listening)
{
    TcpClient client = listener.AcceptTcpClient();
    // Start a thread to handle this client...
    new Thread(() => HandleClient(client)).Start();
}

Очевидно, вы захотите настроить способ создания потоков (возможно, использовать пул потоков, может быть, TPL и т. Д.) И то, как вы изящно останавливаете слушателя.

person Jon Skeet    schedule 17.03.2011
comment
как это решение будет масштабироваться? Было бы разумно иметь два потока - один поток для буферизации входящих запросов, а другой - для итерации через кромку и их обработки? - person kacalapy; 17.03.2011
comment
@kacalapy: он отлично масштабируется для большинства ситуаций, хотя вы, вероятно, захотите использовать пул потоков. Вы не хотите, чтобы одно соединение ожидало полной обработки другого, прежде чем оно получит свою очередь. - person Jon Skeet; 17.03.2011
comment
@JonSkeet Что бы вы порекомендовали для достижения наилучшего результата? Используете threadpool, например ответ ThePretender? - person Syaiful Nizam Yahya; 22.11.2013
comment
@publicENEMY: На самом деле я обычно рекомендую использовать параллельную библиотеку задач, если вы можете ... - person Jon Skeet; 22.11.2013
comment
@JonSkeet знаете ли вы какой-нибудь образец, который не запускает новый поток для получения данных от каждого клиента? - person SHM; 31.08.2016
comment
@SHM: Ну, вы могли бы использовать пул потоков в соответствии с принятым ответом - или вы могли бы использовать NIO и асинхронный код, но это было бы значительно сложнее. - person Jon Skeet; 31.08.2016

Я знаю, что это старый вопрос, но уверен, что многим понравится этот ответ.

// 1
while (listening)
{
    TcpClient client = listener.AcceptTcpClient();
    // Start a thread to handle this client...
    new Thread(() => HandleClient(client)).Start();
}

// 2
while (listening)
{
    TcpClient client = listener.AcceptTcpClient();
    // Start a task to handle this client...
    Task.Run(() => HandleClient(client));
}

// 3
public async void StartListener() //non blocking listener
{
    listener = new TcpListener(ipAddress, port);
    listener.Start();
    while (listening)
    {
        TcpClient client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);//non blocking waiting                    
        // We are already in the new task to handle this client...   
        HandleClient(client);
    }
}
//... in your code
StartListener();
//...
//use Thread.CurrentThread.ManagedThreadId to check task/thread id to make yourself sure
person MrHIDEn    schedule 30.01.2016
comment
Что, если HandleClient() является асинхронным, потому что в этой функции мы ожидаем ReadLineAsync() от программы чтения потоков? - person Xander Luciano; 16.02.2017
comment
В 3) я думаю, что он должен быть асинхронным, но без ожидания, просто HandleClient (клиент) - person MrHIDEn; 19.06.2018
comment
Чтобы ответить на мой предыдущий вопрос, я считаю, что код должен быть await HandleClient(), а не просто HandleClient(). @MrHIDEn Я думаю, вы имеете в виду пожар и тоже забыли, если я правильно помню? Или вы хотите удалить строку await listener... над HandleClient()? - person Xander Luciano; 26.06.2018

Согласно вашему коду, вы запускаете одного слушателя, получаете и обрабатываете сообщение и закрываете программу.

Вам необходимо поддерживать слушателя, и объект TcpClient можно передать другой функции для обработки полученного сообщения. listener.Start ();

        Console.WriteLine("Awaiting connection...");
        client = listener.AcceptTcpClient();
        Console.WriteLine("Connection accepted!");
person Rasheed Abdul    schedule 15.09.2020