Wyślij wiadomość do określonego klienta za pomocą socket.io i node.js

191

Pracuję z socket.io i node.js i do tej pory wydaje się całkiem niezły, ale nie wiem, jak wysłać wiadomość z serwera do określonego klienta, coś takiego:

client.send(message, receiverSessionId)

Ale ani metody .send()ani nie .broadcast()wydają się zaspokajać mojej potrzeby.

To, co znalazłem jako możliwe rozwiązanie, polega na tym, że .broadcast()metoda przyjmuje jako drugi parametr tablicę SessionIds, do której nie wysyła komunikatu, więc mogę przekazać tablicę ze wszystkimi SessionIds podłączonymi w tym momencie do serwera, z wyjątkiem jednego Chciałbym wysłać wiadomość, ale uważam, że musi być lepsze rozwiązanie.

Jakieś pomysły?

Rodolfo Palma
źródło

Odpowiedzi:

95

Cóż, musisz złapać za to klienta (niespodzianka), możesz albo iść prostą drogą:

var io = io.listen(server);
io.clients[sessionID].send()

Co może się zepsuć, wątpię w to, ale zawsze jest to możliwość, która io.clientsmoże ulec zmianie, więc używaj powyższego ostrożnie

Lub sam śledzisz klientów, dlatego dodajesz je do własnego clientsobiektu w connectiondetektorze i usuwasz w disconnectdetektorze.

Chciałbym użyć tego drugiego, ponieważ w zależności od aplikacji możesz chcieć mieć więcej stanu na klientach, więc coś w tym stylu clients[id] = {conn: clientConnect, data: {...}}może wykonać zadanie.

Ivo Wetzel
źródło
6
Ivo, czy możesz wskazać bardziej kompletny przykład lub nieco rozwinąć? Chcę zrozumieć to podejście, ale nie jestem pewien, czy rozpoznaję zmienne / obiekty, których używasz w tym przykładzie. W klientach [id] = {conn: clientConnect, data: {...}}, czy klienci [id] są częścią obiektu io, jak widać w io.clients [sessionID] powyżej? Czym jest także obiekt clientConnect? Dzięki.
AndrewHenderson
@ ivo-wetzel cześć, czy mógłbyś mi pomóc w tym temacie? stackoverflow.com/questions/38817680/…
mahdi pishguy,
178

Odpowiedź Ivo Wetzela nie wydaje się już aktualna w Socket.io 0.9.

Krótko mówiąc, musisz teraz zapisać socket.idi użyć, io.sockets.socket(savedSocketId).emit(...) aby wysłać do niego wiadomości.

Oto jak to działa na klastrowanym serwerze Node.js:

Najpierw musisz ustawić sklep Redis jako sklep, aby wiadomości mogły przechodzić między procesami:

var express = require("express");
var redis = require("redis");
var sio = require("socket.io");

var client = redis.createClient()
var app = express.createServer();
var io = sio.listen(app);

io.set("store", new sio.RedisStore);


// In this example we have one master client socket 
// that receives messages from others.

io.sockets.on('connection', function(socket) {

  // Promote this socket as master
  socket.on("I'm the master", function() {

    // Save the socket id to Redis so that all processes can access it.
    client.set("mastersocket", socket.id, function(err) {
      if (err) throw err;
      console.log("Master socket is now" + socket.id);
    });
  });

  socket.on("message to master", function(msg) {

    // Fetch the socket id from Redis
    client.get("mastersocket", function(err, socketId) {
      if (err) throw err;
      io.sockets.socket(socketId).emit(msg);
    });
  });

});

Pominąłem tutaj kod grupowania, ponieważ sprawia, że ​​jest on bardziej zagracony, ale dodawanie go jest trywialne. Po prostu dodaj wszystko do kodu roboczego. Więcej dokumentów tutaj http://nodejs.org/api/cluster.html

Epeli
źródło
4
Dzięki, że było pomocne. Po prostu musiałem zamiast tego użyć tablicy: io.of('/mynamespace').sockets[socketID].emit(...)(nie wiem, czy to dlatego, że używam przestrzeni nazw)
Adrien Schuler
w środowisku klastrowym, jak mogę się upewnić, że poprawny proces, do którego należy gniazdo, wysyła komunikat?
Gal Ben-Haim
A może lepka sesja dzięki uprzejmości NGINX lub HAProxy @Gal Ben-Haim?
matanster
var holder = new socketio.RedisStore; ^ Błąd typu: niezdefiniowany nie jest funkcją w obiekcie. <anonimowy> (C: \ Users \ Dev \ Desktop \ nouty-server \ server.js: 108: 14) w Module._compile (module.js: 460: 26) o Object.Module._extensions..js (module.js: 478: 10) w Module.load (module.js: 355: 32) w Function.Module._load (module.js: 310: 12) w Function.Module. runMain (module.js: 501: 10) przy uruchomieniu (node.js: 129: 16) w node.js: 814: 3
Lucas Bertollo
1
io.sockets.socket(socket_id)jest usunięty w socket.io 1.0. github.com/socketio/socket.io/issues/1618#issuecomment-46151246
ImMathan
98

każde gniazdo dołącza do pokoju z identyfikatorem gniazda dla nazwy, więc możesz po prostu

io.to(socket#id).emit('hey')

docs: http://socket.io/docs/rooms-and-namespaces/#default-room

Twoje zdrowie

Matic Kogovšek
źródło
7
To najlepsza odpowiedź i działa z nowszymi wersjami socket.io. Tutaj jest ładna ściągawka: stackoverflow.com/questions/10058226/…
pobłogosławiony
4
zwróć uwagę, że jest to „emisja” emitująca zdarzenie. więc jeśli spróbujesz ustawić oddzwonienie, wystąpi błąd. jeśli chcesz wysłać zdarzenie do określonego gniazda z wywołaniem zwrotnym, użyj odpowiedzi @ PHPthinking i użyj io.sockets.connected[socketid].emit();. Testowane z 1.4.6.
tbutcaru
Ale dostałem ten błąd: io.to [„JgCoFX9AiCND_ZhdAAAC”]. Emit („socketFromServe‌ r”, informacje); ^ TypeError: Nie można odczytać właściwości „emit” niezdefiniowanej
Raz
85

Najprostsze, najbardziej eleganckie rozwiązanie

To tak proste jak:

client.emit("your message");

I to wszystko.

Ale jak? Daj mi przykład

To, czego wszyscy potrzebujemy, to w rzeczywistości pełny przykład i oto co następuje. Jest to testowane z najnowszą wersją socket.io (2.0.3), a także korzysta z nowoczesnego Javascript (z którego wszyscy powinniśmy teraz korzystać).

Przykład składa się z dwóch części: serwera i klienta. Za każdym razem, gdy klient łączy się, zaczyna otrzymywać od serwera okresowy numer sekwencyjny. Dla każdego nowego klienta uruchamiana jest nowa sekwencja, więc serwer musi śledzić ich indywidualnie. Właśnie wtedy pojawia się „Muszę wysłać wiadomość do konkretnego klienta” . Kod jest bardzo prosty do zrozumienia. Zobaczmy.

serwer

server.js

const
    io = require("socket.io"),
    server = io.listen(8000);

let
    sequenceNumberByClient = new Map();

// event fired every time a new client connects:
server.on("connection", (socket) => {
    console.info(`Client connected [id=${socket.id}]`);
    // initialize this client's sequence number
    sequenceNumberByClient.set(socket, 1);

    // when socket disconnects, remove it from the list:
    socket.on("disconnect", () => {
        sequenceNumberByClient.delete(socket);
        console.info(`Client gone [id=${socket.id}]`);
    });
});

// sends each client its current sequence number
setInterval(() => {
    for (const [client, sequenceNumber] of sequenceNumberByClient.entries()) {
        client.emit("seq-num", sequenceNumber);
        sequenceNumberByClient.set(client, sequenceNumber + 1);
    }
}, 1000);

Serwer zaczyna nasłuchiwać na porcie 8000 dla połączeń przychodzących. Kiedy ktoś przybywa, dodaje nowego klienta do mapy, aby mógł śledzić swój numer sekwencyjny. Nasłuchuje również disconnectzdarzenia tego klienta , kiedy usunie je z mapy.

Co sekundę uruchamiany jest stoper. Kiedy to nastąpi, serwer przechodzi przez mapę i wysyła wiadomość do każdego klienta z bieżącym numerem sekwencyjnym. Następnie zwiększa ją i zapisuje z powrotem na mapie. To wszystko. Bułka z masłem.

Klient

Część klienta jest jeszcze prostsza. Po prostu łączy się z serwerem i nasłuchuje seq-numwiadomości, drukując ją na konsoli za każdym razem, gdy przybywa.

client.js

const
    io = require("socket.io-client"),
    ioClient = io.connect("http://localhost:8000");

ioClient.on("seq-num", (msg) => console.info(msg));

Uruchamianie przykładu

Zainstaluj wymagane biblioteki:

npm install socket.io
npm install socket.io-client

Uruchom serwer:

node server

Otwórz inne okna terminali i spawnuj tyle klientów, ile chcesz, uruchamiając:

node client

Przygotowałem także listę z pełnym kodem tutaj .

Lucio Paiva
źródło
Czy port 8000 jest normą dla socketio w produkcji?
Czerwony
1
Przepraszam, że tak długo odpowiedziałem, @ingo. 8000 jest powszechnym portem używanym przez społeczność Node podczas testowania zarówno gniazd WWW, jak i serwerów HTTP (3000 jest również powszechny). Oczywiście możesz go użyć w produkcji, ale nie jestem pewien, jak często to jest ... w każdym razie możesz po prostu użyć dowolnego portu, pod warunkiem, że twoje bramy / równoważenia obciążenia / etc są na to przygotowane.
Lucio Paiva,
Jestem programistą Python / PHP i jestem nowy w gniazdach i węzłach, pytanie brzmi: dlaczego potrzebujemy zwiększać numer sekwencyjny tego samego użytkownika co sekundę? czy to tylko na demo?
Umair,
1
@Umair służy wyłącznie do celów demonstracyjnych, nie ma potrzeby zwiększania numeru sekwencji. Chodziło tylko o pokazanie, że możesz nadal wysyłać rzeczy do każdego klienta, a oni to otrzymają.
Lucio Paiva,
W tym przykładzie nie widzę, jak określony klient wysyła tylko wiadomość i tylko do innego klienta. Każdy klient zna tylko swój własny numer sekwencyjny, tak jak go nazwałeś i utworzyłeś na potrzeby wersji demonstracyjnej, i nie ma map z tych numerów sekwencyjnych na identyfikator gniazda, który jest potrzebny do wysłania wiadomości bezpośrednio do klienta o tym identyfikatorze gniazda.
Selçuk,
36

Możesz użyć

// wyślij wiadomość tylko do nadawcy-klienta

socket.emit('message', 'check this');

// lub możesz wysłać do wszystkich słuchaczy, w tym do nadawcy

io.emit('message', 'check this');

// wysyłanie do wszystkich słuchaczy oprócz nadawcy

socket.broadcast.emit('message', 'this is a message');

// lub możesz wysłać go do pokoju

socket.broadcast.to('chatroom').emit('message', 'this is the message to all');

DekoderNT
źródło
Pracowałem dla mnie, musiałem również dodać „const socket = wymagany ('socket.io') (http);” w moim pliku server.js.
iPzard
36

W wersji 1.0 należy użyć:

io.sockets.connected[socketid].emit();
PHPthinking
źródło
1
Tak !!!, poprzednio io.sockets.sockets [socketid] .emit () działało, ale dało mi to niezdefiniowane błędy obiektowe w nowszej wersji socket.io. Zmiana na io.sockets.connected działa.
Fraggle
Dla tych, którzy używają TypeScript, jest to obecnie „kanoniczny” interfejs API do tego, zgodnie z typami.
Avi Cherry
Ale pojawia się błąd: io.sockets.connected [„JgCoFX9AiCND_ZhdAAAC”]. Emit („socketFromServer”, informacje); ^ TypeError: Nie można odczytać właściwości „emit” niezdefiniowanej
Raz
wynika to prawdopodobnie z faktu, że interfejs API został zaktualizowany i nie ma już obiektu takiego jak io.sockets.connected["something"]i jego emitmetody.
Selçuk
12

Bez względu na to, jakiej wersji używamy, po prostu console.log () obiekt „io”, którego używamy w naszym kodzie nodejs po stronie serwera, [np. Io.on ('connection', function (socket) {...});] , widzimy, że „io” jest tylko obiektem json i istnieje wiele obiektów potomnych, w których przechowywane są id gniazda i obiekty gniazda.

Używam socket.io w wersji 1.3.5, btw.

Jeśli spojrzymy na obiekt io, zawiera on,

 sockets:
  { name: '/',
    server: [Circular],
    sockets: [ [Object], [Object] ],
    connected:
     { B5AC9w0sYmOGWe4fAAAA: [Object],
       'hWzf97fmU-TIwwzWAAAB': [Object] },

tutaj możemy zobaczyć gniazda „B5AC9w0sYmOGWe4fAAAA” itd. Więc możemy to zrobić,

io.sockets.connected[socketid].emit();

Ponownie, po dalszej inspekcji możemy zobaczyć takie segmenty,

 eio:
  { clients:
     { B5AC9w0sYmOGWe4fAAAA: [Object],
       'hWzf97fmU-TIwwzWAAAB': [Object] },

Możemy więc stąd pobrać gniazdo

io.eio.clients[socketid].emit();

Poza tym pod silnikiem mamy

engine:
 { clients:
    { B5AC9w0sYmOGWe4fAAAA: [Object],
      'hWzf97fmU-TIwwzWAAAB': [Object] },

Możemy więc pisać

io.engine.clients[socketid].emit();

Myślę, że możemy osiągnąć nasz cel na jeden z 3 sposobów wymienionych powyżej,

  1. io.sockets.connected [socketid] .emit (); LUB
  2. io.eio.clients [socketid] .emit (); LUB
  3. io.engine.clients [socketid] .emit ();
Suman Barick
źródło
Ale dostałem ten błąd: io.eio.clients [„JgCoFX9AiCND_ZhdAAAC”]. Emit („socketFromServer”, informacje); ^ TypeError: Nie można odczytać właściwości „emit” niezdefiniowanej
Raz
Obecnie (z wersją 2.1.1) działało to tylko w przypadku gniazdek połączonych, ale nie pozostałych dwóch.
James
10

Możesz to zrobić

Na serwerze

global.io=require("socket.io")(server);

io.on("connection",function(client){
    console.log("client is ",client.id);
    //This is handle by current connected client 
    client.emit('messages',{hello:'world'})
    //This is handle by every client
    io.sockets.emit("data",{data:"This is handle by every client"})
    app1.saveSession(client.id)

    client.on("disconnect",function(){
        app1.deleteSession(client.id)
        console.log("client disconnected",client.id);
    })

})

    //And this is handle by particular client 
    var socketId=req.query.id
    if(io.sockets.connected[socketId]!=null) {
        io.sockets.connected[socketId].emit('particular User', {data: "Event response by particular user "});
    }

A na kliencie jest bardzo łatwy w obsłudze.

var socket=io.connect("http://localhost:8080/")
    socket.on("messages",function(data){
        console.log("message is ",data);
        //alert(data)
    })
    socket.on("data",function(data){
        console.log("data is ",data);
        //alert(data)
    })

    socket.on("particular User",function(data){
        console.log("data from server ",data);
        //alert(data)
    })
abhaygarg12493
źródło
7

Począwszy od wersji 1.4.5, upewnij się, że w io.to () podałeś poprawnie przedrostek socketId. Wziąłem gniazdo, w którym klient zalogował się do debugowania, i to bez prefiksu, więc skończyłem na wyszukiwaniu, aż się dowiedziałem! Być może będziesz musiał to zrobić, jeśli identyfikator, który masz, nie jest poprzedzony:

io.to('/#' + socketId).emit('myevent', {foo: 'bar'});
risuch
źródło
6

io.sockets.sockets [socket.id] .emit (...) działał dla mnie w wersji 0.9

aljosa
źródło
Witamy w Stack Overflow. Ta odpowiedź wydaje się nie dodawać wiele do istniejących odpowiedzi. Gdy zdobędziesz więcej reputacji , będziesz mógł komentować posty innych osób . To wydaje się lepiej nadawać się do komentarza.
jerry
2

Możesz także zachować referencje klientów. Ale to sprawia, że ​​twoja pamięć jest zajęta.

Utwórz pusty obiekt i ustaw w nim swoich klientów.

const myClientList = {};

  server.on("connection", (socket) => {
    console.info(`Client connected [id=${socket.id}]`);
     myClientList[socket.id] = socket;   
  });
  socket.on("disconnect", (socket) => {
    delete myClientList[socket.id];
  });

następnie zadzwoń do konkretnego klienta przez identyfikator z obiektu

myClientList[specificId].emit("blabla","somedata");
Kamuran Sönecek
źródło
0

Socket.IO umożliwia „przestrzeń nazw” gniazd, co zasadniczo oznacza przypisanie różnych punktów końcowych lub ścieżek.

Może to pomóc: http://socket.io/docs/rooms-and-namespaces/

Igor T.
źródło