【问题标题】:how do i get TcpListener to accept multiple connections and work with each one individually?如何让 TcpListener 接受多个连接并单独处理每个连接?
【发布时间】:2011-07-17 10:08:16
【问题描述】:

我有一个运行良好但只能接收一个连接的 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();
        }
    }
}

【问题讨论】:

    标签: c# .net smtp tcplistener


    【解决方案1】:

    您可以将大部分代码分解到一个单独的线程中:

    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
    }
    

    【讨论】:

    • 为什么不用BeginAcceptTcpClient?在像这样一个非常简单的例子中没有必要,但如果有任何 GUI,异步 BeginAcceptTcpClient 将避免冻结。
    • 这一切都很好,但我正在处理二进制数据,而不是漂亮的字符串。如何告诉listner对象我想要byte[]数组形式的原始数据?
    • @BingBang 你什么都不告诉监听器对象。您从侦听器获取客户端,从客户端获取流,默认情况下为 byte[]。
    【解决方案2】:

    您几乎肯定希望将每个连接转入另一个线程。所以你有一个循环中的“接受”调用:

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

    显然,您需要调整生成线程的方式(可能使用线程池,也可能使用 TPL 等)以及如何优雅地停止侦听器。

    【讨论】:

    • 此解决方案将如何扩展?拥有两个线程是否谨慎 - 一个线程处理传入的请求,另一个线程遍历 hem 并处理它们?
    • @kacalapy:它在大多数情况下都可以很好地扩展,尽管您可能想要使用线程池。您不希望一个连接必须等待另一个连接完成后才能轮到。
    • @JonSkeet 你会推荐什么以获得最佳效果?使用像 ThePretender 回答这样的线程池?
    • @publicENEMY:实际上,如果可以的话,我通常会推荐使用任务并行库...
    • @SHM:你可以按照公认的答案使用线程池——或者你可以使用 NIO 和异步代码,但这会复杂得多。
    【解决方案3】:

    我知道这是个老问题,但我相信很多人会喜欢这个答案。

    // 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
    

    【讨论】:

    • 如果 HandleClient() 是异步的,因为在该函数中我们正在等待来自流读取器的 ReadLineAsync(),该怎么办?
    • 在 3) 我认为它应该是异步的,但没有等待,只是 HandleClient(client)
    • 要回答我之前的问题,我认为代码必须是 await HandleClient() 而不仅仅是 HandleClient()。 @MrHIDEn我认为您指的是火灾并且如果我没记错的话也忘记了?还是您的意思是删除HandleClient() 上方的await listener... 行?
    【解决方案4】:

    根据您的代码,您正在启动一个侦听器并接收和处理消息并关闭程序。

    您需要维护一个监听器,并且可以将 TcpClient 对象传递给另一个函数来处理接收到的消息。 listener.Start();

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

    【讨论】:

      猜你喜欢
      • 2017-08-19
      • 2013-10-23
      • 2012-01-16
      • 2011-06-28
      • 2016-06-18
      • 2012-10-18
      • 2011-06-11
      • 2013-11-24
      • 1970-01-01
      相关资源
      最近更新 更多