Reputation: 10164
I have an SMTP listener that works well but is only able to receive one connection. My C# code is below and I am running it as a service. My goal is to have it runnign on a server and parsing multiple smtp messages sent to it.
currently it parses the first message and stops working. how can I get it to accept the 2nd, 3rd, 4th... SMTP message and process it like it does the first?
here is my code:
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();
}
}
}
Upvotes: 29
Views: 63121
Reputation: 36080
Late answer because this question did not had the answer that I was looking for. Here is what I was looking for:
using System.Net.Sockets;
class Program
{
static void Main()
{
var server = new TcpListener(System.Net.IPAddress.Any, 80);
server.Start();
// Wait for connection...
server.BeginAcceptTcpClient(OnClientConnecting, server);
Console.ReadLine();
}
static void OnClientConnecting(IAsyncResult ar)
{
try
{
Console.WriteLine("Client connecting...");
if (ar.AsyncState is null)
throw new Exception("AsyncState is null. Pass it as an argument to BeginAcceptSocket method");
// Get the server. This was passed as an argument to BeginAcceptSocket method
TcpListener s = (TcpListener)ar.AsyncState;
// listen for more clients. Note its callback is this same method (recusive call)
s.BeginAcceptTcpClient(OnClientConnecting, s);
// Get the client that is connecting to this server
using TcpClient client = s.EndAcceptTcpClient(ar);
Console.WriteLine("Client connected succesfully");
// read data sent to this server by client that just connected
byte[] buffer = new byte[1024];
var i = client.Client.Receive(buffer);
Console.WriteLine($"Received {i} bytes from client");
// reply back the same data that was received to the client
var k = client.Client.Send(buffer, 0, i, SocketFlags.None);
Console.WriteLine($"Sent {k} bytes to slient as reply");
// close the tcp connection
client.Close();
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
}
Then when I make a request to http://localhost/foo
on my browser I get this:
GET /test HTTP/1.1
Host: localhost
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="106", "Google Chrome";v="106", "Not;A=Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,es;q=0.8
Upvotes: 2
Reputation: 49
As per your code, you are starting one listener and receiving and processing message and closing program.
You need to maintain a listener and TcpClient object can be passed to another function to process the message received. listener.Start();
Console.WriteLine("Awaiting connection...");
client = listener.AcceptTcpClient();
Console.WriteLine("Connection accepted!");
Upvotes: 1
Reputation: 1887
I know this is old question but I am sure many will like this answer.
// 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
Upvotes: 8
Reputation: 1274
You can factor out most of your code into a separate thread:
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
}
Upvotes: 42
Reputation: 1503280
You almost certainly want to spin each connection into another thread. So you have the "accept" call in a loop:
while (listening)
{
TcpClient client = listener.AcceptTcpClient();
// Start a thread to handle this client...
new Thread(() => HandleClient(client)).Start();
}
Obviously you'll want to adjust how you spawn threads (maybe use the thread pool, maybe TPL etc) and how you stop the listener gracefully.
Upvotes: 28