Dalam sistem fail mana soket didaftarkan. Contoh: DLL berbilang benang selamat untuk pemesejan soket. Fungsi soket pelayan

antara muka panggilan sistem rangkaian ( soket(), mengikat(), recvfrom(), hantar kepada() dsb.) dalam sistem pengendalian UNIX boleh digunakan untuk susunan protokol lain (dan untuk protokol yang terletak di bawah lapisan pengangkutan).

Apabila membuat soket, anda mesti menentukan jenisnya dengan tepat. Spesifikasi ini dibuat menggunakan tiga parameter panggilan soket(). Parameter pertama menentukan keluarga protokol yang dimiliki oleh soket yang dicipta, dan parameter kedua dan ketiga menentukan protokol khusus dalam keluarga itu.

Parameter kedua digunakan untuk menentukan jenis antara muka untuk bekerja dengan soket - adakah ia akan menjadi soket aliran, soket untuk bekerja dengan datagram atau yang lain. Parameter ketiga menentukan protokol untuk jenis antara muka yang diberikan. DALAM Timbunan protokol TCP/IP Terdapat hanya satu protokol untuk soket aliran - TCP dan hanya satu protokol untuk soket datagram - UDP, jadi untuk protokol pengangkutan TCP/IP parameter ketiga diabaikan.

Dalam susunan protokol lain, mungkin terdapat beberapa protokol dengan jenis antara muka yang sama, contohnya, yang datagram, yang berbeza dalam tahap kebolehpercayaan.

Untuk protokol pengangkutan TCP/IP, kami akan sentiasa menentukan pemalar yang dipratentukan AF_INET (Alamat keluarga - Internet) atau sinonimnya PF_INET (Protokol keluarga - Internet) sebagai parameter pertama.

Parameter kedua akan mengambil nilai yang telah ditetapkan SOCK_STREAM untuk soket strim dan SOCK_DGRAM untuk soket datagram.

Oleh kerana parameter ketiga tidak diambil kira dalam kes kami, kami akan menggantikan nilai 0 ke dalamnya.

Pautan kepada maklumat tentang soket yang dibuat diletakkan di dalamnya jadual fail proses terbuka serupa dengan cara ia dilakukan untuk pip dan FIFO (lihat bengkel 5). Panggilan sistem mengembalikan kepada pengguna deskriptor fail yang sepadan dengan elemen jadual yang dihuni, yang seterusnya kami akan memanggil deskriptor soket. Kaedah menyimpan maklumat tentang soket ini membolehkan, pertama, proses anak untuk mewarisinya daripada proses induk, dan, kedua, menggunakan untuk soket beberapa panggilan sistem yang sudah biasa kepada kita daripada bekerja dengan pip dan FIFO: close( ) , baca tulis() .

Panggilan sistem untuk mencipta soket

Prototaip panggilan sistem

#termasuk #termasuk soket int(domain int, jenis int, protokol int);

Penerangan tentang panggilan sistem

Panggilan sistem soket berfungsi untuk mencipta nod komunikasi maya dalam sistem pengendalian. Penerangan ini tidak penerangan penuh panggilan sistem dan bertujuan untuk digunakan dalam kursus kami sahaja. Untuk maklumat lengkap, lihat Manual UNIX.

Parameter domain mentakrifkan keluarga protokol di mana maklumat akan dipindahkan. Kami akan mempertimbangkan hanya dua daripada keluarga ini daripada beberapa keluarga sedia ada. Terdapat nilai parameter yang telah ditetapkan untuk mereka:

  • PF_INET – untuk Keluarga protokol TCP/IP ;
  • PF_UNIX – untuk keluarga protokol dalaman UNIX, atau dipanggil domain UNIX.

Parameter jenis menentukan semantik pertukaran maklumat: sama ada komunikasi akan dijalankan melalui mesej (datagram), dengan mewujudkan sambungan maya atau dengan cara lain. Kami akan menggunakan hanya dua cara untuk menukar maklumat dengan nilai yang telah ditetapkan untuk parameter jenis:

  • SOCK_STREAM – untuk komunikasi menggunakan pertubuhan sambungan maya ;
  • SOCK_DGRAM – untuk bertukar maklumat melalui mesej.

Parameter protokol menentukan protokol khusus untuk keluarga protokol yang dipilih dan kaedah komunikasi. Ia hanya penting jika terdapat beberapa protokol sedemikian. Dalam kes kami, keluarga protokol dan jenis pertukaran maklumat menentukan protokol secara unik. Oleh itu, kami akan menganggap parameter ini sama dengan 0.

Nilai pulangan

Jika berjaya, panggilan sistem mengembalikan deskriptor fail (nilai lebih besar daripada atau sama dengan 0), yang akan digunakan sebagai rujukan kepada nod komunikasi yang dibuat dalam semua panggilan rangkaian selanjutnya. Jika sebarang ralat berlaku, nilai negatif dikembalikan.

Alamat soket. Menetapkan alamat soket. bind() panggilan sistem

Sebaik sahaja soket dibuat, anda perlu mengkonfigurasi alamatnya. Panggilan sistem digunakan untuk ini mengikat(). Parameter pertama panggilan mesti mengandungi deskriptor soket yang alamatnya sedang dikonfigurasikan. Parameter kedua dan ketiga menentukan alamat ini.

Parameter kedua mestilah penunjuk kepada struktur sockaddr struct yang mengandungi bahagian jauh dan setempat alamat penuh.

Penunjuk jenis struct sockaddr * terdapat dalam banyak panggilan sistem rangkaian; ia digunakan untuk menyampaikan maklumat tentang alamat apa soket adalah atau harus diikat. Mari kita lihat lebih dekat jenis data ini. Struktur struktur sockaddr diterangkan dalam fail dengan cara berikut:

struct sockaddr (short sa_family; char sa_data; );

Komposisi struktur ini disebabkan oleh fakta bahawa panggilan sistem rangkaian boleh digunakan untuk keluarga protokol yang berbeza, yang mentakrifkan ruang alamat secara berbeza untuk alamat soket jauh dan setempat. Pada dasarnya, jenis data ini hanyalah templat umum untuk menghantar struktur data khusus untuk setiap keluarga protokol kepada panggilan sistem. Satu-satunya elemen biasa struktur ini ialah medan sa_family yang pendek (yang, secara semula jadi, boleh mempunyai nama yang berbeza dalam struktur yang berbeza; satu-satunya perkara yang penting ialah mereka semua adalah jenis yang sama dan merupakan elemen pertama dari struktur mereka) untuk menggambarkan keluarga protokol. Kandungan medan ini dianalisis oleh panggilan sistem untuk definisi yang tepat komposisi maklumat yang diterima.

Untuk bekerja dengan keluarga protokol TCP/IP kami akan menggunakan alamat soket jenis berikut, yang diterangkan dalam fail :

struct sockaddr _in( sin_family pendek; /* Keluarga protokol yang dipilih sentiasa AF_INET */ sin_port pendek tidak ditandatangani; /* Nombor port 16-bit dalam susunan bait rangkaian */ struct in_addr sin_addr; /* Alamat antara muka rangkaian */ char sin_zero; /* Medan ini tidak digunakan, tetapi mesti sentiasa diisi dengan sifar */ );

Elemen pertama struktur – sin_family menentukan keluarga protokol. Kami akan memasukkan ke dalamnya pemalar AF_INET yang dipratentukan, sudah diketahui oleh kami (lihat bahagian sebelumnya).

Bahagian terpencil alamat penuh - alamat IP - terkandung dalam struktur jenis struct in_addr, yang kami temui dalam bahagian "Fungsi Terjemahan Alamat IP" inet_ntoa(), inet_aton() " .

Untuk menunjukkan nombor port, elemen struktur sin_port digunakan, di mana nombor port harus disimpan dalam susunan bait rangkaian. Terdapat dua pilihan untuk menentukan nombor port: port tetap seperti yang dikehendaki oleh pengguna dan port yang ditetapkan secara rawak sistem operasi. Pilihan pertama memerlukan menentukan nombor positif, pra-dikenali sebagai nombor port dan untuk protokol UDP biasanya digunakan semasa menyediakan alamat soket dan apabila menghantar maklumat menggunakan panggilan sistem hantar kepada()(lihat bahagian seterusnya). Pilihan kedua memerlukan menyatakan 0 sebagai nombor port. Dalam kes ini sistem operasi sendiri mengikat soket ke nombor port percuma. Kaedah ini biasanya digunakan apabila menyediakan soket untuk program klien, apabila pengaturcara tidak perlu mengetahui nombor port yang tepat terlebih dahulu.

Apakah nombor port yang boleh digunakan oleh pengguna dalam konfigurasi tetap? Nombor port 1 hingga 1023 hanya boleh diberikan kepada soket melalui proses yang dijalankan dengan keistimewaan pentadbir sistem. Biasanya, nombor ini diberikan kepada perkhidmatan rangkaian sistem, tanpa mengira jenis sistem pengendalian yang digunakan, supaya program klien pengguna sentiasa boleh meminta perkhidmatan daripada alamat tempatan yang sama. Terdapat juga beberapa yang digunakan secara meluas program rangkaian, yang menjalankan proses dengan kebenaran pengguna biasa (contohnya, X-Windows). Untuk program sedemikian oleh Perbadanan Internet untuk memberikan nama dan nombor (


Bab 15

Dalam bab ini, anda akan diperkenalkan kepada cara lain untuk berinteraksi antara proses, yang berbeza dengan ketara daripada yang kita bincangkan dalam bab 13 Dan 14. Sehingga kini, semua alatan yang telah kami bincangkan adalah berdasarkan sumber yang dikongsi pada satu komputer. Sumber boleh menjadi kawasan sistem fail, segmen memori kongsi atau baris gilir mesej, tetapi ia hanya boleh digunakan oleh proses yang dijalankan pada mesin yang sama.

Berkeley UNIX memperkenalkan alat komunikasi baharu, antara muka soket, yang merupakan lanjutan daripada konsep paip yang dibincangkan dalam Bab 13. Sistem Linux juga mempunyai antara muka soket.

Anda boleh menggunakan soket dengan cara yang sama seperti paip, tetapi ia menyokong komunikasi dalam rangkaian komputer. Proses pada satu mesin boleh menggunakan soket untuk berkomunikasi dengan proses pada mesin lain, membolehkan sistem pelayan pelanggan diedarkan melalui rangkaian. Proses yang berjalan pada mesin yang sama juga boleh menggunakan soket.

Selain itu, antara muka soket telah disediakan pada Windows melalui spesifikasi Windows Sockets atau WinSock yang tersedia untuk umum. Perkhidmatan soket dalam Windows disediakan oleh fail sistem Winsock.dll. Oleh itu, program yang menjalankan Windows boleh berinteraksi melalui rangkaian dengan komputer yang menjalankan Linux dan UNIX dan sebaliknya, dengan itu melaksanakan sistem pelayan pelanggan. Walaupun antara muka pengaturcaraan untuk WinSock tidak sama sepenuhnya dengan antara muka soket dalam UNIX, ia berdasarkan soket yang sama.

Kami tidak dapat merangkumi semua banyak keupayaan rangkaian Linux dalam satu bab, jadi anda hanya akan menemui antara muka perisian rangkaian asas yang membolehkan anda menulis program rangkaian anda sendiri.

Kami akan membincangkan topik berikut dengan lebih terperinci:

□ cara sambungan soket berfungsi;

□ atribut soket, alamat dan pertukaran maklumat;

□ maklumat rangkaian dan daemon Internet (inetd/xinetd);

□ pelanggan dan pelayan.

Apakah soket?

Soket ialah alat komunikasi yang membolehkan pembangunan sistem pelayan-pelanggan untuk penggunaan tempatan, pada satu mesin atau rangkaian. Ciri OS Linux seperti output, sambungan pangkalan data dan perkhidmatan Web, serta utiliti rangkaian seperti

, bertujuan untuk pendaftaran jauh, dan digunakan untuk pemindahan fail, biasanya menggunakan soket untuk pertukaran data.

Soket dicipta dan digunakan secara berbeza daripada paip kerana ia menyerlahkan perbezaan yang berbeza antara pelanggan dan pelayan. Mekanisme soket membolehkan anda membuat berbilang pelanggan yang dilampirkan pada satu pelayan.

Sambungan berasaskan soket

Sambungan berasaskan soket boleh dianggap sebagai membuat panggilan telefon ke institusi. Panggilan telefon masuk ke dalam organisasi dan dijawab oleh penyambut tetamu yang mengarahkan panggilan ke jabatan yang sesuai (proses pelayan) dan dari sana kepada pekerja yang betul (soket pelayan). Setiap panggilan telefon masuk (pelanggan) dihalakan ke titik akhir yang sesuai, dan ejen perantaraan boleh mengendalikan panggilan telefon berikutnya. Sebelum kita melihat untuk mewujudkan sambungan menggunakan soket pada sistem Linux, kita perlu memahami cara mereka berkelakuan dalam aplikasi soket sedar sambungan.

Pada mulanya aplikasi pelayan mencipta soket, yang, seperti deskriptor fail, mewakili sumber yang diberikan kepada proses pelayan tunggal. Pelayan menciptanya menggunakan panggilan sistem

, dan soket ini tidak boleh dikongsi dengan proses lain.

Seterusnya, pelayan memberikan nama kepada soket. Soket tempatan dengan nama fail yang diberikan dalam sistem fail Linux selalunya terletak dalam direktori /tmp atau /usr/tmp. Untuk soket rangkaian, nama fail akan menjadi pengecam perkhidmatan (nombor port/titik akses) yang dikaitkan dengan rangkaian tertentu yang boleh disambungkan oleh pelanggan. Pengecam ini dengan menyatakan nombor tertentu port yang sepadan dengan proses pelayan yang betul membolehkan Linux menghalakan sambungan masuk di sepanjang laluan tertentu. Sebagai contoh, pelayan Web biasanya mencipta soket pada port 80, pengecam yang dikhaskan untuk tujuan ini. Pelayar web tahu menggunakan port 80 untuk sambungan HTTP mereka ke tapak Web yang ingin dibaca oleh pengguna. Soket dinamakan menggunakan panggilan sistem

. Seterusnya, proses pelayan menunggu klien untuk menyambung ke soket yang dinamakan. Panggilan sistem mencipta baris gilir sambungan masuk. Pelayan boleh menerimanya menggunakan panggilan sistem.

Apabila pelayan memanggil

, soket baharu dicipta yang berbeza daripada soket yang dinamakan. Soket baharu ini hanya digunakan untuk berkomunikasi dengan pelanggan khusus ini. Soket yang dinamakan disimpan untuk sambungan masa hadapan daripada pelanggan lain. Jika pelayan ditulis dengan betul, ia boleh mendapat manfaat daripada berbilang sambungan. Pelayan Web mencapai ini dengan menyampaikan halaman kepada banyak pelanggan pada masa yang sama. Dalam kes pelayan mudah, semua pelanggan berikutnya menunggu dalam baris gilir sehingga pelayan bersedia semula.

Bahagian pelanggan sistem menggunakan soket adalah lebih mudah. Pelanggan mencipta soket tanpa nama menggunakan panggilan

. Ia kemudian memanggil untuk menyambung ke pelayan menggunakan soket bernama pelayan sebagai alamat.

Setelah dipasang, soket boleh digunakan sebagai deskriptor fail peringkat rendah, membenarkan pertukaran data dua arah.

Lengkapkan latihan 15.1 dan 15.2.

Latihan 15.1. Pelanggan tempatan yang mudah. Kami akan melihat panggilan sistem secara terperinci sedikit kemudian apabila kami membincangkan beberapa isu menangani.

1. Sertakan fail pengepala yang diperlukan dan tetapkan pembolehubah:


2. Buat soket untuk pelanggan:

sockfd = soket(AF_UNIX, SOCK_STREAM, 0);

3. Namakan soket seperti yang dipersetujui dengan pelayan:

address.sun_family = AF_UNIX;
strcpy(address.sun_path, "server_socket");

4. Sambungkan soket anda ke soket pelayan:

:
printf("char dari pelayan = %c\n", ch);

Program ini akan ranap jika anda cuba menjalankannya kerana soket pelayan yang dinamakan belum lagi dibuat. (Mesej ralat yang tepat mungkin berbeza pada sistem yang berbeza.)

oops: klien1: Tiada fail atau direktori sedemikian
Latihan 15.2. Pelayan tempatan yang mudah

Berikut ialah program pelayan ringkas, server1.c, yang menerima permintaan sambungan daripada klien. Ia mencipta soket pelayan, memberikannya nama, mencipta baris gilir menunggu, dan menerima permintaan sambungan.

1. Sertakan fail pengepala yang diperlukan dan tetapkan pembolehubah:


struct sockaddr_un server_address;
struct sockaddr_un client_address;

2. Keluarkan semua soket lama dan buat soket tanpa nama untuk pelayan:

server_sockfd = soket(AF_UNIX, SOCK_STREAM, 0);

3. Berikan nama soket:

server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, "server_socket");

4. Buat baris gilir permintaan sambungan dan tunggu permintaan pelanggan:

5. Terima permintaan sambungan:

(struct sockaddr *)&client_address, &client_len);

6. Membaca dan menulis data pelanggan menggunakan

:

Bagaimana ia berfungsi

Dalam contoh ini program pelayan hanya boleh melayani satu pelanggan pada satu masa. Ia hanya membaca watak yang diterima daripada pelanggan, menambahnya, dan menulisnya kembali. Dalam lebih sistem yang kompleks di mana pelayan perlu melakukan lebih banyak kerja bagi pihak pelanggan, pendekatan ini tidak akan sesuai kerana pelanggan lain tidak akan dapat menyambung sehingga pelayan ditutup. Kemudian anda akan melihat beberapa kaedah yang membenarkan berbilang pelanggan untuk menyambung.

Apabila anda menjalankan program pelayan, ia mencipta soket dan menunggu permintaan sambungan. Jika anda menjalankannya di latar belakang, i.e. ia akan berjalan secara bebas, anda kemudian boleh menjalankan pelanggan sebagai tugas keutamaan tinggi.

Semasa menunggu permintaan sambungan, pelayan memaparkan mesej. Dalam contoh yang diberikan, pelayan sedang menunggu permintaan daripada soket sistem fail, dan anda boleh melihatnya menggunakan arahan biasa

.

Adalah idea yang baik untuk memadamkan soket selepas anda selesai mengendalikannya, walaupun program ranap kerana menerima isyarat. Ini akan mengelakkan sistem fail daripada berselerak dengan fail yang tidak digunakan.

$ soket pelayan ls -lF
srwxr-xr-x 1 pengguna neil 0 2007-06-23 11:41 server_socket=

Di sini jenis peranti ialah soket, yang ditunjukkan oleh simbol

sebelum kebenaran dan simbol di hujung nama. Soket dicipta sebagai fail biasa dengan hak akses yang diubah suai oleh semasa. Jika anda menggunakan arahan, anda boleh melihat pelayan berjalan di latar belakang. Ia ditunjukkan tidur (parameter sama dengan ) dan oleh itu tidak menggunakan sumber CPU.
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY PERINTAH MASA
0 1000 23385 10689 17 0 1424 312 361800 S mata/1 0:00 ./server1

Sekarang, apabila anda menjalankan program, anda akan berjaya menyambung ke pelayan. Memandangkan soket pelayan wujud, anda boleh menyambung kepadanya dan bertukar data.

pelayan menunggu char dari pelayan = B

Pada terminal, pelayan dan output klien bercampur, tetapi anda boleh melihat bahawa pelayan menerima watak daripada klien, menambahnya dan mengembalikannya. Pelayan kemudiannya terus berjalan dan menunggu pelanggan seterusnya. Jika anda menjalankan berbilang pelanggan bersama-sama, mereka akan dilayan secara bergilir-gilir, walaupun output yang terhasil mungkin lebih bercelaru.

$ ./klien1 & ./klien1 & ./klien1 &

Atribut soket

Untuk memahami sepenuhnya panggilan sistem yang digunakan dalam contoh ini, anda perlu mengetahui sesuatu tentang rangkaian dalam sistem UNIX.

Soket dicirikan oleh tiga sifat: domain, jenis Dan protokol. Mereka juga mempunyai alamat yang digunakan sebagai nama soket. Format alamat berbeza-beza bergantung pada domain, juga dipanggil keluarga protokol. Setiap keluarga protokol boleh menggunakan satu atau lebih keluarga alamat, yang mentakrifkan format alamat.

Domain Soket

Domain mentakrifkan persekitaran pengendalian rangkaian yang akan digunakan oleh sambungan soket. Domain soket yang paling popular ialah

, yang merujuk kepada Internet dan digunakan pada banyak rangkaian tempatan Linux dan, sudah tentu, di Internet itu sendiri. Protokol Internet (IP) peringkat rendah, yang hanya mempunyai satu keluarga alamat, mengenakan cara khusus untuk menentukan komputer pada rangkaian. Ia dikenali sebagai Alamat IP.
Catatan

Untuk mengatasi beberapa masalah protokol IP standard dengan bilangan alamat tersedia yang terhad, protokol Internet generasi seterusnya IPv6 telah dibangunkan. Ia menggunakan domain soket yang berbeza

dan format alamat lain. IPv6 dijangka akhirnya akan menggantikan IP, tetapi ini akan mengambil masa bertahun-tahun. Walaupun sudah ada pelaksanaan IPv6 untuk Linux, membincangkannya adalah di luar skop buku ini.

Walaupun mesin di Internet hampir selalu mempunyai nama, ia ditukar kepada alamat IP. Contoh alamat IP ialah 192.168.1.99. Semua alamat IP diwakili oleh empat nombor, setiap satunya kurang daripada 256, dan membentuk apa yang dipanggil empat dengan titik. Apabila pelanggan menyambung melalui rangkaian menggunakan soket, ia memerlukan alamat IP komputer pelayan.

Beberapa perkhidmatan mungkin tersedia pada komputer pelayan. Pelanggan boleh mengakses perkhidmatan tertentu pada komputer yang disambungkan ke rangkaian menggunakan port IP. Di dalam sistem, port dikenal pasti oleh integer 16-bit yang unik, dan di luar sistem dengan gabungan alamat IP dan nombor port. Soket ialah titik akhir komunikasi yang mesti dikaitkan dengan port sebelum pemindahan data boleh dilakukan.

Pelayan menunggu permintaan sambungan daripada pelanggan tertentu. Perkhidmatan terkenal mempunyai nombor port khusus yang digunakan oleh semua mesin Linux dan UNIX. Biasanya, tetapi tidak selalu, nombor ini adalah kurang daripada 1024. Contohnya termasuk penimbal cetakan pencetak (515),

(513), (21) dan (80). Yang terakhir dinamakan ialah port standard untuk pelayan Web. Biasanya, nombor port kurang daripada 1024 dikhaskan untuk perkhidmatan sistem dan boleh diservis oleh proses dengan hak superuser. Standard X/Open mentakrifkan pemalar dalam fail pengepala netdb.h untuk menunjukkan bilangan terbesar port rizab.

Oleh kerana terdapat set nombor port standard untuk perkhidmatan standard, komputer boleh menyambung antara satu sama lain dengan mudah tanpa perlu meneka nombor port yang betul. Perkhidmatan tempatan boleh menggunakan alamat port bukan standard.

Domain dalam latihan pertama,

, ialah domain sistem fail UNIX yang boleh digunakan oleh soket yang terletak pada satu komputer, bahkan mungkin tidak pada rangkaian. Jika ya, maka protokol peringkat rendah ialah fail I/O, dan alamatnya ialah nama fail. Soket pelayan menggunakan alamat yang anda lihat muncul dalam direktori semasa semasa anda menjalankan aplikasi pelayan.

Selain itu, domain lain boleh digunakan:

untuk rangkaian berdasarkan protokol ISO standard dan untuk Xerox Sistem Rangkaian(Sistem rangkaian Xerox). Kami tidak akan membincangkannya dalam buku ini. Jenis soket

Domain soket boleh mempunyai pelbagai kaedah komunikasi, setiap satu boleh mempunyai ciri yang berbeza. Dalam kes soket domain

masalah tidak timbul kerana ia menyediakan pertukaran data dua arah yang boleh dipercayai. Dalam domain rangkaian, adalah perlu untuk mengetahui ciri-ciri rangkaian asas dan kesannya ke atas pelbagai mekanisme pemindahan data.

Protokol Internet menyediakan dua mekanisme untuk menghantar data dengan tahap perkhidmatan yang berbeza: aliran Dan datagram.

Soket aliran

Soket strim (agak serupa dengan strim I/O standard) menyediakan sambungan yang merupakan aliran dwiarah bait yang konsisten dan boleh dipercayai. Oleh itu, adalah dijamin bahawa data tidak akan hilang, diduakan atau disusun semula tanpa menunjukkan ralat yang berlaku. Mesej besar dipecahkan, dihantar dan disatukan semula. Ini serupa dengan aliran fail yang mengambil sejumlah besar data dan membahagikannya kepada blok yang lebih kecil untuk menulis ke cakera fizikal. Soket aliran mempunyai gelagat yang boleh diramal.

Soket aliran diterangkan mengikut jenis

, dilaksanakan dalam domain melalui sambungan berdasarkan protokol TCP/IP. Selain itu, ini ialah jenis soket biasa dalam . Dalam bab ini, kita akan memberi tumpuan kepada soket jenis, kerana ia paling kerap digunakan dalam pengaturcaraan. aplikasi rangkaian.
Catatan

TCP/IP adalah singkatan dari Transmission Control Protocol/Internet Protocol. Protokol IP ialah protokol penghantaran paket peringkat rendah yang menyediakan pemilihan laluan apabila menghantar data pada rangkaian dari satu komputer ke komputer yang lain. TCP menyediakan pesanan, kawalan aliran dan penyampaian untuk memastikan penghantaran yang lengkap dan betul jumlah yang besar data atau mesej tentang situasi ralat yang sepadan.

Soket datagram

Tidak seperti penstriman soket datagram, yang diterangkan mengikut jenis

, jangan wujudkan atau kekalkan sambungan. Selain itu, terdapat had saiz datagram yang boleh dihantar. Ia dihantar sebagai mesej rangkaian tunggal, yang boleh hilang, diduplikasi atau tiba tidak pada masanya, i.e. sebelum datagram dihantar selepas itu.

Soket datagram dilaksanakan dalam domain

melalui sambungan UDP/IP dan menyediakan perkhidmatan yang tidak teratur dan tidak boleh dipercayai. (UDP ialah singkatan dari User Datagram Protocol.) Walau bagaimanapun, ia agak cekap sumber kerana ia tidak memerlukan sambungan rangkaian. Mereka pantas kerana... Tiada masa terbuang untuk menyediakan sambungan rangkaian.

Datagram berguna untuk permintaan sekali sahaja kepada perkhidmatan maklumat, untuk menyediakan maklumat status rutin, atau untuk melaksanakan pengelogan data keutamaan rendah. Kelebihan mereka ialah menghentikan pelayan tidak akan menyebabkan kesulitan yang tidak wajar kepada pelanggan dan tidak memerlukan pelanggan dimulakan semula. Oleh kerana pelayan berasaskan datagram biasanya menyimpan data tanpa sambungan, ia boleh dihentikan dan dimulakan semula tanpa mengganggu pelanggan mereka.

Ini menyimpulkan perbincangan kami tentang datagram; untuk maklumat lanjut, lihat bahagian "Datagrams" pada akhir bab ini.

Protokol soket

Jika mekanisme pemindahan data peringkat rendah membenarkan berbilang protokol yang menyediakan jenis soket yang diperlukan, anda boleh memilih protokol atau soket tertentu. Dalam bab ini, kami akan menumpukan pada rangkaian UNIX dan soket sistem fail, yang tidak memerlukan anda memilih protokol selain daripada lalai.

Mencipta soket

Panggilan sistem soket mencipta soket dan mengembalikan pemegang yang boleh digunakan untuk mengakses soket:

#termasuk
#termasuk
soket int(domain int, jenis int, protokol int);

Soket yang dicipta adalah satu titik akhir talian penghantaran. Parameter

menentukan keluarga alamat, parameter menentukan jenis pertukaran data yang digunakan dengan soket ini, dan a ialah protokol yang digunakan.

Dalam jadual 15.1 menunjukkan nama domain.


Jadual 15.1

Domain soket yang paling popular termasuk

, digunakan untuk soket tempatan yang dilaksanakan oleh sistem fail UNIX dan Linux, dan digunakan untuk soket rangkaian UNIX. Soket domain boleh digunakan oleh program yang berkomunikasi melalui rangkaian berasaskan protokol TCP/IP, termasuk Internet. Antara muka Windows Winsock juga menyediakan akses kepada domain soket ini.

Parameter jenis soket menentukan ciri komunikasi untuk digunakan pada soket baharu. Nilai yang mungkin boleh

Dan . ialah aliran bait yang teratur, boleh dipercayai, berasaskan sambungan, dwiarah. Dalam kes domain soket, jenis komunikasi ini secara lalai disediakan oleh sambungan TCP yang diwujudkan antara dua titik akhir soket aliran semasa menyambung. Data boleh dipindahkan dalam dua arah melalui pautan soket. Protokol TCP termasuk cara memecah dan kemudian memasang semula mesej besar dan menghantar semula mana-mana bahagian yang mungkin telah hilang pada rangkaian. - perkhidmatan datagram. Anda boleh menggunakan soket sedemikian untuk menghantar mesej dengan saiz maksimum tetap (biasanya kecil), tetapi tiada jaminan bahawa mesej akan dihantar atau mesej tidak akan disusun semula pada rangkaian. Dalam kes soket domain, pemindahan data jenis ini disediakan oleh datagram UDP (User Datagram Protocol).

Protokol yang digunakan untuk komunikasi biasanya ditentukan oleh jenis soket dan domain. Sebagai peraturan, tidak ada pilihan. Parameter

terpakai dalam kes di mana pilihan masih disediakan. Tetapan 0 membolehkan anda memilih protokol standard yang digunakan dalam semua contoh dalam bab ini.

Panggilan sistem

mengembalikan pemegang sama seperti deskriptor fail peringkat rendah. Apabila soket disambungkan ke titik akhir soket lain, anda boleh menggunakan panggilan sistem pada pemegang soket untuk menghantar dan menerima data menggunakan soket. Panggilan sistem digunakan untuk memadam sambungan soket.

Alamat soket

Setiap domain soket memerlukan format alamat yang berbeza. Dalam domain

alamat diterangkan oleh struktur yang diisytiharkan dalam fail pengepala sys/un.h:
sa_family_t sun_family; /* AF_UNIX */
char sun_path; /* Laluan ke fail */

Untuk mendapatkan alamat jenis yang berbeza boleh dihantar ke dalam panggilan sistem untuk memproses soket, semua format alamat diterangkan oleh struktur serupa yang bermula dengan medan (dalam dalam kes ini

), menyatakan jenis alamat (domain soket). Dalam domain, alamat ditentukan oleh nama fail dalam medan struktur.

Pada sistem Linux moden jenis

, yang ditakrifkan dalam standard X/Open sebagai diisytiharkan dalam fail pengepala sys/un.h, ditafsirkan sebagai jenis . Di samping itu, saiz yang dinyatakan dalam medan adalah terhad (di Linux, 108 aksara ditentukan; pada sistem lain, pemalar bernama, contohnya, boleh digunakan). Oleh kerana saiz struktur alamat boleh berbeza-beza, banyak panggilan sistem soket memerlukan atau menyediakan sebagai output panjang yang akan digunakan untuk menyalin struktur alamat tertentu. alamat ditentukan menggunakan struktur yang dipanggil , ditakrifkan dalam fail netinet/in.h, yang mengandungi sekurang-kurangnya elemen berikut:
short int sin_family; /* AF_INET */
unsigned short int sin_port; /* Nombor port */
struct in_addr sin_addr; /* Alamat Internet */

Struktur alamat IP jenis in_addr ditakrifkan seperti berikut:

unsigned long int s_addr;

Empat bait alamat IP membentuk satu nilai 32-bit. Soket domain

diterangkan sepenuhnya oleh alamat IP dan nombor port. Dari sudut pandangan aplikasi, semua soket bertindak sebagai deskriptor fail, dan alamatnya ditentukan oleh nilai integer unik.

Penamaan Soket

Untuk membuat soket (dicipta dengan memanggil

) boleh diakses oleh proses lain, program pelayan mesti memberikan nama soket. Soket domain dikaitkan dengan nama fail yang layak sepenuhnya pada sistem fail, seperti yang anda lihat dalam program contoh server1. Soket domain dikaitkan dengan nombor port IP.
#termasuk
int bind(int socket, const struct sockaddr *alamat, size_t alamat len);

Panggilan sistem

memberikan alamat yang dinyatakan dalam parameter kepada soket tidak dinamakan yang dikaitkan dengan deskriptor soket. Panjang struktur alamat diluluskan dalam parameter:

Panjang dan format alamat bergantung pada keluarga alamat. Dalam panggilan sistem

penunjuk kepada struktur alamat tertentu mesti dihantar ke jenis alamat generik.

Setelah berjaya disiapkan

mengembalikan 0. Jika gagal, -1 dikembalikan dan pembolehubah diberikan salah satu nilai yang disenaraikan dalam jadual. 15.2.

Jadual 15.2

Mencipta Baris Soket

Untuk menerima permintaan untuk sambungan berasaskan soket masuk, program pelayan mesti membuat baris gilir untuk menahan permintaan yang belum selesai. Ia dibentuk menggunakan panggilan sistem

.
#termasuk
int dengar(int soket, int tunggakan);

Linux boleh mengehadkan bilangan sambungan yang belum selesai yang boleh diadakan dalam baris gilir. Mengikut panggilan maksimum ini

menetapkan panjang baris gilir kepada . Sambungan masuk yang tidak melebihi panjang giliran maksimum disimpan menunggu soket; permintaan sambungan seterusnya akan ditolak dan percubaan sambungan klien akan gagal. Mekanisme ini dilaksanakan melalui panggilan supaya permintaan sambungan yang belum selesai dapat dikekalkan semasa program pelayan sedang sibuk memproses permintaan pelanggan sebelumnya. Selalunya parameter ialah 5. Ia akan mengembalikan 0 pada kejayaan dan -1 pada ralat. Seperti dengan panggilan sistem, ralat boleh ditunjukkan oleh pemalar, DAN.

Menerima permintaan sambungan

Selepas mencipta dan menamakan soket, program pelayan boleh menunggu permintaan untuk membuat sambungan ke soket menggunakan panggilan sistem

:
#termasuk
int terima(int soket, struct sockaddr *alamat, saiz_t *alamat_len);

Panggilan sistem

mengembalikan kawalan apabila program klien cuba menyambung ke soket yang dinyatakan dalam parameter. Pelanggan ini adalah yang pertama menunggu sambungan dalam baris gilir soket ini. Fungsi ini mencipta soket baharu untuk komunikasi dengan pelanggan dan mengembalikan pemegangnya. Soket baharu akan menjadi jenis yang sama seperti soket pelayan yang mendengar permintaan sambungan.

Soket mesti diberi nama terlebih dahulu menggunakan panggilan sistem

dan ia mesti mempunyai baris gilir permintaan sambungan, ruang yang diperuntukkan oleh panggilan sistem. Alamat pelanggan yang memanggil akan diletakkan dalam struktur yang ditunjukkan oleh parameter. Jika alamat pelanggan tidak diminati, parameter ini boleh ditetapkan kepada penuding nol.

Parameter

menentukan panjang struktur alamat klien. Jika alamat pelanggan lebih panjang daripada nilai ini, ia akan dipotong. Sebelum membuat panggilan, panjang alamat yang dijangkakan mesti dinyatakan dalam parameter. Selepas kembali dari panggilan, panjang sebenar struktur alamat pelanggan yang meminta sambungan akan diwujudkan.

Jika tiada permintaan sambungan menunggu dalam baris gilir soket, panggilan untuk menerima akan disekat (jadi program tidak dapat meneruskan pelaksanaan) sehingga klien membuat permintaan sambungan. Anda boleh menukar tingkah laku ini dengan menggunakan bendera

pada deskriptor fail soket dengan memanggilnya dalam program anda seperti ini:
int bendera = fcntl(soket, F_GETFL, 0);
fcntl(soket, F_SETFL, O_NONBLOCK | bendera);
mengembalikan deskriptor fail soket baharu jika terdapat permintaan pelanggan menunggu sambungan, dan -1 jika terdapat ralat. Nilai ralat yang mungkin adalah sama seperti untuk panggilan ditambah pemalar tambahan dalam kes di mana bendera ditetapkan dan tiada permintaan sambungan yang belum selesai. Ralat akan berlaku jika proses terganggu semasa menyekat dalam fungsi.

Permintaan sambungan

Program pelanggan menyambung ke pelayan dengan mewujudkan sambungan antara soket yang tidak dinamakan dan soket pelayan yang mendengar sambungan. Mereka melakukan ini dengan menelefon

:
#termasuk
int connect(int socket, const struct sockaddr *alamat, size_t address_len);

Soket yang dinyatakan dalam parameter

, menyambung ke soket pelayan yang dinyatakan dalam parameter, yang panjangnya sama dengan . Soket mesti ditentukan oleh deskriptor fail yang sah yang diperoleh daripada panggilan sistem.

Jika fungsi

selesai dengan jayanya, ia mengembalikan 0, sekiranya berlaku ralat ia akan mengembalikan -1. Ralat yang mungkin kali ini termasuk nilai yang disenaraikan dalam jadual. 15.3.

Jadual 15.3

Jika sambungan tidak dapat diwujudkan dengan segera, hubungi

akan disekat untuk tempoh menunggu yang tidak ditentukan. Apabila tamat masa yang dibenarkan melebihi, sambungan akan diputuskan dan panggilan akan ditamatkan secara tidak normal. Walau bagaimanapun, jika panggilan diganggu oleh isyarat yang sedang diproses, sambung akan gagal (dengan errno ditetapkan kepada ) tetapi percubaan sambungan tidak akan dibatalkan - sambungan akan dibuat secara tidak segerak dan program perlu menyemak kemudian untuk melihat sama ada ia ditubuhkan dengan jayanya.

Seperti dalam kes panggilan

, kemungkinan menyekat dalam panggilan boleh dikecualikan dengan menetapkan bendera dalam deskriptor fail. Dalam kes ini, jika sambungan tidak dapat diwujudkan serta-merta, panggilan akan gagal dengan pembolehubah sama dengan , dan sambungan akan dibuat secara tidak segerak.

Walaupun sambungan tak segerak sukar dikendalikan, anda boleh menggunakan panggilan tersebut

kepada deskriptor fail soket untuk memastikan soket sedia untuk ditulis. Kita akan membincangkan cabaran itu sedikit kemudian dalam bab ini.

Menutup soket

Anda boleh menamatkan sambungan soket dalam pelayan atau program klien dengan memanggil fungsi tersebut

, sama seperti dalam kes deskriptor fail peringkat rendah. Soket hendaklah ditutup pada kedua-dua hujungnya. Pada pelayan, ini perlu dilakukan apabila ia mengembalikan sifar. Harap maklum bahawa panggilan mungkin disekat jika soket yang mempunyai data yang tidak dihantar adalah daripada jenis berorientasikan sambungan dan . Anda akan mengetahui lebih lanjut tentang menetapkan pilihan soket kemudian dalam bab ini.

Komunikasi menggunakan soket

Memandangkan kita telah menerangkan panggilan sistem asas yang dikaitkan dengan soket, mari kita lihat lebih dekat pada contoh program. Anda akan cuba mengolahnya semula dengan menggantikan soket sistem fail dengan soket rangkaian. Kelemahan soket sistem fail ialah jika pengarang tidak menggunakan nama fail penuh, ia dicipta dalam direktori semasa program pelayan. Untuk menjadikannya berguna dalam kebanyakan kes, anda harus mencipta soket dalam direktori awam (seperti /tmp) yang sesuai untuk pelayan dan pelanggannya. Dalam kes pelayan rangkaian, sudah cukup untuk memilih nombor port yang tidak digunakan.

Contohnya, pilih nombor port 9734. Ini adalah pilihan sewenang-wenangnya untuk mengelak daripada menggunakan port perkhidmatan standard (anda tidak boleh menggunakan nombor port yang lebih rendah daripada 1024 kerana ia dikhaskan untuk kegunaan sistem). Nombor port lain dan perkhidmatan yang mereka sediakan selalunya disenaraikan dalam fail sistem /etc/services. Apabila menulis program yang menggunakan soket, sentiasa pilih nombor port yang tiada dalam fail konfigurasi ini.

Catatan

Anda harus sedar bahawa terdapat ralat yang disengajakan dalam program client2.c dan server2.c, yang anda akan betulkan dalam program client3.c dan server3.c. Tolong jangan gunakan teks contoh client2.c dan server2.c dalam program anda sendiri.

Anda akan menjalankan program pelayan dan klien anda pada rangkaian tempatan, tetapi soket rangkaian berguna bukan sahaja pada rangkaian tempatan; mana-mana mesin dengan sambungan Internet (walaupun talian dail) boleh menggunakan soket rangkaian untuk berkomunikasi dengan komputer lain . Program berdasarkan jaringan rangkaian, boleh digunakan walaupun pada komputer UNIX terpencil, kerana komputer sedemikian biasanya dikonfigurasikan untuk menggunakan rangkaian maya atau rangkaian gelung balik dalaman yang hanya merangkumi dirinya sendiri. Untuk tujuan tunjuk cara, contoh ini menggunakan rangkaian maya, yang juga boleh berguna untuk menyahpepijat aplikasi rangkaian kerana ia menghapuskan sebarang luaran masalah rangkaian.

Rangkaian maya terdiri daripada satu komputer, secara tradisinya dipanggil

, dengan alamat IP standard 127.0.0.1. Ini adalah mesin tempatan. Anda boleh mencari alamatnya dalam fail nod rangkaian etc/hosts, bersama-sama dengan nama dan alamat nod lain yang disertakan dalam rangkaian kongsi.

Setiap rangkaian yang komputer berkomunikasi mempunyai antara muka perkakasan yang dikaitkan dengannya. Komputer pada setiap rangkaian boleh mempunyai nama sendiri dan, sudah tentu, akan mempunyai alamat IP yang berbeza. Sebagai contoh, mesin Neil bernama tilde mempunyai tiga antara muka rangkaian dan oleh itu tiga alamat. Ia ditulis dalam fail /etc/hosts seperti berikut.

127.0.0.1 localhost # Gelung
192.168.1.1 tilde.localnet # Rangkaian Ethernet peribadi tempatan
158.152.X.X tilde.demon.co.uk # Talian komunikasi modem

Baris pertama ialah contoh rangkaian maya, rangkaian kedua diakses menggunakan penyesuai Ethernet, dan yang ketiga ialah pautan modem kepada pembekal perkhidmatan Internet. Anda boleh menulis program yang menggunakan soket rangkaian untuk berkomunikasi dengan pelayan menggunakan mana-mana antara muka yang diberikan tanpa sebarang pengubahsuaian.

Lengkapkan latihan 15.3 dan 15.4.

Latihan 15.3. Pelanggan rangkaian

Berikut ialah program klien client2.c diubah suai yang direka bentuk untuk menggunakan sambungan rangkaian berasaskan soket dalam rangkaian maya. Ia mengandungi pepijat pergantungan perkakasan kecil, tetapi kita akan membincangkannya kemudian dalam bab ini.

1. Sertakan arahan yang diperlukan

dan tetapkan pembolehubah:
#termasuk
#termasuk
struct alamat sockaddr_in;

2. Buat soket pelanggan:

3. Berikan nama kepada soket seperti yang dipersetujui dengan pelayan:

address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_port = 9734;

Selebihnya program adalah sama seperti contoh yang diberikan sebelum ini dalam bab ini. Apabila anda menjalankan versi ini, ia akan ranap kerana tiada pelayan berjalan pada port 9734 pada komputer ini.

oops: klien2: Sambungan ditolak

Bagaimana ia berfungsi

Program klien menggunakan struktur

daripada fail pengepala netinet/in.h untuk menetapkan alamat. Dia cuba menyambung ke pelayan yang dihoskan pada hos dengan alamat IP 127.0.0.1. Program ini menggunakan fungsi untuk menukar perwakilan teks alamat IP kepada bentuk yang sesuai untuk pengalamatan soket. Lihat halaman bantuan dalam talian untuk inet untuk mendapatkan maklumat lanjut tentang fungsi terjemahan alamat lain. Latihan 15.4. Pelayan rangkaian

Anda juga perlu mengubah suai program pelayan yang mendengar sambungan pada nombor port yang anda pilih. Berikut ialah pelayan program pelayan yang diperbetulkan2.c.

1. Masukkan fail pengepala yang diperlukan dan tetapkan pembolehubah:

#termasuk
#termasuk

int server_sockfd, client_sockfd;

2. Buat soket tanpa nama untuk pelayan:

3. Berikan nama soket:

server_address.sin_port.s_addr = inet_addr("127.0.0.1");
alamat_pelayan.sin_port = 9734;
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

Bagaimana ia berfungsi

Program pelayan mencipta soket domain

dan melakukan tindakan yang perlu untuk menerima permintaan untuk menyambung kepadanya. Soket berkomunikasi dengan port yang anda pilih. Alamat yang diberikan menentukan mesin yang dibenarkan untuk disambungkan. Dengan menentukan alamat rangkaian maya yang sama seperti dalam program klien, anda mengehadkan sambungan kepada mesin tempatan sahaja.

Jika anda ingin membenarkan pelayan mewujudkan sambungan dengan klien jauh, anda mesti menentukan set alamat IP yang dibenarkan. Anda boleh menggunakan nilai istimewa

untuk menunjukkan bahawa anda akan menerima permintaan sambungan daripada semua antara muka yang tersedia pada komputer anda. Jika perlu, anda boleh menanda antara muka pada rangkaian yang berbeza untuk memisahkan sambungan LAN daripada sambungan WAN. Pemalar ialah integer 32-bit yang boleh digunakan dalam medan struktur alamat. Tetapi pertama-tama anda perlu menyelesaikan masalah itu.

Pesanan bait pada komputer dan pada rangkaian

Jika anda menjalankan versi pelayan dan program klien yang diberikan pada mesin berdasarkan pemproses Intel yang menjalankan Linux, kemudian gunakan arahan

anda boleh melihat sambungan rangkaian. Perintah ini disertakan dalam kebanyakan sistem UNIX yang dikonfigurasikan untuk rangkaian. Ia memaparkan sambungan pelanggan-pelayan yang menunggu untuk ditutup. Sambungan ditutup selepas kelewatan yang singkat. (Sekali lagi, output mungkin berbeza antara versi Linux.)
$ ./server2 & ./client2
Sambungan Internet aktif (tanpa pelayan)
tcp 1 0 localhost:1574 localhost:1174 TIME_WAIT root
Catatan

Sebelum mencuba contoh seterusnya dalam bab ini, pastikan anda selesai menjalankan program contoh sebelah pelayan kerana ia akan bersaing untuk sambungan pelanggan dan anda akan melihat hasil yang mengelirukan. Anda boleh mengalih keluar kesemuanya (termasuk yang disenaraikan kemudian dalam bab ini) dengan arahan berikut:

membunuh pelayan1 pelayan2 pelayan3 pelayan4 pelayan5

Anda akan dapat melihat nombor port yang diberikan kepada sambungan pelayan-pelanggan. Alamat tempatan mewakili pelayan, dan alamat luaran mewakili klien jauh. (Walaupun klien dihoskan pada mesin yang sama, ia masih bersambung melalui rangkaian.) Untuk memastikan semua soket berbeza, port klien biasanya berbeza daripada soket pelayan yang mendengar permintaan sambungan dan unik dalam komputer.

Memaparkan alamat setempat (soket pelayan) 1574 (atau mungkin memaparkan nama perkhidmatan

) dan port yang dipilih dalam contoh ialah 9734. Mengapa mereka berbeza? Hakikatnya ialah nombor port dan alamat dihantar melalui antara muka soket sebagai nombor binari. Komputer yang berbeza menggunakan susunan bait yang berbeza untuk mewakili integer. Sebagai contoh, pemproses Intel menyimpan integer 32-bit sebagai empat bait ingatan berturut-turut dalam susunan berikut 1-2-3-4, di mana bait pertama adalah yang paling ketara. Pemproses IBM PowerPC akan menyimpan integer dalam susunan bait berikut: 4-3-2-1. Jika memori yang digunakan untuk menyimpan integer hanya disalin bait demi bait, kedua-dua komputer tidak akan bersetuju dengan nilai integer.

Untuk membolehkan jenis komputer yang berbeza bersetuju dengan maksud integer berbilang bait yang dihantar melalui rangkaian, adalah perlu untuk menentukan susunan bait rangkaian. Sebelum menghantar data, program klien dan pelayan mesti menukar perwakilan dalaman mereka sendiri integer kepada endianness rangkaian. Ini dilakukan menggunakan fungsi yang ditakrifkan dalam fail pengepala netinet/in.h. Ini termasuk yang berikut:

#termasuk
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);

Fungsi ini menukar integer 16- dan 32-bit daripada tertib bait rangkaian asli kepada dan sebaliknya. Nama mereka sepadan dengan nama singkatan bagi transformasi yang dilakukan, sebagai contoh, "hos kepada rangkaian, panjang" (htonl, komputer ke rangkaian, integer panjang) dan "hos kepada rangkaian, pendek" (hton, komputer ke rangkaian, integer pendek) . Untuk komputer dengan susunan bait rangkaian, fungsi ini menyediakan operasi kosong.

Untuk memastikan susunan yang betul apabila menghantar integer 16-bit, pelayan dan klien anda mesti menggunakan fungsi ini pada alamat port. Perubahan berikut hendaklah dibuat pada program server3.c:

server_address.sin_addr_s_addr = htonl(INADDR_ANY);

Keputusan dikembalikan oleh fungsi

, tidak perlu menukar kerana, mengikut definisi, ia mengembalikan hasil dalam susunan bait rangkaian. Perubahan berikut perlu dibuat pada program client3.c:
address.sin_port = htons(9734);

Kepada pelayan, terima kasih kepada penggunaan pemalar

, perubahan telah dibuat untuk membenarkan permintaan sambungan diterima daripada mana-mana alamat IP.

Sekarang, dengan melaksanakan program server3 dan client3, anda akan melihat nombor port yang betul digunakan untuk sambungan tempatan:

Proto Recv-Q Send-Q Alamat Tempatan Pengguna Alamat Asing (Negeri).
tcp 1 0 localhost:9734 localhost:1175 TIME_WAIT root
Catatan

Jika anda menggunakan komputer yang mempunyai format sendiri Perwakilan integer adalah sama seperti susunan bait rangkaian, anda tidak akan melihat sebarang perbezaan. Tetapi untuk memastikan interaksi yang betul antara pelanggan dan pelayan dengan seni bina yang berbeza, adalah penting untuk sentiasa menggunakan fungsi penukaran.

Maklumat rangkaian

Sehingga kini, program klien dan pelayan mempunyai alamat dan nombor port yang disusun ke dalamnya. Dalam program pelayan dan klien yang lebih umum, anda boleh menggunakan data rangkaian untuk menentukan alamat dan port yang hendak digunakan.

Jika anda mempunyai kebenaran untuk berbuat demikian, anda boleh menambah pelayan anda pada senarai perkhidmatan yang diketahui dalam fail /etc/services, yang memberikan nama kepada nombor port supaya pelanggan boleh menggunakan nama perkhidmatan simbolik dan bukannya nombor.

Dengan cara yang sama, mengetahui nama komputer, anda boleh menentukan alamat IP dengan memanggil fungsi pangkalan data hos yang akan mencari alamat ini. Mereka melakukan ini dengan merujuk fail konfigurasi seperti dll/hos atau perkhidmatan maklumat rangkaian seperti NIS (Perkhidmatan Maklumat Rangkaian, dahulunya dikenali sebagai Yellow Pages) dan DNS ( Nama domain Perkhidmatan, perkhidmatan nama domain).

Fungsi pangkalan data Hos diisytiharkan dalam fail pengepala antara muka netdb.h:

struct hostent *gethostbyaddr(const void* addr, size_t len, int type);
struct hostent* gethostbyname(const char* name);

Struktur yang dikembalikan oleh fungsi ini mestilah, sekurang-kurangnya, mengandungi elemen berikut.

char *h_name; /* Nama nod */
char **h_aliases; /* Senarai nama panggilan */
int h_addrtype; /* Jenis Alamat */
int h_length; /* Panjang alamat dalam bait */
char **h_addr_list /* Senarai alamat (urutan bait rangkaian) */

Jika tiada kemasukan dalam pangkalan data yang sepadan dengan hos atau alamat yang diberikan, fungsi maklumat akan mengembalikan penunjuk nol.

Begitu juga, maklumat mengenai perkhidmatan dan nombor port yang berkaitan boleh diperoleh menggunakan fungsi maklumat perkhidmatan:

struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);

Parameter

menentukan protokol yang akan digunakan untuk menyambung kepada perkhidmatan, sama ada "tcp" untuk sambungan TCP jenis , atau "udp" untuk datagram UDP jenis .

Struktur

mengandungi sekurang-kurangnya elemen berikut:
char *s_name; /* Nama perkhidmatan */
char **s_aliases; /* Senarai alias (nama tambahan) */
int s_port; /* Nombor port IP */
char *s_proto; /* Jenis perkhidmatan, biasanya "tcp" atau "udp" */

Anda boleh mengumpulkan maklumat komputer daripada pangkalan data hos dengan memanggil fungsi tersebut

dan memaparkan hasilnya. Ambil perhatian bahawa alamat mesti ditukar kepada jenis yang sesuai dan pergi daripada pengumpulan rangkaian kepada rentetan boleh cetak menggunakan penukaran yang ditakrifkan seperti berikut:
#termasuk
char *inet_ntoa(struct in_addr in);

Fungsi ini menukar alamat nod Internet menjadi rentetan format empat kali bertitik. Ia mengembalikan -1 pada ralat, tetapi standard POSIX tidak menentukan ralat tertentu. Satu lagi ciri baharu yang akan anda gunakan ialah

:
int gethostname(char *nama, panjang nama int);

Fungsi ini menulis nama nod semasa kepada rentetan yang ditentukan oleh parameter

. Nama nod akan menjadi rentetan yang ditamatkan nol. Argumen mengandungi panjang nama rentetan dan jika nama nod yang dikembalikan melebihi panjang ini, ia akan dipotong. Fungsi mengembalikan 0 pada kejayaan dan -1 pada ralat. Sekali lagi, pepijat tidak ditakrifkan dalam standard POSIX.

Lakukan Latihan 15.5.

Latihan 15.5. Maklumat rangkaian

Program ini getname.c mendapatkan semula maklumat tentang komputer.

1. Seperti biasa, masukkan fail pengepala yang sesuai dan isytiharkan pembolehubah:


char *hos, **nama, **addrs;

2. Berikan kepada pembolehubah

nilai hujah yang diberikan semasa memanggil program, atau secara lalai nama mesin pengguna:

3. Panggil fungsi gethostbyname dan laporkan ralat jika tiada maklumat ditemui:

fprintf(stderr, "tidak boleh mendapatkan maklumat untuk hos: %s\n", hos);

4. Paparkan nama nod dan sebarang alias yang mungkin ada:

printf("hasil untuk hos %s:\n", hos);
printf("Nama: %s\n", info hos->h_name);
printf("%s", *nama); nama++;

5. Jika nod yang diminta bukan nod IP, laporkan ini dan lengkapkan pelaksanaan:

jika (info hos->h_addrtype != AF_INET) (
fprintf(stderr, "bukan hos IP!\n");

6. Jika tidak, cetak alamat IP:

addrs = hostinfo->h_addr_list;
printf("%s", inet_ntoa(*(struct in_addr*)*addrs));

Untuk menentukan hos dengan alamat IP yang diberikan, anda boleh menggunakan fungsi tersebut

. Anda boleh menggunakannya pada pelayan untuk mengetahui dari mana pelanggan meminta sambungan.

Bagaimana ia berfungsi

Program getname memanggil fungsi gethostbyname untuk mendapatkan maklumat hos daripada pangkalan data hos. Ia memaparkan nama komputer, aliasnya (nama lain yang mana komputer itu dikenali), dan alamat IP yang digunakan pada antara muka rangkaiannya. Pada salah satu mesin pengarang, menjalankan contoh dan menentukan nama tilde sebagai hujah menghasilkan output dua antara muka: rangkaian Ethernet dan talian komunikasi modem.

Bila hendak menggunakan nama hos

, rangkaian maya ditetapkan:

Anda kini boleh mengubah suai program klien anda untuk menyambung kepada mana-mana hos bernama pada rangkaian. Daripada menyambung ke pelayan dalam contoh anda, anda akan menyambung ke perkhidmatan standard dan boleh mendapatkan semula nombor port.

Kebanyakan sistem UNIX dan beberapa sistem pengendalian Linux menjadikan masa dan tarikh sistem mereka tersedia sebagai perkhidmatan standard yang dipanggil

. Pelanggan boleh menyambung ke perkhidmatan ini untuk mengetahui pendapat pelayan tentang masa dan tarikh semasa. Latihan 15:6 menunjukkan program pelanggan, getdate.c, yang melakukan perkara itu. Latihan 15.6. Sambungan kepada perkhidmatan standard

1. Mulakan dengan arahan biasa

dan iklan:
int utama(int argc, char *argv) (

2. Cari alamat hos dan laporkan ralat jika alamat tidak ditemui:

hostinfo = gethostbyname(host);

3. Pastikan terdapat perkhidmatan pada komputer anda

:
servinfo = getservbyname("siang hari", "tcp");
printf("port siang hari ialah %d\n", ntohs(servinfo->s_port));

4. Buat soket:

sockfd = soket(AF_INET, SOCK_STREAM, 0);

5. Buat alamat untuk sambungan:

alamat.keluarga_dosa = AF_INET;
address.sin_port = servinfo->s_port;
address.sin_addr = *(struct in_addr *)*hostinfo->h_addr_list;

6. Kemudian sambung dan dapatkan maklumat:

hasil = sambung(sockfd, (struct sockaddr *)&alamat, len);
result = read(sockfd, buffer, sizeof(buffer));

Anda boleh menggunakan program ini

untuk mendapatkan masa hari daripada mana-mana nod rangkaian yang diketahui.
baca 26 bait: 24 JUN 2007 06:03:03 BST

Jika anda menerima mesej ralat seperti

oops: getdate: Sambungan ditolak
oops: getdate: Tiada fail atau direktori sedemikian

sebabnya mungkin perkhidmatan itu tidak didayakan pada komputer yang anda sambungkan

. Tingkah laku ini telah menjadi standard pada kebanyakan sistem Linux moden. Dalam bahagian seterusnya, anda akan melihat cara mendayakan perkhidmatan ini dan perkhidmatan lain.

Bagaimana ia berfungsi

Apabila anda menjalankan program ini, anda boleh menentukan hos untuk disambungkan. Nombor port perkhidmatan

ditakrifkan oleh fungsi pangkalan data rangkaian, yang mengembalikan maklumat tentang perkhidmatan rangkaian dengan cara yang sama seperti semasa mendapatkan maklumat tentang nod rangkaian. Program ini cuba menyambung ke alamat yang disenaraikan dahulu dalam senarai alamat tambahan untuk hos yang ditentukan. Jika sambungan berjaya, program membaca maklumat yang dikembalikan oleh perkhidmatan siang hari, rentetan aksara yang mengandungi tarikh dan masa sistem.

Daemon Internet (xinetd/inetd)

Sistem UNIX yang menyediakan beberapa perkhidmatan rangkaian sering melakukannya menggunakan superserver. Program ini (daemon Internet xinetd atau inetd) mendengar secara serentak untuk permintaan sambungan dengan berbilang alamat port. Apabila pelanggan menyambung kepada perkhidmatan, program daemon memulakan pelayan yang sepadan. Dengan pendekatan ini, pelayan tidak perlu berjalan secara berterusan; ia boleh dilancarkan atas permintaan.

Catatan

Dalam sistem Linux moden, peranan daemon Internet dimainkan oleh program xinetd. Ia menggantikan program UNIX asal inetd, yang anda masih boleh temui dalam versi yang lebih terkini. sistem awal Linux dan sistem seperti UNIX yang lain.

Program xinetd biasanya dikonfigurasikan menggunakan antara muka pengguna grafik untuk mengurus perkhidmatan rangkaian, tetapi anda juga boleh mengedit fail konfigurasi program secara langsung. Ini termasuk fail /etc/xinetd.conf dan fail dalam direktori /etc/xinetd.d.

Setiap perkhidmatan yang disediakan oleh xinetd mempunyai fail konfigurasi dalam direktori /etc/xinetd.d. xinetd membaca semua fail konfigurasi ini semasa permulaan dan sekali lagi apabila ia menerima arahan yang sesuai.

.
# Lalai: dilumpuhkan
# Penerangan: pelayan siang hari. Ini adalah versi tcp.

Fail konfigurasi berikut adalah untuk perkhidmatan pemindahan fail.

# Lalai: dilumpuhkan
# Pelayan FTP vsftpd mengendalikan sambungan FTP. Dia menggunakan
# untuk pengesahan, nama pengguna biasa, tidak disulitkan dan
# kata laluan, vsftpd direka bentuk untuk selamat.
# Nota: Fail ini mengandungi konfigurasi permulaan vsftpd untuk xinetd.
# Fail konfigurasi untuk program vsftpd itu sendiri terletak di
#log_on_success += DURATION USERID
Soket yang disambungkan oleh program biasanya dikendalikan oleh program xinetd itu sendiri (ia ditandakan dalaman) dan boleh didayakan menggunakan sama ada jenis soket (tcp) atau jenis soket (udp).

Perkhidmatan pemindahan fail

menyambung hanya dengan soket jenis dan disediakan oleh program luaran, dalam kes ini vsftpd. Daemon akan menjalankan program luaran ini apabila pelanggan menyambung ke port .

Untuk mendayakan perubahan konfigurasi perkhidmatan, anda boleh mengedit konfigurasi xinetd dan menghantar isyarat tutup telefon kepada proses daemon, tetapi kami mengesyorkan menggunakan cara yang lebih mesra untuk mengkonfigurasi perkhidmatan. Untuk membenarkan pelanggan anda menyambung ke perkhidmatan

, dayakan perkhidmatan ini menggunakan alatan yang disediakan oleh sistem Linux. Pada sistem SUSE dan openSUSE, perkhidmatan boleh dikonfigurasikan daripada SUSE Pusat Kawalan(Pusat Kawalan SUSE) seperti yang ditunjukkan dalam Rajah. 15.1. Versi topi merah(kedua-dua Enterprise Linux dan Fedora) mempunyai antara muka konfigurasi yang serupa. Ia membolehkan perkhidmatan untuk permintaan TCP dan UDP.

nasi. 15.1


Untuk sistem yang menggunakan inetd dan bukannya xinetd, berikut ialah ekstrak setara daripada fail konfigurasi inetd, /etc/inetd.conf, yang inetd gunakan untuk menentukan masa untuk memulakan pelayan:

#
# Gema, buang, siang hari dan cas digunakan terutamanya untuk
strim siang tcp nowait root internal
siang hari dgram udp tunggu akar dalaman
# Ini adalah perkhidmatan standard.
strim ftp tcp-nowait root /usr/sbin/tcpd /usr/sbin/wu.ftpd
strim telnet tcp nowait root /usr/sbin/tcpd /usr/sbin/in.telnetd
# Tamat fail inetd.conf.

Ambil perhatian bahawa dalam contoh kami, perkhidmatan ftp disediakan oleh program luaran wu.ftpd. Jika sistem anda menjalankan daemon inetd, anda boleh menukar set perkhidmatan yang disediakan dengan mengedit fail /etc/inetd.conf (a # pada permulaan baris menunjukkan bahawa ini adalah baris ulasan) dan memulakan semula proses inetd. Ini boleh dilakukan dengan menghantar isyarat gantung menggunakan arahan

. Untuk memudahkan proses ini, sesetengah sistem dikonfigurasikan supaya atur cara inetd menulis IDnya pada fail. Jika tidak, anda boleh menggunakan arahan:

Pilihan soket

Terdapat banyak pilihan yang boleh digunakan untuk mengawal kelakuan sambungan berasaskan soket - terlalu banyak untuk diterangkan secara terperinci dalam bab ini. Untuk memanipulasi parameter gunakan fungsi

:
#termasuk
int setsockopt(int soket, int level, int option_name,
const void *nilai pilihan, size_t pilihan len);

Anda boleh menetapkan parameter pada tahap hierarki protokol yang berbeza. Untuk menetapkan pilihan pada tahap soket anda mesti nyatakan

sama rata Untuk menetapkan parameter pada tahap protokol yang lebih rendah (TCP, UDP, dsb.), tetapkan parameter tahap kepada nombor protokol (diperolehi sama ada daripada fail pengepala netinet/in.h atau daripada fungsi ).

Dalam hujah

nama parameter yang ditentukan ditunjukkan; argumen mengandungi nilai panjang bait sewenang-wenang yang dihantar tidak berubah kepada pengendali protokol peringkat rendah.

Parameter tahap soket ditakrifkan dalam fail pengepala sys/socket.h dan termasuk yang ditunjukkan dalam Jadual. 15.4 nilai.


Jadual 15.5

Pilihan

dan ambil nilai integer untuk set atau dayakan (1) dan set semula atau lumpuhkan (0). Parameter memerlukan struktur jenis , yang ditakrifkan dalam fail sys/socket.h, yang menentukan keadaan parameter dan nilai selang kelewatan. mengembalikan 0 jika berjaya dan -1 sebaliknya. Halaman panduan bantuan dalam talian menerangkan Pilihan tambahan dan kesilapan.

Berbilang Pelanggan

Setakat ini dalam bab ini, anda telah melihat cara soket digunakan untuk melaksanakan sistem pelayan-pelanggan, tempatan dan beroperasi pada rangkaian. Setelah sambungan berasaskan soket diwujudkan, ia berkelakuan seperti deskriptor fail terbuka peringkat rendah dan seperti paip dwiarah.

Sekarang kita perlu mempertimbangkan kes berbilang pelanggan yang menyambung ke pelayan secara serentak. Anda telah melihat bahawa apabila program pelayan menerima permintaan sambungan daripada klien, soket baharu dicipta dan soket asal mendengar permintaan sambungan kekal tersedia untuk permintaan seterusnya. Jika pelayan tidak dapat menerima segera permintaan sambungan kemudian, mereka akan kekal dalam baris gilir menunggu.

Hakikat bahawa soket asal masih boleh diakses, dan soket itu berkelakuan seperti deskriptor fail, memberikan kami kaedah untuk melayani ramai pelanggan pada masa yang sama. Jika pelayan memanggil fungsi

untuk mencipta salinan kedua dirinya sendiri, soket terbuka akan diwarisi oleh proses anak baharu. Ia kemudian akan dapat menukar data dengan klien yang disambungkan, manakala pelayan utama akan terus menerima permintaan sambungan seterusnya. Pada hakikatnya, anda perlu membuat perubahan yang sangat mudah pada program pelayan anda, seperti yang ditunjukkan dalam Latihan 15.7.

Memandangkan anda sedang mencipta proses kanak-kanak tetapi tidak menunggu proses itu selesai, anda harus membuat pelayan mengabaikan isyarat

, menghalang kemunculan proses zombi. Latihan 15.7. Pelayan untuk berbilang pelanggan

1. Program server4.c bermula dengan cara yang sama seperti pelayan terakhir yang dibincangkan dengan penambahan penting arahan

untuk isyarat fail pengepala.h. Pembolehubah dan prosedur untuk mencipta dan menamakan soket kekal sama:
int server_sockfd, client_sockfd;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
server_sockfd = soket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_port = htons(9734);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

2. Buat baris gilir sambungan, abaikan butiran penamatan proses anak dan tunggu permintaan pelanggan:

dengar (server_sockfd, 5);
isyarat(SIGCHLD, SIG_IGN);
printf("pelayan menunggu\n");

3. Terima permintaan sambungan:

client_len = sizeof(client_address);
client_sockfd = terima(server_sockfd,
(struct_sockaddr*)&alamat_pelanggan, &klien_len);

4. Panggil

untuk membuat proses untuk pelanggan tertentu dan melakukan semakan untuk menentukan sama ada anda ibu bapa atau anak: . Kelewatan lima saat diperlukan untuk menunjukkan ini:
baca(client_sockfd, &ch, 1);
write(client_sockfd, &ch, 1);
close(client_sockfd);

6. Jika tidak, anda mestilah ibu bapa dan kerja anda dengan pelanggan ini telah selesai:

tutup(soket_pelanggan);

Kod tersebut termasuk kelewatan lima saat apabila memproses permintaan pelanggan untuk mensimulasikan pengiraan pelayan atau akses pangkalan data. Jika anda telah melakukan ini pada pelayan sebelumnya, setiap pelaksanaan program client3 akan mengambil masa lima saat. Dengan pelayan baharu, anda akan dapat memproses berbilang program klien3 klien secara selari dengan jumlah masa berlalu hanya lebih lima saat.

$ ./client3 & ./client3 & ./client3 & ps x

Bagaimana ia berfungsi

Program pelayan kini mencipta proses anak baharu untuk mengendalikan setiap klien, jadi anda mungkin melihat berbilang mesej menunggu pelayan kerana program utama terus menunggu permintaan sambungan baharu. Dalam output arahan

(diedit) menunjukkan pelayan proses induk4 dengan PID 26566 menunggu pelanggan baharu, manakala tiga proses klien klien3 dilayan oleh tiga kanak-kanak pelayan. Selepas jeda lima saat, semua pelanggan menerima keputusan mereka dan keluar. Proses pelayan kanak-kanak juga keluar, meninggalkan hanya satu proses pelayan induk.

Program pelayan menggunakan panggilan

untuk mengendalikan berbilang pelanggan. Dalam aplikasi pangkalan data, ini mungkin bukan penyelesaian terbaik, kerana... Program pelayan boleh menjadi agak besar, dan di samping itu terdapat masalah menyelaraskan panggilan pangkalan data daripada berbilang salinan pelayan. Sebenarnya, anda hanya perlukan satu cara untuk mengendalikan berbilang pelanggan dengan satu pelayan tanpa menyekat dan menunggu permintaan pelanggan dihantar. Penyelesaian kepada masalah ini melibatkan pemprosesan berbilang deskriptor fail terbuka secara serentak dan tidak terhad kepada aplikasi soket. Mari kita pertimbangkan fungsinya.

pilih

Selalunya semasa membangunkan aplikasi Linux, anda mungkin perlu menyemak status siri input untuk menentukan tindakan seterusnya yang perlu diambil. Sebagai contoh, program komunikasi seperti emulator terminal memerlukan cara yang cekap untuk membaca secara serentak dari papan kekunci dan dari port bersiri. Pada sistem pengguna tunggal, gelung "tunggu aktif" akan berfungsi, berulang kali mengimbas input untuk data dan membacanya sebaik sahaja ia muncul. Tingkah laku ini sangat membazir masa CPU.

Panggilan sistem

membenarkan program menunggu sehingga data tiba (atau output untuk dilengkapkan) pada berbilang deskriptor fail peringkat rendah secara serentak. Ini bermakna program emulator terminal mungkin menyekat sehingga ia mempunyai kerja yang perlu dilakukan. Begitu juga, pelayan boleh berurusan dengan banyak pelanggan dengan menunggu permintaan pada banyak soket terbuka secara serentak. beroperasi pada struktur data, yang merupakan set deskriptor fail terbuka. Untuk memproses set ini, satu set makro ditakrifkan:
#termasuk #termasuk
batal FD_ZERO(fd_set *fdset);
batal FD_CLR(int fd, fd_set *fdset);
batal FD_SET(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);

Seperti yang dicadangkan oleh nama mereka, makro

memulakan struktur kepada set kosong, dan menetapkan dan mengosongkan elemen set yang sepadan dengan deskriptor fail yang diluluskan sebagai parameter, dan makro mengembalikan nilai bukan sifar jika deskriptor fail yang dirujuk oleh ialah elemen struktur yang ditunjuk oleh parameter. Bilangan maksimum deskriptor fail dalam struktur jenis ditentukan oleh pemalar. juga boleh menggunakan nilai untuk tamat masa untuk mengelakkan sekatan tak terhingga. Nilai ini ditentukan menggunakan struktur. Ia ditakrifkan dalam fail sys/time.h dan mengandungi elemen berikut:
time_t tv_sec; /* Saat */
tv_usec panjang; /* Mikrosaat */
, ditakrifkan dalam sys/types.h, ialah integer. Panggilan sistem diisytiharkan seperti berikut:
#termasuk
int pilih(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *errorfds, struct timeval *timeout);

Panggil. Jika parameter adalah penunjuk nol dan tiada aktiviti pada soket, panggilan mungkin disekat selama-lamanya. mengembalikan kawalan kepada atur cara, set deskriptor akan diubah suai untuk menunjukkan baca atau tulis sedia atau deskriptor ralat. Untuk menyemaknya, anda harus menggunakan makro untuk menentukan deskriptor yang memerlukan perhatian. Adalah mungkin untuk menukar nilai tamat masa untuk menunjukkan baki masa sebelum tamat masa seterusnya berlaku, tetapi tingkah laku ini tidak ditentukan oleh piawaian X/Open. Jika tamat masa melebihi, semua set pemegang akan dikosongkan.

Panggilan pilih mengembalikan jumlah bilangan pemegang dalam set yang diubah suai. Jika ia gagal ia akan mengembalikan -1 dan menetapkan nilai pembolehubah

, menerangkan ralat. Kemungkinan kesilapan - Lakukan senaman 15.8. Latihan 15.8. Fungsi

Program berikut menunjukkan cara menggunakan fungsi pilih: pilih.c. Lagi contoh yang kompleks anda akan melihat sedikit kemudian. Program membaca data dari papan kekunci ( input standard- deskriptor 0) dengan masa menunggu selama 2.5 saat. Data dibaca hanya apabila input sudah sedia. Adalah wajar untuk melanjutkan program untuk memasukkan deskriptor lain, seperti talian bersiri dan soket, bergantung pada sifat aplikasi.

1. Mulakan seperti biasa dengan arahan

dan pengisytiharan, dan kemudian mulakan untuk mengendalikan input papan kekunci:

2. Tunggu input daripada fail stdin selama maksimum 2.5 saat:

hasil = pilih(FD_SETSIZE, &testfds, (fd_set *)NULL,

3. Selepas masa ini, semak

. Jika tiada input, program akan melaksanakan gelung sekali lagi. Jika ralat berlaku, program akan keluar:

4. Jika anda mengalami beberapa aktiviti berkaitan deskriptor fail semasa menunggu, baca input daripada stdin dan cetaknya apabila aksara EOL (akhir baris) diterima, sebelum kombinasi kekunci ditekan +:

$ Pelayan boleh menggunakan fungsi tersebut

serentak ke soket menunggu permintaan sambungan dan ke soket sambungan klien. Setelah aktiviti telah ditangkap, makro boleh digunakan untuk mengulangi semua deskriptor fail yang mungkin dan melihat yang mana yang aktif.

Jika soket mendengar permintaan sambungan sedia untuk input, itu bermakna pelanggan cuba menyambung dan anda boleh memanggil fungsi

tanpa risiko menyekat. Jika pemegang pelanggan menunjukkan sedia, ini bermakna terdapat permintaan pelanggan menunggu anda untuk membaca dan memprosesnya. Membaca 0 bait bermakna proses klien telah keluar dan anda boleh menutup soket dan mengeluarkannya daripada set pemegang anda.

Lakukan Latihan 15.9.

Latihan 15.9. Aplikasi pelayan pelanggan yang dipertingkatkan

1. Dalam contoh terakhir program server5.c, anda akan memasukkan fail pengepala sys/time.h dan sys/ioctl.h bukannya signal.h yang digunakan dalam atur cara sebelumnya dan mengisytiharkan beberapa pembolehubah tambahan untuk mengendalikan panggilan

:
int server_sockfd, client_sockfd;
struct sockaddr_in server_address;
struct sockaddr_in client_address;

2. Buat soket untuk pelayan dan beri nama:

server_sockfd = soket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(9734);
server_len = sizeof(server_address);
bind(serversockfd, (struct sockaddr *)&server_address, server_len);

3. Buat baris gilir permintaan sambungan dan mulakan set

untuk memproses input soket
server_sockfd

6. Jika aktiviti direkodkan pada

, ia boleh menjadi permintaan untuk sambungan baharu dan anda menambah yang sesuai pada set deskriptor:
client_len = sizeof(client_address);
client_sockfd = terima(server_sockfd,
(struct sockaddr*)&client_address, &client_len);
FD_SET(client_sockfd, &readfds);
printf("menambah klien pada fd %d\n", client_sockfd);

Jika bukan pelayan yang aktif, maka klien aktif. Jika diterima

, pelanggan hilang dan boleh dialih keluar daripada set pemegang. Jika tidak, anda "melayan" pelanggan, seperti dalam contoh sebelumnya.
printf("mengalih keluar klien pada fd %d\n", fd);

Untuk kesempurnaan, analogi yang disebutkan pada permulaan bab adalah dalam Jadual. Rajah 15.5 menunjukkan persamaan antara sambungan berasaskan soket dan perbualan telefon.


Jadual 15.5

Datagram

Dalam bab ini, kami menumpukan pada aplikasi pengaturcaraan yang berkomunikasi dengan pelanggan mereka menggunakan sambungan TCP berasaskan soket. Terdapat situasi di mana kos menyediakan dan menyelenggara sambungan soket tidak diperlukan.

Satu contoh yang baik ialah perkhidmatan

, digunakan lebih awal dalam program getdate.c. Anda membuat soket, membuat sambungan, membaca satu respons dan menutup sambungan. Begitu banyak operasi untuk mendapatkan tarikh! juga tersedia melalui sambungan UDP menggunakan datagram. Untuk menggunakannya, hanya hantar satu datagram kepada perkhidmatan dan terima satu datagram sebagai respons yang mengandungi tarikh dan masa. Mudah sahaja.

Perkhidmatan yang disediakan melalui protokol UDP digunakan dalam kes di mana pelanggan perlu membuat permintaan ringkas kepada pelayan, dan ia menjangkakan satu respons pendek. Jika kos masa CPU cukup rendah, pelayan dapat menyediakan perkhidmatan sedemikian dengan memproses permintaan pelanggan satu demi satu dan membenarkan sistem pengendalian mengekalkan barisan permintaan yang masuk. Pendekatan ini memudahkan pengaturcaraan pelayan.

Oleh kerana UDP ialah perkhidmatan tidak terjamin, anda mungkin mengalami kehilangan datagram atau respons pelayan anda. Jika data itu penting kepada anda, anda mungkin perlu memprogramkan klien UDP anda dengan teliti, menyemak ralat dan mencuba semula jika perlu. Dalam amalan, datagram UDP sangat dipercayai pada rangkaian tempatan.

Untuk mengakses perkhidmatan yang disediakan oleh protokol UDP, anda harus menggunakan panggilan sistem

dan , tetapi bukannya menggunakan dan pada soket, anda menggunakan dua panggilan sistem khusus untuk datagram: dan .
/* Mulakan dengan termasuk dan pengisytiharan biasa. */

int utama(int argc, char *argv) (
if (argc == 1) hos = "localhost";
/* Mencari alamat hos dan melaporkan ralat jika ia tidak menemuinya. */
hostinfo = gethostbyname(host);
fprintf(stderr, "tiada hos: %s\n", hos);
/* Menyemak kehadiran perkhidmatan siang hari pada komputer. */
servinfo = getservbyname("siang hari", "udp");
fprintf(stderr, "tiada perkhidmatan siang hari\n");
printf("port siang hari ialah %d\n", ntohs(servinfo->s_port));
/* Mencipta soket UDP. */
sockfd = soket(AF_INEТ, SOCK_DGRAM, 0);
/* Menjana alamat untuk digunakan dalam sendto/recvfrom... panggilan */
alamat.keluarga_dosa = AF_INET;
address.sin_port = servinfo->s_port;
address.sin_addr = *(struct in_addr*)*hostinfo->h_addr_list;
result = sendto(sockfd, buffer, 1, 0, (struct sockaddr *)&alamat, len);
hasil = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr *)&alamat, &len);
printf("baca %d bait: %s", hasil, penimbal);

Seperti yang anda lihat, hanya perubahan kecil diperlukan. Seperti sebelum ini, anda sedang mencari perkhidmatan

menggunakan panggilan, tetapi menentukan perkhidmatan datagram dengan meminta protokol UDP. Soket datagram dibuat menggunakan panggilan dengan . Alamat destinasi ditentukan seperti sebelum ini, tetapi kini bukannya membaca dari soket, anda mesti menghantar datagram.

Memandangkan anda tidak membuat sambungan yang jelas kepada perkhidmatan berasaskan UDP, anda mesti mempunyai cara untuk memberitahu pelayan bahawa anda mahukan balasan. Dalam kes ini, anda menghantar datagram (dalam contoh kami, anda menghantar satu bait daripada penimbal yang anda mahu menerima respons) kepada perkhidmatan dan ia menghantar tarikh dan masa sebagai balasan.

Panggilan sistem

menghantar datagram dari penimbal ke soket menggunakan alamat soket dan panjang alamat. Panggilan ini sebenarnya mempunyai prototaip berikut:

int sendto(int sockfd, void *buffer, size_t len, int flags,

struct sockaddr *to, socklen_t tolen);

Dalam kes penggunaan biasa, parameter

boleh dibiarkan sifar.

Panggilan sistem recvfrom menunggu datagram pada sambungan soket dengan alamat yang diberikan dan meletakkannya dalam penimbal. Panggilan ini mempunyai prototaip berikut:

int recvfrom(int sockfd, void *buffer, size_t len, int flags, dan tamat masa untuk menentukan sama ada data telah tiba, sama seperti pelayan berasaskan sambungan. Jika tidak, anda boleh menggunakan penggera untuk membatalkan operasi pemerolehan data (lihat bab 11).

Ringkasan

Dalam bab ini, kami mencadangkan cara lain untuk proses berkomunikasi - soket. Mereka membolehkan pembangunan benar-benar diedarkan aplikasi pelanggan-pelayan yang berjalan dalam persekitaran rangkaian. Penerangan ringkas telah diberikan tentang beberapa fungsi maklumat pangkalan data hos rangkaian dan cara Linux mengendalikan perkhidmatan sistem standard menggunakan daemon Internet. Anda telah bekerja melalui beberapa contoh program pelayan-pelanggan yang menunjukkan pemprosesan dan perangkaian berbilang pelanggan.

Kesimpulannya, anda telah menjadi biasa dengan panggilan sistem

, yang membolehkan anda memberitahu program tentang aktiviti input dan output pada beberapa deskriptor dan soket fail terbuka sekaligus.

Paip bernama sesuai untuk mengatur komunikasi antara proses dalam kes proses yang berjalan pada sistem yang sama dan dalam kes proses yang berjalan pada komputer yang disambungkan antara satu sama lain oleh rangkaian tempatan atau global. Keupayaan ini ditunjukkan menggunakan sistem pelayan-pelanggan yang dibangunkan dalam Bab 11, bermula dengan Program 11.2.

Walau bagaimanapun, kedua-dua paip yang dinamakan dan peti mel (yang mana kami akan menggunakan istilah generik "paip bernama" demi kesederhanaan jika perbezaan antara mereka tidak ketara) mempunyai kelemahan bahawa ia bukan standard industri. Ini menyukarkan untuk mengalihkan program seperti yang dibincangkan dalam Bab 11 kepada sistem bukan Windows, walaupun paip yang dinamakan adalah protokol bebas dan boleh dijalankan di atas banyak protokol standard industri seperti TCP/IP.

Keupayaan untuk berinteraksi dengan sistem lain disediakan dalam Windows melalui sokongan untuk soket Windows Sockets - analog yang serasi dan hampir tepat bagi Berkeley Sockets, yang berfungsi sebagai standard industri de facto. Bab ini menggambarkan penggunaan Windows Sockets (atau "Winsock") API menggunakan sistem klien/pelayan yang diubah suai daripada Bab 11. Sistem yang terhasil mampu berjalan dalam rangkaian global, menggunakan protokol TCP/IP, yang, sebagai contoh, membenarkan pelayan menerima permintaan daripada klien UNIX atau mana-mana sistem bukan Windows yang lain.

Pembaca yang biasa dengan antara muka Berkeley Sockets boleh melangkau ke hadapan ke contoh yang bukan sahaja menggunakan soket, tetapi juga memperkenalkan keupayaan pelayan baharu dan menunjukkan teknik tambahan untuk bekerja dengan perpustakaan yang menyediakan sokongan selamat benang.

Dengan memanfaatkan kebolehoperasian berasaskan standard antara sistem heterogen, antara muka Winsock memberikan pengaturcara akses kepada protokol dan aplikasi peringkat tinggi seperti ftp, http, RPC dan COM, yang bersama-sama menyediakan set kaya model peringkat tinggi yang menyokong rangkaian antara proses untuk sistem dengan seni bina yang berbeza.

Bab ini menggunakan sistem pelayan-pelanggan ini sebagai mekanisme untuk menunjukkan antara muka Winsock, dan ciri baharu yang menarik akan ditambah apabila pelayan diubah suai. Khususnya, kami akan menggunakan buat kali pertama Titik masuk DLL(Bab 5) dan pelayan DLL dalam proses.(Ciri-ciri baharu ini mungkin disertakan dalam versi asal program dalam Bab 11, tetapi ini akan mengalihkan perhatian anda daripada membangunkan seni bina sistem asas.) Akhir sekali, contoh tambahan akan menunjukkan kepada anda cara mencipta selamat, masuk semula, berbilang benang. perpustakaan.

Oleh kerana antara muka Winsock mesti mematuhi piawaian industri, konvensyen penamaan dan gaya pengaturcaraannya agak berbeza daripada yang kami temui dengan ciri Windows yang diterangkan sebelum ini. Tegasnya, Winsock API bukan sebahagian daripada Win32/64. Di samping itu, Winsock menyediakan fungsi bukan standard tambahan; Fungsi ini digunakan hanya apabila benar-benar perlu. Faedah lain yang disediakan oleh Winsock termasuk kemudahalihan yang lebih baik daripada program yang dihasilkan kepada sistem lain.

Soket tingkap

API Winsock direka bentuk sebagai lanjutan API Berkley Sockets untuk persekitaran Windows dan oleh itu disokong pada semua sistem Windows. Kelebihan Winsock termasuk yang berikut:

Kod sedia ada yang ditulis untuk API Soket Berkeley dialihkan terus.

Sistem Windows boleh disepadukan dengan mudah ke dalam rangkaian yang menggunakan kedua-dua versi IPv4 protokol TCP/IP dan versi IPv6 yang semakin meluas. Di samping segala-galanya, IPv6 membenarkan alamat IP yang lebih panjang, memecahkan halangan alamat 4-bait sedia ada IPv4.

Soket boleh digunakan bersama-sama dengan Windows bertindih I/O (Bab 14), yang, antara lain, membolehkan pelayan membuat skala apabila bilangan pelanggan aktif meningkat.

Soket boleh dianggap sebagai pemegang fail (jenis HANDLE) apabila menggunakan fungsi ReadFile dan WriteFile dan, dengan beberapa batasan, apabila menggunakan fungsi lain, sama seperti soket digunakan sebagai pemegang fail dalam UNIX. Ciri ini berguna apabila anda perlu menggunakan port pelengkapan I/O dan I/O tak segerak.

Terdapat juga sambungan tambahan bukan mudah alih.

Memulakan Winsock

API Winsock disokong oleh perpustakaan DLL (WS2_32.DLL), untuk mengakses yang anda mesti menyambungkan perpustakaan WS_232.LIB kepada program. DLL ini mesti dimulakan menggunakan fungsi bukan standard, khusus Winsock WSAStartup, yang mestilah fungsi Winsock pertama yang dipanggil oleh program. Apabila anda tidak lagi memerlukan fungsi Winsock, anda harus memanggil fungsi WSACleanup. Catatan. Awalan WSA bermaksud "Windows Sockets asynchronous...". Kami tidak menggunakan kemudahan mod tak segerak Winsock di sini, kerana apabila timbul keperluan untuk melaksanakan operasi tak segerak, kami boleh dan akan menggunakan benang.

Walaupun fungsi WSAStartup dan WSACleanup mesti dipanggil, kemungkinan besar ia akan menjadi satu-satunya fungsi bukan standard yang perlu anda tangani. Amalan biasa ialah menggunakan arahan prapemproses #ifdef untuk menyemak nilai pemalar simbolik _WIN32 (biasanya ditentukan oleh Visual C++ pada masa penyusunan), menyebabkan fungsi WSA dipanggil hanya apabila anda menjalankan Windows). Sudah tentu, pendekatan ini menganggap bahawa selebihnya kod adalah bebas platform.

int WSAStartup(WORD wVersionRequired, LPWSADATA ipWSAData);
Pilihan

wVersionRequired - menunjukkan nombor versi utama DLL, yang anda perlukan dan boleh gunakan. Biasanya, versi 1.1 mencukupi untuk menyediakan sebarang kesalingoperasian yang mungkin anda perlukan dengan sistem lain. Walau bagaimanapun, Winsock 2.0 tersedia pada semua sistem Windows, termasuk Windows 9x, dan digunakan dalam contoh di bawah. Versi 1.1 dianggap usang dan secara beransur-ansur tidak lagi digunakan.

Fungsi ini mengembalikan bukan sifar jika versi DLL yang anda minta ini tidak disokong.

Bait rendah parameter wVersionRequired menunjukkan nombor versi utama, dan bait tinggi menunjukkan versi kecil. Biasanya makro MAKEWORD digunakan; oleh itu ungkapan MAKEWORD(2,0) mewakili versi 2.0.

ipWSAData ialah penunjuk kepada struktur WSADATA yang mengembalikan maklumat konfigurasi DLL, termasuk nombor versi utama yang tersedia. Anda boleh membaca tentang cara mentafsir kandungannya dalam bantuan dalam talian Visual Studio.

Anda boleh menggunakan fungsi WSAGetLastError untuk mendapatkan maklumat ralat yang lebih terperinci, tetapi fungsi GetLastError juga sesuai untuk tujuan ini, seperti fungsi ReportError yang dibangunkan dalam Bab 2.

Apabila program selesai dijalankan, atau apabila tidak perlu lagi menggunakan soket, anda harus memanggil fungsi WSACleanup supaya perpustakaan WS_32.DLL yang mengekalkan soket boleh membebaskan sumber yang diperuntukkan untuk proses ini.

Mencipta soket

Setelah DLL Winsock dimulakan, anda boleh menggunakan fungsi standard (Soket Berkeley) untuk mencipta soket dan sambungan yang membolehkan pelayan berkomunikasi dengan pelanggan atau berkomunikasi antara rakan sebaya pada rangkaian.

Jenis data SOCKET Winsock adalah serupa dengan jenis data Windows HANDLE, malah boleh digunakan bersama dengan fungsi ReadFile dan fungsi Windows lain yang memerlukan pemegang HANDLE. Untuk mencipta (atau membuka) soket, gunakan soket.

Soket SOKET(int af, jenis int, protokol int);
Pilihan

Jenis data SOKET ditakrifkan secara berkesan sebagai jenis data int, jadi kod UNIX kekal mudah alih tanpa perlu menggunakan jenis data Windows.

af - menandakan keluarga alamat, atau protokol; Untuk menentukan protokol IP (komponen protokol TCP/IP yang bertanggungjawab untuk protokol Internet), anda harus menggunakan nilai PF_INET (atau AF_INET, yang mempunyai nilai angka yang sama, tetapi biasanya digunakan apabila memanggil fungsi bind) .

jenis - menunjukkan jenis komunikasi: komunikasi berorientasikan sambungan, atau penstriman (SOCK_STREAM), dan komunikasi datagram (SOCK_DGRAM), yang agak setanding dengan paip dan peti mel yang dinamakan, masing-masing.

protokol - adalah berlebihan jika af ditetapkan kepada AF_INET; gunakan nilai 0.

Jika gagal, fungsi soket mengembalikan INVALID_SOCKET.

Winsock boleh digunakan dengan protokol selain TCP/IP dengan menentukan nilai yang berbeza untuk parameter protokol; kami hanya akan menggunakan protokol TCP/IP.

Seperti semua fungsi standard lain, nama fungsi soket tidak boleh mengandungi huruf besar. Ini adalah pelepasan daripada konvensyen Windows dan didorong oleh keperluan untuk mematuhi piawaian industri.

Fungsi soket pelayan

Dalam perbincangan berikut di bawah pelayan akan difahami sebagai proses yang menerima permintaan untuk membentuk sambungan melalui port yang diberikan. Walaupun soket, seperti paip yang dinamakan, boleh digunakan untuk membuat sambungan antara rakan sebaya pada rangkaian, menjadikan perbezaan antara rakan sebaya ini adalah mudah dan mencerminkan perbezaan dalam cara kedua-dua sistem digunakan untuk menyambung antara satu sama lain.

Melainkan dinyatakan sebaliknya, jenis soket dalam contoh kami akan sentiasa SOCK_STREAM. Soket SOCK_DGRAM dibincangkan kemudian dalam bab ini.

Pengikat soket

Langkah seterusnya ialah mengikat soket ke alamatnya dan titik akhir(titik akhir) (arah saluran komunikasi dari aplikasi ke perkhidmatan). Panggilan ke soket diikuti dengan panggilan untuk mengikat adalah sama seperti mencipta paip bernama. Walau bagaimanapun, tiada nama yang boleh digunakan untuk membezakan soket komputer tertentu. Sebaliknya, titik akhir perkhidmatan ialah nombor port(nombor port). Mana-mana pelayan yang diberikan boleh mempunyai berbilang titik akhir. Prototaip fungsi bind diberikan di bawah.

int bind(SOKET s, const struct sockaddr *saddr, int namelen);
Pilihan

s ialah soket tidak terikat yang dikembalikan oleh fungsi soket.

saddr - Diisi sebelum panggilan dan menentukan protokol dan maklumat khusus protokol seperti yang diterangkan di bawah. Antara lain, struktur ini mengandungi nombor port.

namelen - berikan nilai kepada sizeof(sockaddr).

Jika berjaya, fungsi mengembalikan nilai 0, jika tidak SOCKET_ERROR. Struktur sockaddr ditakrifkan seperti berikut:

typedef struct sockaddr SOCKADDR, *PSOCKADDR;

Ahli pertama struktur ini, sa_family, menandakan protokol. Ahli kedua, sa_data, bergantung kepada protokol. Versi Internet struktur sa_data ialah struktur sockaddr_in:

keluarga dosa pendek; /* AF_INET */
struct in_addr sin_addr; /* Alamat IP 4-bait */
typedef struct sockaddr_in SOCKADDR_IN, *PSOCKADDR IN;

Perhatikan penggunaan jenis data integer pendek untuk nombor port. Di samping itu, nombor port dan maklumat lain mesti disimpan dalam susunan bait yang sesuai, dengan bait paling ketara diletakkan dalam kedudukan big-endian, untuk memastikan keserasian binari dengan sistem lain. Struktur sin_addr mengandungi substruktur s_addr, diisi dengan alamat IP 4-bait yang biasa, seperti 127.0.0.1, menunjukkan sistem yang permintaan sambungannya harus diterima. Secara amnya, permintaan daripada mana-mana sistem akan dipenuhi, jadi INADDR_ANY harus digunakan, walaupun parameter simbolik ini mesti ditukar kepada format yang betul, seperti yang ditunjukkan dalam coretan kod di bawah.

Fungsi inet_addr boleh digunakan untuk menukar rentetan teks yang mengandungi alamat IP kepada format yang diperlukan, jadi ahli sin_addr.s_addr pembolehubah sockaddr_in dimulakan seperti berikut:

sa.sin_addr.s_addr = inet_addr("192 .13.12.1");

Soket terikat yang mana protokol, nombor port dan alamat IP ditakrifkan kadangkala dirujuk sebagai dinamakan soket(bernama soket).

Meletakkan soket yang berkaitan ke dalam keadaan mendengar

Fungsi mendengar menjadikan pelayan tersedia untuk membentuk sambungan dengan klien. Tiada ciri serupa untuk paip bernama.

int dengar(SOKET s, int nQueueSize);

Parameter nQueueSize menentukan bilangan permintaan sambungan yang anda ingin beratur pada soket. Dalam Winsock 2.0 nilai parameter ini tidak mempunyai had atas, tetapi dalam versi 1.1 ia dihadkan oleh had SOMAXCON (daripada 5).

Menerima permintaan sambungan pelanggan

Akhir sekali, pelayan boleh menunggu sambungan dengan klien menggunakan fungsi terima, yang mengembalikan soket bersambung baharu untuk digunakan untuk operasi I/O. Ambil perhatian bahawa soket asal, yang kini dalam keadaan mendengar, digunakan semata-mata sebagai parameter kepada fungsi terima dan tidak terlibat secara langsung dalam operasi I/O.

Fungsi terima menyekat sehingga permintaan sambungan diterima daripada klien, di mana ia mengembalikan soket I/O baharu. Walaupun di luar skop buku ini, adalah mungkin untuk mencipta soket tidak menyekat, dan pelayan (Program 12.2) menggunakan urutan berasingan untuk menerima permintaan, membolehkan anda membuat pelayan tidak menyekat juga.

SOKET terima(SOKET s, LPSOCKADDR lpAddr, LPINT lpAddrLen);
Pilihan

s - soket mendengar. Untuk meletakkan soket ke dalam keadaan mendengar, anda mesti memanggil fungsi soket, ikat dan dengar dahulu.

lpAddr ialah penunjuk kepada struktur sockaddr_in yang menyediakan alamat sistem klien.

lpAddrLen ialah penunjuk kepada pembolehubah yang akan mengandungi saiz struktur sockaddr_in yang dikembalikan. Sebelum memanggil terima, pembolehubah ini mesti dimulakan kepada sizeof(struct sockaddr_in).

Mencabut dan menutup soket

Untuk menutup soket, gunakan fungsi shutdown(s, how). Argumen bagaimana boleh mengambil salah satu daripada dua nilai: 1, menunjukkan bahawa sambungan boleh ditutup hanya untuk menghantar mesej dan 2, menunjukkan bahawa sambungan boleh ditutup untuk kedua-dua menghantar dan menerima mesej. Fungsi penutupan tidak mengeluarkan sumber yang berkaitan dengan soket, tetapi ia memastikan semua data dihantar dan diterima sebelum soket ditutup. Walau bagaimanapun, sebaik sahaja fungsi penutupan dipanggil, aplikasi seharusnya tidak lagi menggunakan soket.

Apabila anda selesai menggunakan soket, anda harus menutupnya dengan memanggil fungsi closesocket(SOKET s). Pelayan terlebih dahulu menutup soket yang dicipta oleh fungsi terima, bukannya soket mendengar yang dicipta oleh fungsi soket. Pelayan harus menutup soket mendengar hanya apabila ia dimatikan atau berhenti menerima permintaan sambungan klien. Walaupun anda menganggap soket sebagai pemegang HANDLE dan menggunakan fungsi ReadFile dan WriteFile, anda tidak akan dapat memusnahkan soket hanya dengan memanggil fungsi CloseHandle; Untuk melakukan ini, anda harus menggunakan fungsi closesocket.

Contoh: Menyediakan dan Menerima Permintaan Sambungan Pelanggan

Coretan kod berikut menunjukkan cara membuat soket dan menerima permintaan sambungan klien.

Contoh ini menggunakan dua fungsi standard, htons ("hos kepada rangkaian pendek") dan htonl ("hos kepada rangkaian panjang"), yang menukar integer kepada bentuk big-endian yang diperlukan protokol IP.

Nombor port pelayan boleh menjadi sebarang nombor daripada julat yang dibenarkan untuk integer pendek, tetapi untuk ditentukan pengguna perkhidmatan biasanya menggunakan nombor dalam julat 1025-5000. Nombor port yang lebih rendah dikhaskan untuk perkhidmatan terkenal seperti telnet atau ftp, manakala nombor port yang lebih tinggi dikhaskan untuk perkhidmatan standard yang lain.

struct sockaddr_in SrvSAddr; /* Struktur alamat pelayan. */
struct sockaddr_in ConnectAddr;
AddrLen = sizeof(ConnectAddr);
sockio = terima(SrvSock, (struct sockaddr *) &ConnectAddr, &AddrLen);
... Menerima permintaan dan menghantar respons ...

Fungsi pelanggan soket

Stesen pelanggan yang ingin mewujudkan sambungan dengan pelayan juga mesti mencipta soket dengan memanggil fungsi soket. Langkah seterusnya adalah untuk pelayan membuat sambungan, dan sebagai tambahan, anda mesti menentukan nombor port, alamat hos dan maklumat lain. Terdapat hanya satu fungsi tambahan - menyambung.

Mewujudkan sambungan pelanggan dengan pelayan

Jika terdapat pelayan dengan soket dalam mod mendengar, pelanggan boleh menyambung kepadanya menggunakan fungsi sambung.

int connect(SOKET s, LPSOCKADDR lpName, int nNameLen);
Pilihan

s ialah soket yang dicipta menggunakan fungsi soket.

lpName ialah penunjuk kepada struktur sockaddr_in yang dimulakan dengan nilai nombor port dan alamat IP sistem dengan soket yang dikaitkan dengan port tertentu yang berada dalam keadaan mendengar.

Mulakan nNameLen dengan sizeof (struct sockaddr_in).

Nilai pulangan 0 menunjukkan kejayaan menyelesaikan fungsi, manakala nilai SOCKET_ERROR menunjukkan ralat, yang mungkin disebabkan, antara lain, tidak mempunyai soket mendengar dihidupkan. alamat yang ditentukan.

Soket s tidak perlu dikaitkan dengan port sebelum fungsi sambung dipanggil, walaupun ini mungkin berlaku. Jika perlu, sistem memperuntukkan port dan menentukan protokol.

Contoh: menyambungkan klien ke pelayan

Coretan kod yang ditunjukkan di bawah menghubungkan klien ke pelayan. Ini memerlukan hanya dua panggilan fungsi, tetapi struktur alamat mesti dimulakan sebelum fungsi sambung dipanggil. Peperiksaan kesilapan yang mungkin tiada di sini, tetapi program sebenar harus memasukkannya. Contoh menganggap bahawa alamat IP (rentetan teks seperti "192.76.33.4") ditentukan dalam argumen baris arahan argv.

ClientSAddr.sin_addr.s_addr = inet_addr(argv);
ConVal = connect(ClientSock, (struct sockaddr *)&ClientSAddr, sizeof(ClientSAddr));

Menghantar dan menerima data

Program yang menggunakan soket bertukar data menggunakan fungsi hantar dan recv, yang prototaipnya hampir sama (penuding penimbal fungsi hantar didahului oleh pengubah suai const). Di bawah ini hanyalah prototaip fungsi hantar.

int hantar(SOKET s, const char * lpBuffer, int nBufferLen, int nFlags);

Nilai pulangan ialah bilangan bait yang sebenarnya dipindahkan. SOCKET_ERROR menunjukkan ralat.

nFlags boleh digunakan untuk menunjukkan mendesak mesej (seperti mesej kecemasan), dan nilai MSG_PEEK membolehkan anda melihat data yang diterima tanpa membacanya.

Perkara paling penting yang perlu anda ingat ialah fungsi hantar dan recv bukan atom(atom) dan oleh itu tiada jaminan bahawa data yang diminta akan benar-benar dihantar atau diterima. Penghantaran mesej "pendek" ("kiriman pendek") sangat jarang berlaku, walaupun mungkin, yang juga benar berkaitan dengan penerimaan mesej "pendek" ("penerimaan pendek"). Konsep mesej dalam erti kata yang ada dalam kes paip yang dinamakan tidak terdapat di sini, dan oleh itu anda mesti menyemak nilai pulangan dan menghantar semula atau menerima data sehingga semua itu telah dihantar.

Fungsi ReadFile dan WriteFile juga boleh digunakan dengan soket, hanya dalam kes ini, apabila memanggil fungsi, anda mesti menghantar soket ke jenis HANDLE.

Perbandingan paip dan soket yang dinamakan

Paip bernama, yang diterangkan dalam Bab 11, sangat serupa dengan soket, tetapi terdapat perbezaan ketara dalam cara ia digunakan.

Paip bernama boleh berorientasikan mesej, yang sangat memudahkan program.

Paip bernama memerlukan penggunaan fungsi ReadFile dan WriteFile, manakala soket juga boleh menggunakan fungsi hantar dan recv.

Tidak seperti paip yang dinamakan, soket sangat fleksibel sehingga memberikan pengguna keupayaan untuk memilih protokol untuk digunakan dengan soket, seperti TCP atau UDP. Di samping itu, pengguna mempunyai peluang untuk memilih protokol berdasarkan sifat perkhidmatan yang disediakan atau faktor lain.

Soket adalah berdasarkan standard industri, menjadikannya serasi dengan sistem bukan Windows.

Terdapat juga perbezaan dalam model pengaturcaraan pelayan dan klien.

Perbandingan pelayan paip dan soket yang dinamakan

Mewujudkan sambungan dengan berbilang pelanggan menggunakan soket memerlukan panggilan berulang ke fungsi terima. Setiap panggilan mengembalikan soket yang disambungkan seterusnya. Berbanding dengan paip yang dinamakan, perbezaan berikut wujud:

Paip bernama memerlukan setiap tika paip bernama dan pemegang HANDLE dibuat menggunakan fungsi CreateNamedPipe, manakala tika soket dicipta menggunakan fungsi terima.

Bilangan soket pelanggan yang dibenarkan adalah tidak terhad (fungsi dengar hanya mengehadkan bilangan pelanggan yang boleh beratur), manakala bilangan contoh paip yang dinamakan, bergantung pada perkara yang ditentukan apabila CreateNamedPipe mula-mula dipanggil, mungkin terhad.

Tiada fungsi pembantu soket yang serupa dengan fungsi TransactNamedPipe.

Paip bernama tidak mempunyai nombor port yang ditakrifkan dengan jelas dan dibezakan dengan nama.

Dalam kes pelayan paip bernama, mendapatkan pemegang jenis HANDLE yang boleh digunakan memerlukan panggilan dua fungsi (CreateNamedPipe dan ConnectNamedPipe), manakala pelayan soket memerlukan panggilan empat fungsi (soket, ikat, dengar dan terima).

Perbandingan pelanggan paip dan soket yang dinamakan

Dalam kes paip yang dinamakan, anda mesti memanggil fungsi WaitNamedPipe dan CreateFile secara berurutan. Jika soket digunakan, susunan panggilan ini diterbalikkan, kerana fungsi soket boleh dianggap sebagai mencipta soket dan menyekat fungsi sambung.

Perbezaan tambahan ialah fungsi sambung ialah fungsi klien soket, manakala fungsi ConnectNamedPipe digunakan oleh pelayan paip bernama.

Contoh: fungsi penerimaan mesej dalam kes soket

Selalunya mudah untuk menghantar dan menerima mesej dalam satu blok. Seperti yang kita lihat dalam Bab 11, saluran membenarkan anda melakukan ini. Walau bagaimanapun, dalam kes soket, pengepala yang mengandungi saiz mesej diperlukan, diikuti oleh mesej itu sendiri. Fungsi ReceiveMessage, yang akan digunakan dalam contoh, direka bentuk untuk menerima mesej sedemikian. Perkara yang sama boleh dikatakan mengenai fungsi SendMessage, yang direka untuk menghantar mesej.

Sila ambil perhatian bahawa mesej diterima dalam dua bahagian: pengepala dan kandungan. Di bawah ini kita andaikan bahawa jenis tersuai MESSAGE sepadan dengan pengepala 4-bait. Tetapi walaupun pengepala 4-bait memerlukan panggilan berulang ke fungsi recv untuk memastikan ia dibaca sepenuhnya, kerana fungsi recv bukan atom.

Nota khusus untuk Win64

Jenis pembolehubah yang digunakan untuk menyimpan saiz mesej ialah jenis data berketepatan tetap LONG32, yang cukup memadai untuk menampung nilai parameter saiz yang disertakan dalam mesej apabila berinteraksi dengan sistem selain Windows, dan yang sesuai untuk kemungkinan penyusunan semula program seterusnya untuk kegunaannya pada platform Win64 (lihat Bab 16).

DWORD ReceiveMessage (MESEJ *pMsg, SOCKET sd) (
/* Mesej terdiri daripada medan saiz mesej 4-bait diikuti dengan kandungan sebenar. */
/* Baca mesej. */
/* Tajuk dibaca dahulu, kemudian kandungannya. */
nRemainRecv = 4; /* Saiz medan pengepala. */
pBuffer = (LPBYTE)pMsg; /* recv mungkin tidak memindahkan semua bait yang diminta. */
manakala (nRemainRecv > 0 && !Putuskan sambungan) (
/* Baca kandungan mesej. */
manakala (nRemainRecv > 0 && !Putuskan sambungan) (
nXfer = recv(sd, pBuffer, nRemainRecv, 0);

Contoh: Pelanggan berasaskan soket

Program 12.1 ialah kerja semula program klien NP client (Program 11.2) yang digunakan untuk paip bernama. Penukaran program dijalankan dengan cara yang sangat mudah dan hanya memerlukan beberapa penjelasan.

Daripada menemui pelayan menggunakan peti mel, pengguna memasukkan alamat IP pelayan pada baris arahan. Jika tiada alamat IP dinyatakan, alamat lalai 127.0.0.1 digunakan, sepadan dengan sistem setempat.

Untuk menghantar dan menerima mesej, fungsi seperti ReceiveMessage digunakan, yang tidak dibentangkan di sini.

Nombor port, SERVER_PORT, ditakrifkan dalam fail pengepala ClntSrvr.h.

Walaupun kod itu ditulis untuk dijalankan pada Windows, satu-satunya pergantungan pada Windows adalah melalui penggunaan panggilan fungsi yang diawali dengan WSA.

Program 12.1. klienSK: pelanggan berasaskan soket
/* Bab 12. clientSK.с */
/* Pelanggan baris arahan berbenang tunggal. */
/* VERSI BERASASKAN SOKET WINDOWS. */
/* Membaca urutan arahan untuk dihantar ke proses pelayan*/
/* melalui sambungan soket. Menunggu respons dan memaparkannya. */

#define _NOEXCLUSIONS /* Diperlukan untuk mendayakan definisi soket. */
#include "ClntSrvr.h" /* Mentakrifkan struktur rekod permintaan dan tindak balas. */

/* Fungsi mesej untuk melayan permintaan dan respons. */
/* Selain itu, ReceiveResponseMessage memaparkan mesej yang diterima. */
DWORD SendRequestMessage statik(PERMINTAAN *, SOKET);
statik DWORD ReceiveResponseMessage(RESPONSE *, SOKET);
struct sockaddr_in ClientSAddr; /* Alamat soket pelanggan. */
int _tmain(DWORD argc, LPTSTR argv) (
SOCKET ClientSock = INVALID_SOCKET;
PERMINTAAN Permintaan; /* Lihat ClntSrvr.h. */
RESPON Respon; /* Lihat ClntSrvr.h. */
TCHAR PromptMsg = _T("\nMasukkan arahan> ");
TCHAR QuitMsg = _T("$Quit");
/* Permintaan: menamatkan pelanggan. */
TCHAR ShutMsg = _T("$ShutDownServer"); /* Hentikan semua benang. */
CHAR DefaultIPAddr = "127.0.0.1"; /* Sistem tempatan. */
/* Sambung ke pelayan. */
/* Ikuti prosedur standard untuk memanggil urutan fungsi soket/sambung oleh klien. */
ClientSock = soket(AF_INET, SOCK_STREAM, 0);
memset(&ClientSAddr, 0, sizeof(ClientSAddr));
ClientSAddr.sin_family = AF_INET;
jika (argc >= 2) ClientSAddr.sin_addr.s_addr = inet_addr(argv);
else ClientSAddr.sin_addr.s_addr = inet_addr(DefaultIPAddr);
ClientSAddr.sin_port = htons(SERVER_PORT);
/* Nombor port ditakrifkan sebagai 1070. */
sambung(ClientSock, (struct sockaddr *)&ClientSAddr, sizeof(ClientSAddr));
/* Gelung utama untuk memaparkan gesaan arahan, menghantar permintaan dan menerima respons. */
_tprintf(_T("%s"), PromptMsg);
/* Input adalah dalam format kad bebas, tetapi arahan kepada pelayan mesti dinyatakan dalam format ASCII. */
_fgetts(Req, MAX_RQRS_LEN-1, stdin);
untuk (j = 0; j<= _tcslen(Req) Request.Record[j] = Req[j];
/* Buang simbol baris baru di hujung baris. */
Request.Record = "\0";
if (strcmp(Request.Record, QuitMsg) == 0 || strcmp(Request.Record, ShutMsg) == 0) Quit = TRUE;
SendRequestMessage(&Request, ClientSock);
ReceiveResponseMessage(&Response, ClientSock);
penutupan (ClientSock, 2); /* Larang menghantar dan menerima mesej. */
_tprintf(_T("\n****Keluar dari program klien\n"));

Contoh: Pelayan Soket Lanjutan

Program serverSK (program 12.2) adalah serupa dengan program serverNP (program 11.3), kerana versinya yang diubah suai dan dipertingkatkan.

Dalam versi program yang lebih baik, benang pelayan dicipta permintaan(atas permintaan), dan bukannya sebagai kumpulan benang bersaiz tetap. Setiap kali pelayan menerima permintaan sambungan pelanggan, urutan pekerja pelayan dibuat dan apabila pelanggan ditamatkan, pelaksanaan benang ditamatkan.

Pelayan mencipta yang berasingan menerima aliran(benang terima), yang membenarkan utas utama meninjau bendera penutupan global sementara panggilan terima kekal disekat. Walaupun soket boleh ditakrifkan sebagai tidak menyekat, aliran menyediakan penyelesaian tujuan am yang mudah. Perlu diingatkan bahawa kebanyakan kefungsian lanjutan Winsock direka untuk menyokong operasi tak segerak, manakala utas Windows menyediakan keupayaan untuk memanfaatkan kefungsian berasaskan piawaian mod soket segerak yang lebih ringkas.

Disebabkan oleh beberapa kerumitan program, pengurusan rangkaian telah dipertingkatkan, yang memungkinkan untuk menyediakan sokongan untuk keadaan setiap rangkaian.

Pelayan ini juga menyokong pelayan dalam proses(pelayan dalam proses), yang dicapai dengan memuatkan DLL semasa permulaan. Nama DLL ditentukan pada baris arahan, dan benang pelayan mula-mula cuba menentukan titik masuk DLL. Jika berjaya, utas pelayan memanggil titik masuk DLL; jika tidak, pelayan mencipta proses dengan cara yang sama seperti yang dilakukan dalam program serverNP. Contoh DLL diberikan dalam Program 12.3. Kerana membuang pengecualian oleh DLL akan membunuh keseluruhan proses pelayan, panggilan fungsi DLL dilindungi oleh pengendali pengecualian mudah.

Jika mahu, anda boleh memasukkan pelayan dalam proses dalam program serverNP. Kelebihan terbesar pelayan dalam proses ialah mereka tidak memerlukan sebarang penukaran konteks kepada proses lain, yang boleh menghasilkan peningkatan prestasi yang ketara.

Memandangkan kod pelayan menggunakan ciri khusus Windows, khususnya, keupayaan kawalan benang dan beberapa yang lain, ia, tidak seperti kod klien, ternyata terikat dengan Windows.

Program 12.2. serverSK: pelayan berasaskan soket dengan pelayan dalam proses
/* Bab 12. Sistem pelayan-pelanggan. PROGRAM SERVER. VERSI BERASASKAN SOKET. */
/* Melaksanakan arahan yang dinyatakan dalam permintaan dan mengembalikan respons. */
/* Jika titik masuk perpustakaan kongsi boleh ditemui, arahan */
/* dilaksanakan di dalam proses, sebaliknya di luar proses. */
/* CIRI PILIHAN: argv boleh mengandungi nama perpustakaan */
/* DLL menyokong pelayan dalam proses. */

#include "ClntSrvr.h" /* Mentakrifkan struktur rekod permintaan dan respons. */

/* Struktur alamat soket pelayan. */
struct sockaddr_in ConnectSAddr; /* Soket bersambung. */
WSADATA WSStartData; /* Struktur data perpustakaan soket. */

typedef struct SERVER_ARG_TAG ( /* Argumen benang pelayan. */
/* Penjelasan terkandung dalam ulasan ke utas utama. */
HINSTANCE dlhandle; /* Kendalikan ke pustaka kongsi. */

ShutFlag statik meruap = SALAH;
SrvSock SOKET statik, ConnectSock;
int _tmain(DWORD argc, LPCTSTR argv) (
/* Mendengar pelayan dan soket bersambung. */
SERVER_ARG srv_arg;
/* Mulakan perpustakaan WSA; versi 2.0 ditentukan, tetapi versi 1.1 juga akan berfungsi. */
WSAStartup(MAKEWORD(2, 0), &WSStartData);
/* Buka perpustakaan arahan dinamik jika namanya dinyatakan pada baris arahan. */
if (argc > 1) hDll = LoadLibrary(argv);
/* Mulakan tatasusunan arg benang. */
untuk (ith = 0; ith< MAXCLIENTS; ith++) {
srv_arg.dlhandle = hDll;
/* Ikuti prosedur standard untuk memanggil urutan fungsi soket/ikat/dengar/terima oleh klien. */
SrvSock = soket(AF_INET, SOCK_STREAM, 0);
SrvSAddr.sin_family = AF_INET;
SrvSAddr.sin_addr.s_addr = htonl(INADDR_ANY);
SrvSAddr.sin_port = htons(SERVER_PORT);
bind(SrvSock, (struct sockaddr *)&SrvSAddr, sizeof SrvSAddr);
dengar (SrvSock, MAX_CLIENTS);

/* Benang utama menjadi benang pendengaran/sambungan/kawalan.*/
/* Cari sel kosong dalam tatasusunan arg benang pelayan. */
/* parameter status: 0 – sel adalah percuma; 1 - aliran berhenti; 2 - benang sedang berjalan; 3 – keseluruhan sistem dihentikan. */
untuk (ith = 0; ith< MAX_CLIENTS && !ShutFlag;) {
jika (srv_arg.status==1 || srv_arg.status==3) ( /* Pelaksanaan thread ditamatkan sama ada secara normal atau disebabkan permintaan berhenti. */
WaitForSingleObject(srv_arg.srv_thd INFINITE);
CloseHandle(srv_arg.srv_tnd);
jika (srv_arg.status == 3) ShutFlag = BENAR;
else srv_arg.status = 0;
/* Lepaskan sel benang ini. */
jika (srv_arg.status == 0 || ShutFlag) putus;
ith = (ith + 1) % MAXCLIENTS;
/* Mengganggu kitaran pengundian. */
/* Pilihan alternatif: Gunakan peristiwa untuk menjana isyarat yang menunjukkan bahawa sel telah dikeluarkan. */
/* Tunggu percubaan sambungan pada soket ini. */
/* Asingkan benang untuk mengundi ShutFlag. */
hAcceptTh = (HANDLE)_beginthreadex(NULL, 0, AcceptTh, &srv_arg, 0, &ThId);
tstatus = WaitForSingleObject(hAcceptTh, CS_TIMEOUT);
jika (tstatus == WAIT_OBJECT_0) putus; /* Sambungan diwujudkan. */
hAcceptTh = NULL; /* Sediakan untuk sambungan seterusnya. */
_tprintf(_T("Menghentikan pelayan. Menunggu semua urutan pelayan selesai\n"));
/* Tamatkan benang penerima jika ia masih berjalan. */
/* Butiran lanjut tentang logik penyiapan yang digunakan */
/* karya disenaraikan di laman web buku. */
if (hDll != NULL) FreeLibrary(hDll);
jika (hAcceptTh != NULL) TerminateThread(hAcceptTh, 0);
/* Tunggu semua urutan pelayan aktif selesai. */
untuk (ith = 0; ith< MAXCLIENTS; ith++) if (srv_arg .status != 0) {
WaitForSingleObject(srv_arg.srv_thd, INFINITE);
CloseHandle(srv_arg.srv_thd);

statik DWORD WINAPI AcceptTh(SERVER_ARG * pThArg) (
/* Benang penerima, yang membenarkan utas utama meninjau bendera penamatan. Selain itu, utas ini mencipta utas pelayan. */
AddrLen = sizeof(ConnectSAddr);
pThArg->sock = terima(SrvSock, /* Ini adalah panggilan menyekat. */
(struct sockaddr *)&ConnectSAddr, &AddrLen);
/* Sambungan baharu. Buat urutan pelayan. */
pThArg->srv_thd = (HANDLE)_beginthreadex(NULL, 0, Server, pThArg, 0, &ThId);
pulangan 0; /* Benang pelayan terus berjalan. */

Pelayan DWORD WINAPI statik(SERVER_ARG * pThArg)
/* Fungsi utas pelayan. Strim dibuat atas permintaan. */
/* Setiap benang mengekalkan permintaan, tindak balas dan struktur data lognya sendiri pada tindanan. */
/* ... Pengisytiharan standard daripada serverNP ditinggalkan ... */
int (*dl_addr)(char *, char *);
char *ws = "\0\t\n"; /* Ruang. */
GetStartupInfo(&StartInfoCh);
/* Cipta nama fail sementara. */
sprintf(TempFile, "%s%d%s", "ServerTemp", pThArg->number, ".tmp");
manakala (!Selesai && !ShutFlag) ( /* Gelung arahan utama. */
Putuskan sambungan = ReceiveRequestMessage(&Request, ConnectSock);
Selesai = Putuskan sambungan || (strcmp(Request.Record, "$Quit") == 0) || (strcmp(Request.Record, "$ShutDownServer") == 0);
/* Hentikan urutan ini apabila ia menerima arahan "$Quit" atau "$ShutDownServer". */
hTrapFile = CreateFile(TempFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &TempSA, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
/* Semak kehadiran arahan ini dalam DLL. Untuk memudahkan perintah */
/* perpustakaan kongsi mempunyai lebih banyak lagi keutamaan yang tinggi dibandingkan */
/* dengan arahan proses. Pertama sekali, anda perlu mengekstrak nama arahan.*/
i = strcspn(Request.Record, ws); /* Saiz token. */
memcpy(sys_command, Request.Record, i);
dl_addr = NULL; /* Akan ditetapkan jika fungsi GetProcAddress berjaya. */
if (pThArg->dlhandle != NULL) (/* Menyemak sokongan untuk pelayan "dalam proses". */
dl_addr = (int (*)(char *, char *))GetProcAddress(pThArg->dlhandle, sys_command);
/* Lindungi proses pelayan daripada pengecualian yang dilemparkan dalam DLL*/
(*dl_addr)(Request.Record, TempFile);
) __kecuali (EXCEPTION_EXECUTE_HANDLER) (
ReportError(_T("Pengecualian dalam DLL"), 0, FALSE);
if (dl_addr == NULL) ( /* Tiada sokongan pelayan dalam proses. */
/* Buat proses untuk melaksanakan arahan. */
/* ... Sama seperti dalam serverNP ... */
) /* Tamat gelung arahan utama. Dapatkan arahan seterusnya. */
/* Akhir kitaran arahan. Kosongkan sumber; keluar dari arus. */
_tprintf(_T("Penutupan pelayan# %d\n"), pThArg->nombor);
if (strcmp(Request.Record, "$ShutDownServer") == 0) (

Nota Keselamatan

Seperti yang dibentangkan di sini, sistem pelayan pelanggan ini Tidak adalah selamat. Jika sistem anda menjalankan pelayan dan seseorang mengetahui nombor port yang anda jalankan dan nama komputer, maka mereka boleh menyerang sistem anda. Pengguna lain yang menjalankan program klien pada komputer mereka akan dapat melaksanakan arahan pada sistem anda yang, sebagai contoh, membenarkan mereka memadam atau menukar fail.

Perbincangan penuh tentang kaedah untuk membina sistem selamat adalah di luar skop buku ini. Walau bagaimanapun, Bab 15 menunjukkan cara untuk selamat Objek Windows, dan Latihan 12.14 mencadangkan penggunaan protokol SSL.

Pelayan Dalam Proses

Seperti yang dinyatakan sebelum ini, penambahbaikan utama program serverSK adalah berkaitan dengan kemasukan pelayan dalam proses. Program 12.3 menunjukkan cara menulis DLL yang menyediakan perkhidmatan jenis ini. Program ini mengandungi dua fungsi yang anda sudah tahu - fungsi mengira perkataan dan fungsi toupper.

Mengikut konvensyen, parameter pertama ialah baris arahan dan yang kedua ialah nama fail output. Selain itu, anda harus sentiasa ingat bahawa fungsi itu akan dilaksanakan dalam urutan yang sama dengan pelayan, dan ini memerlukan keperluan keselamatan rangkaian yang ketat, termasuk, tetapi tidak terhad kepada, perkara berikut:

Fungsi tidak boleh mengubah persekitaran proses dalam apa jua cara. Sebagai contoh, jika salah satu fungsi menukar direktori kerja, ia akan menjejaskan keseluruhan proses.

Begitu juga, fungsi tidak seharusnya mengubah hala input dan output standard.

Ralat pengaturcaraan seperti indeks atau penuding di luar sempadan atau limpahan tindanan boleh merosakkan memori kepunyaan benang lain atau proses itu sendiri.

Kebocoran sumber, seperti yang disebabkan oleh kegagalan untuk mengembalikan memori yang dibebaskan dengan segera kepada sistem atau kegagalan untuk menutup pemegang, akhirnya akan memberi kesan negatif ke atas operasi keseluruhan sistem pelayan.

Keperluan ketat sedemikian tidak dikenakan ke atas proses atas sebab satu proses, sebagai peraturan, tidak boleh menyebabkan kerosakan kepada proses lain, dan selepas proses itu selesai pelaksanaannya, sumber yang didudukinya dikeluarkan secara automatik. Oleh sebab itu, perkhidmatan biasanya dibangunkan dan dinyahpepijat sebagai benang, dan hanya selepas ia yakin bahawa ia akan berfungsi dengan pasti barulah ia ditukar kepada DLL.

Program 12.3 menyediakan DLL kecil yang mengandungi dua fungsi.

Program 12.3. arahan: contoh dalam pelayan proses
/* Bab 12. perintah.p. */
/* Perintah pelayan dalam proses untuk digunakan dalam pelayanSK dan sebagainya. */
/* Terdapat beberapa arahan yang dilaksanakan sebagai DLL. */
/* Setiap fungsi arahan mengambil dua parameter dan menyediakan */
/* pelaksanaan selamat dalam mod berbilang benang. Parameter pertama */
/* ialah rentetan: perintah arg1 arg2 ... argn */
/* (iaitu baris arahan biasa), dan yang kedua ialah nama fail output. … */

statik void extract_token(int, char *, char *);

int wcip(char * perintah, char * output_file)
/* Pembilang perkataan; dalam proses. */
/* NOTA: versi ringkas; keputusan mungkin berbeza daripada yang disediakan oleh utiliti wc. */
manakala ((c = fgetc(fin)) != EOF) (
/* … Kod standard - tidak penting untuk contoh ini … */
/* Tulis keputusan. */
fprintf(fout, " %9d %9d %9d %s\n", nl, nw, nc, input_file);

int toupperip(char * perintah, char * output_file)
/* Menukar input kepada huruf besar; dilaksanakan dalam suatu proses. */
/* Token kedua menentukan fail input (token pertama ialah "toupperip"). */
extract_token(1, arahan, input_file);
fin = fopen(input_file, "r");
fout = fopen(output_file, "w");
manakala ((c = fgetc (fin)) != EOF) (
jika (isalpha(c)) c = toupper(c);

statik void extract_token(int it, char * command, char * token) (
/* Mengeluarkan nombor token "it" daripada "command" (nombor token pertama */
/* ialah "0"). Hasilnya masuk ke "token" */
/* Ruang digunakan sebagai pembatas token. … */

Mesej berorientasikan baris , titik perjalanan DLL dan TLS

Program serverSK dan clientSK berkomunikasi antara satu sama lain dengan bertukar-tukar mesej, setiap satunya terdiri daripada pengepala 4-bait yang mengandungi saiz mesej dan kandungan itu sendiri. Alternatif biasa untuk pendekatan ini ialah mesej dipisahkan antara satu sama lain oleh aksara hujung baris (atau suapan baris).

Kesukaran bekerja dengan mesej sedemikian ialah panjang mesej tidak diketahui terlebih dahulu, dan oleh itu setiap aksara yang masuk perlu diperiksa. Walau bagaimanapun, mendapatkan semula satu aksara pada satu masa adalah amat tidak cekap, jadi aksara itu disimpan dalam penimbal yang kandungannya mungkin termasuk satu atau lebih aksara dan komponen akhir baris satu atau lebih mesej. Dalam kes ini, dalam selang waktu antara panggilan ke fungsi penerimaan mesej, adalah perlu untuk mengekalkan kandungan dan keadaan penimbal tidak berubah. Dalam persekitaran satu benang, sel memori statik boleh digunakan untuk tujuan ini, tetapi perkongsian Berbilang benang menggunakan pembolehubah statik yang sama tidak mungkin.

Dalam rumusan yang lebih umum, kita dihadapkan di sini masalah menyelamatkan keadaan jangka panjang dalam persekitaran berbilang benang(masalah keadaan berterusan berbilang benang). Masalah ini timbul apabila fungsi selamat benang mesti menyokong kegigihan beberapa maklumat daripada satu panggilan fungsi ke panggilan seterusnya. Masalah yang sama berlaku apabila bekerja dengan fungsi strok yang disertakan dalam perpustakaan standard C, yang direka untuk mengimbas rentetan untuk mencari kejadian berurutan bagi token tertentu.

Menyelesaikan masalah keadaan jangka panjang dalam persekitaran berbilang benang

Penyelesaian yang dikehendaki menggabungkan beberapa komponen:

DLL yang mengandungi fungsi untuk menghantar dan menerima mesej.

Fungsi yang mewakili titik masuk ke DLL.

Strim Kawasan Storan Setempat (TLS, Bab 7). Menyambungkan proses ke perpustakaan disertai dengan penciptaan indeks DLL, dan memutuskan sambungannya disertai dengan kemusnahan. Nilai indeks disimpan dalam storan statik yang boleh diakses oleh semua benang.

Struktur di mana penimbal dan keadaan semasanya disimpan. Struktur diedarkan apabila urutan baharu menyertai pustaka dan alamatnya disimpan dalam entri TLS untuk urutan tersebut. Apabila benang ditanggalkan daripada perpustakaan, memori yang diduduki oleh strukturnya dibebaskan.

Oleh itu, TLS bertindak sebagai kedai statik, dan setiap urutan mempunyai salinan unik kedai ini sendiri.

Contoh: DLL selamat-benang untuk pemesejan soket

Program 12.4 ialah DLL yang mengandungi dua fungsi untuk diproses rentetan watak(yang namanya dalam kes ini mengandungi "CS", daripada rentetan aksara- rentetan aksara), atau fungsi penstriman soket: SendCSMessage dan ReceiveCSMessage, serta titik masuk DllMain (lihat Bab 5). Kedua-dua fungsi ini memainkan peranan yang sama seperti fungsi ReceiveMessage, serta fungsi yang digunakan dalam Program 12.1 dan 12.2, dan sebenarnya menggantikannya.

Fungsi DllMain ialah contoh perwakilan untuk menyelesaikan masalah keadaan jangka panjang dalam persekitaran berbilang benang dan menggabungkan TLS dan DLL.

Membebaskan sumber apabila benang ditanggalkan (kes DLL_THREAD_DETACH) amat penting dalam persekitaran pelayan; Jika anda tidak melakukan ini, sumber pelayan akhirnya akan kehabisan, yang boleh menyebabkannya ranap, mengalami prestasi yang lemah, atau kedua-duanya.

Catatan

Beberapa konsep yang digambarkan di bawah tidak berkaitan secara langsung dengan soket, tetapi masih dibincangkan di sini dan bukannya dalam bab sebelumnya kerana contoh tersebut memberikan peluang yang berguna untuk menggambarkan teknik untuk mencipta DLL selamat benang dalam tetapan yang realistik.

Kod klien dan pelayan yang menggunakan DLL ini, diubah suai sedikit daripada program 12.1 dan 12.2, tersedia di tapak Web buku.

Program 12.4. SendReceiveSKST: Thread Safe DLL
/* SendReceiveSKST.с - DLL soket strim berbilang benang. */
/* Penamatan aksara digunakan sebagai pembatas mesej */
/* rentetan ("\0"), jadi saiz mesej tidak diketahui terlebih dahulu. */
/* Data masuk ditimbal dan disimpan di antara */
/* panggilan fungsi. */
/* Kawasan simpanan setempat benang digunakan untuk tujuan ini */
/* (Thread Local Storage, TLS) menyediakan setiap thread */
/* "storan statik" peribadi sendiri. */

#include "ClntSrvr.h" /* Mentakrifkan rekod permintaan dan respons. */

/* "static_buf" mengandungi "static_buf_len" bait data baki. */
/* Aksara baris akhir (aksara nol) mungkin ada atau tidak */
/* dan tidak hadir. */
char static_buf ;

statik DWORD TlsIx = 0; /* Indeks TLS – SETIAP PROSES MEMPUNYAI INDEKS SENDIRI.*/
/* Untuk perpustakaan berbenang tunggal, takrifan berikut akan digunakan:
static char static_buf ;
statik LONG32 statik_buf_len; */
/* Fungsi DLL utama. */

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) (
/* Tiada sambungan untuk benang utama, jadi operasi sambungan benang juga mesti dilakukan semasa proses menyambung. */
/* Tunjukkan bahawa memori belum diperuntukkan. */
kembalikan BENAR; /* Nilai ini sebenarnya diabaikan. */
/* Tanggalkan benang utama juga. */

BOOL ReceiveCSMessage(REQUEST *pRequest, SOCKET sd) (
/* Nilai kembali TRUE menunjukkan ralat atau pemutusan sambungan. */
LONG32 nRemainRecv = 0, nXfer, k; /* Mesti ditandatangani integer. */
CHAR TempBuf;
p = (STATIC_BUF *)TlsGetValue(TlsIx);
if (p == NULL) ( /* Mulakan pada panggilan pertama. */
/* Storan ini hanya akan diedarkan oleh benang yang memerlukannya */
/* perlu. Jenis strim lain mungkin menggunakan TLS untuk tujuan lain. */
p = malloc(saiz(STATIC_BUF));
jika (p == NULL) kembalikan BENAR; /* Ralat. */
p->static_buf_len = 0; /* Memulakan negeri. */
/* Kira sehingga aksara baris baharu, meninggalkan sisa data dalam penimbal statik. */
untuk (k = 0; k< p->static_buf_len && p->static_buf[k] != "\0"; k++) (
mesej[k] = p->static_buf[k];
) /* k – bilangan aksara yang dihantar. */
jika (k< p->static_buf_len) ( /* Satu aksara nol telah dikesan dalam penimbal statik. */
p->static_buf_len –= (k + 1); /* Laraskan keadaan penimbal statik. */
memcpy(p->static_buf, &(p->static_buf), p->static_buf_len);
kembali SALAH; /* Tiada input soket diperlukan. */

/* Keseluruhan penimbal statik telah dipindahkan. Penamat talian tidak dikesan.*/
nRemainRecv = sizeof(TempBuf) – 1 – p->static_buf_len;
pBuffer = mesej + p->static_buf_len;
manakala (nRemainRecv > 0 && !Putuskan sambungan) (
nXfer = recv(sd, TempBuf, nRemainRecv, 0);
/* Hantar semua aksara sehingga aksara nol, jika ada, kepada mesej sasaran. */
untuk (k =0; k< nXfer && TempBuf[k] != "\0"; k++) {
jika (k >= nXfer) ( /*Penamat talian tidak dikesan, baca lebih lanjut*/
) else ( /* Penamat talian dikesan. */
memcpy(p->static_buf, &TempBuf, nXfer – k – 1);
p->static_buf_len = nXfer – k – 1;

BOOL SendCSMessage(RESPONSE *pResponse, SOCKET sd) (
/* Hantar permintaan kepada pelayan pada soket sd. */
nRemainSend = strlen(pBuffer) + 1;
manakala (nRemainSend > 0 && !Putuskan sambungan) (
/* Menghantar tidak menjamin bahawa keseluruhan mesej akan dihantar. */
nXfer = hantar(sd, pBuffer, nRemainSend, 0);
fprintf(stderr, "\nMemutuskan sambungan pelayan sebelum menghantar permintaan penyelesaian");

Saya sangat menyukai keseluruhan siri artikel, ditambah pula saya sentiasa mahu mencuba diri saya sebagai penterjemah. Mungkin artikel itu kelihatan terlalu jelas kepada pemaju yang berpengalaman, tetapi nampaknya saya akan berguna dalam apa jua keadaan.
Artikel pertama - http://habrahabr.ru/post/209144/

Penerimaan dan penghantaran paket data

pengenalan
Helo, nama saya Glenn Fiedler dan saya mengalu-alukan kedatangan anda ke artikel kedua saya dalam siri "Pengaturcaraan Rangkaian untuk Pembangun Permainan".

Dalam artikel sebelumnya, kami membincangkan pelbagai cara untuk memindahkan data antara komputer melalui rangkaian, dan pada akhirnya kami memutuskan untuk menggunakan protokol UDP dan bukannya TCP. Kami memutuskan untuk menggunakan UDP agar dapat menghantar data tanpa kelewatan yang berkaitan dengan menunggu paket dihantar semula.

Sekarang saya akan memberitahu anda cara menggunakan UDP untuk menghantar dan menerima paket dalam amalan.

soket BSD
Kebanyakan sistem pengendalian moden mempunyai beberapa jenis pelaksanaan soket berdasarkan soket BSD (soket Berkeley).

Soket BSD beroperasi pada fungsi mudah seperti "soket", "bind", "sendto" dan "recvfrom". Sudah tentu, anda boleh mengakses fungsi ini secara langsung, tetapi dalam kes ini kod anda akan bergantung pada platform, kerana pelaksanaannya mungkin berbeza sedikit pada sistem pengendalian yang berbeza.

Oleh itu, walaupun saya akan memberikan contoh mudah pertama interaksi dengan soket BSD, dalam perkara berikut kami tidak akan menggunakannya secara langsung. Sebaliknya, selepas menguasai fungsi asas, kami akan menulis beberapa kelas yang mengabstrak semua kerja dengan soket, supaya pada masa hadapan kod kami akan bebas platform.

Ciri-ciri OS yang berbeza
Pertama, mari tulis kod yang akan menentukan OS semasa supaya kita boleh mengambil kira perbezaan dalam operasi soket:

// pengesanan platform #define PLATFORM_WINDOWS 1 #define PLATFORM_MAC 2 #define PLATFORM_UNIX 3 #if takrif(_WIN32) #define PLATFORM PLATFORM_WINDOWS #elif takrif(__APPLE__) #define PLATFORM PLATFORM_MAC #else #define PLATFORM PLATFORM_UNIX_UNIX
Sekarang mari masukkan fail pengepala yang diperlukan untuk berfungsi dengan soket. Memandangkan set fail pengepala yang diperlukan bergantung pada OS semasa, di sini kami menggunakan kod #define yang ditulis di atas untuk menentukan fail yang perlu disertakan.

#if PLATFORM == PLATFORM_WINDOWS #include #elif PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX #include #termasuk #termasuk #endif
Pada sistem UNIX, fungsi soket disertakan sebagai standard. perpustakaan sistem, jadi kami tidak memerlukan mana-mana perpustakaan pihak ketiga dalam kes ini. Walau bagaimanapun, pada Windows, untuk tujuan ini kita perlu memasukkan perpustakaan winsock.

Berikut ialah helah kecil tentang cara anda boleh melakukan ini tanpa mengubah projek atau fail:

#if PLATFORM == PLATFORM_WINDOWS #pragma comment(lib, "wsock32.lib") #endif
Saya suka helah ini kerana saya malas. Anda boleh, sudah tentu, memasukkan perpustakaan dalam projek atau dalam makefile.

Memulakan Soket
Pada kebanyakan sistem pengendalian seperti unix (termasuk macosx) tiada langkah khas diperlukan untuk memulakan fungsi soket, tetapi pada Windows anda perlu melakukan beberapa langkah dahulu - anda perlu memanggil fungsi "WSAStartup" sebelum menggunakan sebarang fungsi soket, dan selepas selesai kerja - panggil "WSACleanup".

Mari tambah dua ciri baharu:

Inline bool InitializeSockets() ( #if PLATFORM == PLATFORM_WINDOWS WSADATA WsaData; kembalikan WSAStartup(MAKEWORD(2,2), &WsaData) == NO_ERROR; #else return true; #endif ) inline void ShutdownSockets() ( #if PLATFORM == PLATFORM_WINDOWS WSACleanup(); #endif )
Kami kini mempunyai permulaan soket bebas platform dan kod penutupan. Pada platform yang tidak memerlukan permulaan, kod ini tidak melakukan apa-apa.

Buat soket
Sekarang kita boleh buat soket UDP. Ini dilakukan seperti ini:

Pemegang int = soket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); jika (mengendalikan<= 0) { printf("failed to create socket\n"); return false; }
Seterusnya, kita mesti mengikat soket ke nombor port tertentu (contohnya, 30000). Setiap soket mesti mempunyai port uniknya sendiri, kerana apabila paket baharu tiba, nombor port menentukan soket mana ia dihantar. Jangan gunakan nombor port yang lebih rendah daripada 1024 - ia dikhaskan oleh sistem.

Jika anda tidak peduli apa nombor port yang hendak digunakan untuk soket, anda hanya boleh menghantar "0" ke fungsi tersebut, dan kemudian sistem itu sendiri akan memperuntukkan anda beberapa port yang tidak dihuni.

Alamat Sockaddr_in; alamat.keluarga_dosa = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons((unsigned short) port); if (bind(handle, (const sockaddr*) &address, sizeof(sockaddr_in))< 0) { printf("failed to bind socket\n"); return false; }
Kini soket kami sedia untuk menghantar dan menerima paket data.

Tetapi apakah fungsi "htons" misteri ini dipanggil dalam kod? Ini hanyalah fungsi pembantu kecil yang menukar susunan bait integer 16-bit daripada semasa (kecil atau besar-endian) kepada big-endian yang digunakan untuk komunikasi rangkaian. Ia perlu dipanggil setiap kali anda menggunakan integer apabila bekerja dengan soket secara langsung.

Anda akan melihat fungsi "htons" dan rakan 32-bitnya "htonl" beberapa kali lagi dalam artikel ini, jadi beri perhatian.

Menukar soket kepada mod tidak menyekat
Secara lalai, soket berada dalam apa yang dipanggil "mod menyekat". Ini bermakna jika anda cuba membaca data daripadanya menggunakan "recvfrom", fungsi itu tidak akan kembali sehingga soket menerima paket dengan data yang boleh dibaca. Tingkah laku ini tidak sesuai dengan kita sama sekali. Permainan ialah aplikasi masa nyata yang berjalan pada 30 hingga 60 bingkai sesaat, dan permainan tidak boleh berhenti dan menunggu paket data tiba!

Anda boleh menyelesaikan masalah ini dengan menukar soket kepada "mod tidak menyekat" selepas ia dibuat. Dalam mod ini, fungsi "recvfrom", jika tiada data untuk dibaca dari soket, segera mengembalikan nilai tertentu yang menunjukkan bahawa ia perlu dipanggil semula apabila terdapat data pada soket.

Anda boleh menetapkan soket kepada mod tidak menyekat seperti berikut:

#if PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX int nonBlocking = 1; if (fcntl(handle, F_SETFL, O_NONBLOCK, nonBlocking) == -1) ( printf("gagal set non-blocking socket\n"); return false; ) #elif PLATFORM == PLATFORM_WINDOWS DWORD nonBlocking = 1; if (ioctlsocket(handle, FIONBIO, &nonBlocking) != 0) ( printf("gagal set non-blocking socket\n"); return false; ) #endif
Seperti yang anda lihat, tiada fungsi "fcntl" dalam Windows, jadi kami menggunakan "ioctlsocket" sebaliknya.

Menghantar bungkusan
UDP ialah protokol tanpa sambungan, jadi setiap kali kami menghantar paket, kami perlu menentukan alamat penerima. Anda boleh menggunakan soket UDP yang sama untuk menghantar paket ke alamat IP yang berbeza - hujung soket yang satu lagi tidak semestinya komputer yang sama.

Anda boleh memajukan paket ke alamat tertentu seperti berikut:

Int sent_bait = sendto(penangan, (const char*)packet_data, packet_size, 0, (sockaddr*)&address, sizeof(sockaddr_in)); if (sent_bait != packet_size) ( printf("gagal menghantar paket: nilai pulangan = %d\n", sent_bait); return false; )
Sila ambil perhatian bahawa nilai pulangan daripada fungsi "sendto" hanya menunjukkan sama ada paket berjaya dihantar dari komputer tempatan. Tetapi ia tidak menunjukkan sama ada paket itu diterima oleh destinasi! UDP tidak mempunyai cara untuk menentukan sama ada paket telah sampai ke destinasi yang dimaksudkan atau tidak.

Dalam kod di atas, kami lulus struktur "sockaddr_in" sebagai alamat destinasi. Bagaimana kita mendapatkan struktur ini?

Katakan kita mahu menghantar paket ke 207.45.186.98:30000.

Mari tulis alamat dalam borang berikut:

Int a yang tidak ditandatangani = 207; int b tidak ditandatangani = 45; int c yang tidak ditandatangani = 186; int d yang tidak ditandatangani = 98; port pendek yang tidak ditandatangani = 30000;
Dan anda perlu melakukan beberapa lagi transformasi untuk membawanya ke bentuk yang "sendto" faham:

Alamat_destinasi int tidak ditandatangani = (a<< 24) | (b << 16) | (c << 8) | d; unsigned short destination_port = port; sockaddr_in address; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(destination_address); address.sin_port = htons(destination_port);
Seperti yang anda lihat, mula-mula kita menggabungkan nombor a, b, c, d (yang berada dalam julat ) menjadi satu integer, di mana setiap bait adalah salah satu daripada nombor asal. Kami kemudiannya memulakan struktur "sockaddr_in" dengan alamat destinasi dan port kami, mengingati untuk menukar susunan bait menggunakan fungsi "htonl" dan "htons".

Perlu diserlahkan secara berasingan kes apabila anda perlu menghantar paket kepada diri sendiri: dalam kes ini, anda tidak perlu mengetahui alamat IP mesin tempatan, tetapi anda hanya boleh menggunakan 127.0.0.1 sebagai alamat (gelung balik tempatan alamat), dan paket akan dihantar ke komputer tempatan.

Menerima pakej
Selepas kami mengikat soket UDP ke port, semua paket UDP yang tiba di alamat IP dan port soket kami akan beratur. Oleh itu, untuk menerima paket, kami hanya memanggil "recvfrom" dalam gelung sehingga ia menimbulkan ralat, bermakna tiada paket yang tinggal dalam baris gilir untuk dibaca.

Oleh kerana UDP adalah tanpa sambungan, paket boleh datang dari banyak komputer yang berbeza pada rangkaian. Setiap kali kami menerima paket, fungsi "recvfrom" memberikan kami alamat IP dan port penghantar, jadi kami tahu siapa yang menghantar paket.

Kod untuk menerima paket dalam gelung:

Manakala (benar) ( unsigned char packet_data; unsigned int maximum_packet_size = sizeof(packet_data); #if PLATFORM == PLATFORM_WINDOWS typedef int socklen_t; #endif sockaddr_in from; socklen_t fromLength = sizeof(from); int receivedfrombytes = rec (char *)packet_data, maximum_packet_size, 0, (sockaddr*)&from, &fromLength); jika (received_bait<= 0) break; unsigned int from_address = ntohl(from.sin_addr.s_addr); unsigned int from_port = ntohs(from.sin_port); // process received packet }
Paket yang lebih besar daripada saiz penimbal terima hanya akan dialih keluar secara senyap daripada baris gilir. Jadi jika anda menggunakan penimbal 256 bait seperti dalam contoh di atas, dan seseorang menghantar paket 300 bait kepada anda, ia akan dibuang. Anda bukan sahaja akan mendapat 256 bait pertama daripada paket.

Tetapi kerana kami menulis protokol kami sendiri, ini tidak akan menjadi masalah bagi kami. Sentiasa berhati-hati dan pastikan saiz penimbal terima cukup besar untuk menampung paket terbesar yang mungkin dihantar kepada anda.

Menutup soket
Pada kebanyakan sistem seperti unix, soket ialah deskriptor fail, jadi untuk menutup soket selepas digunakan, anda boleh menggunakan fungsi "tutup" standard. Walau bagaimanapun, Windows, seperti biasa, menonjol, dan di dalamnya kita perlu menggunakan "closesocket".

#if PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX tutup(soket); #elif PLATFORM == PLATFORM_WINDOWS closesocket(soket); #endif
Teruskan, Windows!

Kelas soket
Jadi, kami telah mengetahui semua operasi asas: mencipta soket, mengikatnya ke port, bertukar kepada mod tidak menyekat, menghantar dan menerima paket, dan akhirnya menutup soket.

Tetapi, seperti yang anda mungkin perasan, semua operasi ini berbeza sedikit dari platform ke platform, dan, sudah tentu, sukar untuk mengingati ciri platform berbeza dan menulis semua #ifdefs ini setiap kali apabila bekerja dengan soket.

Oleh itu, kami akan membuat kelas pembalut "Soket" untuk semua operasi ini. Kami juga akan membuat kelas "Alamat" untuk memudahkan kerja dengan alamat IP. Ia akan membolehkan kami untuk tidak melakukan semua manipulasi dengan "sockaddr_in" setiap kali kami ingin menghantar atau menerima paket.

Jadi, kelas Socket kami:

Soket Kelas ( awam: Socket(); ~Socket(); bool Open(unsigned short port); void Close(); bool IsOpen() const; bool Hantar(const Alamat & destinasi, const void * data, saiz int); int Receive(Alamat & penghantar, void * data, saiz int); private: int handle; );
Dan kelas Alamat:

Alamat Kelas ( awam: Alamat(); Alamat(aksara tidak ditandatangani, aksara b tidak ditandatangani, aksara c tidak ditandatangani, aksara d tidak ditandatangani, port pendek tidak ditandatangani); Alamat(alamat int tidak ditandatangani, port pendek tidak ditandatangani); const int GetAddress() tidak ditandatangani; unsigned char GetA() const; unsigned char GetB() const; unsigned char GetC() const; unsigned char GetD() const; unsigned short GetPort() const; bool operator == (const Address & other) const; bool operator ! = (Alamat const & lain-lain) const;peribadi: alamat int tidak ditandatangani; port pendek tidak ditandatangani; );
Anda perlu menggunakannya untuk penerimaan dan penghantaran seperti berikut:

// buat soket const int port = 30000; Soket soket; if (!socket.Open(port)) ( printf("gagal mencipta soket!\n"); return false; ) // hantar paket const char data = "hello world!"; socket.Send(Alamat(127,0,0,1,port), data, saiz(data)); // terima paket semasa (benar) ( Pengirim alamat; penimbal char tidak ditandatangani; int bytes_read = socket. Receive(sender, buffer, sizeof(buffer)); if (!bytes_read) break; // proses paket )
Seperti yang anda lihat, ini lebih mudah daripada bekerja dengan soket BSD secara langsung. Dan juga kod ini akan sama untuk semua OS, kerana semua fungsi bergantung pada platform terletak di dalam kelas Socket dan Address.

Kesimpulan
Kami kini mempunyai alat bebas platform untuk menghantar dan menerima paket UDP.

UDP tidak menyokong sambungan, dan saya ingin membuat contoh yang akan menunjukkan ini dengan jelas. Jadi saya menulis program kecil yang membaca senarai alamat IP daripada fail teks dan menghantar paket kepada mereka, satu sesaat. Setiap kali program menerima paket, ia memaparkan alamat dan port komputer penghantar dan saiz paket yang diterima ke konsol.

Anda boleh mengkonfigurasi program dengan mudah supaya walaupun pada mesin tempatan anda boleh mendapatkan beberapa nod bertukar paket antara satu sama lain. Untuk melakukan ini, hanya tetapkan port yang berbeza kepada contoh program yang berbeza, sebagai contoh:

>Nod 30000
>Nod 30001
>Nod 30002
Dan lain-lain…

Setiap nod akan memajukan paket ke semua nod lain, membentuk sesuatu seperti sistem peer-to-peer mini.

Saya membangunkan program ini pada MacOSX, tetapi ia harus disusun pada mana-mana OS seperti unix dan pada Windows, tetapi jika anda perlu membuat sebarang pengubahsuaian untuk ini, sila beritahu saya.

Tag: Tambah tag

VSEVOLOD STAKHOV

Pengaturcaraan Soket

Sebilangan besar program pelayan rangkaian disusun menggunakan soket. Pada asasnya, soket adalah serupa dengan deskriptor fail dengan satu perbezaan yang sangat penting - soket digunakan untuk komunikasi antara aplikasi sama ada pada rangkaian atau pada mesin tempatan. Oleh itu, pengaturcara tidak mempunyai masalah dengan penghantaran data; soket melakukannya untuknya. Anda hanya perlu memastikan bahawa parameter soket kedua-dua aplikasi sepadan.

Oleh itu, soket rangkaian ialah struktur berpasangan yang disegerakkan dengan ketat antara satu sama lain. Untuk mencipta soket, mana-mana sistem pengendalian yang menyokongnya menggunakan fungsi soket (mujurlah, soket cukup piawai sehingga boleh digunakan untuk memindahkan data antara aplikasi yang dijalankan pada platform yang berbeza). Format fungsi ialah:

soket int(domain int, jenis int, protokol int);

Parameter domain menentukan jenis protokol pengangkutan, i.e. protokol penghantaran paket rangkaian. Protokol berikut kini disokong (tetapi ambil perhatian bahawa jenis struktur alamat akan berbeza untuk jenis protokol yang berbeza):

  • PF_UNIX atau PF_LOCAL– komunikasi tempatan untuk UNIX OS (dan seumpamanya).
  • PF_INET – IPv4, IP-Protokol Internet, yang paling biasa sekarang (alamat 32-bit).
  • PF_INET6– IPv6, generasi seterusnya bagi protokol IP (IPng) – alamat 128-bit.
  • PF_IPX – IPX– Protokol Novell.

Protokol lain disokong, tetapi 4 ini adalah yang paling popular.

Parameter jenis bermaksud jenis soket, i.e. bagaimana data akan dipindahkan: pemalar SOCK_STREAM biasanya digunakan; penggunaannya bermakna pemindahan data selamat dalam strim dua arah dengan kawalan ralat. Dengan kaedah pemindahan data ini, pengaturcara tidak perlu risau tentang pengendalian ralat rangkaian, walaupun ini tidak melindungi daripada ralat logik, yang penting untuk pelayan rangkaian.

Parameter protokol menentukan jenis protokol khusus untuk domain tertentu, contohnya IPPROTO_TCP atau IPPROTO_UDP (parameter jenis hendaklah SOCK_DGRAM dalam kes ini).

Fungsi soket hanya mencipta titik akhir dan mengembalikan pemegang soket; Sehingga soket disambungkan ke alamat jauh menggunakan fungsi sambung, data tidak boleh dihantar melaluinya! Jika paket hilang pada rangkaian, mis. Apabila kegagalan komunikasi berlaku, isyarat Paip Pecah – SIGPIPE dihantar kepada aplikasi yang mencipta soket, jadi adalah dinasihatkan untuk menetapkan pengendali kepada isyarat ini menggunakan fungsi isyarat. Selepas soket disambungkan kepada yang lain dengan fungsi sambung, data boleh dihantar ke atasnya sama ada menggunakan fungsi baca – tulis standard atau fungsi recv – hantar khusus. Selepas selesai kerja, soket mesti ditutup menggunakan fungsi tutup. Untuk membuat aplikasi klien, hanya sambungkan soket tempatan ke soket jauh (pelayan) menggunakan fungsi sambung. Format fungsi ini ialah:

int connect(int sock_fd, const struct *sockaddr serv_addr, socketlen_t addr_len);

Jika terdapat ralat, fungsi mengembalikan -1; status ralat boleh diperoleh menggunakan sistem pengendalian. Jika berjaya, 0 dikembalikan. Soket, setelah disambungkan, selalunya tidak boleh disambungkan semula, seperti, sebagai contoh, berlaku dalam protokol IP. Parameter sock_fd menentukan deskriptor soket, struktur serv_addr memberikan alamat titik akhir jauh, addr_len mengandungi panjang serv_addr (jenis socketlen_t mempunyai asal-usul sejarah, ia biasanya sama dengan jenis int). Parameter yang paling penting dalam fungsi ini ialah alamat soket jauh. Sememangnya, ia tidak sama untuk protokol yang berbeza, jadi saya akan menerangkan di sini struktur alamat hanya untuk protokol IP (v4). Untuk ini, struktur khusus sockaddr_in digunakan (ia mesti dihantar terus ke jenis sockaddr apabila memanggil sambung). Medan struktur ini kelihatan seperti ini:

struct sockaddr_in(

Sa_keluarga_t dosa_keluarga; – mentakrifkan keluarga alamat, mestilah sentiasa AF_INET

U_int16_t sin_port; – port soket dalam susunan bait rangkaian

Struct in_addr sin_addr; – struktur yang mengandungi alamat IP

Struktur yang menerangkan alamat IP:

struct in_addr(

U_int32_t s_addr; – alamat IP soket dalam susunan bait rangkaian

Perhatikan susunan bait khas dalam semua medan integer. Untuk menukar nombor port kepada susunan bait rangkaian, anda boleh menggunakan makro htons (port pendek tidak ditandatangani). Adalah sangat penting untuk menggunakan jenis integer khusus ini - integer pendek yang tidak ditandatangani.

Alamat IPv4 dibahagikan kepada tunggal, siaran (siaran) dan kumpulan (berbilang siaran). Setiap alamat tunggal menghala ke satu antara muka hos, alamat siaran menghala ke semua hos pada rangkaian, dan alamat berbilang hantaran menghala ke semua hos dalam kumpulan berbilang siaran. Struktur in_addr boleh diberikan kepada mana-mana alamat ini. Tetapi untuk pelanggan soket, dalam kebanyakan kes satu alamat diberikan. Pengecualian adalah apabila anda perlu mengimbas keseluruhan rangkaian tempatan untuk mencari pelayan, kemudian anda boleh menggunakan alamat siaran. Kemudian, kemungkinan besar, pelayan mesti melaporkan alamat IP sebenar dan soket untuk pemindahan data selanjutnya mesti disambungkan kepadanya. Menghantar data melalui alamat penyiaran bukanlah idea yang baik, kerana pelayan mana yang memproses permintaan itu tidak diketahui. Oleh itu, soket berorientasikan sambungan pada masa ini hanya boleh menggunakan alamat tunggal. Untuk pelayan soket mendengar alamat, keadaannya berbeza: di sini ia dibenarkan menggunakan alamat siaran untuk segera bertindak balas kepada permintaan pelanggan untuk lokasi pelayan. Tetapi perkara pertama dahulu. Seperti yang anda perhatikan, dalam struktur sockaddr_in medan alamat IP diwakili sebagai integer panjang yang tidak ditandatangani, dan kami digunakan untuk alamat sama ada dalam format x.x.x.x (172.16.163.89) atau dalam format aksara (myhost.com). Untuk menukar yang pertama, gunakan fungsi inet_addr (const char *ip_addr) dan untuk yang kedua, gunakan fungsi gethostbyname (const char *host). Mari lihat kedua-duanya:

u_int32_t inet_addr(const char *ip_addr)

– mengembalikan integer segera yang sesuai untuk digunakan dalam struktur sockaddr_in pada alamat IP yang dihantar kepadanya dalam format x.x.x.x. Jika ralat berlaku, INADDR_NONE dikembalikan.

struct HOSTENT* gethostbyname(const char *host_name)

– mengembalikan struktur maklumat tentang hos berdasarkan namanya. Jika tidak berjaya, mengembalikan NULL. Nama itu dicari dahulu dalam fail hos dan kemudian dalam DNS. Struktur HOSTENT menyediakan maklumat tentang hos yang diperlukan. Daripada semua medannya, yang paling penting ialah medan char **h_addr_list, yang mewakili senarai alamat IP untuk hos tertentu. Biasanya h_addr_list digunakan, mewakili alamat IP pertama hos, anda juga boleh menggunakan ungkapan h_addr untuk ini. Selepas melaksanakan fungsi gethostbyname, senarai h_addr_list struktur HOSTENT mengandungi alamat IP simbolik yang mudah, jadi anda juga mesti menggunakan fungsi inet_addr untuk menukar kepada format sockaddr_in.

Jadi, kami telah menyambungkan soket klien ke soket pelayan menggunakan fungsi sambung. Anda kemudian boleh menggunakan fungsi pemindahan data. Untuk melakukan ini, anda boleh menggunakan sama ada fungsi I/O tahap rendah standard untuk fail, kerana soket pada asasnya ialah deskriptor fail. Malangnya, fungsi kerja peringkat rendah dengan fail mungkin berbeza untuk sistem pengendalian yang berbeza, jadi anda perlu menyemak manual untuk sistem pengendalian anda. Sila ambil perhatian bahawa komunikasi rangkaian mungkin berakhir dengan isyarat SIGPIPE dan fungsi baca/tulis akan mengembalikan ralat. Anda harus sentiasa ingat untuk menyemak ralat, sebagai tambahan, anda tidak boleh lupa bahawa pemindahan data melalui rangkaian boleh menjadi sangat perlahan, dan fungsi input/output adalah segerak, dan ini boleh menyebabkan kelewatan yang ketara dalam program.

Untuk memindahkan data antara soket, terdapat fungsi khas yang biasa kepada semua sistem pengendalian - ini adalah fungsi keluarga recv dan hantar. Format mereka sangat serupa:

int hantar(int sockfd, void *data, size_t len, int flags);

– menghantar penimbal data.

int recv(int sockfd, void *data, size_t len, int flags);

– menerima penimbal data.

Argumen pertama ialah deskriptor soket, yang kedua ialah penunjuk kepada data yang akan dipindahkan, yang ketiga ialah panjang penampan, dan yang keempat ialah bendera. Jika berjaya, bilangan bait yang dipindahkan dikembalikan; jika tidak berjaya, kod ralat negatif dikembalikan. Bendera membolehkan anda menukar parameter pemindahan (contohnya, dayakan mod operasi tak segerak), tetapi untuk kebanyakan tugas, cukup untuk membiarkan medan bendera sifar untuk mod pemindahan biasa. Apabila menghantar atau menerima data, fungsi menyekat pelaksanaan program sebelum keseluruhan penimbal dihantar. Dan apabila menggunakan protokol tcp/ip, respons mesti datang dari soket jauh yang menunjukkan kejayaan menghantar atau menerima data, jika tidak, paket dihantar semula. Apabila menghantar data, pertimbangkan rangkaian MTU (saiz bingkai maksimum dihantar pada satu masa). Ia mungkin berbeza untuk rangkaian yang berbeza, contohnya, untuk Rangkaian Ethernet ia bersamaan dengan 1500.

Jadi, demi kesempurnaan, saya akan memberikan contoh paling mudah program C yang melaksanakan pelanggan soket:

#termasuk /* Perpustakaan soket standard untuk Linux */

#termasuk /* Untuk OS Windows gunakan #include */

#termasuk

int utama())(

Int sockfd = -1;

/* Deskriptor soket */

Char buf;

Char s = "Pelanggan sedia";

HOSTENT *h = NULL;

Sockaddr_in addr;

Port pendek tidak ditandatangani = 80;

Addr.sin_family = AF_INET;

/* Buat soket */

Jika(sockfd == -1)

/* Sama ada soket telah dibuat */

Pulangan -1;

H = gethostbyname("www.myhost.com");

/* Dapatkan alamat hos */

Jika(h == NULL)

/* Adakah terdapat alamat sedemikian? */

Pulangan -1;

Addr.sin_addr.s_addr = inet_addr(h->h_addr_list);

/* Tukar alamat IP kepada nombor */

If(connect(sockfd, (sockaddr*) &addr, sizeof(addr)))

/* Cuba menyambung ke soket jauh */

Pulangan -1;

/* Sambungan berjaya - teruskan */

Jika(hantar(sockfd, s, sizeof(s), 0)< 0)

Pulangan -1;

Jika(recv(sockfd, buf, sizeof(buf), 0)< 0)

Pulangan -1;

Tutup(sockfd);

/* Tutup soket */

/* Untuk Windows, fungsi closesocket(s) digunakan */

Pulangan 0;

Anda lihat, menggunakan soket tidak begitu sukar. Aplikasi pelayan menggunakan prinsip yang sama sekali berbeza untuk bekerja dengan soket. Mula-mula, soket dicipta, kemudian alamat setempat diberikan kepadanya menggunakan fungsi bind, tetapi anda boleh menetapkan alamat siaran kepada soket. Kemudian fungsi dengar mula mendengar alamat, permintaan sambungan diletakkan dalam baris gilir. Iaitu, fungsi dengar memulakan soket untuk menerima mesej. Selepas ini, anda perlu menggunakan fungsi terima, yang mengembalikan soket baharu yang telah dikaitkan dengan pelanggan. Ia adalah perkara biasa bagi pelayan untuk menerima banyak sambungan pada selang masa yang singkat. Oleh itu, anda perlu sentiasa menyemak baris gilir sambungan masuk menggunakan fungsi terima. Untuk mengatur tingkah laku ini, mereka paling kerap menggunakan keupayaan sistem pengendalian. Untuk Windows OS, versi operasi pelayan berbilang benang lebih kerap digunakan; selepas menerima sambungan, utas baru dibuat dalam program, yang memproses soket. Dalam sistem *nix, penciptaan proses anak oleh fungsi fork lebih kerap digunakan. Pada masa yang sama, kos overhed dikurangkan kerana fakta bahawa sebenarnya terdapat salinan proses dalam sistem fail proc. Dalam kes ini, semua pembolehubah proses anak adalah sama dengan ibu bapa. Dan proses kanak-kanak boleh mengendalikan sambungan masuk dengan serta-merta. Proses ibu bapa terus mendengar. Sila ambil perhatian bahawa port bernombor 1 hingga 1024 adalah keistimewaan dan mendengarnya tidak selalu mungkin. Satu lagi perkara: anda tidak boleh mempunyai dua soket berbeza untuk mendengar port yang sama pada alamat yang sama! Pertama, mari kita lihat format fungsi di atas untuk membuat soket pelayan:

int bind(int sockfd, const struct *sockaddr, socklen_t addr_len);

– memberikan alamat setempat kepada soket untuk membolehkannya menerima sambungan masuk. Untuk alamat, anda boleh menggunakan pemalar INADDR_ANY, yang membolehkan anda menerima sambungan masuk daripada semua alamat dalam subnet tertentu. Format fungsi adalah serupa dengan menyambung. Sekiranya berlaku ralat, mengembalikan nilai negatif.

int dengar(int sockfd, int tunggakan);

– fungsi mencipta baris gilir soket masuk (bilangan sambungan ditentukan oleh parameter tunggakan, ia tidak boleh melebihi nombor SOMAXCONN, yang bergantung pada OS). Selepas membuat baris gilir, anda boleh menunggu sambungan menggunakan fungsi terima. Soket biasanya menyekat, jadi pelaksanaan program digantung sehingga sambungan diterima. Sekiranya ralat, -1 dikembalikan.

int terima(int sockfd, struct *sockaddr, socklen_t addr_len)

– fungsi menunggu sambungan masuk (atau mengeluarkannya daripada baris gilir sambungan) dan mengembalikan soket baharu yang telah dikaitkan dengan klien jauh. Dalam kes ini, soket sockfd asal kekal tidak berubah. Struktur sockaddr diisi dengan nilai dari soket jauh. Sekiranya ralat, -1 dikembalikan.

Jadi berikut ialah contoh pelayan soket ringkas yang menggunakan fungsi garpu untuk mencipta proses kanak-kanak yang mengendalikan sambungan:

int utama())(

Pid_t pid;

/* ID proses anak */

Int sockfd = -1;

/* Tangani ke soket untuk mendengar */

Int s = -1;

/* Terima deskriptor soket */

Char buf;

/* Penunjuk kepada penimbal untuk menerima */

Char str = "Pelayan sedia";

/* Rentetan untuk dihantar ke pelayan */

HOSTENT *h = NULL;

/* Struktur untuk mendapatkan alamat IP */

Sockaddr_in addr;

/* Struktur protokol Tcp/ip */

Sockaddr_in raddr;

Port pendek tidak ditandatangani = 80;

/* Isikan medan struktur: */

Addr.sin_family = AF_INET;

Addr.sin_port = htons(port);

sockfd = soket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

/* Buat soket */

Jika(sockfd == -1)

/* Sama ada soket telah dibuat */

Pulangan -1;

Addr.sin_addr.s_addr = INADDR_ANY;

/* Dengar di semua alamat */

If(bind(sockfd, (sockaddr*) &addr, sizeof(addr)))

/* Berikan alamat setempat kepada soket */

Pulangan -1;

Jika(dengar(sockfd, 1))

/* Mula mendengar */

Pulangan -1;

S = terima(sockfd, (sockaddr *) &raddr, sizeof(raddr));

/* Terima sambungan */

Pid = garpu();

/* melahirkan proses anak */

Jika(pid == 0)(

/* Ini adalah proses kanak-kanak */

Jika(recv(s, buf, sizeof(buf), 0)< 0)

/* Hantar rentetan s ke soket jauh */

Pulangan -1;

Jika(hantar(s, str, sizeof(str), 0)< 0)

/* Terima respons daripada pelayan jauh */

Pulangan -1;

Printf("Rentetan yang diterima ialah: %s", buf);

/* Cetak penimbal ke output standard */

Tutup (s);

/* Tutup soket */

Pulangan 0;

/* Keluar daripada proses kanak-kanak */

Tutup(sockfd);

/* Tutup soket mendengar */

Pulangan 0;

Apabila mencipta benang untuk memproses soket, lihat manual OS, kerana memanggil fungsi penciptaan benang boleh berbeza dengan ketara pada sistem yang berbeza. Tetapi prinsip pemprosesan untuk benang tetap sama. Fungsi pemprosesan hanya perlu menghantar penuding soket sebagai hujah (biasanya sebarang jenis data dalam format void * boleh dihantar ke fungsi strim, yang memerlukan penggunaan cast jenis).

Nota penting untuk sistem Windows. Saya perhatikan bahawa sistem soket tidak berfungsi tanpa menggunakan fungsi WSAStartup untuk memulakan perpustakaan soket. Program soket Windows harus bermula seperti ini:

WSADATA wsaData;

WSAStartup(0x0101, &wsaData);

Dan apabila keluar dari program, tulis yang berikut:

WSACleanup();

Memandangkan kebanyakan operasi soket disekat, pelaksanaan tugas mesti sering diganggu dengan menunggu penyegerakan. Oleh itu, sistem *nix-like sering mengelak daripada menyekat konsol dengan mencipta jenis program khas - daemon. Daemon bukan milik konsol maya dan berlaku apabila proses kanak-kanak memanggil garpu, dan proses induk ditamatkan sebelum anak ke-2 (dan ini selalu berlaku dengan cara ini). Selepas ini, proses anak ke-2 menjadi proses utama dan tidak menghalang konsol. Berikut ialah contoh tingkah laku program ini:

pid = garpu();

/* Buat proses anak pertama */

jika (pid<0){

/* Ralat semasa memanggil garpu */

Printf("Ralat Forking:) ");

Keluar(-1);

)lain jika (pid !=0)(

/* Ini ibu bapa pertama! */

Printf("Ini adalah Bapa 1 ");

)lain(

Pid = garpu();

/* Kerja ibu bapa pertama tamat */

/* Dan kami memanggil proses anak lain */

Jika (pid<0){

Printf("Ralat forking :) ");

Keluar(-1);

)lain jika (pid !=0)(

/* Ini adalah ibu bapa kedua */

Printf("Ini adalah bapa 2");

)lain(

/* Dan ini adalah proses anak ke-2 yang sama */

/* Tukar ke mod daemon "standard" */

Setsid();

/* Jalankan daemon dalam mod superuser */

Umask(0); /* Topeng fail standard */

Chdir("/"); /* Pergi ke direktori akar */

Daemoncode(); /* Sebenarnya kod daemon itu sendiri */

/* Apabila anda memotong daemon, daemon kanak-kanak muncul */

Itu sahaja. Saya rasa ini sudah cukup untuk mencipta pelayan soket mudah.