Apakah ada cara untuk beberapa proses untuk berbagi soket pendengaran?

90

Dalam pemrograman soket, Anda membuat soket mendengarkan dan kemudian untuk setiap klien yang terhubung, Anda mendapatkan soket aliran normal yang dapat Anda gunakan untuk menangani permintaan klien. OS mengatur antrian koneksi masuk di belakang layar.

Dua proses tidak dapat diikat ke port yang sama pada saat yang sama - bagaimanapun juga secara default.

Saya bertanya-tanya apakah ada cara (pada OS terkenal mana pun, terutama Windows) untuk meluncurkan banyak contoh proses, sehingga semuanya terikat ke soket, dan dengan demikian mereka secara efektif berbagi antrian. Setiap contoh proses kemudian bisa menjadi single threaded; itu hanya akan memblokir saat menerima koneksi baru. Ketika klien terhubung, salah satu contoh proses idle akan menerima klien itu.

Ini akan memungkinkan setiap proses memiliki implementasi single-threaded yang sangat sederhana, tidak berbagi apa-apa kecuali melalui memori bersama yang eksplisit, dan pengguna akan dapat menyesuaikan bandwidth pemrosesan dengan memulai lebih banyak instance.

Apakah fitur seperti itu ada?

Edit: Bagi mereka yang bertanya "Mengapa tidak menggunakan utas?" Jelas benang adalah pilihan. Tetapi dengan beberapa utas dalam satu proses, semua objek dapat dibagikan dan perhatian besar harus dilakukan untuk memastikan bahwa objek tidak dibagikan, atau hanya terlihat oleh satu utas pada satu waktu, atau benar-benar tidak dapat diubah, dan bahasa yang paling populer dan runtime tidak memiliki dukungan bawaan untuk mengelola kompleksitas ini.

Dengan memulai beberapa proses pekerja yang identik, Anda akan mendapatkan sistem serentak yang defaultnya adalah tanpa berbagi, membuatnya lebih mudah untuk membangun implementasi yang benar dan skalabel.

Daniel Earwicker
sumber
2
Saya setuju, banyak proses dapat mempermudah pembuatan implementasi yang benar dan kuat. Skalabel, saya tidak yakin, itu tergantung pada domain masalah Anda.
MarkR

Jawaban:

92

Anda dapat berbagi soket antara dua (atau lebih) proses di Linux dan bahkan Windows.

Di Linux (Atau OS tipe POSIX), penggunaan fork()akan menyebabkan anak bercabang memiliki salinan dari semua deskriptor file induk. Apa pun yang tidak ditutupnya akan terus dibagikan, dan (misalnya dengan soket mendengarkan TCP) dapat digunakan ke accept()soket baru untuk klien. Ini adalah berapa banyak server, termasuk Apache dalam banyak kasus, berfungsi.

Pada Windows, hal yang sama pada dasarnya benar, kecuali tidak ada fork()pemanggilan sistem sehingga proses induk perlu menggunakan CreateProcessatau sesuatu untuk membuat proses anak (yang tentu saja dapat menggunakan eksekusi yang sama) dan perlu meneruskannya ke pegangan yang dapat diwariskan.

Menjadikan soket pendengaran sebagai pegangan yang diwariskan bukanlah aktivitas yang sepenuhnya sepele, tetapi juga tidak terlalu rumit. DuplicateHandle()perlu digunakan untuk membuat pegangan duplikat (namun masih dalam proses induk), yang akan memiliki set flag yang diwariskan di atasnya. Kemudian Anda dapat memberikan pegangan itu dalam STARTUPINFOstruktur ke proses anak di CreateProcess sebagai STDIN, OUTatau ERRpegangan (dengan asumsi Anda tidak ingin menggunakannya untuk hal lain).

EDIT:

Membaca pustaka MDSN, tampaknya WSADuplicateSocketmekanisme yang lebih tepat atau lebih baik untuk melakukan ini; Ini masih tidak sepele karena proses induk / anak perlu menyelesaikan pegangan mana yang perlu diduplikasi oleh beberapa mekanisme IPC (meskipun ini bisa sesederhana file di sistem file)

KLARIFIKASI:

Sebagai jawaban atas pertanyaan awal OP, tidak, banyak proses tidak bisa bind(); hanya proses induk asli akan memanggil bind(), listen()dll, proses anak hanya akan memproses permintaan oleh accept(), send(), recv()dll

MarkR
sumber
3
Beberapa proses dapat mengikat dengan menentukan opsi soket SocketOptionName.ReuseAddress.
Aaron Clauson
Tapi apa gunanya? Prosesnya lebih berat daripada utas.
Anton Tykhyy
7
Proses lebih berat daripada utas, tetapi karena mereka hanya membagikan hal-hal yang dibagikan secara eksplisit, lebih sedikit sinkronisasi yang diperlukan yang membuat pemrograman lebih mudah dan bahkan bisa lebih efisien dalam beberapa kasus.
MarkR
11
Selain itu, jika proses anak mengalami crash atau putus dengan cara tertentu, kemungkinannya kecil akan mempengaruhi induknya.
MarkR
4
Juga bagus untuk dicatat bahwa, di linux, Anda dapat "mengirimkan" soket ke program lain tanpa menggunakan fork () dan tidak memiliki hubungan induk / anak, menggunakan Soket Unix.
Rahly
35

Kebanyakan orang lain telah memberikan alasan teknis mengapa ini berhasil. Berikut beberapa kode python yang dapat Anda jalankan untuk mendemonstrasikannya sendiri:

import socket
import os

def main():
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serversocket.bind(("127.0.0.1", 8888))
    serversocket.listen(0)

    # Child Process
    if os.fork() == 0:
        accept_conn("child", serversocket)

    accept_conn("parent", serversocket)

def accept_conn(message, s):
    while True:
        c, addr = s.accept()
        print 'Got connection from in %s' % message
        c.send('Thank you for your connecting to %s\n' % message)
        c.close()

if __name__ == "__main__":
    main()

Perhatikan bahwa memang ada dua proses id yang mendengarkan:

$ lsof -i :8888
COMMAND   PID    USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  26972 avaitla    3u  IPv4 0xc26aa26de5a8fc6f      0t0  TCP localhost:ddi-tcp-1 (LISTEN)
Python  26973 avaitla    3u  IPv4 0xc26aa26de5a8fc6f      0t0  TCP localhost:ddi-tcp-1 (LISTEN)

Berikut adalah hasil dari menjalankan telnet dan programnya:

$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for your connecting to parent
Connection closed by foreign host.
$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for your connecting to child
Connection closed by foreign host.
$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for your connecting to parent
Connection closed by foreign host.

$ python prefork.py 
Got connection from in parent
Got connection from in child
Got connection from in parent
Anil Vaitla
sumber
2
Jadi untuk satu koneksi, baik orang tua atau anak mendapatkannya. Tapi siapa yang mendapatkan hubungan itu tidak pasti, bukan?
Hot.PxL
1
ya, menurut saya itu tergantung pada proses apa yang dijadwalkan untuk dijalankan oleh os.
Anil Vaitla
14

Saya ingin menambahkan bahwa soket dapat dibagikan di Unix / Linux melalui soket AF__UNIX (soket antar-proses). Apa yang tampaknya terjadi adalah deskriptor soket baru dibuat yang agak mirip dengan alias yang asli. Deskriptor soket baru ini dikirim melalui soket AFUNIX ke proses lain. Ini sangat berguna dalam kasus di mana proses tidak dapat bercabang () untuk membagikan deskriptor file itu. Misalnya, saat menggunakan pustaka yang mencegah hal ini karena masalah threading. Anda harus membuat soket domain Unix dan menggunakan libancillary untuk mengirimkan deskriptor.

Lihat:

Untuk membuat Soket AF_UNIX:

Contoh kode:

zachthehack
sumber
13

Sepertinya pertanyaan ini telah dijawab sepenuhnya oleh MarkR dan zackthehack tetapi saya ingin menambahkan bahwa Nginx adalah contoh model pewarisan soket mendengarkan.

Ini deskripsi yang bagus:

         Implementation of HTTP Auth Server Round-Robin and
                Memory Caching for NGINX Email Proxy

                            June 6, 2007
             Md. Mansoor Peerbhoy <[email protected]>

...

Alur proses pekerja NGINX

Setelah proses utama NGINX membaca file konfigurasi dan bercabang ke dalam jumlah proses pekerja yang dikonfigurasi, setiap proses pekerja masuk ke dalam loop di mana ia menunggu setiap kejadian pada set soketnya masing-masing.

Setiap proses pekerja dimulai hanya dengan soket pendengar, karena belum ada koneksi yang tersedia. Oleh karena itu, set deskriptor peristiwa untuk setiap proses pekerja dimulai hanya dengan soket pendengar.

(CATATAN) NGINX dapat dikonfigurasi untuk menggunakan salah satu dari beberapa mekanisme polling peristiwa: aio / devpoll / epoll / eventpoll / kqueue / poll / rtsig / select

Ketika koneksi tiba di salah satu soket pendengar (POP3 / IMAP / SMTP), setiap proses pekerja muncul dari jajak pendapat acara, karena setiap proses pekerja NGINX mewarisi soket pendengar. Kemudian, setiap proses pekerja NGINX akan mencoba memperoleh mutex global. Salah satu proses pekerja akan mendapatkan kunci, sedangkan yang lain akan kembali ke loop polling acara masing-masing.

Sementara itu, proses pekerja yang memperoleh mutex global akan memeriksa peristiwa yang dipicu, dan akan membuat permintaan antrean kerja yang diperlukan untuk setiap peristiwa yang dipicu. Peristiwa sesuai dengan deskriptor soket tunggal dari kumpulan deskriptor tempat pekerja mengawasi peristiwa.

Jika peristiwa yang dipicu sesuai dengan koneksi masuk baru, NGINX menerima koneksi dari soket pendengar. Kemudian, ini mengaitkan struktur data konteks dengan deskriptor file. Konteks ini menyimpan informasi tentang koneksi (apakah POP3 / IMAP / SMTP, apakah pengguna belum diautentikasi, dll). Kemudian, soket yang baru dibuat ini ditambahkan ke set deskriptor acara untuk proses pekerja tersebut.

Pekerja sekarang melepaskan mutex (yang berarti bahwa kejadian apa pun yang tiba di pekerja lain dapat diproses), dan mulai memproses setiap permintaan yang sebelumnya dimasukkan ke antrean. Setiap permintaan sesuai dengan acara yang ditandai. Dari setiap deskriptor soket yang diberi sinyal, proses pengerjaan mengambil struktur data konteks terkait yang sebelumnya terkait dengan deskriptor tersebut, dan kemudian memanggil fungsi panggilan balik terkait yang melakukan tindakan berdasarkan status koneksi tersebut. Misalnya, dalam kasus koneksi IMAP yang baru dibuat, hal pertama yang akan dilakukan NGINX adalah menulis pesan selamat datang IMAP standar ke
soket yang terhubung (* OK IMAP4 ready).

Oleh dan oleh, setiap proses pekerja menyelesaikan pemrosesan entri antrian pekerjaan untuk setiap acara yang luar biasa, dan kembali ke loop pemungutan suara acara. Setelah koneksi dibuat dengan klien, kejadian biasanya lebih cepat, karena setiap kali soket yang terhubung siap untuk membaca, kejadian baca dipicu, dan tindakan yang sesuai harus diambil.

richardw
sumber
11

Tidak yakin seberapa relevan ini dengan pertanyaan awal, tetapi di Linux kernel 3.9 ada tambalan yang menambahkan fitur TCP / UDP: dukungan TCP dan UDP untuk opsi soket SO_REUSEPORT; Opsi soket baru memungkinkan beberapa soket pada host yang sama untuk mengikat ke port yang sama, dan dimaksudkan untuk meningkatkan kinerja aplikasi server jaringan multithread yang berjalan di atas sistem multi inti. informasi lebih lanjut dapat ditemukan di tautan LWN LWN SO_REUSEPORT di Linux Kernel 3.9 seperti yang disebutkan di tautan referensi:

opsi SO_REUSEPORT tidak standar, tetapi tersedia dalam bentuk yang serupa di sejumlah sistem UNIX lainnya (terutama, BSD, tempat idenya berasal). Tampaknya menawarkan alternatif yang berguna untuk memeras kinerja maksimum dari aplikasi jaringan yang berjalan pada sistem multi inti, tanpa harus menggunakan pola garpu.

Walid
sumber
Dari artikel LWN ini hampir terlihat seperti SO_REUSEPORTmembuat kumpulan utas, di mana setiap soket berada di utas yang berbeda tetapi hanya satu soket dalam grup yang melakukan accept. Dapatkah Anda mengonfirmasi semua soket di grup masing-masing mendapatkan salinan data?
jww
3

Memiliki satu tugas yang tugas utamanya adalah mendengarkan koneksi masuk. Ketika koneksi diterima, itu menerima koneksi - ini membuat deskriptor soket terpisah. Soket yang diterima diteruskan ke salah satu tugas pekerja Anda yang tersedia, dan tugas utama kembali mendengarkan.

s = socket();
bind(s);
listen(s);
while (1) {
  s2 = accept(s);
  send_to_worker(s2);
}
HUAGHAGUAH
sumber
Bagaimana soket diteruskan ke pekerja? Ingatlah bahwa idenya adalah bahwa seorang pekerja adalah proses yang terpisah.
Daniel Earwicker
fork () mungkin, atau salah satu ide lain di atas. Atau mungkin Anda benar-benar memisahkan soket I / O dari pemrosesan data; mengirim payload ke proses pekerja melalui mekanisme IPC. OpenSSH dan alat OpenBSD lainnya menggunakan metodologi ini (tanpa utas).
HUAGHAGUAH
3

Di bawah Windows (dan Linux) dimungkinkan untuk satu proses untuk membuka soket dan kemudian meneruskan soket itu ke proses lain sehingga proses kedua juga dapat menggunakan soket itu (dan meneruskannya secara bergantian, jika ingin melakukannya) .

Panggilan fungsi penting adalah WSADuplicateSocket ().

Ini mengisi struktur dengan informasi tentang soket yang ada. Struktur ini kemudian, melalui mekanisme IPC pilihan Anda, diteruskan ke proses lain yang ada (perhatikan saya katakan ada - ketika Anda memanggil WSADuplicateSocket (), Anda harus menunjukkan proses target yang akan menerima informasi yang dipancarkan).

Proses penerimaan kemudian dapat memanggil WSASocket (), meneruskan struktur informasi ini, dan menerima pegangan ke soket yang mendasarinya.

Kedua proses sekarang memegang pegangan ke soket dasar yang sama.


sumber
2

Kedengarannya seperti apa yang Anda inginkan adalah satu proses mendengarkan untuk klien baru dan kemudian menyerahkan koneksi setelah Anda mendapatkan koneksi. Untuk melakukan itu lintas utas mudah dan di .Net Anda bahkan memiliki metode BeginAccept dll. Untuk mengurus banyak pipa untuk Anda. Untuk menyerahkan koneksi melintasi batas proses akan menjadi rumit dan tidak akan memiliki keunggulan kinerja.

Sebagai alternatif, Anda dapat memiliki beberapa proses terikat dan mendengarkan pada soket yang sama.

TcpListener tcpServer = new TcpListener(IPAddress.Loopback, 10090);
tcpServer.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
tcpServer.Start();

while (true)
{
    TcpClient client = tcpServer.AcceptTcpClient();
    Console.WriteLine("TCP client accepted from " + client.Client.RemoteEndPoint + ".");
}

Jika Anda menjalankan dua proses, masing-masing menjalankan kode di atas, ini akan berfungsi dan proses pertama tampaknya mendapatkan semua koneksi. Jika proses pertama dihentikan, proses kedua kemudian mendapatkan koneksi. Dengan berbagi soket seperti itu, saya tidak yakin persis bagaimana Windows memutuskan proses mana yang mendapatkan koneksi baru meskipun tes cepat menunjukkan proses terlama untuk mendapatkannya terlebih dahulu. Seperti apakah itu berbagi jika proses pertama sibuk atau semacamnya saya tidak tahu.

Aaron Clauson
sumber
2

Pendekatan lain (yang menghindari banyak detail kompleks) di Windows jika Anda menggunakan HTTP, adalah menggunakan HTTP.SYS . Ini memungkinkan banyak proses untuk mendengarkan URL yang berbeda di port yang sama. Di Server 2003/2008 / Vista / 7 ini adalah cara kerja IIS, sehingga Anda dapat berbagi port dengannya. (Di XP SP2 HTTP.SYS didukung, tetapi IIS5.1 tidak menggunakannya.)

API tingkat tinggi lainnya (termasuk WCF) menggunakan HTTP.SYS.

Richard
sumber