2011-03-17 109 views
11

我有一個SMTP偵聽器,運行良好,但只能夠接收一個連接。我的C#代碼在下面,我正在將它作爲服務運行。我的目標是讓它在服務器上運行並解析發送給它的多個smtp消息。我如何讓TcpListener接受多個連接並單獨使用每個連接?

目前它解析第一條消息並停止工作。我怎樣才能讓它接受第二,第三,第四... 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(); 
     } 
    } 
} 

回答

25

你可以將大部分代碼到一個單獨的線程:

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 
} 
+0

爲什麼不使用'BeginAcceptTcpClient'?在這樣一個非常簡單的例子中,它是沒有必要的,但如果有GUI,異步'BeginAcceptTcpClient'將會避免凍結。 – i486 2017-09-18 09:28:47

18

你幾乎肯定要分拆每一個連接到另一個線程。所以,你必須「接受」呼叫在一個循環:

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

顯然你要調整你如何產生線程(也許使用線程池,也許TPL等),以及如何停止監聽正常。

+0

該解決方案如何擴展?有兩個線程 - 一個線程來處理傳入的請求,另一個線程通過下襬並處理它們會是明智的嗎? – kacalapy 2011-03-17 16:24:26

+1

@kacalapy:在大多數情況下它可以很好地伸縮,儘管你可能想要使用線程池。您不希望一個連接在等待轉換之前不得不等待另一個連接完成處理。 – 2011-03-17 16:27:02

+0

@JonSkeet對於最佳結果,你會推薦什麼?使用線程池像ThePretender答案? – 2013-11-21 22:51:34

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 
+1

如果'HandleClient()'是異步的,因爲在那個函數中我們正在等待來自streamreader的'ReadLineAsync()'? – 2017-02-16 08:58:23