المجموعات والمستخدمون وإدارة الاتصالات
أديروا المجموعات، واستهدفوا مستخدمين أو اتصالات محددة، وتعاملوا مع أحداث دورة حياة الاتصال والانفصال.
المجموعات والمستخدمون وإدارة الاتصالات درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.
لماذا نستخدم المجموعات والمستخدمين؟
في التطبيقات العملية، نادرًا ما تريد بث الرسائل إلى جميع العملاء. تتيح لك المجموعات في SignalR استهداف مجموعات فرعية من الاتصالات، مثل غرفة محادثة، بينما تتيح لك المستخدمون مخاطبة جميع الاتصالات التابعة لمستخدم تمت مصادقته.
إضافة الاتصالات إلى المجموعات
استدعِ Groups.AddToGroupAsync() مع معرّف الاتصال واسم المجموعة. ويمكن أن ينتمي الاتصال إلى مجموعات متعددة في الوقت نفسه.
public class ChatHub : Hub
{
public async Task JoinRoom(string roomName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
await Clients.Group(roomName)
.SendAsync("UserJoined", Context.User!.Identity!.Name);
}
public async Task LeaveRoom(string roomName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
await Clients.Group(roomName)
.SendAsync("UserLeft", Context.User!.Identity!.Name);
}
}الإرسال إلى المجموعات
استخدم Clients.Group(name) للإرسال إلى جميع الاتصالات في مجموعة. وتشمل خيارات الاستهداف الأخرى GroupExcept وGroups، لإرسال الرسائل إلى مجموعات متعددة دفعة واحدة.
public async Task SendToRoom(string roomName, string message)
{
var sender = Context.User!.Identity!.Name;
// Everyone in the room
await Clients.Group(roomName)
.SendAsync("ReceiveMessage", sender, message);
// Everyone in the room except the sender
await Clients.GroupExcept(roomName, Context.ConnectionId)
.SendAsync("ReceiveMessage", sender, message);
}الاستهداف المستند إلى المستخدم
عندما يفتح المستخدم عدة علامات تبويب في المتصفح أو يستخدم عدة أجهزة، تكون لديه اتصالات متعددة. يرسل Clients.User(userId) الرسالة إلى جميع اتصالات ذلك المستخدم في الوقت نفسه.
// Send to all connections of user with ID "user-123"
await Clients.User("user-123")
.SendAsync("Notification", "Your order has shipped!");
// SignalR uses the NameIdentifier claim by default.
// Context.UserIdentifier returns the current user's ID.
var myId = Context.UserIdentifier;
await Clients.User(myId!).SendAsync("Ack", "Server received your message");موفّر معرّف مستخدم مخصص
تستخدم SignalR افتراضيًا مطالبة NameIdentifier باعتبارها معرّف المستخدم. نفّذ IUserIdProvider لاستخدام مطالبة أخرى أو منطق مخصص.
public class EmailUserIdProvider : IUserIdProvider
{
public string? GetUserId(HubConnectionContext connection)
{
return connection.User?.FindFirst(ClaimTypes.Email)?.Value;
}
}
// Register:
builder.Services.AddSingleton<IUserIdProvider, EmailUserIdProvider>();تتبّع حالة الاتصال
لا تحفظ SignalR حالة الاتصال بين مرات انقطاع الاتصال. أنشئ خريطة خاصة بك لمعرّفات المستخدمين ومعرّفات الاتصالات في قاموس آمن للاستخدام مع الخيوط، إذا كان الخادم واحدًا، أو في Redis، إذا كانت الخوادم متعددة.
public class ConnectionTracker
{
private readonly ConcurrentDictionary<string, HashSet<string>> _userConnections
= new();
public void AddConnection(string userId, string connectionId)
=> _userConnections.GetOrAdd(userId, _ => new()).Add(connectionId);
public void RemoveConnection(string userId, string connectionId)
{
if (_userConnections.TryGetValue(userId, out var conns))
{
conns.Remove(connectionId);
if (conns.Count == 0) _userConnections.TryRemove(userId, out _);
}
}
public IEnumerable<string> GetConnections(string userId)
=> _userConnections.TryGetValue(userId, out var c) ? c : Enumerable.Empty<string>();
}إدارة المجموعات تلقائيًا في OnConnected/OnDisconnected
من الأنماط الشائعة إضافة المستخدمين الذين تمت مصادقتهم تلقائيًا إلى مجموعتهم الشخصية في OnConnectedAsync، حتى تتمكن من استهدافهم باسم المجموعة دون تتبّع مخصص.
public override async Task OnConnectedAsync()
{
// Add to a personal group named by user ID
var userId = Context.UserIdentifier;
if (userId is not null)
await Groups.AddToGroupAsync(Context.ConnectionId, userId);
await base.OnConnectedAsync();
}
// Now send to a user by their personal group:
await _hub.Clients.Group(userId)
.SendAsync("Update", payload);الإرسال من خدمات الخلفية باستخدام المجموعات
باستخدام IHubContext، يمكنك استهداف المجموعات والمستخدمين من خدمات الخلفية، وهو أمر مثالي للإشعارات غير المتزامنة التي تطلقها الأحداث في نظامك.
public class OrderShippedHandler : INotificationHandler<OrderShipped>
{
private readonly IHubContext<OrderHub> _hub;
public OrderShippedHandler(IHubContext<OrderHub> hub) => _hub = hub;
public async Task Handle(OrderShipped notification, CancellationToken ct)
{
// Push to the customer's personal group
await _hub.Clients.Group(notification.CustomerId.ToString())
.SendAsync("OrderShipped",
new { notification.OrderId, notification.TrackingNumber },
ct);
}
}استمرارية عضوية المجموعات
لا تُحفَظ عضوية المجموعة — فإذا أعيد تشغيل الخادم أو فقد ناقل Redis الخلفي حالته، تصبح المجموعات فارغة. أعيدوا الانضمام إلى المجموعات عند إعادة الاتصال باستخدام حدث العميل onreconnected.
// JavaScript client: re-join groups after reconnect
connection.onreconnected(async connectionId => {
console.log(`Reconnected as ${connectionId}`);
// Re-join previously joined rooms
for (const room of joinedRooms) {
await connection.invoke("JoinRoom", room);
}
});الحضور: اكتشاف الاتصال وعدم الاتصال
تتبّعوا حالة الاتصال من خلال عدّ الاتصالات لكل مستخدم في OnConnected/OnDisconnected، وبثّ أحداث الحضور إلى المجموعات المعنية.
public override async Task OnConnectedAsync()
{
var userId = Context.UserIdentifier!;
var count = _tracker.AddConnection(userId, Context.ConnectionId);
if (count == 1) // first connection for this user
await Clients.Others.SendAsync("UserOnline", userId);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? ex)
{
var userId = Context.UserIdentifier!;
var count = _tracker.RemoveConnection(userId, Context.ConnectionId);
if (count == 0) // no more connections
await Clients.Others.SendAsync("UserOffline", userId);
await base.OnDisconnectedAsync(ex);
}تحقق سريع
عندما يفتح أحد المستخدمين ثلاث علامات تبويب في المتصفح، ماذا تفعل Clients.User(userId)؟
مراجعة: المجموعات والمستخدمون وإدارة الاتصالات
أهم النقاط:
Groups.AddToGroupAsync/RemoveFromGroupAsync: إدارة عضوية المجموعاتClients.Group(name): الإرسال إلى مجموعة فرعية من الاتصالاتClients.User(userId): الإرسال إلى جميع اتصالات مستخدم- طبّقوا
IUserIdProviderلاستخدام مطالبات مخصصة لمعرّف المستخدم - لا تُحفَظ المجموعات — أعيدوا الانضمام إليها عند إعادة الاتصال
- تتبّعوا أعداد الاتصالات في ConcurrentDictionary لاكتشاف الحضور
الأسئلة الشائعة
هل درس «المجموعات والمستخدمون وإدارة الاتصالات» مجاني؟
نعم — نص درس «المجموعات والمستخدمون وإدارة الاتصالات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.
ماذا ستتعلم في «المجموعات والمستخدمون وإدارة الاتصالات»؟
أديروا المجموعات، واستهدفوا مستخدمين أو اتصالات محددة، وتعاملوا مع أحداث دورة حياة الاتصال والانفصال. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟
لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «المجموعات والمستخدمون وإدارة الاتصالات»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟
نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- مراكز SignalR والاتصالات
- المجموعات والمستخدمون وإدارة الاتصالات
- المراكز محدّدة الأنواع
- التوسّع باستخدام Redis Backplane