How to send acknowledgment (Consumer) in RabbitMQ externally? - c#

I have an application where it sending message to RMQ broker as below:
var connectionFactory = new ConnectionFactory()
{
HostName = "localhost"
};
using (var connection = connectionFactory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare("demo",
durable:true,
exclusive:false,
autoDelete:false,
arguments:null);
Console.WriteLine("Click enters to send random case Id");
do
{
Console.ReadLine();
var message = new {CaseId = new Random().Next()};
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
channel.BasicPublish("", "demo", null, body);
Console.WriteLine("Successfully send message.");
} while (true);
}
}
It successfully sent the message.
There is another app called the consumer app.
The code is below:
private void InitiateRabbitMq()
{
var connectionFactory = new ConnectionFactory()
{
HostName = "localhost"
};
var connection = connectionFactory.CreateConnection();
var channel = connection.CreateModel();
MessageHandler messageReceiver = new MessageHandler(channel);
channel.BasicConsume("demo", false, messageReceiver);
}
The message handler is:
public class MessageHandler : DefaultBasicConsumer
{
private readonly IModel _channel;
public MessageHandler(IModel channel)
{
_channel = channel;
}
public override async void HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey,
IBasicProperties properties, ReadOnlyMemory<byte> body)
{
var message = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(body.ToArray()));
_processor.process(message);
}
}
Here is the process method parth which is another class:
client.BeginTransfer(transfer,
HandleUploadProgressChanged,
HandleUploadComplete,
HandleUploadInterrupted,
HandleUploadCancelled,
3600,
UploadFilesFinishedCallback);
Once begin transfer completed. It invoked UploadFilesFinishedCallback . I want to acknowledge in this method. How can I do it?

This is how you Acknowledge message:
channel.BasicAck(deliveryTag, false);
So it means your Function "UploadFilesFinishedCallback" must have the deliveryTag
==> and this means that also your "process" function must have the deliveryTag (which currently gets only the message content)
solution:
add new parameter "deliveryTag" to function "process", and to function "UploadFilesFinishedCallback"
you can use it in the callback like this:
client.BeginTransfer(transfer,
HandleUploadProgressChanged,
HandleUploadComplete,
HandleUploadInterrupted,
HandleUploadCancelled,
3600,
() => { UploadFilesFinishedCallback(deliveryTag) });
(depends on the signature of the callback function)

Related

C# UdpClient.Connect() raises exception on 2nd call

I tried to send form a single UdpClient to several different open UDP sockets from localhost to localhost. However in the first version only the first message of the loop was sent, the rest did not even make it out.
When trying to isolate the error the second call to udpClient.Connect() raises a SocketException pointing to the Error WSAEISCONN 10056
The official documentation of UdpClient.Connect() here states in the remark section if you want to send to different endpoints, call Connect again. However this is the opposite of what the error tells me.
So is this just an error in the docs of the Connect methode, or do I miss something here?
Too fast requests should not be an issue with only 3 requests every 3 Seconds and as I use the normal Send(buffer) call, there should be no packets waiting to be sent to the previous endpoint.
Simple example to reproduce (I used .net 6, Win10):
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace UdpClientIssue;
internal class Program
{
static void Main(string[] args)
{
var sender = CreateSender(3_000, 40001, 40002, 40003);
var reciverA = CreateReciver("ReciverA", 40001);
var reciverB = CreateReciver("ReciverB", 40002);
var reciverC = CreateReciver("ReciverC", 40003);
reciverA.Start();
reciverB.Start();
reciverC.Start();
sender.Start();
sender.Join();
}
static void Reciver(int port)
{
var localEP = new IPEndPoint(IPAddress.Loopback, port);
using var listener = new UdpClient(localEP);
var buff = new byte[1024];
while (true)
{
var senderEP = new IPEndPoint(IPAddress.None, 0);
var data = listener.Receive(ref senderEP);
var message = Encoding.UTF8.GetString(data);
Console.WriteLine($"Recived at {port}: {message}");
}
}
static void Sender(int interval_ms, params int[] ports)
{
int messageNumber = 0;
while (true)
{
Thread.Sleep(interval_ms);
using (var udpClient = new UdpClient())
{
foreach (var remotePort in ports)
{
var message = $"Message {++messageNumber} to {remotePort}";
var sendBuffer = Encoding.UTF8.GetBytes(message);
Console.WriteLine($"Sending to {remotePort}: {message}");
var remoteEP = new IPEndPoint(IPAddress.Loopback, remotePort);
//This errors on second iteration
//udpClient.Connect(remoteEP);
//udpClient.Send(sendBuffer);
//This works
udpClient.Send(sendBuffer, remoteEP);
}
}
messageNumber += 100 - ports.Length;
}
}
static Thread CreateReciver(string name, int port)
{
var ts = new ThreadStart(() => Reciver(port));
var t = new Thread(ts)
{
Name = name
};
return t;
}
static Thread CreateSender(int interval_ms, params int[] ports)
{
var ts = new ThreadStart(() => Sender(interval_ms, ports));
var t = new Thread(ts)
{
Name = "Sender"
};
return t;
}
}
I believe it's error in the docs. Description of this error message says:
Socket is already connected.
A connect request was made on an already-connected socket
And source code explicitly throws this exception with this error code if socket was already connected.
Here you see it just forwards connect to Socket:
public void Connect(IPEndPoint endPoint)
{
ThrowIfDisposed();
ArgumentNullException.ThrowIfNull(endPoint);
CheckForBroadcast(endPoint.Address);
Client.Connect(endPoint);
_active = true;
}
And in Socket.Connect you see it throws this errors if socket is already connected:
if (_isConnected)
{
throw new SocketException((int)SocketError.IsConnected);
}
Fortunately you already know the better way of doing this - just pass remote endpoint to Send call. UDP is connectionless protocol anyway so calling Connect only sets default remote endpoint and doesn't perform actual connection.

Discord.net send a message through console

use the console (type things into it) in order to send a message to a sepcified channel id , below is the closest i've gotten.
public static async Task sendMessage()
{
string message;
string idString;
ulong id;
Console.WriteLine("message plz");
message = Console.ReadLine();
Console.WriteLine("channel plz");
idString = Console.ReadLine();
id = Convert.ToUInt64(idString);
DiscordSocketClient _client = new DiscordSocketClient(); // 2
ITextChannel channel = (ITextChannel)_client.GetChannel(id); // 4
await channel.SendMessageAsync(message);
}
some of it is copy pasted from the internet, some isn't. the problem i found through debugging is the channel does not get set from ITextChannel channel = (ITextChannel)_client.GetChannel(id); // 4

Why doesn't IServerStreamWriter send notification responses?

I work on a cross-platform application. For connection between them, I use gRPC technology. When a client connects to server, it is added to an observers list located in server implementation. When a client connects, I want to send a message to the rest of connected clients telling them that a new client connected. The problem is that when I want to send a response to clients that a new client connected, using the observers from my list, I get the following exception:
Grpc.Core.RpcException: 'Status(StatusCode=Unknown, Detail="Exception was thrown by handler.")'
This is my proto file where I declared my server:
syntax = "proto3";
package com.example.grpc.chat;
message ChatMessage {
string from = 1;
string message = 2;
}
message ChatMessageFromServer {
ChatMessage message = 2;
}
service ChatService {
rpc Login(ChatMessage ) returns (stream ChatMessageFromServer);
}
The server code :
public class ChatServiceImpl : ChatService.ChatServiceBase
{
private static HashSet<IServerStreamWriter<ChatMessageFromServer>> responseStreams = new HashSet<IServerStreamWriter<ChatMessageFromServer>>();
/*
* if the stream object (from "for" statement inside this method) isn't the responseStream object given in the list with parameters,
* the rest of clients aren't notified when a new login request is pushed.
*/
public override async Task Login(global::Com.Example.Grpc.Chat.ChatMessage request,
IServerStreamWriter<global::Com.Example.Grpc.Chat.ChatMessageFromServer> responseStream,
ServerCallContext context)
{
Console.WriteLine("Login method from server");
responseStreams.Add(responseStream);
// Create a server message that wraps the client message
var message = new ChatMessageFromServer
{
Message = new ChatMessage
{
From = "login",
Message = "hello"
}
};
// If stream variable isn't equal to responseStream from list of parameters, the client corresponding to that stream isn't notified and it's thrown the above exception
foreach (var stream in responseStreams)
{
await stream.WriteAsync(message);
}
}
}
The client code where the client send a login request:
public partial class ChatForm : Form
{
private const string Host = "localhost";
private const int Port = 9090;
private ChatService.ChatServiceClient _chatService;
public ChatForm()
{
//InitializeComponent();
InitializeGrpc();
}
private void InitializeGrpc()
{
// Create a channel
var channel = new Channel(Host + ":" + Port, ChannelCredentials.Insecure);
// Create a client with the channel
_chatService = new ChatService.ChatServiceClient(channel);
}
private async void ChatForm_Load(object sender, EventArgs e)
{
var message = new ChatMessage
{
From = "Unknown",
Message = "Login text"
};
// Open a connection to the server
try
{
using (var call = _chatService.Login(message))
{
// Read messages from the response stream
while (await call.ResponseStream.MoveNext(CancellationToken.None))
{
var serverMessage = call.ResponseStream.Current;
var otherClientMessage = serverMessage.Message;
var displayMessage = string.Format("{0}:{1}{2}", otherClientMessage.From, otherClientMessage.Message, Environment.NewLine);
chatTextBox.Text += displayMessage;
}
}
}
catch (RpcException )
{
throw;
}
}
}
Your notifyObservers method is asynchronous but has a void return type, which means you can't await it. You're effectively starting the method, and returning as soon as you hit the first await operator that uses an incomplete awaitable (the first WriteAsync call, probably).
You then return the task with a ReservationResponse, and the operation completes.
When that first awaitable call completes, notifyObservers will continue, but at that point the operation has already completed, so when you try to write to the response stream, the system will throw the error you're seeing.
I strongly suspect you should return a Task from notifyObservers and await that from your main entry method:
// Names changed to be conventional C#
public override async Task<ReservationResponse> SaveReservation(
global::Res.Protocol.ReservationRequest request, ServerCallContext context)
{
// some code for saving my reservation in repository database
ReservationResponse response = new Res.Protocol.ReservationResponse
{
Type = ReservationResponse.Types.Type.Savereservation,
Journey = GetProtoJourney(journey)
};
await NotifyObserversAsync(response);
// Note: no Task.FromResult, as you're in an async method. The response
// will already be wrapped in a task.
return new ReservationResponse
{
Type = ReservationResponse.Types.Type.Savereservation
};
}
public async Task NotifyObserversAsync(Res.Protocol.ReservationResponse response)
{
foreach (var ob in responseStreams)
{
await ob.WriteAsync(response);
}
}

Long running RabbitMQ connection in ASP.NET

I know that establishing a RabbitMQ connection is expensive, so it is not recommended to {connect / publish / disconnect} whenever I want to put a message to queue. However, I couldn' t find a way to have a long running RabbitMQ connection across many requests.
What I want to achieve is to have only one(or a limited # of) RabbitMQ connection(s) in my ASP.NET application that runs on IIS and use it for different requests, instead of creating different connections for each request.
The following code is what I have up to now. I need somehow to remove these using statements and keep my connection open.
Thanks a lot
public ReturnCode AtomicPublish(string userName, string password, string virtualHost, string hostName, string exchangeName, string queueName, string message)
{
using (IConnection conn = new ConnectionFactory()
{
UserName = userName,
Password = password,
VirtualHost = virtualHost,
HostName = hostName
}.CreateConnection())
{
using (IModel model = conn.CreateModel())
{
model.ExchangeDeclare(exchangeName, ExchangeType.Fanout, true);
model.QueueDeclare(queueName, true, false, false, null);
model.QueueBind(queueName, exchangeName, "", null);
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes(message);
model.BasicPublish(exchangeName, string.Empty, null, messageBodyBytes);
}
}
return ReturnCode.OK;
}
you should use server side storage for your variable connection,
if connection is same for all requests of same user save it in the Session objerct of ASP.NET,
if it is same for all users save it in the Cache application object.
then when you need to load it again and re-use it, get it back from Session or Cache, see here an example: Application vs Session vs Cache
off the top of my head.
private static IConnection _connection {get;set;}
private static object LockObject = new object();
private static IConnection GetConnection (string username, string password, string virtualHost, string hostName)
get{
// do work here in case the connection is closed as well.
if (_connection == null){
lock(LockObject){
if (_connection == null){
_connection = new ConnectionFactory
{
UserName = userName,
Password = password,
VirtualHost = virtualHost,
HostName = hostName
}.CreateConnection();
}
}
}
return _connection;
}
}
public ReturnCode AtomicPublish(string userName, string password, string virtualHost, string hostName, string exchangeName, string queueName, string message)
{
using (IModel model = GetConnection(userName, password, virtualHost, hostName).CreateModel()) //lazy loads the get connection
{
model.ExchangeDeclare(exchangeName, ExchangeType.Fanout, true);
model.QueueDeclare(queueName, true, false, false, null);
model.QueueBind(queueName, exchangeName, "", null);
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes(message);
model.BasicPublish(exchangeName, string.Empty, null, messageBodyBytes);
}
return ReturnCode.OK;
}
Like you said, establishing a RabbitMQ connection is expensive, so when you open a connection you shouldn't send a message and disconnect.
In fact, RabbitMQ is based on the protocole AMQP which gives the possibility to send many messages to many receivers in different topics(instead of using queues), which means that every receiver is listening in a different topic like this exemple :
using System;
using System.Linq;
using RabbitMQ.Client;
using System.Text;
class EmitLogTopic
{
public static void Main(string[] args)
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using(var connection = factory.CreateConnection())
using(var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "topic_logs",
type: "topic");
var routingKey = (args.Length > 0) ? args[0] : "anonymous.info";
var message = (args.Length > 1)
? string.Join(" ", args.Skip( 1 ).ToArray())
: "Hello World!";
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "topic_logs",
routingKey: routingKey,
basicProperties: null,
body: body);
Console.WriteLine(" [x] Sent '{0}':'{1}'", routingKey, message);
}
}
}
this link provides a tutorial for more informations.

RabbitMQ and SharedQueue closed

Im using RabbitMQ to send simple short int information, first I'm sending id to one project like that:
private void SendPgcIdToRabbitMQ(string id)
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
bool durable = true;
channel.QueueDeclare("XQueue", durable, false, false, null);
var body = Encoding.UTF8.GetBytes(id);
channel.BasicPublish("", "XQueue", null, body);
Console.WriteLine(" [x] Sent {0}", id);
}
}
}
and listener of it:
public void Listener()
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare("XQueue", true, false, false, null);
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume("XQueue", false, consumer);
Console.WriteLine(" [*] Waiting for messages. " +
"To exit press CTRL+C");
while (true) {
var ea =
(BasicDeliverEventArgs)consumer.Queue.Dequeue();
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] Received {0}", message);
AddPGCFileID(message);
channel.BasicAck(ea.DeliveryTag, false);
Thread.Sleep(500);
}
}
}
}
It works fine, so after receiving message I'm processing some operation wit it, then I get second ID and create other queue to do this same:
private void SendSurveyIdToRabbitMQ(int yID)
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection()) {
using (var channel = connection.CreateModel()) {
bool durable = true;
channel.QueueDeclare("YQueue", durable, false, false, null);
var body = Encoding.UTF8.GetBytes(yID.ToString());
channel.BasicPublish("", "YQueue", null, body);
Console.WriteLine(" [x] Sent {0}", yID);
}
}
}
and receive:
public void InquiryListener()
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection()) {
using (var channel = connection.CreateModel()) {
channel.QueueDeclare("YQueue", true, false, false, null);
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume("YQueue", false, consumer);
Console.WriteLine(" [*] Waiting for messages. " +
"To exit press CTRL+C");
while (true) {
var ea =
(BasicDeliverEventArgs)consumer.Queue.Dequeue();
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] Received {0}", message);
StartProcessing(Convert.ToInt32(message));
channel.BasicAck(ea.DeliveryTag, false);
Thread.Sleep(500);
}
}
}
}
First queue sending and receiving works fine but at second I get:
It is strange because it was working that way, from some time I'm geting this problem. I whas reseting rabbitmq, removin all queues etc. can't find where is a problem. Any ideas?
edit:
I whas debuging to know if second process is ending properly (eariel crash on second proces don't cause problem with rabbitmq) and it passed, I whas supriced because no error ocurs on YQueue, but after about minute of working my process (only waiting, non incomming message, non processing) I gey this same exception on XQueue
Check first if the queue is empty before executing while(true){ ... }.

Categories

Resources