სოკეტების გამოყენება UDP-თან მუშაობისთვის. რა განსხვავებაა პორტსა და სოკეტს შორის? UDP სოკეტის შექმნა

Socket vs Socket part 2, ან თქვით „არა“ TCP პროტოკოლს - არქივი WASM.RU

პირველ ნაწილში, რომელიც მიეძღვნა შეკრების პროგრამებში MSWindows სოკეტების გამოყენების საფუძვლებს, ვისაუბრეთ იმაზე, თუ რა არის სოკეტები, როგორ იქმნება ისინი და რა პარამეტრებია მითითებული. ამავდროულად, წარსულში აღინიშნა არაკავშირზე ორიენტირებული UDP პროტოკოლი, რომელიც არ იძლევა გარანტიას პაკეტების მიწოდების, აგრეთვე მათი დანიშნულების ადგილზე მისვლის თანმიმდევრობით. გაკვეთილის მაგალითმა შემდეგ გამოიყენა ჩვენი საყვარელი TCP პროტოკოლი. ჩვენთან ყველაფერი კარგად იყო, მაგრამ საბოლოოდ იყო არაერთი გადაუჭრელი კითხვა, კერძოდ, როგორ მოვაწყოთ ურთიერთგაცვლა ქსელში რამდენიმე კომპიუტერს შორის, როგორ გადავიტანოთ რამე ერთდროულად ბევრ კომპიუტერზე და ა.შ.

საერთოდ, პირველი ნაწილის წაკითხვა სულაც არ არის საჭირო ამჟამინდელის გასაგებად, თუმცა გზაში გამუდმებით მივმართავ. ასე მიდის. Ჰაჰა...

ასე რომ, ჩვენ ვაყენებთ პრობლემას: ჩვენ გვაქვს ლოკალური ქსელი, ვთქვათ, ათეული კომპიუტერისგან, ჩვენ უნდა მოვაწყოთ შეტყობინებების გაცვლა ნებისმიერ ორ მათგანს შორის და (სურვილისამებრ) ერთსა და ყველა დანარჩენს შორის.

მესმის, მესმის მოთხოვნის გუნდი, რომელიც ამბობს, გამოიყენეთ Windows-ის ჩაშენებული ფუნქციები, როგორიცაა:

net send 192.168.0.4 ჟენია გილოცავს!

net send Node4 ველი თქვენს პასუხს!

ამაზე მხოლოდ ორი წინააღმდეგობაა. პირველი, თქვენ არასოდეს იცით, რისი გაკეთება შეუძლია ჩვენს ოპერაციულ სისტემას ან სხვა მზა პროგრამებს, ჩვენ გვინდა ვისწავლოთ როგორ დავწეროთ ჩვენი საკუთარი პროგრამები, არა? მეორეც, ფაქტი არ არის, რომ მესიჯი ადამიანიდან ადამიანზე გადადის. ზოგადად, ოპერატორმა შეიძლება არაფერი იცოდეს... ან თუნდაც არაფერი იცოდეს...

ჩემთვის, ამ ამოცანის დაყენებისას ყველაზე მნიშვნელოვანი იყო ქსელის ყველა კომპიუტერზე ერთდროულად გადაცემის შესაძლებლობა. წარმოიდგინეთ, რომ ჩვენ დავწერეთ გარკვეული პროგრამა... ვინ თქვა - ტროას? არა, არა და არა! არავითარი ტროიანები. უბრალოდ პატარა (ძალიან) საბუღალტრო პროგრამა, მაგალითად. რამაც გარკვეული დროის შემდეგ შეძლო ჩვენს ლოკალურ ქსელში არსებულ ბევრ კომპიუტერზე დამკვიდრება. ახლა კი დადგა დანიშნული დრო, დროა დავაბალანსოთ ბალანსი, შევაჯამოთ, ასე ვთქვათ, კვარტლის შედეგები... ყველაფერი უნდა გაკეთდეს სწრაფად და სასურველია ერთდროულად. როგორ გავაკეთოთ ეს იმ მასალის ფარგლებში, რომელიც პირველ ნაწილში შევისწავლეთ, გაურკვეველი დარჩა.

პასუხი, როგორც ყოველთვის, მოდის WindowsAPI-დან. ვეძებთ და ვპოულობთ. ფუნქცია გაგზავნა() – აგზავნის მონაცემებს მითითებულ მისამართზე. მაშინ რა განსხვავებაა პირველ ნაწილში უკვე შესწავლილი ფუნქციისგან? გაგზავნა() ? თურმე გაგზავნა() შეუძლია მაუწყებლობა სპეციალურ IP მისამართზე. მაგრამ, გთხოვთ გაითვალისწინოთ, რომ ეს მუშაობს მხოლოდ SOCK_DGRAM ტიპის სოკეტებისთვის! და სოკეტები, რომლებიც გაიხსნა SOCK_DGRAM მნიშვნელობის გამოყენებით, როგორც სოკეტის ტიპის პარამეტრი, მოქმედებს UDP პროტოკოლით და არა TCP! ეს ცხადყოფს ამ სტატიის ქვესათაურის მნიშვნელობას... რა თქმა უნდა, ეს მხოლოდ ლიტერატურული მოწყობილობაა, არც ერთი პროტოკოლი არ არის უკეთესი ან უარესი მეორეზე, ისინი უბრალოდ... განსხვავებულია, სულ ესაა. მიუხედავად იმისა, რომ ორივე არის სატრანსპორტო ფენის პროტოკოლი, რომელიც "... უზრუნველყოფს მონაცემთა გადაცემას განაცხადის პროცესებს შორის." ორივე იყენებს ქსელის ფენის პროტოკოლს, როგორიცაა IP მონაცემთა გადასაცემად (მიღება). რომლის მეშვეობითაც ისინი (მონაცემები) შემდეგ შედიან ფიზიკურ დონეზე, ე.ი. გადაცემაზე ოთხშაბათი... და როგორი ოთხშაბათია, ვინ იცის. შეიძლება ეს არის სპილენძის კაბელი, ან იქნებ საერთოდ არ არის ოთხშაბათი, არამედ ხუთშაბათი, და არა სპილენძის კაბელი, არამედ ეთერში...

ქსელის პროტოკოლების ურთიერთქმედების სქემა.

UDPსერ ატაგრამა როტოკოლი

TCP-Tგათავისუფლება Cკონტროლი როტოკოლი

ICMP-Iინტერნეტი Cკონტროლი ესე როტოკოლი (საკონტროლო შეტყობინებების გაცვლის პროტოკოლი)

ARPკაბა რეზოლუცია როტოკოლი (მისამართის აღმოჩენის პროტოკოლი)

ზოგადად, თუ ნახატი არანაირად არ დაგეხმარა, არ აქვს მნიშვნელობა. მნიშვნელოვანია გვესმოდეს ერთი რამ, რომ TCP არის სატრანსპორტო ფენის პროტოკოლი, რომელიც უზრუნველყოფს საიმედო მონაცემთა ტრანსპორტირება განაცხადის პროცესებს შორის ლოგიკური კავშირის დაყენება (ხაზგასმა ჩემია). მაგრამ UDP არ არის. და შემდგომ. სადღაც, აპლიკაციის დონეზე, ერთ-ერთ ცარიელ მართკუთხედში, განთავსდება ჩვენი აპლიკაცია.

მოდით, აქ დავასრულოთ შესავალი ნაწილი და გადავიდეთ იმაზე, თუ როგორ გამოვიყენოთ იგი თავიდანვე.

მთელი მასალის საჩვენებლად, როგორც ყოველთვის, გამოიყენება სასწავლო მაგალითი, რომლის ჩამოტვირთვაც შესაძლებელია< >. ჩვენ გამოვტოვებთ ყველა Windows აპლიკაციისთვის საერთო ნაწილს და აღვწერთ მხოლოდ იმას, რაც ეხება სოკეტების მუშაობას. პირველ რიგში, თქვენ უნდა მოაწყოთ Windows Sockets DLL ფუნქციის გამოყენებით WSAStartup() , რომელიც დააბრუნებს ნულს წარმატების შემთხვევაში, ან, წინააღმდეგ შემთხვევაში, შეცდომის ერთ-ერთ კოდს. შემდეგ, ძირითადი აპლიკაციის ფანჯრის ინიციალიზაციისას, გახსენით სოკეტი შეტყობინებების მისაღებად:

    სოკეტის გამოძახება, AF_INET, \

    SOCK_DGRAM, \ ; განსაზღვრავს სოკეტის ტიპს - UDP პროტოკოლი!

    0 ; პროტოკოლის ტიპი

    თუ eax != INVALID_SOCKET ; თუ შეცდომა არ არის

    mov hSocket, eax; დამახსოვრება სახელური

ამის შემდეგ, ჩვეულებისამებრ, უნდა ვუთხრათ Windows-ს, რომ გაგზავნოს შეტყობინებები მითითებულ ფანჯარაში ჩვენ გახსნილი სოკეტიდან:

    გამოძახება WSAAsyncSelect, hSocket, hWnd, WM_SOCKET, FD_READ

სად hSocket- სოკეტის აღმწერი
hWnd- სახელური ფანჯარასთან, რომლის პროცედურებზეც გაიგზავნება შეტყობინებები
WM_SOCKET- შეტყობინება, ჩვენ მიერ განსაზღვრული section.const
FD_READ– ნიღაბი, რომელიც აზუსტებს ჩვენთვის საინტერესო მოვლენებს, ამ შემთხვევაში ეს არის სოკეტიდან მონაცემების მზადყოფნა წასაკითხად.

მესმის, ხმაში სასოწარკვეთილებით მესმის გაკვირვებული გუნდი: ფარულ აპლიკაციას დაჰპირდნენ, მაგრამ აი მთავარი ფანჯარა და ეს ყველაფერი... ფაქტია, რომ ამის გარეშე არ შეგიძლია, რადგან... ოპერაციული სისტემა აგზავნის ყველა შეტყობინებას ჩვენს აპლიკაციაში მისი ფანჯრის პროცედურის მეშვეობით. გამოსავალი მარტივია. საჭიროების შემთხვევაში, დამალეთ აპლიკაციის ეს ყველაზე მნიშვნელოვანი ფანჯარა. Როგორ? მაგალითად, გააკეთეთ კომენტარი ხაზი:

    გამოძახება ShowWindow, hwnd, SW_SHOWNORMAL

ან, უფრო სწორად, გამოიყენეთ:

    გამოიძახეთ ShowWindow, hwnd, SW_HIDE

ამის შემდეგ ჩვენი აპლიკაციაც დაიწყება, მთავარი ფანჯარა შეიქმნება, ვინდოუსიდან გადაეგზავნება WM_CREATE შეტყობინება ყველა შედეგით... მხოლოდ მისი ფანჯარა არ ჩანს არც დესკტოპზე და არც დავალების პანელზე. თუ ეს გინდოდა, მოხარული ვარ. ყოველ შემთხვევაში, გავაგრძელოთ...

ამისათვის ჩვენ გადავიყვანთ პორტის ნომერს ქსელის ბაიტის შეკვეთაში სპეციალური API ფუნქციის გამოყენებით:

    მოიწვიე htons, პორტი

    mov sin.sin_port, ax

    mov sin.sin_family, AF_INET

    mov sin.sin_addr, INADDR_ANY

მცირე ლირიკული გადახვევა, არ არის აუცილებელი ამ სტატიის მნიშვნელობის გასაგებად .

ჩვენი სოკეტების პორტების ნომრები განხილული იყო პირველი ნაწილის ბოლოს. ძნელია რეკომენდაციების მიცემა, თუ როგორი უნდა იყოს ისინი. ერთადერთი, რაც შეიძლება ითქვას, არის ის, რომ ისინი ვერ იქნებიან. უგუნურია მცდელობა გამოიყენოთ პორტის ნომრები, რომლებიც განსაზღვრულია ფართოდ გამოყენებული სერვისებისთვის, როგორიცაა:

პროტოკოლის საშუალებით TCP: 20, 21 – ftp; 23 – ტელნეტი; 25 – სმტპ; 80 – http; 139 - NetBIOS სესიის სერვისი;

პროტოკოლის საშუალებით UDP: 53 – DNS; 137, 138 – NetBIOS; 161 – SNMP;

რა თქმა უნდა, API-ს აქვს სპეციალური ფუნქცია getservbyport() , რომელიც, პორტის ნომრის მინიჭებით, აბრუნებს შესაბამისი სერვისის სახელს. უფრო ზუსტად, ფუნქცია თავად აბრუნებს მაჩვენებელს სტრუქტურას, რომლის შიგნით არის მაჩვენებელი ამ სახელზე...

შეგიძლიათ მას ასე უწოდოთ:

    invoke htons, პორტი; პორტის ნომრის გადაქცევა ქსელის ბაიტის წესრიგში

    გამოძახება getservbyport, ax, 0;

გაითვალისწინეთ, რას ამბობს Win32 პროგრამისტის მითითება getservbyport:

„...აბრუნებს მაჩვენებელს სტრუქტურაზე, რომელიც განაწილებულია Windows Sockets-ის მიერ. აპლიკაცია არასოდეს არ უნდა შეეცადოს ამ სტრუქტურის ან მისი რომელიმე კომპონენტის შეცვლას. გარდა ამისა, ამ სტრუქტურის მხოლოდ ერთი ეგზემპლარი არის გამოყოფილინაკადი, ასე რომ, აპლიკაციამ უნდა დააკოპიროს ნებისმიერი ინფორმაცია, რომელიც მას მოითხოვს Windows Sockets ფუნქციის ნებისმიერ სხვა ზარამდე."

და აქ არის თავად სტრუქტურა:

  1. s_name DWORD ?; მიუთითებს სტრიქონზე სერვისის სახელით

    s_aliases DWORD ?;

    s_port WORD ?; პორტის ნომერი

    s_proto DWORD ?;

API-ს ასევე აქვს "დაწყვილებული" ფუნქცია, ასე ვთქვათ: getservbyname(), რომელიც სერვისის სახელზე დაყრდნობით აბრუნებს ინფორმაციას გამოყენებული პორტის ნომრის შესახებ.

სამწუხაროდ, ამ ფუნქციებიდან პრაქტიკულ სარგებელს ვერ მივიღებთ. ასე რომ, იცოდე, რომ ისინი არსებობენ და დაივიწყე ისინი...

    invoke bind, hSocket, addr sin, sizeof sin

    თუ eax == SOCKET_ERROR; თუ არის შეცდომა

    გამოძახება MessageBox, NULL, addr ...

ამ ეტაპზე, მოსამზადებელი სამუშაოები მიმღები სოკეტის შექმნისა და კონფიგურაციის შესახებ დატაგრამების გამოყენებით შეიძლება ჩაითვალოს დასრულებულად. არ არის საჭირო სოკეტის დაყენება პორტზე მოსასმენად გამოძახების ფუნქციის გამოყენებით მოუსმინე, როგორც გავაკეთეთ SOCK_STREAM ტიპის სოკეტისთვის პირველ ნაწილში. ახლა ჩვენი აპლიკაციის მთავარი ფანჯრის პროცედურაში შეგვიძლია დავამატოთ კოდი, რომელიც შესრულდება, როდესაც WM_SOCKET შეტყობინება მოვა სოკეტიდან:

    ; თუ შეტყობინება მიიღება სოკეტიდან (hSocket)

    Elseif uMsg == WM_SOCKET

  1. თუ ცული == FD_READ;

  2. თუ ცული == NULL ; არანაირი შეცდომა

    ; მიიღეთ მონაცემები (64 ბაიტი) სოკეტიდან BytRecu ბუფერში

    invoke recv, hSocket, addr BytRecu, 64, 0;

ახლა მოდით ვისაუბროთ იმაზე, თუ როგორ უნდა გახსნათ სოკეტი შეტყობინებების გაგზავნისთვის. აქ მოცემულია პროგრამის ყველა საჭირო მოქმედება:

    გამოძახების სოკეტი, AF_INET, SOCK_DGRAM, 0

      მოიწვიე htons, პორტი

      mov sin_to.sin_port, ax

      mov sin_to.sin_family, AF_INET

      გამოძახება inet_addr, მისამართი IP მისამართი

      mov sin_to.sin_addr, eax

    რაც შეეხება მონაცემთა გადაცემას, ყველაფერი რაც თქვენ უნდა გააკეთოთ არის:

      გამოძახება sendto, hSocket1, adr BytSend1, 64, 0, \

      addr sin_to, sizeof sin_to

    ამ API ფუნქციის გამოძახებისას პარამეტრის მნიშვნელობები შემდეგია:

    hSocket1- სახელური ადრე გახსნილ სოკეტზე
    adrBytSend1- ბუფერის მისამართი, რომელიც შეიცავს მონაცემებს გადაცემისთვის
    64 - მონაცემთა ზომა ბუფერში, ბაიტებში
    0 - მაჩვენებელი..., MSDN-ის მაგალითში ეს მხოლოდ 0-ია
    addrsin_to- მაჩვენებელი სტრუქტურაზე, რომელიც შეიცავს დანიშნულების მისამართს
    sizeofsin_to- ამ სტრუქტურის ზომა ბაიტებში.

    თუ ფუნქციის შესრულებისას გაგზავნა() შეცდომები არ მომხდარა, შემდეგ აბრუნებს გადაცემული ბაიტების რაოდენობას, წინააღმდეგ შემთხვევაში გამომავალი არის SOCKET_ERROR eax-ში.

    ახლა დროა ვისაუბროთ იმავე სამაუწყებლო მისამართზე, რომელიც დასაწყისში იყო ნახსენები. სტრუქტურაში ჩვენ წინასწარ შევავსეთ ველი დანიშნულების IP მისამართით, სადაც მითითებულია, თუ სად უნდა გაგზავნოთ მონაცემები. თუ ეს მისამართი არის 127.0.0.1, ბუნებრივია, ჩვენი მონაცემები ჩვენს კომპიუტერზე შორს არ წავა. ლიტერატურაში ნათლად წერია, რომ ქსელში გაგზავნილი პაკეტი მისამართით 127.x.x.x არ გადაიცემა არცერთ ქსელზე. უფრო მეტიც, როუტერმა ან კარიბჭე არასოდეს არ უნდა გაავრცელოს მარშრუტიზაციის ინფორმაცია ქსელის ნომრისთვის 127 - ეს მისამართი არ არის ქსელის მისამართი. ლოკალური ქსელის ყველა კომპიუტერზე ერთდროულად გასაგზავნად, თქვენ უნდა გამოვიყენოთ მისამართი, რომელიც ჩამოყალიბებულია ჩვენი საკუთარი IP მისამართიდან, მაგრამ ყველა დაბალ ოქტეტში, დაახლოებით 192.168.0.255.

    ეს ყველაფერი, ფაქტობრივად. როდესაც პროგრამა იხურება, თქვენ უნდა დახუროთ სოკეტები და გაათავისუფლოთ Sockets DLL რესურსები; ეს კეთდება უბრალოდ:

      გამოძახება closesocket, hSocket

      გამოძახება closesocket, hSocket1

      გამოიძახეთ WSACleanup

    მრავალძაფიანი აპლიკაციებისთვის შემდეგ WSAC გასუფთავებასოკეტის ოპერაციები დასრულებულია ყველა ძაფისთვის.

    ამ სტატიის ყველაზე რთული ნაწილი ჩემთვის იყო იმის გადაწყვეტა, თუ როგორ უკეთესად წარმომედგინა Windows Sockets API-ის გამოყენება. თქვენ ალბათ უკვე გინახავთ ერთი მიდგომა, როდესაც ერთ აპლიკაციაში ერთდროულად გამოიყენებოდა როგორც მიმღები, ასევე სოკეტი შეტყობინებების გასაგზავნად. არანაკლებ მიმზიდველი ჩანს კიდევ ერთი მეთოდი, როდესაც ერთის და მეორის კოდი მკაფიოდ არის გამიჯნული, თუნდაც ის, რაც არსებობს სხვადასხვა აპლიკაციებში. საბოლოოდ, მეც განვახორციელე ეს მეთოდი, რომლის გაგებაც დამწყებთათვის შეიძლება ცოტა გაუადვილდეს. მეორეში<архиве

    ამ ფუნქციის გარეშე გაგზავნა ()გამოიმუშავებს SOCKET_ERROR-ს!

    და ბოლოს, ჩვენ შეგვიძლია აღვნიშნოთ რამდენიმე საერთო პრობლემა, რომელიც წარმოიქმნება სოკეტებთან მუშაობისას. ფანჯრის შეტყობინების დასამუშავებლად, რომელიც მიუთითებს იმაზე, რომ სოკეტის მდგომარეობა შეიცვალა, ჩვენ ვიყენებდით პირდაპირ შეტყობინებებს Windows-დან მთავარ აპლიკაციის ფანჯარაში, როგორც ყოველთვის. არსებობს კიდევ ერთი მიდგომა თითოეული სოკეტისთვის ცალკეული ფანჯრების შექმნისას.

    ზოგადად რომ ვთქვათ, მთავარი ფანჯრის მიერ შეტყობინებების ცენტრალიზებული დამუშავება ერთი შეხედვით უფრო ადვილი გასაგები მეთოდია, მაგრამ პრაქტიკაში მაინც შეიძლება იყოს პრობლემა. თუ პროგრამა ერთდროულად იყენებს ერთზე მეტ სოკეტს, მან უნდა შეინახოს სოკეტების აღწერების სია. როდესაც ჩნდება შეტყობინება სოკეტებიდან, სიაში მთავარი ფანჯრის პროცედურა ეძებს ინფორმაციას, რომელიც დაკავშირებულია ამ სოკეტის აღმწერთან და აგზავნის შეტყობინებას მდგომარეობის ცვლილების შესახებ, ამისთვის განკუთვნილი პროცედურის შემდგომ. რომელიც უკვე ასე თუ ისე რეაგირებს, რაღაცას აკეთებს იქ... ეს მიდგომა აიძულებს ქსელური ამოცანების დამუშავებას პროგრამის ბირთვში იყოს ინტეგრირებული, რაც ართულებს ქსელის ფუნქციების ბიბლიოთეკების შექმნას. ამ ქსელის ფუნქციების გამოყენებისას, დამატებითი კოდი უნდა დაემატოს აპლიკაციის მთავარ ფანჯრის დამმუშავებელს.

    შეტყობინებების დამუშავების მეორე მეთოდით, აპლიკაცია ქმნის დამალულ ფანჯარას მათი მისაღებად. ის ემსახურება აპლიკაციის მთავარი ფანჯრის პროცედურის გამოყოფას ქსელური შეტყობინებების დამუშავებისგან. ამ მიდგომამ შეიძლება გაამარტივოს ძირითადი აპლიკაცია და გააადვილოს არსებული ქსელის კოდის გამოყენება სხვა პროგრამებში. ამ მიდგომის უარყოფითი მხარეა Windows-ის – მომხმარებლის მეხსიერების გადაჭარბებული გამოყენება, რადგან თითოეული შექმნილი ფანჯრისთვის, საკმაოდ დიდი მოცულობაა დაცული.

    რომელი მეთოდი აირჩიოთ, თქვენზეა დამოკიდებული. Კიდევ ერთი რამ. ექსპერიმენტების დროს შეიძლება დაგჭირდეთ თქვენი პერსონალური firewall-ის გამორთვა. მაგალითად, Outpost Pro 2.1.275 სწავლის რეჟიმში გამოეხმაურა სოკეტში გადატანის მცდელობას, მაგრამ როდესაც გადაცემა ხელით დაუშვა, მონაცემები მაინც არ მოვიდა. ამდენი UDP-სთვის. მიუხედავად იმისა, რომ ეს შეიძლება ასე არ იყოს. იგივე სიტუაციაში ჩემს ZoneAlarmPro 5.0.590-თან არანაირი პრობლემა არ ყოფილა.

    პ.ს.სტატიის მეორე ნაწილის დასრულებისას ინტერნეტში შემთხვევით წავაწყდი ტროას წყაროს კოდს ჩვენს საყვარელ MASM ენაზე. ყველაფერი კომპილირებული და გაშვებულია, ერთი ის არის, რომ კლიენტს არ სურს სერვერთან დაკავშირება და Windows 2000 sp4-შიც კი ხანდახან შეცდომით იშლება და ამბობს, რომ აპლიკაცია დაიხურება და ეს ყველაფერი... პირადად მე რა ისევე როგორც ამ ტროას არის ის, რომ პროგრამა უბრალოდ არ ინახავს დაწკაპუნებების ჟურნალს, ან „აპარავს“ ფაილს პაროლებით და აგზავნის მას ელექტრონული ფოსტით, და აქვს დისტანციურად კონტროლირებადი ფუნქციების ფართო სპექტრი, დანერგილი ძალიან ორიგინალური გზით. თუ ჩვენ მოვახერხეთ მთელი ამ ბიზნესის ამოქმედება, მაშინ ალბათ მალე გამოჩნდება მესამე ნაწილი, რომელიც მიეძღვნება კონკრეტული იმპლემენტაციის აღწერას... მათთვის, ვინც ყურადღებით წაიკითხა ორივე სტატია და გააცნობიერა სოკეტის API ფუნქციების მოქმედება, იქ იქ არაფერია რთული. როგორც ჩანს... სხვათა შორის, ავტორი თავად წერს რედმეში, რომ ის (ტროას) საგანმანათლებლო მიზნებისთვის დაწერა. Მაშინ. ჩვენ ამას გამოვიყენებთ.

    დირექტორი ან

დროა გამოვიყენოთ Erlang დანიშნულებისამებრ - ქსელის სერვისის დანერგვა. ყველაზე ხშირად, ასეთი სერვისები მზადდება ვებ სერვერის საფუძველზე, HTTP პროტოკოლის თავზე. მაგრამ ჩვენ ავიღებთ ქვემოთ მოცემულ დონეს - TCP და UDP სოკეტებს.

ვფიქრობ, თქვენ უკვე იცით, როგორ მუშაობს ქსელი, რა არის ინტერნეტ პროტოკოლი, მომხმარებლის მონაცემთა გრამის პროტოკოლი და გადაცემის კონტროლის პროტოკოლი. ეს თემა პროგრამისტების უმეტესობისთვის ნაცნობია. მაგრამ თუ რაიმე მიზეზით გამოგრჩათ, ჯერ მოგიწევთ დაეწიოთ და შემდეგ დაუბრუნდეთ ამ გაკვეთილს.

UDP სოკეტი

მოდით გავიხსენოთ ზოგადად რა არის UDP:

  • მოკლე შეტყობინების გადაცემის პროტოკოლი (Datagram);
  • სწრაფი გადაზიდვა;
  • კლიენტსა და სერვერს შორის მუდმივი კავშირის არარსებობა, მოქალაქეობის არმქონე;
  • შეტყობინების მიწოდება და მიწოდების შეკვეთა არ არის გარანტირებული.

UDP-თან მუშაობისთვის გამოიყენება gen_udp მოდული.

მოდით გავუშვათ ორი კვანძი და დავამყაროთ კომუნიკაცია მათ შორის.

პირველ კვანძზე გახსენით UDP პორტზე 2000:

1> (ok, Socket) = gen_udp:open(2000, ). (კარგი, #პორტი<0.587>}

რეკავს gen_udp:open/2, ჩვენ გადავცემთ პორტის ნომერს და ვარიანტების ჩამონათვალს. ყველა შესაძლო ვარიანტის სია საკმაოდ დიდია, მაგრამ ჩვენ გვაინტერესებს ორი მათგანი:

ორობითი-- სოკეტი იხსნება ორობით რეჟიმში. ალტერნატიულად, სოკეტის გახსნა შესაძლებელია ტექსტის რეჟიმში ოფციის მითითებით სია. განსხვავება ისაა, თუ როგორ განვიხილავთ სოკეტიდან მიღებულ მონაცემებს - როგორც ბაიტის ნაკადს, თუ როგორც ტექსტს.

(აქტიური, ჭეშმარიტი)-- სოკეტი ღიაა აქტიურ რეჟიმში, რაც ნიშნავს, რომ სოკეტზე შემოსული მონაცემები იგზავნება შეტყობინებების სახით იმ თემის საფოსტო ყუთში, რომელიც ფლობს სოკეტს. მეტი ამის შესახებ ქვემოთ.

მე-2 კვანძზე გახსენით UDP პორტზე 2001:

1> (ok, Socket) = gen_udp:open(2001, ). (კარგი, #პორტი<0.587>}

და ჩვენ გამოგიგზავნით შეტყობინებას 1-ლი კვანძიდან მე-2-ზე:

2> gen_udp:send(Socket, (127,0,0,1), 2001,<<"Hello from 2000">>). კარგი

რეკავს gen_udp:send/4, ჩვენ გადავცემთ სოკეტს, მიმღების მისამართს და პორტს და თავად შეტყობინებას.

მისამართი შეიძლება იყოს დომენის სახელი, როგორც სტრიქონი ან ატომი, ან IPv4 მისამართი, როგორც 4 რიცხვი, ან IPv6 მისამართი, როგორც 8 ნომრის ტოპი.

მე-2 კვანძზე ჩვენ დავრწმუნდებით, რომ შეტყობინება მოვიდა:

2> <0.587>,{127,0,0,1},2000,<<"Hello from 2000">>) კარგი

შეტყობინება ჩამოდის ტუპის სახით (udp, Socket, SenderAddress, SenderPort, Packet).

მოდით გავუგზავნოთ შეტყობინება მე-2 კვანძიდან პირველს:

3> gen_udp:send(Socket, (127,0,0,1), 2000,<<"Hello from 2001">>). კარგი

პირველ კვანძზე, ჩვენ დავრწმუნდებით, რომ შეტყობინება მოვიდა:

3> flush(). Shell-მა მიიღო (udp,#Port<0.587>,{127,0,0,1},2001,<<"Hello from 2001">>) კარგი

როგორც ხედავთ, აქ ყველაფერი მარტივია.

აქტიური და პასიური სოკეტის რეჟიმი

და gen_udp, და gen_tcp, ორივეს აქვს ერთი მნიშვნელოვანი პარამეტრი: შემომავალ მონაცემებთან მუშაობის რეჟიმი. ეს შეიძლება იყოს აქტიური რეჟიმი (აქტიური, ჭეშმარიტი)ან პასიური რეჟიმი (აქტიური, ყალბი).

აქტიურ რეჟიმში, თემა იღებს შემომავალ პაკეტებს, როგორც შეტყობინებები მის საფოსტო ყუთში. და მათი მიღება და დამუშავება შესაძლებელია მიღების დარეკვით, როგორც ნებისმიერი სხვა შეტყობინებები.

udp სოკეტისთვის ეს არის შეტყობინებები, როგორიცაა:

(udp, Socket, SenderAddress, SenderPort, Packet)

ჩვენ უკვე ვნახეთ ისინი:

(udp, #პორტი<0.587>,{127,0,0,1},2001,<<"Hello from 2001">>}

tcp სოკეტისთვის მსგავსი შეტყობინებები:

(tcp, სოკეტი, პაკეტი)

აქტიური რეჟიმი მარტივი გამოსაყენებელია, მაგრამ სახიფათო, რადგან კლიენტს შეუძლია გადაფაროს ძაფების შეტყობინებების რიგი, ამოიწუროს მეხსიერება და გააფუჭოს კვანძი. ამიტომ რეკომენდებულია პასიური რეჟიმი.

პასიურ რეჟიმში, მონაცემები უნდა მოიძებნოს ზარებით gen_udp:recv/3და gen_tcp:recv/3:

Gen_udp:recv(Socket, Length, Timeout) -> (ok, (მისამართი, პორტი, პაკეტი)) | (შეცდომა, მიზეზი) gen_tcp:recv(Socket, Length, Timeout) -> (ok, Packet) | (შეცდომა, მიზეზი)

აქ ჩვენ მივუთითებთ რამდენი ბაიტი მონაცემების წაკითხვა გვინდა სოკეტიდან. თუ ეს მონაცემები არსებობს, მაშინ ჩვენ მას დაუყოვნებლივ ვიღებთ. თუ არა, ზარი იბლოკება, სანამ საკმარისი მონაცემები არ მივა. თქვენ შეგიძლიათ მიუთითოთ Timeout, რათა თავიდან აიცილოთ თემის დიდი ხნის განმავლობაში დაბლოკვა.

თუმცა, gen_udp:recvუგულებელყოფს Length არგუმენტს და აბრუნებს ნებისმიერ მონაცემს სოკეტზე. ან ბლოკავს და რაღაც მონაცემებს ელოდება თუ სოკეტზე არაფერია. გაუგებარია, რატომ არის საერთოდ არგუმენტი Length API-ში.

ამისთვის gen_tcp:recvსიგრძის არგუმენტი მუშაობს ისე, როგორც მოსალოდნელია. თუ ვარიანტი არ არის მითითებული (პაკეტი, ზომა), რომელიც ქვემოთ იქნება განხილული.

ჯერ კიდევ არის ვარიანტი (აქტიური, ერთხელ). ამ შემთხვევაში სოკეტი იწყებს აქტიურ რეჟიმში, იღებს პირველ მონაცემთა პაკეტს შეტყობინების სახით და დაუყოვნებლივ გადადის პასიურ რეჟიმში.

TCP სოკეტი

მოდით გავიხსენოთ ზოგადად რა არის TCP:

  • მონაცემთა გადაცემის სანდო პროტოკოლი უზრუნველყოფს შეტყობინების მიწოდებისა და მიწოდების შეკვეთის გარანტიას;
  • მუდმივი კავშირი კლიენტსა და სერვერს შორის, აქვს მდგომარეობა;
  • დამატებითი ხარჯები კავშირების დამყარებისა და დახურვისთვის და მონაცემთა გადაცემისთვის.

უნდა აღინიშნოს, რომ მუდმივი კავშირების შენარჩუნება ათასობით კლიენტთან დიდი ხნის განმავლობაში ძვირია. ყველა კავშირი უნდა მუშაობდეს ერთმანეთისგან დამოუკიდებლად, რაც ნიშნავს სხვადასხვა ძაფებში. მრავალი პროგრამირების ენისთვის (მაგრამ არა Erlang) ეს სერიოზული პრობლემაა.

სწორედ ამიტომ არის HTTP პროტოკოლი იმდენად პოპულარული, რომელიც, მიუხედავად იმისა, რომ მუშაობს TCP სოკეტზე, გულისხმობს ურთიერთქმედების მოკლე დროს. ეს საშუალებას აძლევს შედარებით მცირე რაოდენობის თემას (ათეულობით ან ასობით) მოემსახუროს კლიენტების მნიშვნელოვნად დიდ რაოდენობას (ათასობით, ათიათასობით).

ზოგიერთ შემთხვევაში, რჩება კლიენტსა და სერვერს შორის ხანგრძლივი მუდმივი კავშირების არსებობის აუცილებლობა. მაგალითად, ჩეთებისთვის ან მრავალმოთამაშიანი თამაშებისთვის. აქ კი ერლანგს ცოტა კონკურენტი ჰყავს.

TCP-თან მუშაობისთვის გამოიყენება gen_tcp მოდული.

TCP სოკეტთან მუშაობა უფრო რთულია, ვიდრე UDP სოკეტთან მუშაობა. ჩვენ ახლა გვაქვს კლიენტისა და სერვერის როლები, რომლებიც საჭიროებენ განსხვავებულ განხორციელებას. მოდით განვიხილოთ სერვერის განხორციელების ვარიანტი.

მოდული (სერვერი). -ექსპორტი(). start() -> start(1234). დაწყება (პორტი) -> spawn(?MODULE, სერვერი, ), კარგი. server(Port) -> io:format("start server at port ~p~n", ), (ok, ListenSocket) = gen_tcp:listen(Port, ), ) || ID<- lists:seq(1, 5)], timer:sleep(infinity), ok. accept(Id, ListenSocket) ->io:format("Socket #~p დაელოდე კლიენტს~n", ), (ok, _Socket) = gen_tcp:accept(ListenSocket), io:format("Socket #~p, სესია დაიწყო~n", ), handle_connection (ID, ListenSocket). handle_connection(Id, ListenSocket) -> მიღება (tcp, Socket, Msg) -> io:format("Socket #~p მიიღო შეტყობინება: ~p~n", ), gen_tcp:send(Socket, Msg), handle_connection(Id , ListenSocket); (tcp_closed, _Socket) ->

არსებობს ორი ტიპის სოკეტი: მოუსმინე სოკეტსდა Socket-ის მიღება. არსებობს მხოლოდ ერთი მოსმენის სოკეტი, ის იღებს ყველა კავშირის მოთხოვნას. თქვენ გჭირდებათ მრავალი Accept Socket, ერთი თითოეული კავშირისთვის. ძაფი, რომელიც ქმნის სოკეტს, ხდება სოკეტის მფლობელი. თუ მფლობელის ძაფი გადის, სოკეტი ავტომატურად იკეტება. ამიტომ, ჩვენ ვქმნით ცალკე ძაფს თითოეული სოკეტისთვის.

Listen Socket ყოველთვის უნდა იყოს გაშვებული და ამისათვის მისი მფლობელის თემა არ უნდა შეწყდეს. ამიტომ შიგნით სერვერი/1დავამატეთ გამოწვევა ტაიმერი: ძილი (უსასრულობა). ეს დაბლოკავს ძაფს და ხელს შეუშლის მის დასრულებას. ეს განხორციელება, რა თქმა უნდა, საგანმანათლებლოა. კარგი იქნებოდა სერვერის სწორად გაჩერების შესაძლებლობა, მაგრამ აქ ეს შეუძლებელია.

Accept Socket და მისთვის განკუთვნილი თემა შეიძლება შეიქმნას დინამიურად, როგორც კლიენტები გამოჩნდება. პირველი, შეგიძლიათ შექმნათ ერთი ასეთი თემა და დარეკოთ gen_tcp:accept/1და დაველოდოთ კლიენტს. ეს ზარი იბლოკება. ის მთავრდება, როდესაც კლიენტი გამოჩნდება. შემდეგ თქვენ შეგიძლიათ მოემსახუროთ მიმდინარე კლიენტს ამ თემაში და შექმნათ ახალი თემა, რომელიც ელოდება ახალ კლიენტს.

მაგრამ აქ ჩვენ გვაქვს განსხვავებული განხორციელება. ჩვენ წინასწარ ვქმნით აუზს რამდენიმე ძაფისგან და ისინი ყველა ელოდება კლიენტებს. ერთ კლიენტთან მუშაობის დასრულების შემდეგ, სოკეტი არ იკეტება, მაგრამ ელოდება ახალს. ასე რომ, იმის ნაცვლად, რომ მუდმივად გავხსნათ ახალი სოკეტები და დავხუროთ ძველი, ვიყენებთ გრძელვადიანი სოკეტების აუზს.

ეს უფრო ეფექტურია, როდესაც კლიენტების დიდი რაოდენობაა. პირველ რიგში, იმიტომ, რომ ჩვენ უფრო სწრაფად ვიღებთ კავშირებს. მეორეც, იმის გამო, რომ ჩვენ უფრო ფრთხილად ვმართავთ სოკეტებს, როგორც სისტემის რესურსს.

ძაფები ეკუთვნის Erlang კვანძს და ჩვენ შეგვიძლია შევქმნათ იმდენი მათგანი, რამდენიც გვინდა. მაგრამ სოკეტები ეკუთვნის ოპერაციულ სისტემას. მათი რაოდენობა შეზღუდულია, თუმცა საკმაოდ დიდი. (ეს არის ლიმიტი ფაილის აღწერების რაოდენობის შესახებ, რომლის გახსნის საშუალებას ოპერაციული სისტემა აძლევს მომხმარებლის პროცესს, ჩვეულებრივ 2 10 - 2 16).

ჩვენი აუზის ზომა არის სათამაშოს ზომის - 5 ნაკადი-სოკეტის წყვილი. სინამდვილეში, ჩვენ გვჭირდება რამდენიმე ასეული წყვილის აუზი. ასევე კარგი იქნება ამ აუზის გაზრდა და შემცირება მუშაობის დროს, რათა მოერგოს მიმდინარე დატვირთვას.

კლიენტთან მიმდინარე სესია მუშავდება ფუნქციაში სახელური_კავშირი/2. ჩანს, რომ სოკეტი აქტიურ რეჟიმშია და თემა იღებს მსგავს შეტყობინებებს (tcp, Socket, Msg), სად მესგ-- ეს არის ორობითი მონაცემები კლიენტისგან. ამ მონაცემებს ვუგზავნით კლიენტს, ანუ ვახორციელებთ ბანალურ ექო სერვისს :)

როდესაც კლიენტი ხურავს კავშირს, თემა იღებს შეტყობინებას (tcp_closed, _Socket), უბრუნდება მიღება/2და ელოდება შემდეგ კლიენტს.

ასე გამოიყურება ასეთი სერვერის მუშაობა ორი telnet კლიენტით:

$ telnet localhost 1234 ვცდილობ 127.0.0.1... დაკავშირებულია localhost-თან. გაქცევის სიმბოლოა "^]". გამარჯობა კლიენტისგან 1 გამარჯობა კლიენტისგან 1 ზოგიერთი შეტყობინება კლიენტისგან 1 გარკვეული შეტყობინება კლიენტისგან 1 ახალი შეტყობინება კლიენტისგან 1 ახალი შეტყობინება კლიენტისგან 1 კლიენტისგან 1 აპირებს კავშირის დახურვას კლიენტი 1 აპირებს კავშირის დახურვას ^] telnet> quit Connection დახურულია.

$ telnet localhost 1234 ვცდილობ 127.0.0.1... დაკავშირებულია localhost-თან. გაქცევის სიმბოლოა "^]". გამარჯობა კლიენტისგან 2 გამარჯობა კლიენტისგან 2 შეტყობინება კლიენტისგან 2 შეტყობინება კლიენტისგან 2 კლიენტისგან 2 კვლავ აქტიურია კლიენტი 2 კვლავ აქტიურია, მაგრამ კლიენტი 2 კვლავ აქტიურია, მაგრამ კლიენტი 2 კვლავ აქტიურია და ახლა კლიენტი 2 აპირებს კავშირის დახურვას და ახლა კლიენტი 2 აპირებს კავშირის დახურვას ^] telnet> დახურეთ კავშირი.

2> სერვერი: დაწყება(). სერვერის დაწყება პორტში 1234 ok სოკეტი #1 დაელოდე კლიენტს სოკეტი #2 დაელოდე კლიენტს სოკეტი #3 დაელოდე კლიენტს სოკეტი #4 დაელოდე კლიენტს სოკეტი #5 დაელოდე კლიენტს სოკეტი #1, სესია დაიწყო სოკეტი #1 მიიღო შეტყობინება:<<"hello from client 1\r\n">> სოკეტმა #1 მიიღო შეტყობინება:<<"some message from client 1\r\n">> სოკეტი #2, სესია დაიწყო სოკეტმა #2 მიიღო შეტყობინება:<<"hello from client 2\r\n">> სოკეტმა #2 მიიღო შეტყობინება:<<"message from client 2\r\n">> სოკეტმა #1 მიიღო შეტყობინება:<<"new message from client 1\r\n">> სოკეტმა #2 მიიღო შეტყობინება:<<"client 2 is still active\r\n">> სოკეტმა #1 მიიღო შეტყობინება:<<"client 1 is going to close connection\r\n">> სოკეტი #1, სესია დახურულია სოკეტი #1 დაელოდე კლიენტს Socket #2 მიიღო შეტყობინება:<<"but client 2 is still active\r\n">> სოკეტმა #2 მიიღო შეტყობინება:<<"and now client 2 is going to close connection\r\n">> სოკეტი #2, სესია დახურულია სოკეტი #2 დაელოდე კლიენტს

სერვერი პასიურ რეჟიმში

ეს ყველაფერი კარგია, მაგრამ კარგი სერვერი უნდა მუშაობდეს პასიურ რეჟიმში. ანუ კლიენტისგან უნდა მიიღოს მონაცემები არა საფოსტო ყუთში შეტყობინებების სახით, არამედ დარეკვით gen_tcp:recv/2,3.

ნიუანსი ის არის, რომ აქ ჩვენ უნდა მივუთითოთ, რამდენი მონაცემის წაკითხვა გვინდა. როგორ შეუძლია სერვერმა იცოდეს, რამდენი მონაცემი გამოუგზავნა მას კლიენტმა? როგორც ჩანს, კლიენტმა თავად უნდა თქვას, რამდენი მონაცემების გაგზავნას აპირებს. ამისათვის კლიენტი ჯერ აგზავნის მცირე სერვისის პაკეტს, რომელშიც მიუთითებს მისი მონაცემების ზომაზე, შემდეგ კი თავად აგზავნის მონაცემებს.

ახლა ჩვენ უნდა გადავწყვიტოთ რამდენი ბაიტი უნდა დაიკავოს ამ სერვისის პაკეტმა. თუ ეს არის 1 ბაიტი, მაშინ მასში 255-ზე დიდი რიცხვი არ შეიძლება ჩაალაგოთ.შეგიძლიათ რიცხვი 65535 ჩაალაგოთ 2 ბაიტად, ხოლო 4294967295 4 ბაიტად. 1 ბაიტი აშკარად არ არის საკმარისი. სავარაუდოა, რომ კლიენტს დასჭირდება 255 ბაიტზე მეტი მონაცემების გაგზავნა. 2 ბაიტიანი სათაური კარგია. ზოგჯერ საჭიროა 4 ბაიტიანი სათაური.

ასე რომ, კლიენტი აგზავნის 2-ბაიტიანი სერვისის პაკეტს, სადაც მითითებულია, თუ რამდენი მონაცემი მოჰყვება მას და შემდეგ თავად მონაცემებს:

შეტყობინება =<<"Hello">>, ზომა = byte_size(Msg), Header =<>, gen_tcp:send(სოკეტი,<

>),

კლიენტის სრული კოდი:

მოდული (კლიენტი 2). -ექსპორტი(). start() -> start("localhost", 1234). დაწყება (ჰოსტი, პორტი) -> spawn(?MODULE, კლიენტი, ). გაგზავნა (Pid, Msg) -> Pid ! (გაგზავნა, მესიჯი), კარგი. გაჩერება (Pid) -> Pid ! გაჩერდი, კარგი. კლიენტი(ჰოსტი, პორტი) -> io:format("კლიენტი ~p უკავშირდება ~p:~p~n", ), (ok, Socket) = gen_tcp:connect(ჰოსტი, პორტი, ), loop(Socket). loop(Socket) -> მიღება (გაგზავნა, შეტყობინება) -> io:format("კლიენტი ~p გაგზავნა ~p~n", ), ზომა = ბაიტი_ზომა (Msg), სათაური =<>, gen_tcp:send(სოკეტი,<

>), loop(Socket); (tcp, Socket, Msg) -> io:format("Client ~p got message: ~p~n", ), loop(Socket); stop -> io:format("კლიენტი ~p ხურავს კავშირს და წყვეტს~n", ), gen_tcp:close(Socket) 200-ის შემდეგ -> loop(Socket) დასასრული.

სერვერი ჯერ კითხულობს 2 ბაიტს, განსაზღვრავს მონაცემთა ზომას და შემდეგ კითხულობს ყველა მონაცემს:

(ok, Header) = gen_tcp:recv(Socket, 2),<> = სათაური, (ok, Msg) = gen_tcp:recv(სოკეტი, ზომა),

სერვერის კოდში არის ფუნქციები დაწყება/0და დაწყება/1არ შეცვლილა, დანარჩენი ცოტა შეიცვალა:

სერვერი(პორტი) -> io:format("სერვერის დაწყება პორტში ~p~n", ), (ok, ListenSocket) = gen_tcp:listen(პორტი, ), ) || ID<- lists:seq(1, 5)], timer:sleep(infinity), ok. accept(Id, ListenSocket) ->io:format("Socket #~p დაელოდე კლიენტს~n", ), (ok, Socket) = gen_tcp:accept(ListenSocket), io:format("Socket #~p, სესია დაიწყო~n", ), handle_connection (ID, ListenSocket, Socket). handle_connection(Id, ListenSocket, Socket) -> case gen_tcp:recv(Socket, 2) of (ok, Header) -><> = Header, (ok, Msg) = gen_tcp:recv(Socket, Size), io:format("Socket #~p მიიღო შეტყობინება: ~p~n", ), gen_tcp:send(Socket, Msg), handle_connection( ID, ListenSocket, Socket); (შეცდომა, დახურულია) -> io:format("Socket #~p, სესია დახურულია ~n", ), accept(Id, ListenSocket) დასასრული.

სესიის მაგალითი კლიენტის მხრიდან:

2> Pid = client2:start(). კლიენტი<0.40.0>უკავშირდება "localhost"-ს:1234<0.40.0>3> კლიენტი2: გაგზავნა (Pid,<<"Hello">>). კლიენტი<0.40.0>გაგზავნა<<"Hello">> კარგი კლიენტი<0.40.0>მივიღე შეტყობინება:<<"Hello">> 4> კლიენტი2: გაგზავნა (Pid,<<"Hello again">>). კლიენტი<0.40.0>გაგზავნა<<"Hello again">> კარგი კლიენტი<0.40.0>მივიღე შეტყობინება:<<"Hello again">> 5> client2:stop(Pid). კლიენტი<0.40.0>ხურავს კავშირს და ჩერდება კარგად

და სერვერის მხრიდან:

2> server2:start(). სერვერის დაწყება პორტში 1234 ok სოკეტი #1 დაელოდე კლიენტს სოკეტი #2 დაელოდე კლიენტს სოკეტი #3 დაელოდე კლიენტს სოკეტი #4 დაელოდე კლიენტს სოკეტი #5 დაელოდე კლიენტს სოკეტი #1, სესია დაიწყო სოკეტი #1 მიიღო შეტყობინება:<<"Hello">> სოკეტმა #1 მიიღო შეტყობინება:<<"Hello again">> სოკეტი #1, სესია დახურულია სოკეტი #1 დაველოდოთ კლიენტს

ეს ყველაფერი კარგია, მაგრამ ნამდვილად არ არის საჭირო სათაურის პაკეტთან ხელით გამკლავება. ეს უკვე განხორციელდა ქ gen_tcp. კლიენტის მხარეს სოკეტის გახსნისას პარამეტრებში უნდა მიუთითოთ სერვისის პაკეტის ზომა:

(ok, Socket) = gen_tcp:connect(ჰოსტი, პორტი, ),

და სერვერის მხარეს:

(ok, ListenSocket) = gen_tcp:listen(პორტი, ),

და ქრება ამ სათაურის ფორმირებისა და ანალიზის საჭიროება.

კლიენტის მხრივ, გაგზავნა გამარტივებულია:

Gen_tcp:send(Socket, Mesg),

და სერვერის მხრიდან ეს აადვილებს მიღებას:

Handle_connection(Id, ListenSocket, Socket) -> case gen_tcp:recv(Socket, 0) of (ok, Msg) -> io:format("Socket #~p მიიღო შეტყობინება: ~p~n", ), gen_tcp:send (Socket, Msg), handle_connection (Id, ListenSocket, Socket); (შეცდომა, დახურულია) -> io:format("Socket #~p, სესია დახურულია ~n", ), accept(Id, ListenSocket) დასასრული.

ახლა დარეკვისას gen_tcp:recv/2ჩვენ ვაზუსტებთ სიგრძე = 0. gen_tcpმან იცის, რამდენი ბაიტი უნდა წაიკითხოს სოკეტიდან.

ტექსტურ პროტოკოლებთან მუშაობა

სერვისის სათაურის ვარიანტის გარდა, არსებობს კიდევ ერთი მიდგომა. თქვენ შეგიძლიათ წაიკითხოთ სოკეტიდან თითო ბაიტი, სანამ არ შეგხვდებათ სპეციალური ბაიტი, რომელიც სიმბოლოა პაკეტის დასასრულს. ეს შეიძლება იყოს ნულოვანი ბაიტი ან ახალი ხაზის სიმბოლო.

ეს ვარიანტი ტიპიურია ტექსტური პროტოკოლებისთვის (SMTP, POP3, FTP).

არ არის საჭირო სოკეტიდან წაკითხვის საკუთარი განხორციელების დაწერა, ყველაფერი უკვე დანერგილია gen_tcp. თქვენ უბრალოდ უნდა მიუთითოთ სოკეტის პარამეტრებში (პაკეტი, 2)ვარიანტი (პაკეტი, ხაზი).

(ok, ListenSocket) = gen_tcp:listen(პორტი, ),

წინააღმდეგ შემთხვევაში, სერვერის კოდი უცვლელი რჩება. მაგრამ ახლა ჩვენ შეგვიძლია კვლავ დავუბრუნდეთ telnet კლიენტს.

$ telnet localhost 1234 ვცდილობ 127.0.0.1... დაკავშირებულია localhost-თან. გაქცევის სიმბოლოა "^]". გამარჯობა გამარჯობა გამარჯობა კიდევ ერთხელ გამარჯობა ^] telnet> quit კავშირი დახურულია.

ჩვენ დაგვჭირდება TCP სერვერი, ტექსტური პროტოკოლი და ტელნეტ კლიენტი ჩვენი კურსის მუშაობისას.

სოკეტები

სოკეტიარის ორმხრივი საკომუნიკაციო არხის ერთი ბოლო ქსელში გაშვებულ ორ პროგრამას შორის. ორი სოკეტის ერთმანეთთან შეერთებით, შეგიძლიათ მონაცემების გადაცემა სხვადასხვა პროცესებს შორის (ლოკალური ან დისტანციური). სოკეტის დანერგვა უზრუნველყოფს ქსელის და სატრანსპორტო ფენის პროტოკოლების ინკაპსულაციას.

სოკეტები თავდაპირველად შეიქმნა UNIX-ისთვის კალიფორნიის უნივერსიტეტში, ბერკლიში. UNIX-ში კომუნიკაციის I/O მეთოდი მიჰყვება გახსნის/წაკითხვის/ჩაწერის/დახურვის ალგორითმს. სანამ რესურსს გამოიყენებთ, ის უნდა გაიხსნას შესაბამისი ნებართვებითა და სხვა პარამეტრებით. რესურსის გახსნის შემდეგ, მონაცემების წაკითხვა ან ჩაწერა შესაძლებელია. რესურსის გამოყენების შემდეგ მომხმარებელმა უნდა გამოიძახოს Close() მეთოდი, რათა ოპერაციულ სისტემას მიანიშნებდეს, რომ ეს დასრულებულია რესურსით.

როდის დაემატა ფუნქციები UNIX ოპერაციულ სისტემას? პროცესთაშორისი კომუნიკაცია (IPC)და ქსელის გაცვლა, ნასესხები იყო შეყვანის-გამოსვლის ნაცნობი ნიმუში. UNIX-სა და Windows-ში კომუნიკაციისთვის გამოვლენილი ყველა რესურსი იდენტიფიცირებულია სახელურებით. ეს აღწერები, ან სახელურები, შეუძლია მიუთითოს ფაილზე, მეხსიერებაზე ან სხვა საკომუნიკაციო არხზე, მაგრამ რეალურად მიუთითოს ოპერაციული სისტემის მიერ გამოყენებული შიდა მონაცემთა სტრუქტურაზე. სოკეტი, როგორც იგივე რესურსი, ასევე წარმოდგენილია აღწერით. მაშასადამე, სოკეტებისთვის, სახელურის სიცოცხლე შეიძლება დაიყოს სამ ფაზად: გახსენით (შექმენით) სოკეტი, მიიღეთ ან გაგზავნეთ სოკეტიდან და ბოლოს დახურეთ სოკეტი.

IPC ინტერფეისი სხვადასხვა პროცესებს შორის კომუნიკაციისთვის აგებულია I/O მეთოდების თავზე. ისინი აადვილებენ სოკეტებს მონაცემების გაგზავნასა და მიღებას. თითოეული სამიზნე მითითებულია სოკეტის მისამართით, ამიტომ ეს მისამართი შეიძლება მითითებული იყოს კლიენტში სამიზნეთან კავშირის დასამყარებლად.

სოკეტების ტიპები

არსებობს ორი ძირითადი ტიპის სოკეტი - ნაკადის სოკეტები და დატაგრამის სოკეტები.

ნაკადის სოკეტები

ნაკადის სოკეტი არის კავშირზე დაფუძნებული სოკეტი, რომელიც შედგება ბაიტების ნაკადისგან, რომელიც შეიძლება იყოს ორმხრივი, რაც ნიშნავს, რომ აპლიკაციას შეუძლია მონაცემების გაგზავნა და მიღება ამ ბოლო წერტილის მეშვეობით.

ნაკადის სოკეტი უზრუნველყოფს შეცდომის გამოსწორებას, ამუშავებს მიწოდებას და ინარჩუნებს მონაცემთა თანმიმდევრულობას. მასზე შეიძლება დაეყრდნოთ მოწესრიგებული, დუბლირებული მონაცემების მიწოდებას. ნაკადის სოკეტი ასევე შესაფერისია დიდი რაოდენობით მონაცემების გადასაცემად, რადგან თითოეული გაგზავნილი შეტყობინებისთვის ცალკეული კავშირის დამყარების ხარჯი შეიძლება იყოს აკრძალული მცირე რაოდენობის მონაცემებისთვის. ნაკადის სოკეტები ამ დონის ხარისხის მიღწევას პროტოკოლის გამოყენებით გადაცემის კონტროლის პროტოკოლი (TCP). TCP უზრუნველყოფს მონაცემების მეორე მხარეს წვდომას სწორი თანმიმდევრობით და შეცდომების გარეშე.

ამ ტიპის სოკეტისთვის ბილიკი ყალიბდება შეტყობინებების გაგზავნამდე. ეს უზრუნველყოფს ურთიერთქმედებაში მონაწილე ორივე მხარის მიღებას და რეაგირებას. თუ აპლიკაცია ორ შეტყობინებას გაუგზავნის მიმღებს, გარანტირებულია, რომ შეტყობინებები მიიღება იმავე თანმიმდევრობით.

თუმცა, ინდივიდუალური შეტყობინებები შეიძლება დაიყოს პაკეტებად და არ არსებობს ჩანაწერების საზღვრების განსაზღვრის საშუალება. TCP-ის გამოყენებისას ეს პროტოკოლი ზრუნავს გადაცემული მონაცემების შესაბამისი ზომის პაკეტებად დაყოფაზე, ქსელში გაგზავნაზე და მეორე მხარეს აწყობაზე. აპლიკაციამ იცის მხოლოდ, რომ აგზავნის ბაიტების გარკვეულ რაოდენობას TCP ფენაში და მეორე მხარე იღებს ამ ბაიტებს. თავის მხრივ, TCP ეფექტურად არღვევს ამ მონაცემებს შესაბამისი ზომის პაკეტებად, იღებს ამ პაკეტებს მეორე მხარეს, ამოიღებს მათ მონაცემებს და აერთიანებს მათ ერთად.

ნაკადები ეფუძნება აშკარა კავშირებს: სოკეტი A ითხოვს კავშირს B სოკეტთან, ხოლო სოკეტი B ან იღებს ან უარყოფს კავშირის მოთხოვნას.

თუ მონაცემები გარანტირებული უნდა იყოს მეორე მხარეს გადასატანად ან მონაცემთა ზომა დიდია, ნაკადის სოკეტები სასურველია, ვიდრე დატაგრამის სოკეტები. ამიტომ, თუ საიმედო კომუნიკაციას ორ აპლიკაციას შორის უდიდესი მნიშვნელობა აქვს, აირჩიეთ ნაკადის სოკეტები.

ელ.ფოსტის სერვერი არის აპლიკაციის მაგალითი, რომელმაც უნდა მიაწოდოს შინაარსი სწორი თანმიმდევრობით, დუბლირების ან გამოტოვების გარეშე. ნაკადის სოკეტი ეყრდნობა TCP-ს, რათა უზრუნველყოს შეტყობინებების მიწოდება დანიშნულების ადგილამდე.

დატაგრამის სოკეტები

Datagram სოკეტებს ზოგჯერ უწოდებენ უკავშირო სოკეტებს, ანუ მათ შორის აშკარა კავშირი არ არის დამყარებული - შეტყობინება იგზავნება მითითებულ სოკეტში და, შესაბამისად, შეიძლება მიღებული იყოს მითითებული სოკეტიდან.

ნაკადის სოკეტები უფრო საიმედო მეთოდს იძლევა, ვიდრე დატაგრამის სოკეტები, მაგრამ ზოგიერთი აპლიკაციისთვის აშკარა კავშირის დამყარებასთან დაკავშირებული ზედნადები მიუღებელია (მაგალითად, დღის დროის სერვერი, რომელიც უზრუნველყოფს დროის სინქრონიზაციას თავის კლიენტებს). ბოლოს და ბოლოს, სერვერთან საიმედო კავშირის დამყარებას დრო სჭირდება, რაც უბრალოდ იწვევს სერვისის შეფერხებებს და სერვერის აპლიკაციის დავალება ვერ ხერხდება. ზედნადების შესამცირებლად, თქვენ უნდა გამოიყენოთ დატაგრამის სოკეტები.

დატაგრამის სოკეტების გამოყენება მოითხოვს კლიენტიდან სერვერზე მონაცემების გადაცემას მომხმარებლის მონაცემთა პროგრამის პროტოკოლი (UDP). ამ პროტოკოლში დაწესებულია გარკვეული შეზღუდვები შეტყობინებების ზომაზე და განსხვავებით ნაკადის სოკეტებისგან, რომლებსაც შეუძლიათ საიმედოდ გაგზავნონ შეტყობინებები დანიშნულების სერვერზე, დატაგრამის სოკეტები არ იძლევა საიმედოობას. თუ მონაცემები დაიკარგება სადმე ქსელში, სერვერი არ შეატყობინებს შეცდომებს.

განხილული ორი ტიპის გარდა, ასევე არსებობს სოკეტების განზოგადებული ფორმა, რომელსაც ეწოდება დაუმუშავებელი ან ნედლი.

ნედლი სოკეტები

ნედლი სოკეტების გამოყენების მთავარი მიზანია გვერდის ავლით მექანიზმი, რომლითაც კომპიუტერი ამუშავებს TCP/IP-ს. ეს მიიღწევა TCP/IP სტეკის სპეციალური იმპლემენტაციის უზრუნველყოფით, რომელიც არღვევს ბირთვში TCP/IP სტეკის მიერ მოწოდებულ მექანიზმს - პაკეტი გადაეცემა პირდაპირ აპლიკაციას და, შესაბამისად, მუშავდება ბევრად უფრო ეფექტურად, ვიდრე კლიენტის მეშვეობით გავლისას. მთავარი პროტოკოლის დასტა.

განმარტებით, ნედლეული სოკეტი არის სოკეტი, რომელიც იღებს პაკეტებს, გვერდს უვლის TCP და UDP ფენებს TCP/IP დასტაში და აგზავნის მათ პირდაპირ აპლიკაციაში.

ასეთი სოკეტების გამოყენებისას პაკეტი არ გადის TCP/IP ფილტრში, ე.ი. არანაირად არ არის დამუშავებული და ჩნდება ნედლი სახით. ამ შემთხვევაში მიმღები აპლიკაციის პასუხისმგებლობაა ყველა მონაცემის სწორად დამუშავება და ისეთი მოქმედებების შესრულება, როგორიცაა სათაურების ამოღება და ველების გარჩევა - მაგალითად, აპლიკაციაში მცირე TCP/IP სტეკის ჩართვა.

თუმცა, ხშირად არ შეიძლება დაგჭირდეთ პროგრამა, რომელიც ეხება ნედლეულ სოკეტებს. თუ თქვენ არ წერთ სისტემურ პროგრამულ უზრუნველყოფას ან პაკეტის სნაიფერის მსგავს პროგრამას, თქვენ არ დაგჭირდებათ ასეთ დეტალებზე წასვლა. Raw სოკეტები ძირითადად გამოიყენება სპეციალიზებული დაბალი დონის პროტოკოლის აპლიკაციების შემუშავებაში. მაგალითად, სხვადასხვა TCP/IP უტილიტები, როგორიცაა trace route, ping ან arp, იყენებენ ნედლეულ სოკეტებს.

ნედლეულ სოკეტებთან მუშაობა მოითხოვს ძირითადი TCP/UDP/IP პროტოკოლების მყარ ცოდნას.

პორტები

პორტი განსაზღვრულია იმისთვის, რომ დაუშვას მრავალ აპლიკაციასთან ერთდროული ურთიერთქმედების პრობლემა. არსებითად, ის აფართოებს IP მისამართის კონცეფციას. კომპიუტერს, რომელიც ერთდროულად მუშაობს მრავალ აპლიკაციას, იღებს პაკეტს ქსელიდან, შეუძლია დაადგინოს სამიზნე პროცესი უნიკალური პორტის ნომრის გამოყენებით, რომელიც მითითებულია კავშირის დამყარებისას.

სოკეტი შედგება აპარატის IP მისამართისა და TCP აპლიკაციის მიერ გამოყენებული პორტის ნომრისგან. იმის გამო, რომ IP მისამართი უნიკალურია ინტერნეტში და პორტის ნომრები უნიკალურია ცალკეულ მანქანაზე, სოკეტის ნომრები ასევე უნიკალურია მთელ ინტერნეტში. ეს მახასიათებელი საშუალებას აძლევს პროცესს დაუკავშირდეს ქსელში სხვა პროცესს, რომელიც დაფუძნებულია მხოლოდ სოკეტის ნომერზე.

პორტის ნომრები დაცულია გარკვეული სერვისებისთვის - ეს არის ცნობილი პორტის ნომრები, როგორიცაა პორტი 21, რომელიც გამოიყენება FTP-ში. თქვენს აპლიკაციას შეუძლია გამოიყენოს ნებისმიერი პორტის ნომერი, რომელიც არ არის რეზერვირებული და ჯერ არ გამოიყენება. სააგენტო ინტერნეტის მინიჭებული ნომრების ავტორიტეტი (IANA)ინახავს საყოველთაოდ ცნობილი პორტის ნომრების სიას.

როგორც წესი, კლიენტ-სერვერის აპლიკაცია სოკეტების გამოყენებით შედგება ორი განსხვავებული აპლიკაციისგან - კლიენტი, რომელიც იწყებს კავშირს სამიზნესთან (სერვერთან) და სერვერი, რომელიც ელოდება კლიენტისგან კავშირს.

მაგალითად, კლიენტის მხარეს, აპლიკაციამ უნდა იცოდეს სამიზნე მისამართი და პორტის ნომერი. კავშირის მოთხოვნის გაგზავნით, კლიენტი ცდილობს დაამყაროს კავშირი სერვერთან:

თუ მოვლენები წარმატებით განვითარდება, იმ პირობით, რომ სერვერი დაიწყება სანამ კლიენტი შეეცდება მასთან დაკავშირებას, სერვერი ეთანხმება კავშირს. თანხმობის მიცემის შემდეგ, სერვერის აპლიკაცია ქმნის ახალ სოკეტს კონკრეტულად იმ კლიენტთან ურთიერთობისთვის, რომელმაც დაამყარა კავშირი:

ახლა კლიენტს და სერვერს შეუძლიათ ერთმანეთთან ურთიერთობა, კითხულობენ შეტყობინებებს თითოეული საკუთარი სოკეტიდან და, შესაბამისად, წერენ შეტყობინებებს.

სოკეტებთან მუშაობა .NET-ში

სოკეტის მხარდაჭერა .NET-ში უზრუნველყოფილია სახელების სივრცის კლასებით System.Net.Sockets- დავიწყოთ მათი მოკლე აღწერით.

კლასები სოკეტებთან მუშაობისთვის
Კლასი აღწერა
MulticastOption MulticastOption კლასი ადგენს IP მისამართის მნიშვნელობას IP ჯგუფში გაწევრიანების ან გასვლისთვის.
NetworkStream NetworkStream კლასი ახორციელებს საბაზისო ნაკადის კლასს, საიდანაც ხდება მონაცემების გაგზავნა და მიღება. ეს არის მაღალი დონის აბსტრაქცია, რომელიც წარმოადგენს კავშირს TCP/IP საკომუნიკაციო არხთან.
TcpClient TcpClient კლასი ეფუძნება Socket კლასს უმაღლესი დონის TCP სერვისების უზრუნველსაყოფად. TcpClient გთავაზობთ რამდენიმე მეთოდს ქსელში მონაცემთა გაგზავნისა და მიღებისთვის.
TcpListener ეს კლასი ასევე ეფუძნება დაბალი დონის Socket კლასს. მისი მთავარი დანიშნულებაა სერვერის აპლიკაციები. ის უსმენს კლიენტებისგან შემომავალ კავშირების მოთხოვნებს და აცნობებს განაცხადის ნებისმიერ კავშირს.
UdpClient UDP არის უკავშირო პროტოკოლი, ამიტომ .NET-ში UDP სერვისის განსახორციელებლად საჭიროა სხვადასხვა ფუნქციონირება.
SocketException ეს გამონაკლისი იშლება, როდესაც შეცდომა ხდება სოკეტზე.
სოკეტი ბოლო კლასი System.Net.Sockets სახელთა სივრცეში არის თავად Socket კლასი. ის უზრუნველყოფს სოკეტის აპლიკაციის ძირითად ფუნქციონირებას.

სოკეტის კლასი

Socket კლასი მნიშვნელოვან როლს ასრულებს ქსელის პროგრამირებაში, უზრუნველყოფს როგორც კლიენტის, ასევე სერვერის ფუნქციონირებას. უპირველეს ყოვლისა, ამ კლასის მეთოდებზე გამოძახებები ახორციელებს უსაფრთხოებასთან დაკავშირებულ აუცილებელ შემოწმებას, მათ შორის უსაფრთხოების ნებართვების შემოწმებას, რის შემდეგაც ისინი გადაგზავნიან მეთოდების კოლეგებს Windows Sockets API-ში.

სანამ Socket კლასის გამოყენების მაგალითზე გადავიდეთ, მოდით გადავხედოთ ამ კლასის რამდენიმე მნიშვნელოვან თვისებასა და მეთოდს:

Socket კლასის თვისებები და მეთოდები
საკუთრება ან მეთოდი აღწერა
მისამართიოჯახი აძლევს სოკეტის მისამართების ოჯახს - მნიშვნელობას Socket.AddressFamily ნუმერაციისგან.
ხელმისაწვდომია აბრუნებს წასაკითხად ხელმისაწვდომი მონაცემების რაოდენობას.
ბლოკირება იღებს ან ადგენს მნიშვნელობას, რომელიც მიუთითებს, არის თუ არა სოკეტი დაბლოკვის რეჟიმში.
დაკავშირებულია აბრუნებს მნიშვნელობას, რომელიც მიუთითებს, არის თუ არა სოკეტი დაკავშირებული დისტანციურ ჰოსტთან.
LocalEndPoint იძლევა ლოკალურ საბოლოო წერტილს.
პროტოკოლის ტიპი იძლევა სოკეტის პროტოკოლის ტიპს.
RemoteEndPoint იძლევა დისტანციური სოკეტის ბოლო წერტილს.
SocketType იძლევა სოკეტის ტიპს.
Accept() ქმნის ახალ სოკეტს შემომავალი კავშირის მოთხოვნის დასამუშავებლად.
Bind() აკავშირებს სოკეტს ადგილობრივ ბოლო წერტილთან, რათა მოუსმინოს შემომავალი კავშირის მოთხოვნებს.
დახურვა () აიძულებს სოკეტს დახუროს.
დაკავშირება () ამყარებს კავშირს დისტანციურ ჰოსტთან.
GetSocketOption() აბრუნებს SocketOption მნიშვნელობას.
IOControl () აყენებს დაბალი დონის მუშაობის რეჟიმებს სოკეტისთვის. ეს მეთოდი უზრუნველყოფს დაბალი დონის წვდომას ფუძემდებლურ Socket კლასზე.
მოუსმინე () აყენებს სოკეტს მოსმენის (ლოდინის) რეჟიმში. ეს მეთოდი მხოლოდ სერვერის აპლიკაციებისთვისაა.
მიღება () იღებს მონაცემებს დაკავშირებული სოკეტიდან.
გამოკითხვა () განსაზღვრავს სოკეტის სტატუსს.
აირჩიეთ () ამოწმებს ერთი ან მეტი სოკეტის სტატუსს.
გაგზავნა () აგზავნის მონაცემებს დაკავშირებულ სოკეტში.
SetSocketOption() აყენებს სოკეტის ვარიანტს.
Გათიშვა() გამორთავს სოკეტზე გაგზავნისა და მიღების ოპერაციებს.

აპლიკაციები, რომლებიც იყენებენ TCP-სა და UDP-ს, ფუნდამენტურად განსხვავებულია, რადგან UDP არის არასანდო, უკავშირო მონაცემთა გრამის პროტოკოლი და ფუნდამენტურად განსხვავდება TCP-ის კავშირზე ორიენტირებული, ბაიტებზე სანდო გადაცემისგან. თუმცა, არის შემთხვევები, როდესაც აზრი აქვს UDP-ის გამოყენებას TCP-ის ნაცვლად. ჩვენ განვიხილავთ ასეთ შემთხვევებს პუნქტში 22.4. ზოგიერთი პოპულარული აპლიკაცია აგებულია UDP-ის გამოყენებით, როგორიცაა DNS (დომენის სახელების სისტემა), NFS (ქსელის ფაილური სისტემა) და SNMP (ქსელის მართვის მარტივი პროტოკოლი).

ნახ. სურათი 8.1 გვიჩვენებს ფუნქციის გამოძახებას ტიპიური UDP კლიენტ-სერვერის სქემისთვის. კლიენტი არ ამყარებს კავშირს სერვერთან. ამის ნაცვლად, კლიენტი უბრალოდ აგზავნის დატაგრამას სერვერზე sendto ფუნქციის გამოყენებით (აღწერილია შემდეგ ნაწილში), რომელიც არგუმენტად იღებს მიმღების (სერვერის) მისამართს. ანალოგიურად, სერვერი არ ამყარებს კავშირს კლიენტთან. ამის ნაცვლად, სერვერი უბრალოდ იძახებს recvfrom ფუნქციას, რომელიც ელოდება რომელიმე კლიენტისგან მონაცემების მიღებას. ფუნქცია recvfrom აბრუნებს კლიენტის მისამართს (მოცემული პროტოკოლისთვის) დატაგრამასთან ერთად, რათა სერვერმა შეძლოს პასუხის გაგზავნა ზუსტად იმ კლიენტზე, რომელმაც გაგზავნა დატაგრამა.

ბრინჯი. 8.1. სოკეტის ფუნქციები UDP კლიენტ-სერვერის მოდელისთვის

სურათი 8.1 ასახავს კლიენტსა და სერვერს შორის ტიპიური UDP მონაცემთა გრამის გაცვლის სცენარის დროის დიაგრამას. ჩვენ შეგვიძლია შევადაროთ ეს მაგალითი ნახატზე ნაჩვენები ტიპიური TCP გაცვლას. 4.1.

ამ თავში ჩვენ აღვწერთ ახალ ფუნქციებს, რომლებიც გამოიყენება UDP სოკეტებთან, recvfrom და sendto, და გადავამუშავებთ ჩვენს კლიენტ-სერვერის მოდელს UDP-ის გამოსაყენებლად. ჩვენ ასევე განვიხილავთ დაკავშირების ფუნქციის გამოყენებას UDP სოკეტთან და ასინქრონული შეცდომების კონცეფციას.

8.2. recvfrom და sendto ფუნქციები

ეს ორი ფუნქცია წაკითხვის და ჩაწერის სტანდარტული ფუნქციების მსგავსია, მაგრამ მოითხოვს დამატებით სამ არგუმენტს.

ssize_t recvfrom(int sockfd, void * buff, size_t nbytes, int flags,

struct sockaddr * from , socklen_t * adrlen);

ssize_t გაგზავნეთ (int sockfd, const void * buff, size_t nbytes, int flags,

const struct sockaddr * to , socklen_t addrlen);

ორივე ფუნქცია აბრუნებს წარმატებულად დაწერილი ან წაკითხული ბაიტების რაოდენობას, შეცდომის შემთხვევაში -1.

პირველი სამი არგუმენტი, sockfd, buff და nbytes, იდენტურია წაკითხვისა და ჩაწერის ფუნქციების პირველი სამი არგუმენტის: სახელური, ბუფერის მაჩვენებელი წასაკითხად ან ჩასაწერად და წასაკითხი ან ჩასაწერი ბაიტების რაოდენობა. .

დროშების არგუმენტს განვიხილავთ მე-14 თავში, სადაც განვიხილავთ recv, send, recvmsg და sendmsg ფუნქციებს, რადგან ისინი ახლა არ გვჭირდება ჩვენს მარტივ მაგალითში. ახლა ჩვენ ყოველთვის დავაყენებთ flags არგუმენტს ნულზე.

sendto ფუნქციის არგუმენტი არის სოკეტის მისამართის სტრუქტურა, რომელიც შეიცავს დანიშნულების პროტოკოლის მისამართს (როგორიცაა IP მისამართი და პორტის ნომერი). ამ სოკეტის მისამართის სტრუქტურის ზომა მითითებულია adrlen არგუმენტით. recvform ფუნქცია ავსებს არგუმენტის მიერ მითითებულ სოკეტის მისამართის სტრუქტურას დატაგრამის გამგზავნის პროტოკოლის მისამართით. სოკეტის მისამართების სტრუქტურაში შენახული ბაიტების რაოდენობა ასევე უბრუნდება გამოძახების პროცესს, როგორც მთელი რიცხვი, რომელზეც მითითებულია adrlen არგუმენტი. გაითვალისწინეთ, რომ sendto-ს ბოლო არგუმენტი არის მთელი რიცხვი, ხოლო ბოლო არგუმენტი recvfrom-ისთვის არის მაჩვენებელი მთელი რიცხვის (მნიშვნელობის შედეგის არგუმენტი).

recvfrom ფუნქციის ბოლო ორი არგუმენტი იგივეა, რაც მისაღები ფუნქციის ბოლო ორი არგუმენტი: სოკეტის მისამართის სტრუქტურის შიგთავსი დასრულების შემდეგ გვეუბნება, ვინ გაგზავნა დატაგრამა (UDP-ის შემთხვევაში) ან ვინ წამოიწყო კავშირი ( TCP-ის შემთხვევა). sendto ფუნქციის ბოლო ორი არგუმენტი მსგავსია კავშირის ფუნქციის ბოლო ორი არგუმენტის: ჩვენ ვავსებთ სოკეტის მისამართის სტრუქტურას დატაგრამის მიმღების პროტოკოლის მისამართით (UDP-ის შემთხვევაში) ან ჰოსტის მისამართით, რომლითაც კავშირი დამყარდება (TCP-ის შემთხვევაში).

ორივე ფუნქცია აბრუნებს როგორც ფუნქციის მნიშვნელობა წაკითხული ან ჩაწერილი მონაცემების სიგრძეს. recvfrom ფუნქციის ტიპიური გამოყენებისას დატაგრამის პროტოკოლით, დაბრუნების მნიშვნელობა არის მომხმარებლის მონაცემების რაოდენობა მიღებულ დატაგრამაში.

დატაგრამას შეიძლება ჰქონდეს ნულოვანი სიგრძე. UDP-სთვის ეს აბრუნებს IP მონაცემთაგრამას, რომელიც შეიცავს IP სათაურს (როგორც წესი, 20 ბაიტი IPv4-ისთვის ან 40 ბაიტი IPv6-ისთვის), 8-ბაიტი UDP სათაური და მონაცემების გარეშე. ეს ასევე ნიშნავს, რომ recvfrom-დან null დაბრუნება მისაღებია დატაგრამის პროტოკოლისთვის: ეს არ მიუთითებს, რომ მეორე მხარემ დახურა კავშირი, ისევე როგორც null დაბრუნება წაკითხულიდან TCP სოკეტზე. ვინაიდან UDP პროტოკოლი არ არის ორიენტირებული კავშირზე, არ არსებობს ისეთი მოვლენა, როგორიცაა კავშირის დახურვა.

თუ არგუმენტი from recvfrom არის null მაჩვენებელი, მაშინ შესაბამისი სიგრძის არგუმენტი (addrlen) ასევე უნდა იყოს null მაჩვენებელი, რაც იმას ნიშნავს, რომ ჩვენ არ გვაინტერესებს მონაცემთა გამგზავნის მისამართი.

ორივე recvfrom და sendto ფუნქციები შეიძლება გამოყენებულ იქნას TCP-ით, თუმცა ისინი, როგორც წესი, არ არის საჭირო.

8.3. UDP echo სერვერი: ძირითადი ფუნქცია

ჩვენ ახლა გადავამუშავებთ ჩვენს მარტივ კლიენტ-სერვერის მოდელს მე-5 თავიდან UDP-ის გამოყენებით. ჩვენს UDP კლიენტისა და სერვერის პროგრამებში ფუნქციების გამოძახების დიაგრამა ნაჩვენებია ნახ. 8.1. ნახ. 8.2 აჩვენებს გამოყენებულ ფუნქციებს. ჩამონათვალი 8.1 აჩვენებს მთავარ სერვერის ფუნქციას.

ბრინჯი. 8.2. მარტივი კლიენტ-სერვერის მოდელი UDP-ის გამოყენებით

ჩამონათვალი 8.1. UDP ექო სერვერი

//udpcliserv/udpserv01.с

1 #include "unp.h"

3 intmain (int argc, char **argv)

6 struct sockaddr_in servaddr, cliaddr;

7 sockfd = სოკეტი (AF_INET, SOCK_DGRAM, 0);

8 bzero(&servaddr, sizeof(servaddr));

9 servaddr.sin_family = AF_INET;

10 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

12 Bind(sockfd, (SA*)&servaddr, sizeof(servaddr));

13 dg_echo(sodkfd, (SA*)&cliaddr, sizeof(cliaddr));

შექმენით UDP სოკეტი, დაუკავშირდით ცნობილ პორტს bind ფუნქციის გამოყენებით

7-12 ჩვენ ვქმნით UDP სოკეტს სოკეტის ფუნქციის მეორე არგუმენტად SOCK_DGRAM (IPv4 datagram სოკეტის) მითითებით. როგორც TCP სერვერის მაგალითში, bind ფუნქციის IPv4 მისამართი მითითებულია როგორც INADDR_ANY, ხოლო სერვერის ცნობილი პორტის ნომერი არის SERV_PORT მუდმივი unp.h სათაურიდან.

13 dg_echo ფუნქცია იწოდება სერვერის მიერ კლიენტის მოთხოვნის დასამუშავებლად.

8.4. UDP ექო სერვერი: dg_echo ფუნქცია

ჩამონათვალი 8.2 აჩვენებს dg_echo ფუნქციას.

ჩამონათვალი 8.2. dg_echo ფუნქცია: სტრიქონების გამოხმაურება დატაგრამის სოკეტზე

1 #include "unp.h"

3 dg_echo (int sockfd, SA *pcliaddr, socklen_t clilen)

6 socklen_t len;

7 char mesg;

10 n = Recvfrom (sockfd, mesg, MAXLINE, 0, pcliaddr, &len);

11 Sendto (sockfd, mesg, n, 0, pcliaddr, len);

მონაცემთა გრამის წაკითხვა, გამომგზავნის ასახვა

8-12 ეს ფუნქცია არის მარტივი ციკლი, რომელშიც სერვერის პორტში მისული შემდეგი დატაგრამა იკითხება recvfrom ფუნქციით და იგზავნება უკან sendto ფუნქციის გამოყენებით.

მიუხედავად ამ ფუნქციის სიმარტივისა, გასათვალისწინებელია მთელი რიგი მნიშვნელოვანი დეტალები. ჯერ ერთი, ეს ფუნქცია არასოდეს სრულდება. იმის გამო, რომ UDP არის უკავშირო პროტოკოლი, არ არსებობს TCP-ში გამოყენებული ფაილის ბოლოს დროშის ექვივალენტი.

მეორეც, ეს ფუნქცია საშუალებას გაძლევთ შექმნათ სერიული სერვერი და არა პარალელური, რომელიც მივიღეთ TCP-ის შემთხვევაში. ვინაიდან არ არსებობს ჩანგლის ფუნქციის გამოძახება, ერთი სერვერის პროცესი ამუშავებს კლიენტის ყველა დამუშავებას. ზოგადად, TCP სერვერების უმეტესობა პარალელურია, ხოლო UDP სერვერების უმეტესობა სერიულია.

UDP დონეზე მყოფი სოკეტისთვის, მონაცემთა გრამები ირიბად ბუფერირებულია რიგის სახით. მართლაც, ყველა UDP სოკეტს აქვს მიმღების ბუფერი, და ამ სოკეტზე შემოსული ყველა დატაგრამა მოთავსებულია მის მიმღებ ბუფერში. როდესაც პროცესი იძახებს recvfrom ფუნქციას, ბუფერიდან შემდეგი დატაგრამა უბრუნდება პროცესს FIFO (First In, First Out) თანმიმდევრობით. ამგვარად, თუ ბევრი დატაგრამა მოხვდება სოკეტზე, სანამ პროცესს შეუძლია წაიკითხოს უკვე რიგის მონაცემები სოკეტისთვის, მაშინ შემომავალი მონაცემთა გრამები უბრალოდ ემატება სოკეტის მიღების ბუფერს. მაგრამ ამ ბუფერს აქვს შეზღუდული ზომა. ჩვენ განვიხილეთ ეს ზომა და როგორ გავზარდოთ ის SO_RCVBUF სოკეტის ვარიანტის გამოყენებით 7.5 ნაწილში.

ნახ. სურათი 8.3 გვიჩვენებს ჩვენი TCP კლიენტ-სერვერის მოდელის განზოგადებას მე-5 თავიდან, სადაც ორი კლიენტი ამყარებს კავშირს სერვერთან.

ბრინჯი. 8.3. TCP კლიენტ-სერვერის მოდელის განზოგადება ორი კლიენტით

აქ არის ორი მიმაგრებული სოკეტი და სერვერის კვანძზე თითოეულ მიმაგრებულ სოკეტს აქვს საკუთარი მიღების ბუფერი. ნახ. სურათი 8.4 გვიჩვენებს შემთხვევას, როდესაც ორი კლიენტი აგზავნის მონაცემთა გრამას UDP სერვერზე.

ბრინჯი. 8.4. UDP კლიენტ-სერვერის მოდელის განზოგადება ორი კლიენტით

არსებობს მხოლოდ ერთი სერვერის პროცესი და მას აქვს ერთი სოკეტი, რომელზედაც სერვერი იღებს ყველა შემოსულ დატაგრამას და საიდანაც აგზავნის ყველა პასუხს. ამ სოკეტს აქვს მიმღები ბუფერი, რომელშიც მოთავსებულია ყველა შემომავალი დატაგრამა.

ჩამონათვალი 8.1-ის მთავარი ფუნქცია პროტოკოლზეა დამოკიდებული (ის ქმნის AF_INET ოჯახის სოკეტს და შემდეგ გამოყოფს და ახდენს IPv4 სოკეტის მისამართის სტრუქტურის ინიციალიზებას), მაგრამ dg_echo ფუნქცია პროტოკოლისგან დამოუკიდებელია. dg_echo ფუნქციის პროტოკოლისგან დამოუკიდებელი არის ის, რომ გამოძახების პროცესმა (ჩვენს შემთხვევაში მთავარმა ფუნქციამ) უნდა გამოყოს სწორი ზომის სოკეტის მისამართის სტრუქტურა მეხსიერებაში და ამ სტრუქტურის მაჩვენებელი მის ზომასთან ერთად გადაეცემა არგუმენტების სახით. dg_echo ფუნქცია. dg_echo ფუნქცია არასოდეს იჭრება ამ სტრუქტურაში: ის უბრალოდ გადასცემს მას recvfrom და sendto ფუნქციებს. recvfrom ფუნქცია ავსებს ამ სტრუქტურას კლიენტის IP მისამართით და პორტის ნომრით, და რადგანაც იგივე მაჩვენებელი (pcliaddr) გადაეცემა sendto ფუნქციას, როგორც დანიშნულების მისამართი, დატაგრამა ამგვარად აისახება კლიენტზე, რომელმაც გაგზავნა დატაგრამა.

8.5. UDP echo კლიენტი: ძირითადი ფუნქცია

UDP კლიენტის ძირითადი ფუნქცია ნაჩვენებია სიაში 8.3.

ჩამონათვალი 8.3. UDP echo კლიენტი

//udpcliserv/udpcli01.c

1 #include "unp.h"

3 ძირითადი (int argc, char **argv)

6 struct sockaddr_in servaddr;

7 თუ (argc != 2)

8 err_quit("გამოყენება: udpcli");

9 bzero(&servaddr, sizeof(servaddr));

10 servaddr.sin_family = AF_INET;

11 servaddr.sin_port = htons(SERV_PORT);

12 Inet_pton(AF_INET, argv, &servaddr.sin_addr);

13 sockfd = სოკეტი (AF_INET, SOCK_DGRAM, 0);

14 dg_cli(stdin, sockfd, (SA*)&servaddr, sizeof(servaddr));

სოკეტის მისამართის სტრუქტურის შევსება სერვერის მისამართით

9-12 IPv4 სოკეტის მისამართის სტრუქტურა ივსება სერვერის IP მისამართით და პორტის ნომრით. ეს სტრუქტურა გადაეცემა dg_cli ფუნქციას. ის განსაზღვრავს სად უნდა გაიგზავნოს მონაცემთა გრამები.

13-14 იქმნება UDP სოკეტი და გამოიძახება dg_cli ფუნქცია.

8.6. UDP echo კლიენტი: dg_cli ფუნქცია

ჩამონათვალი 8.4 აჩვენებს dg_cli ფუნქციას, რომელიც ასრულებს სამუშაოს უმეტესობას კლიენტის მხარეს.

ჩამონათვალი 8.4. ფუნქცია dg_cli: კლიენტის ციკლი

1 #include "unp.h"

7 while (Fgets(Sendline, MAXLINE, fp) != NULL) (

8 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

9 n = Recvfrom (sockfd, recvline, MAXLINE, 0, NULL, NULL);

10 recvline[n] = 0; /* null მთავრდება */

11 Fputs (recvline, stdout);

7-12 კლიენტის მხარის დამუშავების ციკლში არის ოთხი ნაბიჯი: სტრიქონის წაკითხვა სტანდარტული შეყვანიდან fgets-ის გამოყენებით, სტრიქონის გაგზავნა სერვერზე sendto-ს გამოყენებით, სერვერის ასახული პასუხის წაკითხვა recvfrom-ის გამოყენებით და ასახული სტრიქონის სტანდარტულ გამომავალზე გადატანა. fputs ფუნქციები.

ჩვენმა კლიენტმა არ სთხოვა ბირთვს დინამიურად მინიჭებული პორტის მინიჭება მის სოკეტზე (მაშინ, როცა TCP კლიენტი ამას აკეთებდა Connect-ის დარეკვისას). UDP სოკეტისთვის, როდესაც sendto გამოიძახება პირველად, ბირთვი ირჩევს დინამიურად მინიჭებულ პორტს, თუ ამ სოკეტთან უკვე არ არის დაკავშირებული ლოკალური პორტი. როგორც TCP-ის შემთხვევაში, კლიენტს შეუძლია ცალსახად დარეკოს bind, მაგრამ ეს იშვიათად კეთდება.

გაითვალისწინეთ, რომ recvfrom ფუნქციის გამოძახებისას, null მაჩვენებლები მითითებულია, როგორც მეხუთე და მეექვსე არგუმენტები. ეს ეუბნება ბირთვს, რომ ჩვენ არ გვაინტერესებს ვიცოდეთ ვინ გამოაგზავნა პასუხი. არსებობს რისკი, რომ ნებისმიერმა პროცესმა, იქნება ეს იმავე კვანძზე თუ სხვა რომელიმე კვანძზე, შეუძლია გაგზავნოს დატაგრამა კლიენტის IP მისამართსა და პორტში, რომელსაც კლიენტი წაიკითხავს, ​​თუ ვივარაუდებთ, რომ ეს არის სერვერის პასუხი. ჩვენ განვიხილავთ ამ სიტუაციას 8.8 ნაწილში.

როგორც dg_echo სერვერის ფუნქცია, dg_cli კლიენტის ფუნქცია პროტოკოლისგან დამოუკიდებელია, მაგრამ კლიენტის ძირითადი ფუნქცია პროტოკოლზეა დამოკიდებული. ძირითადი ფუნქცია გამოყოფს და ინიციალიზებს სოკეტის მისამართის სტრუქტურას კონკრეტული პროტოკოლის ტიპის, და შემდეგ გადასცემს dg_cli ფუნქციას მაჩვენებელს სტრუქტურას მის ზომასთან ერთად.

8.7. დაკარგული მონაცემთა გრამები

ჩვენს მაგალითში UDP კლიენტი და სერვერი არასანდოა. თუ კლიენტის დატაგრამა დაიკარგება (ვთქვათ, რომ ის იგნორირებულია რომელიმე როუტერის მიერ კლიენტსა და სერვერს შორის), კლიენტი სამუდამოდ დაიბლოკება recvfrom ფუნქციის გამოძახებისას dg_cli ფუნქციის შიგნით და ელოდება სერვერის პასუხს. არასოდეს მოვა. ანალოგიურად, თუ კლიენტის დატაგრამა მივა სერვერზე, მაგრამ სერვერის პასუხი დაკარგულია, კლიენტი სამუდამოდ დაიბლოკება recvfrom ფუნქციის გამოძახებაში. ამ სიტუაციის თავიდან აცილების ერთადერთი გზა არის კლიენტის recvfrom ფუნქციის გამოძახების დროის ამოწურვა. ამას განვიხილავთ განყოფილებაში 14.2.

recvfrom ფუნქციის გამოძახებაში დროის ამოწურვის უბრალოდ დაყენება არ არის სრული გამოსავალი. მაგალითად, თუ მითითებული ვადის ამოწურვა ამოიწურება და პასუხი არ მიიღება, დანამდვილებით ვერ ვიტყვით რისი ბრალია - ან ჩვენი დატაგრამა ვერ მიაღწია სერვერს, ან სერვერის პასუხი არ დაბრუნდა. თუ კლიენტის მოთხოვნა შეიცავდა მოთხოვნას, როგორიცაა "გარკვეული თანხის გადარიცხვა A ანგარიშიდან B ანგარიშზე" (ჩვენი მარტივი ექო სერვერის შემთხვევისგან განსხვავებით), მაშინ დიდი განსხვავება იქნება მოთხოვნის დაკარგვასა და პასუხის დაკარგვას შორის. . ჩვენ უფრო მეტს ვისაუბრებთ UDP კლიენტ-სერვერის მოდელისთვის საიმედოობის დამატებაზე 22.5-ე განყოფილებაში.

8.8. მიღებული პასუხის შემოწმება

8.6 განყოფილების ბოლოს, ჩვენ აღვნიშნეთ, რომ ნებისმიერ პროცესს, რომელმაც იცის კლიენტის დინამიურად მინიჭებული პორტის ნომერი, შეუძლია ჩვენს კლიენტს გაუგზავნოს მონაცემთა გრამები და ისინი შერეული იქნება სერვერის ნორმალურ პასუხებში. ჩვენ მხოლოდ შეგვიძლია შევცვალოთ recvfrom ფუნქციის გამოძახება ჩამონათვალში 8.4, რათა დააბრუნოს პასუხის გამგზავნის IP მისამართი და პორტი, და იგნორირება გაუკეთოს ნებისმიერ მონაცემთა გრამას, რომელიც მოდის სხვა სერვერიდან, გარდა იმისა, რომელზედაც ჩვენ ვაგზავნით დატაგრამას. თუმცა, აქ არის რამდენიმე ხაფანგი, როგორც დავინახავთ.

პირველ რიგში, ჩვენ ვცვლით კლიენტის მთავარ ფუნქციას (იხ. ჩამონათვალი 8.3), რათა იმუშაოს სტანდარტულ ექო სერვერთან (იხ. ცხრილი 2.1). ჩვენ უბრალოდ ვცვლით დავალებას

servaddr.sin_port = htons(SERV_PORT);

დავალება

servaddr.sin_port = htons(7);

ახლა ჩვენ შეგვიძლია გამოვიყენოთ ნებისმიერი კვანძი, რომელიც მუშაობს სტანდარტულ ექო სერვერზე ჩვენს კლიენტთან.

შემდეგ ჩვენ გადავწერთ dg_cli ფუნქციას, რათა გამოვყოთ სხვა სოკეტის მისამართის სტრუქტურა მეხსიერებაში, რათა შეინახოს recvfrom-ის მიერ დაბრუნებული სტრუქტურა. ჩვენ ვაჩვენებთ მას ჩამონათვალში 8.5.

ჩამონათვალი 8.5. dg_cli ფუნქციის ვერსია, რომელიც ამოწმებს დაბრუნებულ სოკეტის მისამართს

//udpcliserv/dgcliaddr.c

1 #include "unp.h"

3 dg_cli (FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

6 char sendline, recvline;

7 socklen_t len;

8 struct sockaddr *preply_addr;

9 preply_addr = Malloc(servlen);

10 while (Fgets(Sendline, MAXLINE, fp) != NULL) (

11 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

12 len = servlen;

13 n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);

14 if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0) (

15 printf("პასუხი %s-დან (იგნორირებულია)\n",

18 recvline[n] = 0; /* null მთავრდება */

19 Fputs (recvline, stdout);

სხვა სოკეტის მისამართის სტრუქტურის განთავსება მეხსიერებაში

9 ჩვენ გამოვყოფთ სხვა სოკეტის მისამართის სტრუქტურას მეხსიერებაში malloc ფუნქციის გამოყენებით. გაითვალისწინეთ, რომ dg_cli ფუნქცია კვლავ პროტოკოლისგან დამოუკიდებელია. ვინაიდან ჩვენ არ გვაინტერესებს რა ტიპის სოკეტის მისამართის სტრუქტურასთან გვაქვს საქმე, ჩვენ ვიყენებთ მის ზომას მხოლოდ malloc ფუნქციის გამოძახებაში.

დაბრუნებული მისამართების შედარება

12-13 recvfrom ფუნქციის გამოძახებისას ჩვენ ვეუბნებით ბირთვს დააბრუნოს დატაგრამის წყაროს მისამართი. ჩვენ ჯერ ვადარებთ recvfrom ფუნქციის მიერ დაბრუნებულ სიგრძეს, როგორც მნიშვნელობა-შედეგის არგუმენტს, შემდეგ კი შევადარებთ თავად სოკეტის მისამართის სტრუქტურებს memcmp ფუნქციის გამოყენებით.

ჩვენი კლიენტის ახალი ვერსია მშვენივრად მუშაობს, თუ სერვერი არის ჰოსტზე ერთი IP მისამართით. მაგრამ ეს პროგრამა შეიძლება არ იმუშაოს, თუ სერვერს აქვს რამდენიმე ქსელური ინტერფეისი (მრავალსახლიანი სერვერი). ჩვენ ვმართავთ ამ პროგრამას freebsd4 კვანძზე წვდომით, რომელსაც აქვს ორი ინტერფეისი და ორი IP მისამართი:

macosx% მასპინძლობს freebsd4

freebsd4.unpbook.com აქვს მისამართი 172.24.37.94

freebsd4.unpbook.com აქვს მისამართი 135.197.17.100

macosx% udpcli02 135.197.17.100

პასუხი 172.24.37.94:7-დან (იგნორირებულია)

ნახ. 1.7 ხედავთ, რომ ჩვენ დავაყენეთ IP მისამართი სხვა ქვექსელიდან. ეს ჩვეულებრივ მისაღებია. IP იმპლემენტაციის უმეტესობა იღებს შემომავალ IP მონაცემთაგრამას, რომელიც განკუთვნილია ჰოსტის ნებისმიერი IP მისამართისთვის, მიუხედავად ინტერფეისისა, რომელზეც ის მოდის. RFC 1122 ამას უწოდებს სუსტი სისტემის მოდელს. თუ სისტემა განახორციელებს იმას, რასაც ეს დოკუმენტი უწოდებს ძლიერი საბოლოო სისტემის მოდელს, ის იღებს შემომავალ დატაგრამას მხოლოდ იმ შემთხვევაში, თუ დატაგრამა მოხვდება ინტერფეისზე, რომლისთვისაც ის არის განკუთვნილი.

recvfrom ფუნქციით დაბრუნებული IP მისამართი (UDP datagram-ის წყაროს IP მისამართი) არ არის IP მისამართი, რომელზეც ჩვენ გავაგზავნეთ დატაგრამა. როდესაც სერვერი აგზავნის პასუხს, მიმღების IP მისამართია 172.24.37.94. ბირთვის მარშრუტიზაციის ფუნქცია კვანძზე freebsd4 ირჩევს 172.24.37.94 გამავალ ინტერფეისად. ვინაიდან სერვერს არ აქვს ასოცირებული IP მისამართი თავის სოკეტთან (სერვერს აქვს ასოცირებული ზოგადი მისამართი თავის სოკეტთან, რომლის გადამოწმება შეგვიძლია netstat პროგრამის გაშვებით freebsd4 კვანძზე), ბირთვი ირჩევს IP დატაგრამის წყაროს მისამართს. ეს მისამართი ხდება გამავალი ინტერფეისის პირველადი IP მისამართი. თუ ჩვენ გავაგზავნით დატაგრამას სხვა რამეზე, გარდა ინტერფეისის პირველადი IP მისამართისა (ანუ ალტერნატიულ სახელზე, მეტსახელად), მაშინ ჩვენი ტესტი, რომელიც ნაჩვენებია სიაში 8.5, ასევე ჩაიშლება.

ერთი გამოსავალი იქნება კლიენტმა შეამოწმოს პასუხი ჰოსტის დომენის სახელი მისი IP მისამართის ნაცვლად. ამისათვის სერვერის სახელი მოძებნილია DNS-ში (იხ. თავი 11) recvfrom ფუნქციით დაბრუნებული IP მისამართის საფუძველზე. კიდევ ერთი გამოსავალია, რომ UDP სერვერმა შექმნას ერთი სოკეტი თითოეული IP მისამართისთვის, რომელიც კონფიგურირებულია ჰოსტზე, დააკავშიროს ეს IP მისამართი სოკეტთან, დარეკოს არჩევა თითოეულ ამ სოკეტზე (დაელოდება, რომ რომელიმე მათგანი მზად იქნება წასაკითხად) და შემდეგ უპასუხა წასაკითხად მზად სოკეტიდან. ვინაიდან პასუხისთვის გამოყენებული სოკეტი ასოცირდება IP მისამართთან, რომელიც იყო კლიენტის მოთხოვნის დანიშნულების მისამართი (წინააღმდეგ შემთხვევაში, დატაგრამა არ იქნებოდა მიწოდებული სოკეტში), შეგვიძლია დარწმუნებული ვიყოთ, რომ პასუხის გამგზავნი და მიმღები მოთხოვნის მისამართი იგივეა. ჩვენ ვაჩვენებთ ამ მაგალითებს სექციაში 22.6.

ᲨᲔᲜᲘᲨᲕᲜᲐ

Solaris სისტემაზე მრავალი ქსელური ინტერფეისით, სერვერის პასუხის წყაროს IP მისამართი არის კლიენტის მოთხოვნის მიმღების IP მისამართი. ამ განყოფილებაში აღწერილი სცენარი ვრცელდება ბერკლიდან მიღებული იმპლემენტაციებისთვის, რომლებიც ირჩევენ წყაროს IP მისამართს გამავალი ინტერფეისის საფუძველზე.

8.9. კლიენტის გაშვება სერვერის გაშვების გარეშე

შემდეგი სცენარი, რომელსაც ჩვენ განვიხილავთ, არის კლიენტის გაშვება სერვერის გაშვების გარეშე. თუ ამას გავაკეთებთ და კლიენტის მხარეს ერთ ხაზს შევიყვანთ, არაფერი მოხდება. კლიენტი სამუდამოდ დაბლოკილია მისი recvfrom ფუნქციის გამოძახებისას, ელოდება სერვერის პასუხს, რომელიც არასოდეს მოდის. მაგრამ ამას არ აქვს მნიშვნელობა ამ მაგალითში, რადგან ჩვენ ახლა ვცდილობთ უფრო ღრმად გავიგოთ პროტოკოლები და რა ხდება ჩვენს ქსელურ აპლიკაციაში.

ჩვენ ჯერ ვაწარმოებთ tcpdump-ს macosx ჰოსტზე, შემდეგ კი ვმართავთ კლიენტს იმავე ჰოსტზე და ვაყენებთ სერვერის ჰოსტს freebsd4-ზე. შემდეგ შევდივართ ერთ ხაზში, მაგრამ ეს ხაზი არ აისახება სერვერის მიერ.

macosx% udpcli01 172.24.37.94

გამარჯობა მსოფლიოჩვენ შევდივართ ამ ხაზზე,

მაგრამ საპასუხოდ ვერაფერს ვიღებთ

ჩამონათვალი 8.6 აჩვენებს tcpdump-ის გამომავალს.

ჩამონათვალი 8.6. tcpdump გამომავალი, როდესაც სერვერის პროცესი არ მუშაობს სერვერის კვანძზე

01 0.0 arp who-has freebsd4 უთხარი macosx

02 0.003576 (0.0036) arp პასუხი freebsd4 არის-ზე 0:40:5:42:d6:de

03 0.003601 (0.0000) macosx.51139 > freebsd4.9877: udp 13

04 0.009781 (0.0062) freebsd4 >

პირველი, რაც ჩვენ შევამჩნიეთ, არის ის, რომ ARP მოთხოვნა და პასუხი მიიღება მანამ, სანამ კლიენტის ჰოსტი შეძლებს UDP მონაცემთა გრაფიკის გაგზავნას სერვერის ჰოსტში. (ჩვენ დავტოვეთ ეს გაცვლა პროგრამის გამომავალში, რათა კიდევ ერთხელ ხაზგასმით აღვნიშნოთ, რომ ARP მოთხოვნა ყოველთვის იგზავნება და პასუხი მიიღება IP დატაგრამის გაგზავნამდე.)

მე-3 სტრიქონზე ვხედავთ, რომ კლიენტის დატაგრამა იგზავნება, მაგრამ სერვერის ჰოსტი პასუხობს მე-4 ხაზზე ICMP პორტის მიუწვდომელი შეტყობინებით. (13 სიგრძე მოიცავს 12 სიმბოლოს პლუს ახალ ხაზს.) თუმცა, ეს ICMP შეცდომა არ უბრუნდება კლიენტის პროცესს იმ მიზეზების გამო, რომლებსაც ქვემოთ მოკლედ ჩამოვთვლით. ამის ნაცვლად, კლიენტი მუდმივად დაბლოკილია recvfrom ფუნქციის გამოძახებით სიაში 8.4. ჩვენ ასევე აღვნიშნავთ, რომ ICMPv6-ს აქვს ICMPv4-ის მსგავსი „პორტის მიუწვდომელი“ შეცდომა (იხ. ცხრილები A.5 და A.6), ამიტომ აქ წარმოდგენილი შედეგები მსგავსია IPv6-ისთვის.

ეს ICMP შეცდომა არის ასინქრონული შეცდომა. შეცდომა გამოწვეული იყო sendto ფუნქციით, მაგრამ sendto ფუნქცია ნორმალურად დასრულდა. გავიხსენოთ სექციიდან 2.9, რომ ნორმალური დაბრუნება UDP გამომავალი ოპერაციიდან ნიშნავს მხოლოდ იმას, რომ დატაგრამა დაემატა ბმული ფენის გამომავალი რიგს. ICMP შეცდომა არ ბრუნდება მანამ, სანამ არ გავა გარკვეული დრო (4 ms სიაში 8.6), რის გამოც მას ასინქრონული ეწოდება.

ძირითადი წესი არის ის, რომ ასინქრონული შეცდომები არ დაბრუნდება UDP სოკეტისთვის, თუ სოკეტი არ არის მიმაგრებული. ჩვენ ვაჩვენებთ, თუ როგორ უნდა გამოიძახოთ დაკავშირების ფუნქცია UDP სოკეტზე 8.11 ნაწილში. ყველას არ ესმის, რატომ მიიღეს ეს გადაწყვეტილება, როდესაც სოკეტები პირველად განხორციელდა. (დანერგვის მოსაზრებები განხილულია გვერდებზე 748-749.) განვიხილოთ UDP კლიენტი, რომელიც თანმიმდევრულად აგზავნის სამ მონაცემთაგრამას სამ სხვადასხვა სერვერზე (ანუ სამ სხვადასხვა IP მისამართზე) ერთი UDP სოკეტით. კლიენტი შემოდის ციკლში, რომელიც უწოდებს recvfrom ფუნქციას პასუხების წასაკითხად. ორი დატაგრამა მიწოდებულია სწორად (ანუ სერვერი მუშაობდა სამი კვანძიდან ორზე), მაგრამ მესამე კვანძი არ მუშაობდა სერვერზე, ხოლო მესამე კვანძი პასუხობს ICMP პორტის მიუწვდომელ შეტყობინებას. ეს ICMP შეცდომის შეტყობინება შეიცავს დატაგრამის IP სათაურს და UDP სათაურს, რამაც გამოიწვია შეცდომა. (ICMPv4 და ICMPv6 შეცდომის შეტყობინებები ყოველთვის შეიცავს IP სათაურს და UDP სათაურის მთელ ან ნაწილს, რათა შეტყობინების მიმღებს შეეძლოს განსაზღვროს რომელმა სოკეტმა გამოიწვია შეცდომა. ეს ნაჩვენებია სურათებში 28.5 და 28.6.) კლიენტმა, რომელმაც გაგზავნა სამი მონაცემთა გრამი, უნდა იცოდეს. დატაგრამის მიმღებმა, რომელმაც გამოიწვია შეცდომა, ზუსტად დაადგინოს, რომელმა სამი დატაგრამამ გამოიწვია შეცდომა. მაგრამ როგორ შეუძლია ბირთვს გადასცეს ეს ინფორმაცია პროცესს? ერთადერთი, რაც recvfrom-ს შეუძლია დააბრუნოს, არის errno ცვლადის მნიშვნელობა. მაგრამ recvfrom ფუნქციას არ შეუძლია შეცდომით დააბრუნოს UDP დატაგრამის მიმღების IP მისამართი და პორტის ნომერი. შესაბამისად, გადაწყდა, რომ ეს ასინქრონული შეცდომები უბრუნდება პროცესს მხოლოდ იმ შემთხვევაში, თუ პროცესმა დაურთო UDP სოკეტი მხოლოდ ერთ კონკრეტულ თანატოლს.

ᲨᲔᲜᲘᲨᲕᲜᲐ

Linux აბრუნებს ICMP პორტის მიუწვდომელ შეცდომებს, თუნდაც მიუმაგრებელი სოკეტისთვის, თუ SO_DSBCOMPAT სოკეტის ვარიანტი არ არის ჩართული. ყველა მიმღების მიუწვდომელი შეცდომა, რომელიც ნაჩვენებია ცხრილში 1 დაბრუნდა. A.5, 0, 1, 4, 5, 11 და 12 კოდების შეცდომის გამოკლებით.

ჩვენ დავუბრუნდებით ასინქრონული შეცდომების საკითხს UDP სოკეტების განყოფილებაში 28.7 და ვაჩვენებთ მარტივ გზას, რომ მივიღოთ ეს შეცდომები დაუმაგრებელ სოკეტზე ჩვენი დემონის გამოყენებით.

8.10. UDP კლიენტ-სერვერის საბოლოო მაგალითი

ნახ. სურათზე 8.5, დიდი შავი წერტილები აჩვენებს ოთხ მნიშვნელობას, რომლებიც უნდა იყოს მითითებული ან არჩეული, როდესაც კლიენტი აგზავნის UDP მონაცემთაგრამას.

ბრინჯი. 8.5. UDP კლიენტ-სერვერის მოდელის შეჯამება კლიენტის თვალსაზრისით

კლიენტმა უნდა მიუთითოს სერვერის IP მისამართი და პორტის ნომერი, რათა გამოიძახოს sendto ფუნქცია. როგორც წესი, კლიენტის IP მისამართი და პორტის ნომერი ავტომატურად ირჩევა ბირთვის მიერ, თუმცა ჩვენ აღვნიშნეთ, რომ კლიენტს შეუძლია დარეკოს bind ფუნქცია. ჩვენ ასევე აღვნიშნეთ, რომ თუ ეს ორი მნიშვნელობა არჩეულია კლიენტისთვის ბირთვის მიერ, მაშინ კლიენტისთვის დინამიურად მინიჭებული პორტი არჩეულია ერთხელ, პირველად გამოიძახება sendto და აღარასოდეს იცვლება. თუმცა, კლიენტის IP მისამართი შეიძლება შეიცვალოს თითოეული UDP მონაცემთა გრამისთვის, რომელსაც კლიენტი აგზავნის, თუ ვივარაუდებთ, რომ კლიენტი არ აკავშირებს კონკრეტულ IP მისამართს სოკეტში bind ფუნქციის გამოყენებით. მიზეზი ახსნილია ნახ. 8.5: თუ კლიენტის კვანძს აქვს რამდენიმე ქსელის ინტერფეისი, კლიენტს შეუძლია მათ შორის გადართვა (ნახ. 8.5-ში, ერთი მისამართი მიუთითებს მარცხნივ გამოსახულ ბმულის ფენას, მეორე ეხება მარჯვნივ გამოსახულს). ამ სცენარის უარეს შემთხვევაში, კლიენტის IP მისამართი, რომელიც არჩეულია ბირთვის მიერ გამავალი ბმულის ფენის საფუძველზე, შეიცვლება თითოეული დატაგრამისთვის.

რა მოხდება, თუ კლიენტი აკავშირებს IP მისამართს თავის სოკეტთან, მაგრამ ბირთვი გადაწყვეტს, რომ გამავალი დატაგრამა უნდა გაიგზავნოს სხვა ბმული ფენიდან? ამ შემთხვევაში, IP დატაგრამა შეიცავს წყაროს IP მისამართს, რომელიც განსხვავდება გამავალი ბმულის ფენის IP მისამართისგან (იხ. სავარჯიშო 8.6).

ნახ. სურათი 8.6 გვიჩვენებს იგივე ოთხ მნიშვნელობას, მაგრამ სერვერის თვალსაზრისით.

ბრინჯი. 8.6. UDP კლიენტ-სერვერის მოდელის შეჯამება სერვერის თვალსაზრისით

სერვერს შეუძლია ისწავლოს მინიმუმ ოთხი პარამეტრი თითოეული დატაგრამასთვის, რომელიც მას იღებს: წყაროს IP მისამართი, დანიშნულების IP მისამართი, წყაროს პორტის ნომერი და დანიშნულების პორტის ნომერი. ზარები, რომლებიც აბრუნებს ამ ინფორმაციას TCP და UDP სერვერებზე, ნაჩვენებია ცხრილში. 8.1.

ცხრილი 8.1. სერვერისთვის ხელმისაწვდომი ინფორმაცია შემომავალი IP დატაგრამიდან

TCP სერვერს ყოველთვის აქვს მარტივი წვდომა ოთხივე ნაწილზე დაკავშირებულ სოკეტზე და ეს ოთხი მნიშვნელობა რჩება მუდმივი კავშირის სიცოცხლის განმავლობაში. თუმცა, UDP კავშირის შემთხვევაში, დანიშნულების IP მისამართის მიღება შესაძლებელია მხოლოდ სოკეტის ვარიანტის IP_RECVDSTADDR IPv4-ისთვის ან IPV6_PKTINFO IPv6-ისთვის დაყენებით და შემდეგ recvmsg ფუნქციის გამოძახებით recvfrom ფუნქციის ნაცვლად. იმის გამო, რომ UDP უკავშიროა, დანიშნულების IP მისამართი შეიძლება შეიცვალოს სერვერზე გაგზავნილი თითოეული დატაგრამისთვის. UDP სერვერს ასევე შეუძლია მიიღოს დატაგრამები, რომლებიც განკუთვნილია ჰოსტის ერთ-ერთი სამაუწყებლო მისამართისთვის ან მულტიკასტის მისამართისთვის, რასაც განვიხილავთ თავებში 20 და 21. ჩვენ გაჩვენებთ, თუ როგორ უნდა განვსაზღვროთ UDP მონაცემთა გრაფიკის დანიშნულების მისამართი სექციაში 20.2 მას შემდეგ, რაც აღვწერთ recvmsg ფუნქცია.

8.11. დაკავშირების ფუნქცია UDP-სთვის
ᲨᲔᲜᲘᲨᲕᲜᲐ
ᲨᲔᲜᲘᲨᲕᲜᲐ
ᲨᲔᲜᲘᲨᲕᲜᲐ

ცხრილი 8.2

ᲨᲔᲜᲘᲨᲕᲜᲐ

ბრინჯი. 8.7. UDP მიმაგრებული სოკეტი

ბრინჯი. 8.8

ზარის დაკავშირება რამდენჯერმე UDP სოკეტზე

პროცესს დაკავშირებული UDP სოკეტით შეუძლია კვლავ გამოიძახოს ამ სოკეტზე დაკავშირების ფუნქცია:

c – დააყენეთ ახალი IP მისამართი და პორტი;

გ – გამორთეთ სოკეტი.

პირველი შემთხვევა, რომელიც დაკავშირებულია UDP სოკეტისთვის ახალი თანატოლის მითითებით, განსხვავდება TCP სოკეტთან დაკავშირების ფუნქციის გამოყენებისგან: TCP სოკეტისთვის, დაკავშირების ფუნქციის გამოძახება შესაძლებელია მხოლოდ ერთხელ.

UDP სოკეტის გასათიშად ჩვენ ვუწოდებთ დაკავშირების ფუნქციას, მაგრამ სოკეტის მისამართის სტრუქტურის ოჯახის ელემენტს (sin_family IPv4-ისთვის ან sin6_family IPv6-ისთვის) ვაყენებთ AF_UNSPEC-ზე. ამან შეიძლება გამოიწვიოს EAFNOSUPPORT შეცდომა, მაგრამ ეს ნორმალურია. ეს არის უკვე დაკავშირებულ UDP სოკეტზე დაკავშირების ფუნქციის გამოძახების პროცესი, რომელიც საშუალებას აძლევს სოკეტის გათიშვას.

ᲨᲔᲜᲘᲨᲕᲜᲐ

დაკავშირების ფუნქციის BSD სახელმძღვანელოში ტრადიციულად ნათქვამია: „მონაცემთა სექციებს შეუძლიათ დაარღვიონ კავშირები არასწორი მისამართებით, როგორიცაა ცარიელი მისამართები“. სამწუხაროდ, არც სახელმძღვანელოში ნათქვამია, რას წარმოადგენს „ცარიელი მისამართი“ და არც ის, რომ შეცდომის შედეგად დაბრუნდა (რაც ნორმალურია). POSIX სტანდარტი ცალსახად აცხადებს, რომ მისამართების ოჯახი უნდა იყოს დაყენებული AF_UNSPEC, მაგრამ შემდეგ აცხადებს, რომ დაკავშირების ამ ზარმა შეიძლება დააბრუნოს ან არ დააბრუნოს EAFNOSUPPORT შეცდომა.

Შესრულება

როდესაც აპლიკაცია იძახებს sendto ფუნქციას დაუმაგრებელ UDP სოკეტზე, ბერკლიდან მიღებული ბირთვის იმპლემენტაციები დროებით უკავშირდება სოკეტს, აგზავნის მონაცემთა გრამას და შემდეგ გათიშულია სოკეტიდან. ამრიგად, sendto ფუნქციის გამოძახება, რათა გაგზავნოს ორი მონაცემთა გრამა თანმიმდევრულად მიუმაგრებელ სოკეტზე, მოიცავს შემდეგ ექვს ნაბიჯს, რომელსაც ასრულებს ბირთვი:

გ – სოკეტის მიმაგრება;

გ – პირველი დატაგრამის გამომავალი;

გ – სოკეტის გათიშვა;

გ – სოკეტის მიმაგრება;

გ – მეორე დატაგრამის გამომავალი;

გ – სოკეტის გათიშვა.

ᲨᲔᲜᲘᲨᲕᲜᲐ

გასათვალისწინებელი კიდევ ერთი პუნქტია მარშრუტიზაციის ცხრილში ძიების რაოდენობა. პირველი დროებითი კავშირი ეძებს დანიშნულების IP მისამართს მარშრუტიზაციის ცხრილში და ინახავს (ქეში) ამ ინფორმაციას. მეორე დროებითი კავშირი აღნიშნავს, რომ მიმღების მისამართი ემთხვევა მარშრუტიზაციის ცხრილის ქეშებულ მისამართს (ვვარაუდობთ, რომ ორივე sendto ფუნქციას ეძლევა ერთი და იგივე მიმღები) და არ საჭიროებს მარშრუტიზაციის ცხრილის ხელახლა ძებნას.

როდესაც აპლიკაციამ იცის, რომ ერთსა და იმავე თანატოლს გაუგზავნის რამდენიმე მონაცემთა გრამას, უფრო ეფექტურია სოკეტის ცალსახად მიმაგრება. დაკავშირების ზარი, რომელსაც მოჰყვება ორი ზარი ჩასაწერად, ახლა მოიცავს ბირთვის მიერ შესრულებულ შემდეგ ნაბიჯებს:

გ – სოკეტის მიმაგრება;

გ – პირველი დატაგრამის გამომავალი;

c – მეორე დატაგრამის გამომავალი.

ამ შემთხვევაში, ბირთვი აკოპირებს სოკეტის მისამართის სტრუქტურას, რომელიც შეიცავს დანიშნულების IP მისამართს და პორტს მხოლოდ ერთხელ, და როდესაც sendingto გამოიძახება ორჯერ, კოპირება ხდება ორჯერ. B აღნიშნავს, რომ გამორთული UDP სოკეტის დროებით ხელახლა მიმაგრება შეადგენს თითოეული UDP გადაცემის ღირებულების დაახლოებით მესამედს.

8.12. dg_cli ფუნქცია (გაგრძელება)

მოდით დავუბრუნდეთ dg_cli ფუნქციას, რომელიც ნაჩვენებია სიაში 8.4 და ხელახლა ჩავწეროთ დაკავშირების ფუნქციის გამოსაძახებლად. ჩამონათვალი 8.7 აჩვენებს ახალ ფუნქციას.

ჩამონათვალი 8.7. dg_cli ფუნქცია იძახებს დაკავშირების ფუნქციას

//udpcliserv/dgcliconnect.c

1 #include "unp.h"

3 dg_cli (FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

6 char sendline, recvline;

7 Connect(sockfd, (SA*)pservaddr, servlen);

8 while (Fgets(Sendline, MAXLINE, fp) != NULL) (

9 Write(sockfd, sendline, strlen(sendline));

10 n = წაკითხვა (sockfd, recvline, MAXLINE);

11 recvline[n] = 0; /* null მთავრდება */

12 Fputs (recvline, stdout);

წინა ვერსიის ცვლილებებია დაკავშირების ფუნქციის ზარის დამატება და sendto და recvrom ფუნქციური ზარების ჩანაცვლება ჩაწერისა და წაკითხვის ფუნქციის ზარებით. dg_cli ფუნქცია რჩება პროტოკოლისგან დამოუკიდებელი, რადგან ის არ ხვდება დაკავშირების ფუნქციაზე გადაცემული სოკეტის მისამართის სტრუქტურაში. ჩვენი კლიენტის ძირითადი ფუნქცია, რომელიც ნაჩვენებია სიაში 8.3, იგივე რჩება.

თუ პროგრამას გავუშვით macosx ჰოსტზე, სადაც მითითებულია freebsd4 ჰოსტის IP მისამართი (რომელიც არ მუშაობს ჩვენს სერვერზე 9877 პორტზე), მივიღებთ შემდეგ გამომავალს:

macosx% udpcli04 172.24.37.94

გამარჯობა მსოფლიო

წაკითხვის შეცდომა: კავშირი უარყოფილია

პირველი, რასაც ვამჩნევთ, არის ის, რომ კლიენტის პროცესის დაწყებისას შეცდომას არ ვიღებთ. შეცდომა ჩნდება მხოლოდ მას შემდეგ, რაც ჩვენ პირველ დატაგრამას გამოვგზავნით სერვერზე. სწორედ ამ დატაგრამის გაგზავნა იწვევს ICMP შეცდომას სერვერის ჰოსტიდან. მაგრამ როდესაც TCP კლიენტი უწოდებს connect , მიუთითებს სერვერის კვანძზე, რომელიც არ აწარმოებს სერვერის პროცესს, დაკავშირება აბრუნებს შეცდომას, რადგან დაკავშირების ზარი იწვევს TCP სამმხრივი ხელის ჩამორთმევის პირველი პაკეტის გაგზავნას და ეს არის ეს პაკეტი, რომელიც იწვევს RST სეგმენტის მიღებას თანატოლისგან (იხ. ნაწილი 4.3).

ჩამონათვალი 8.8 აჩვენებს tcpdump-ის გამომავალს.

ჩამონათვალი 8.8. გამომავალი tcpdump-დან dg_cli ფუნქციის გაშვებისას

macosx% tcpdump

01 0.0 macosx.51139 > freebsd4 9877:udp 13

02 0.006180 (0.0062) freebsd4 > macosx: icmp: freebsd4 udp პორტი 9877 მიუწვდომელია

მაგიდაზე A.5 ჩვენ ასევე ვხედავთ, რომ ბირთვი აკავშირებს ICMP შეცდომას ECONNREFUSED შეცდომასთან, რომელიც შეესაბამება err_sys ფუნქციის მიერ Connection უარყოფილი შეტყობინების სტრიქონის გამომავალს.

ᲨᲔᲜᲘᲨᲕᲜᲐ

სამწუხაროდ, ყველა ბირთვი არ აბრუნებს ICMP შეტყობინებებს მიმაგრებულ UDP სოკეტში, როგორც ეს ვაჩვენეთ ამ განყოფილებაში. როგორც წესი, ბერკლიდან მიღებული ბირთვები აბრუნებენ ამ შეცდომას, მაგრამ სისტემის V ბირთვები არა. მაგალითად, თუ ჩვენ გავუშვით ერთი და იგივე კლიენტი Solaris 2.4 ჰოსტზე და ვიყენებთ დაკავშირების ფუნქციას ჰოსტთან დასაკავშირებლად, რომელიც არ მუშაობს ჩვენს სერვერზე, მაშინ tcpdump-ის გამოყენებით შეგვიძლია გადავამოწმოთ, რომ ICMP პორტის მიუწვდომელი შეცდომა დაბრუნებულია სერვერის ჰოსტის მიერ. მაგრამ გამოწვეული კლიენტის წაკითხვის ფუნქცია არასოდეს სრულდება. ეს სიტუაცია გამოსწორებულია Solaris 2.5-ში. UnixWare არ აბრუნებს შეცდომას, ხოლო AIX, Digital Unix, HP-UX და Linux აბრუნებს.

8.13. ნაკადის კონტროლის ნაკლებობა UDP-ში

ჩამონათვალი 8.9

//udpcliserv/dgcliloop1.c

1 #include "unp.h"

8char გაგზავნის ხაზი;

ჩამონათვალი 8.10

//udpcliserv/dgecholoop1.c

1 #include "unp.h"

3 სტატიკური ინტ რაოდენობა;

7 socklen_t len;

8 char mesg;

11 len = კლილენ;

17 recvfrom_int(int signo)

ჩამონათვალი 8.11. გამომავალი სერვერის კვანძზე

freebsd % netstat -s -p udp

მიღებულია 71208 დატაგრამა

0 არასრული სათაურით

0 ცუდი მონაცემთა სიგრძის ველით

0 ცუდი გამშვები ჯამით

0 საკონტროლო ჯამის გარეშე

832 დაეცა სოკეტის არქონის გამო

0 არა ჰეშირებული PCB-სთვის

137685 datagrams გამომავალი

freebsd % udpserv06გაუშვით ჩვენი სერვერი

კლიენტი აგზავნის მონაცემთა გრამას

^ C

freebsd % netstat -s -p udp

მიღებულია 73208 დატაგრამა

0 არასრული სათაურით

0 ცუდი მონაცემთა სიგრძის ველით

0 ცუდი გამშვები ჯამით

0 საკონტროლო ჯამის გარეშე

832 დაეცა სოკეტის არქონის გამო

16 მაუწყებლობის/მულტიკასტის მონაცემთა გრამა დაეცა სოკეტის არარსებობის გამო

0 არა ჰეშირებული PCB-სთვის

137685 datagrams გამომავალი

aix % udpserv06

^?

მიიღო 2000 დატაგრამა

UDP სოკეტი იღებს ბუფერს

მოცემული სოკეტისთვის რიგით დადგმული UDP მონაცემთა გრამების რაოდენობა შემოიფარგლება მისი მიღების ბუფერის ზომით. ჩვენ შეგვიძლია შევცვალოთ ეს SO_RCVBUF სოკეტის ოფციის გამოყენებით, როგორც ეს ავღნიშნეთ სექციაში 7.5. FreeBSD-ზე ნაგულისხმევი UDP სოკეტის მიმღები ბუფერის ზომაა 42,080 ბაიტი, რაც საშუალებას იძლევა შეინახოს მხოლოდ 30 ჩვენი 1400 ბაიტიანი მონაცემთა გრამა. თუ ჩვენ გავზრდით სოკეტის მიღების ბუფერის ზომას, შეიძლება ველოდოთ, რომ სერვერი მიიღებს დამატებით მონაცემთა გრამას. ჩამონათვალი 8.12 არის შეცვლილი dg_echo ფუნქცია Listing 8.10-დან, რომელიც ზრდის სოკეტის მიღების ბუფერის ზომას 240 კბ-მდე. თუ ჩვენ გავუშვით ეს სერვერი Sun სისტემაზე და კლიენტი RS/6000 სისტემაზე, მიღებული დატაგრამის რაოდენობა იქნება 103. ვინაიდან ეს მხოლოდ ოდნავ უკეთესია, ვიდრე წინა მაგალითი ნაგულისხმევი ბუფერის ზომით, ცხადია, რომ ჩვენ ჯერ კიდევ ვართ. ვერ მიიღო პრობლემის გადაწყვეტა.

ჩამონათვალი 8.12. dg_echo ფუნქცია, რომელიც ზრდის სოკეტის მიღების ბუფერის ზომას

//udpcliserv/dgecholooor2.c

1 #include "unp.h"

2 სტატიკური void recvfrom_int(int);

3 სტატიკური ინტ რაოდენობა;

5 dg_echo (int sockfd, SA *pcliaddr, socklen_t clilen)

8 socklen_t len;

9 char mesg;

10 სიგნალი(SIGINT, recvfrom_int);

11 n = 240 * 1024;

12 Setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

14 len = კლილენ;

15 Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);

20 recvfrom_int(int signo)

22 printf("\nმიღებულია %d მონაცემთაგრამა\n", რაოდენობა);

ᲨᲔᲜᲘᲨᲕᲜᲐ

რატომ ვაყენებთ სოკეტის მიღების ბუფერის ზომას 240G-1024 ბაიტზე სიაში 8.12? ნაგულისხმევი მაქსიმალური სოკეტის მიღების ბუფერის ზომა BSD/OS 2.1-ში არის 262,144 ბაიტი (256G-1024), მაგრამ იმის გამო, რომ ბუფერი გამოიყოფა მეხსიერებაში (აღწერილია მე-2 თავში), ის რეალურად შემოიფარგლება 246,723 ბაიტით. 4.3BSD-ზე დაფუძნებული მრავალი ადრეული სისტემა ზღუდავდა ბუფერის ზომას დაახლოებით 52000 ბაიტამდე.

8.14. UDP-სთვის გამავალი ინტერფეისის განსაზღვრა

თქვენ ასევე შეგიძლიათ გამოიყენოთ თანდართული UDP სოკეტი, რათა მიუთითოთ გამავალი ინტერფეისი, რომელიც გამოყენებული იქნება მონაცემთა გრაფიკების გაგზავნისთვის კონკრეტულ მიმღებზე. ეს გამოწვეულია კავშირის ფუნქციის გვერდითი ეფექტის გამო, რომელიც გამოიყენება UDP სოკეტზე: ბირთვი ირჩევს ლოკალურ IP მისამართს (თუ ვივარაუდებთ, რომ პროცესს ჯერ არ მოუწოდებია bind, რომ ის მკაფიოდ დააყენოს). ლოკალური მისამართი შეირჩევა მარშრუტიზაციის ცხრილში დანიშნულების მისამართის მოძიებით, ინტერფეისის პირველადი IP მისამართის აღებით, საიდანაც, ცხრილის მიხედვით, მონაცემთა გრამები გაიგზავნება.

ჩამონათვალი 8.13 აჩვენებს მარტივ UDP პროგრამას, რომელიც უერთდება მოცემულ IP მისამართს დაკავშირების ფუნქციის გამოყენებით და შემდეგ უწოდებს getsockname ფუნქციას, გამოსცემს ლოკალურ IP მისამართს და პორტს.

ჩამონათვალი 8.13. UDP პროგრამა დაკავშირების ფუნქციის გამოყენებით გამავალი ინტერფეისის დასადგენად

//udpcliserv/udpcli09.c

1 #include "unp.h"

3 ძირითადი (int argc, char **argv)

6 socklen_t len;

7 struct sockaddr_in cliaddr, servaddr;

8 თუ (argc != 2)

9 err_quit("გამოყენება: udpcli");

10 sockfd = სოკეტი (AF_INET, SOCK_DGRAM, 0);

11 bzero(&servaddr, sizeof(servaddr));

12 servaddr.sin_family = AF_INET;

13 servaddr.sin_port = htons(SERV_PORT);

14 Inet_pton(AF_INET, argv, &servaddr.sin_addr);

15 Connect(sockfd, (SA*)&servaddr, sizeof(servaddr));

16 len = sizeof(cliaddr);

17 Getsockname(sockfd, (SA*)&cliaddr, &len);

18 printf("ლოკალური მისამართი %s\n", Sock_ntop((SA*)&cliaddr, len));

თუ პროგრამას გავუშვით freebsd ჰოსტზე მრავალი ქსელის ინტერფეისით, მივიღებთ შემდეგ გამომავალს:

freebsd % udpcli09 206.168.112.96

ადგილობრივი მისამართი 12.106.32.254:52329

freebsd % udpcli09 192.168.42.2

ადგილობრივი მისამართი 192.168.42.1:52330

freebsd % udpcli09 127.0.0.1

ადგილობრივი მისამართი 127.0.0.1:52331

ნახ. 1.7 გვიჩვენებს, რომ როდესაც ჩვენ ვატარებთ პროგრამას პირველ ორჯერ, ბრძანების ხაზის არგუმენტი არის IP მისამართი სხვადასხვა Ethernet ქსელში. ბირთვი ანიჭებს ადგილობრივ IP მისამართს პირველადი ინტერფეისის მისამართს შესაბამის Ethernet ქსელში. UDP სოკეტზე დაკავშირებისას არაფერი ეგზავნება ამ ჰოსტს - ეს არის სრულიად ლოკალური ოპერაცია, რომელიც ინახავს თანატოლის IP მისამართს და პორტს. ჩვენ ასევე ვხედავთ, რომ კავშირის გამოძახება დაუკავშირებელ UDP სოკეტზე ასევე ანიჭებს დინამიურად მინიჭებულ პორტს სოკეტს.

ᲨᲔᲜᲘᲨᲕᲜᲐ

სამწუხაროდ, ეს ტექნოლოგია არ მუშაობს ყველა იმპლემენტაციაში, რაც განსაკუთრებით ეხება SVR4-დან წარმოებულ ბირთვებს. მაგალითად, ეს არ მუშაობს Solaris 2.5-ზე, მაგრამ მუშაობს AIX, Digital Unix, Linux, MacOS X და Solaris 2.6-ზე.

8.15. TCP და UDP ექო სერვერი შერჩეული ფუნქციის გამოყენებით

ჩვენ ახლა გავაერთიანებთ ჩვენს პარალელურ TCP echo სერვერს მე-5 თავდან და ჩვენს სერიულ UDP echo სერვერს ამ თავიდან ერთ სერვერად, რომელიც იყენებს არჩევის ფუნქციას TCP და UDP სოკეტების მულტიპლექსისთვის. ჩამონათვალი 8.14 აჩვენებს ამ სერვერის პირველ ნაწილს.

ჩამონათვალი 8.14. ექო სერვერის პირველი ნაწილი ამუშავებს TCP და UDP სოკეტებს არჩევის ფუნქციის გამოყენებით

//udpcliserv/udpservselect01.c

1 #include "unp.h"

3 ძირითადი (int argc, char **argv)

5 int listenfd, connfd, udpfd, nready, maxfdp1;

6 char mesg;

7 pid_t childpid;

10 socklen_t len;

11 კონსტანტი = 1;

12 struct sockaddr_in cliaddr, servaddr;

13 void sig_chld(int);

14 /* შექმენით TCP მოსმენის სოკეტი */

15 listenfd = სოკეტი (AF_INET, SOCK_STREAM, 0);

16 bzero(&servaddr, sizeof(servaddr));

17 servaddr.sin_family = AF_INET;

18 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

19 servaddr.sin_port = htons(SERV_PORT);

20 Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

21 Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));

22 Listen(listenfd, LISTENQ);

23 /* შექმენით UDP სოკეტი */

24 udpfd = სოკეტი (AF_INET, SOCK_DGRAM, 0);

25 bzero(&servaddr, sizeof(servaddr));

26 servaddr.sin_family = AF_INET;

27 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

28 servaddr.sin_port = htons(SERV_PORT);

29 Bind(udpfd, (SA*)&servaddr, sizeof(servaddr));

TCP მოსმენის სოკეტის შექმნა

14-22 იქმნება TCP მოსმენის სოკეტი და ასოცირდება სერვერზე ადრე ცნობილ პორტთან. ჩვენ ვაყენებთ სოკეტის ოფციას SO_REUSEADDR იმ შემთხვევაში, თუ არსებობს კავშირები ამ პორტზე.

UDP სოკეტის შექმნა

23-29 UDP სოკეტიც იქმნება და ასოცირდება იმავე პორტთან. მაშინაც კი, თუ იგივე პორტი გამოიყენება TCP და UDP სოკეტებისთვის, არ არის საჭირო SO_REUSEADDR სოკეტის პარამეტრის დაყენება ამ გამოძახებამდე, რადგან TCP პორტები დამოუკიდებელია UDP პორტებისგან.

ჩამონათვალი 8.15 აჩვენებს ჩვენი სერვერის მეორე ნაწილს.

ჩამონათვალი 8.15. ექო სერვერის მეორე ნახევარი ამუშავებს TCP და UDP არჩევის ფუნქციის გამოყენებით

udpcliserv/udpservselect01.c

30 სიგნალი (SIGCHLD, sig_chld); /* უნდა დარეკოთ waitpid() */

31 FD_ZERO(&rset);

32 maxfdp1 = max(listenfd, udpfd) + 1;

34 FD_SET( listenfd, &rset);

35 FD_SET(udpfd, &rset);

36 თუ ((მზად არის = აირჩიეთ (maxfdp1, &rset, NULL, NULL, NULL))

37 თუ (შეცდომა == EINTR)

38 გაგრძელება; /* დაბრუნება for()-ზე */

40 err_sys ("შეცდომის არჩევა");

42 if (FD_ISSET(მოსმენაfd, &rsset)) (

43 len = sizeof(cliaddr);

44 connfd = Accept(listenfd, (SA*)&cliaddr, &len);

45 if ((childpid = Fork()) == 0) ( /* ბავშვის პროცესი */

46 Close(listenfd); /* ხურავს მოსმენის სოკეტს */

47 str_echo (connfd); /* მოთხოვნის დამუშავება */

50 Close(connfd); /* მშობელი ხურავს მიმაგრებულ სოკეტს */

52 თუ (FD_ISSET(udpfd, &rsset)) (

53 len = sizeof(cliaddr);

54 n = Recvfrom(udpfd, mesg, MAXLINE, 0, (SA*)&cliaddr, &len);

55 Sendto(udpfd, mesg, n, 0, (SA*)&cliaddr, len);

SIGCHLD სიგნალის დამმუშავებლის ინსტალაცია

30 SIGCHLD სიგნალისთვის დაყენებულია დამმუშავებელი, რადგან TCP კავშირები დამუშავებული იქნება ბავშვის პროცესის მიერ. ჩვენ ვაჩვენეთ ეს სიგნალის დამმუშავებელი სიაში 5.8.

არჩევის ფუნქციის გამოსაძახებლად მზადება

31-32 ჩვენ ვაწარმოებთ აღმწერთა ნაკრების ინიციალიზაციას select ფუნქციისთვის და ვიანგარიშებთ მაქსიმუმს ორი დესკრიპტორიდან, რომელსაც დაველოდებით როცა მზად იქნება.

არჩევის ფუნქციის გამოძახება

34-41 ჩვენ მოვუწოდებთ Select ფუნქციას, ველოდებით მხოლოდ TCP სოკეტს ან UDP სოკეტს, რომელსაც ჩვენ ვუსმენთ, რომ მზად იყოს წასაკითხად. ვინაიდან ჩვენმა sig_chld სიგნალის დამმუშავებელმა შეიძლება შეაჩეროს შერჩეული ფუნქციის გამოძახება, ჩვენ ვამუშავებთ EINTR შეცდომას.

ახალი კლიენტის კავშირის მართვა

42-51 ჩვენ ვიყენებთ მიღებას ფუნქციას ახალი კლიენტის კავშირის მისაღებად, და როდესაც მოსასმენი TCP სოკეტი მზად არის წასაკითხად, ვიყენებთ fork ფუნქციას ბავშვის პროცესის გასაჩენად და გამოვიძახოთ ჩვენი str_echo ფუნქცია ბავშვის პროცესზე. ეს არის ეტაპების იგივე თანმიმდევრობა, რაც ჩვენ მივყევით მე-5 თავში.

შემომავალი დატაგრამის დამუშავება

52-57 თუ UDP სოკეტი მზად არის წასაკითხად, დატაგრამა მოვიდა. ჩვენ მას ვკითხულობთ recvfrom ფუნქციის გამოყენებით და ვუგზავნით კლიენტს sendto ფუნქციის გამოყენებით.

8.16. Შემაჯამებელი

ჩვენი echo კლიენტისა და echo სერვერის გადაყვანა UDP-ის ნაცვლად TCP-ის გამოსაყენებლად მარტივი იყო. მაგრამ ამავე დროს, ჩვენ დავკარგეთ TCP პროტოკოლით გათვალისწინებული მრავალი შესაძლებლობა: დაკარგული პაკეტების იდენტიფიცირება და ხელახალი გადაცემა, შემოწმება, მოდის თუ არა პაკეტები სწორი თანამოსაუბრესგან და ა.შ. ჩვენ დავუბრუნდებით ამ თემას სექციაში 22.5 და ვნახავთ, როგორ გავაუმჯობესოთ UDP აპლიკაციის სანდოობა.

UDP სოკეტებს შეუძლიათ წარმოქმნან ასინქრონული შეცდომები, ეს არის შეცდომები, რომლებიც მოხსენებულია პაკეტის გაგზავნიდან გარკვეული პერიოდის შემდეგ. TCP სოკეტები ყოველთვის აცნობებენ მათ აპლიკაციას, მაგრამ UDP-ით სოკეტი უნდა იყოს დაკავშირებული ამ შეცდომების მისაღებად.

UDP-ს აკლია ნაკადის კონტროლი, რისი დემონსტრირება ძალიან მარტივია. ეს, როგორც წესი, არ არის პრობლემა, რადგან ბევრი UDP აპლიკაცია აგებულია მოთხოვნა-პასუხის მოდელის გამოყენებით და არ არის შექმნილი დიდი რაოდენობით მონაცემების გადასატანად.

არსებობს მრავალი სხვა რამ, რაც გასათვალისწინებელია UDP აპლიკაციების წერისას, მაგრამ ჩვენ მათ 22-ე თავში განვიხილავთ ინტერფეისების, მაუწყებლობის და მულტიკასტის ფუნქციების გაშუქების შემდეგ.

Სავარჯიშოები

1. ვთქვათ, გვაქვს ორი აპლიკაცია, ერთი იყენებს TCP და მეორე იყენებს UDP. TCP სოკეტის მიღების ბუფერი შეიცავს 4096 ბაიტი მონაცემს, ხოლო UDP სოკეტის მიღების ბუფერი შეიცავს 2048 ბაიტის ორ მონაცემთაგრამას. TCP აპლიკაცია იძახებს წაკითხვის ფუნქციას მესამე არგუმენტით 4096, ხოლო UDP აპლიკაცია იძახებს recvfrom ფუნქციას მესამე არგუმენტით 4096. არის რაიმე განსხვავება ამ ზარებს შორის?

2. ჩამონათვალში 8.2 რა მოხდება, თუ შევცვლით sendto ფუნქციის ბოლო არგუმენტს (რომელსაც დავარქვით len) არგუმენტით clilen?

3. შეადგინეთ და გაუშვით UDP სერვერი განცხადებებიდან 8.1 და 8.4, შემდეგ კი კლიენტი განცხადებებიდან 8.3 და 8.4. დარწმუნდით, რომ კლიენტი და სერვერი ერთად მუშაობენ.

4. გაუშვით პინგ პროგრამა ერთ ფანჯარაში, მიუთითეთ -i ვარიანტი 60 (ერთი პაკეტის გაგზავნა ყოველ 60 წამში; ზოგიერთი სისტემა იყენებს I გადამრთველს i-ს ნაცვლად), -v ოფცია (დაბეჭდეთ ყველა მიღებული ICMP შეცდომის შეტყობინება) და დააყენეთ loopback მისამართი თავისთვის (ჩვეულებრივ 127.0.0.1). ჩვენ გამოვიყენებთ ამ პროგრამას სერვერის ჰოსტის მიერ დაბრუნებული ICMP პორტის მიუწვდომელი შეცდომის სანახავად. შემდეგ გაუშვით ჩვენი კლიენტი წინა სავარჯიშოდან სხვა ფანჯარაში, მიუთითეთ ზოგიერთი კვანძის IP მისამართი, რომელიც არ მუშაობს სერვერზე. Რა ხდება?

5. ნახ. 8.3, ჩვენ ვთქვით, რომ თითოეულ დაკავშირებულ TCP სოკეტს აქვს საკუთარი მიღების ბუფერი. როგორ ფიქრობთ, მოსმენის სოკეტს აქვს საკუთარი მიღების ბუფერი?

6. გამოიყენეთ sock პროგრამა (იხ. განყოფილება B.3) და ინსტრუმენტი, როგორიცაა tcpdump (იხ. ნაწილი B.5), რათა გადაამოწმოთ პუნქტი 8.10: თუ კლიენტი იყენებს bind ფუნქციას IP მისამართის მის სოკეტთან დასაკავშირებლად, მაგრამ აგზავნის დატაგრამას, რომელიც წარმოიქმნება სხვა ინტერფეისიდან, შემდეგ მიღებული დატაგრამა შეიცავს IP მისამართს, რომელიც ასოცირდება სოკეტთან, მაშინაც კი, თუ ის არ ემთხვევა საწყისი ინტერფეისს.

7. შეადგინეთ პროგრამები 8.13 განყოფილებიდან და გაუშვით კლიენტი და სერვერი სხვადასხვა კვანძზე. განათავსეთ printf კლიენტზე ყოველ ჯერზე, როდესაც მონაცემთა გრამა ჩაიწერება სოკეტში. ცვლის თუ არა ეს მიღებული პაკეტების პროცენტს? რატომ? გამოიძახეთ printf სერვერიდან ყოველ ჯერზე, როცა მონაცემთა გრამა იკითხება სოკეტიდან. ცვლის თუ არა ეს მიღებული პაკეტების პროცენტს? რატომ?

8. რა არის ყველაზე დიდი სიგრძე, რომელიც შეგვიძლია გადავცეთ sendto ფუნქციაზე UDP/IPv4 სოკეტისთვის, ანუ რა არის მონაცემთა ყველაზე დიდი რაოდენობა, რომელიც შეიძლება მოთავსდეს UDP/IPv4 დატაგრამაში? რა იცვლება UDP/IPv6-ის შემთხვევაში?

შეცვალეთ ჩამონათვალი 8.4, რომ გაგზავნოთ ერთი მაქსიმალური ზომის UDP მონაცემთაგრამა, წაიკითხოთ იგი და ამობეჭდოთ recvfrom-ის მიერ დაბრუნებული ბაიტების რაოდენობა.

9. შეცვალეთ ჩამონათვალი 8.15 RFC 1122-თან შესაბამისობისთვის: IP_RECVDSTADDR უნდა იყოს გამოყენებული UDP სოკეტისთვის.

8.9 განყოფილების ბოლოს, ჩვენ აღვნიშნეთ, რომ ასინქრონული შეცდომები არ ბრუნდება UDP სოკეტზე, თუ სოკეტი არ არის მიმაგრებული. ჩვენ შეგვიძლია რეალურად ვუწოდოთ დაკავშირების ფუნქცია UDP სოკეტზე (იხ. განყოფილება 4.3). მაგრამ ეს არ გამოიწვევს რაიმე TCP კავშირის მსგავსს: არ არსებობს სამმხრივი ხელის ჩამორთმევა. ბირთვი უბრალოდ ამოწმებს, არის თუ არა ცნობილი დანიშნულების ადგილი მიუწვდომელი, შემდეგ ჩაწერს თანატოლის IP მისამართს და პორტის ნომერს, რომლებიც შეიცავს სოკეტის მისამართების სტრუქტურას, რომელიც გადაეცემა დაკავშირების ფუნქციას და დაუყოვნებლივ უბრუნებს კონტროლს დარეკვის პროცესს.

ᲨᲔᲜᲘᲨᲕᲜᲐ

დაკავშირების ფუნქციის გადატვირთვა ამ ახალი ფუნქციით UDP სოკეტებისთვის შეიძლება დამაბნეველი იყოს. თუ კონვენცია არის ის, რომ sockname არის ადგილობრივი პროტოკოლის მისამართი და peername არის დისტანციური პროტოკოლის მისამართი, მაშინ ამ ფუნქციას უკეთესად ეწოდოს setpeername. ანალოგიურად, bind ფუნქციას უკეთესად ეწოდოს setsockname.

ამის გათვალისწინებით, აუცილებელია გავიგოთ განსხვავება ორ ტიპის UDP სოკეტს შორის.

გ– შეუერთებელი UDP სოკეტი არის ნაგულისხმევი UDP სოკეტი შექმნილი.

c– დაკავშირებული UDP სოკეტი არის UDP სოკეტზე დაკავშირების ფუნქციის გამოძახების შედეგი.

დაკავშირებულ UDP სოკეტს აქვს სამი განსხვავება დაუმაგრებელი სოკეტისგან, რომელიც იქმნება ნაგულისხმევად.

1. ჩვენ აღარ შეგვიძლია დავაყენოთ დანიშნულების IP მისამართი და პორტი გამომავალი ოპერაციისთვის. ანუ, sendto ფუნქციის ნაცვლად ვიყენებთ ჩაწერის ან გაგზავნის ფუნქციას. დაკავშირებულ UDP სოკეტში ჩაწერილი ყველაფერი ავტომატურად იგზავნება დაკავშირების ფუნქციით მითითებულ მისამართზე (როგორიცაა IP მისამართი და პორტი).

ᲨᲔᲜᲘᲨᲕᲜᲐ

TCP-ის მსგავსად, ჩვენ შეგვიძლია გამოვიძახოთ sendto ფუნქცია მიმაგრებულ UDP სოკეტზე, მაგრამ დანიშნულების მისამართს ვერ დავაზუსტებთ. sendto ფუნქციის მეხუთე არგუმენტი (სოკეტის მისამართის სტრუქტურის მაჩვენებელი) უნდა იყოს null მაჩვენებელი, ხოლო მეექვსე არგუმენტი (სოკეტის მისამართის სტრუქტურის ზომა) უნდა იყოს null. POSIX სტანდარტი განსაზღვრავს, რომ როდესაც მეხუთე არგუმენტი არის null მაჩვენებელი, მეექვსე არგუმენტი იგნორირებულია.

2. recvfrom ფუნქციის ნაცვლად ვიყენებთ read ან recv ფუნქციას. ბირთვის მიერ დაბრუნებული მხოლოდ მონაცემთა გრამები შეყვანის ოპერაციისთვის დაკავშირებულ UDP სოკეტზე არის მონაცემთა გრამები, რომლებიც მოდის დაკავშირების ფუნქციაში მითითებული მისამართიდან. მონაცემთა გრამები, რომლებიც განკუთვნილია დაკავშირებული UDP სოკეტის ლოკალური პროტოკოლის მისამართისთვის (როგორიცაა IP მისამართი და პორტი), მაგრამ მოდის პროტოკოლის მისამართიდან, გარდა იმისა, რომელზედაც იყო დაკავშირებული სოკეტი დაკავშირების ფუნქციის გამოყენებით, არ იგზავნება დაკავშირებულ სოკეტში. ეს ზღუდავს მიმაგრებულ UDP სოკეტს, რათა მას შეეძლოს გაცვალოს მონაცემთა გრამები ერთ და მხოლოდ ერთ თანატოლთან.

ᲨᲔᲜᲘᲨᲕᲜᲐ

უფრო ზუსტად, დატაგრამები გაცვლა ხდება მხოლოდ ერთი IP მისამართით, და არა ერთ თანამოსაუბრესთან, რადგან ეს შეიძლება იყოს მულტიკასტის IP მისამართი, რითაც წარმოადგენს თანამოსაუბრეთა ჯგუფს.

3. ასინქრონული შეცდომები პროცესს უბრუნდება მხოლოდ მიმაგრებულ UDP სოკეტზე ოპერაციებისთვის. შედეგად, როგორც უკვე ვთქვით, დაუმაგრებელი UDP სოკეტი არ იღებს ასინქრონულ შეცდომებს.

მაგიდაზე 8.2 აერთიანებს პირველ აბზაცში ჩამოთვლილ თვისებებს, როგორც გამოიყენება 4.4BSD.

ცხრილი 8.2. TCP და UDP სოკეტები: შესაძლებელია თუ არა დანიშნულების პროტოკოლის მისამართის მითითება

ᲨᲔᲜᲘᲨᲕᲜᲐ

POSIX განსაზღვრავს, რომ პინის ოპერაცია, რომელიც არ აკონკრეტებს დანიშნულების მისამართს მიუერთებელ UDP სოკეტზე, უნდა დააბრუნოს ENOTCONN შეცდომა და არა EDESTADDRREQ შეცდომა.

Solaris 2.5 საშუალებას აძლევს sendto ფუნქციას, რომელიც განსაზღვრავს მიმაგრებული UDP სოკეტის დანიშნულების მისამართს. POSIX განსაზღვრავს, რომ EISCONN შეცდომა უნდა დაბრუნდეს ამ სიტუაციაში.

ნახ. სექცია 8.7 აჯამებს ინფორმაციას მიმაგრებული UDP სოკეტის შესახებ.

ბრინჯი. 8.7. UDP მიმაგრებული სოკეტი

აპლიკაცია იძახებს დაკავშირების ფუნქციას, რომელშიც მითითებულია თანამოსაუბრის IP მისამართი და პორტის ნომერი. შემდეგ ის იყენებს წაკითხვისა და ჩაწერის ფუნქციებს მეორე მხარესთან მონაცემების გასაცვლელად.

ნებისმიერი სხვა IP მისამართიდან ან პორტიდან (რომელსაც ჩვენ აღვნიშნავთ როგორც "???" სურათზე 8.7) არ იგზავნება მიმაგრებულ სოკეტში, რადგან წყაროს IP მისამართი ან UDP პორტი არ ემთხვევა პროტოკოლის მისამართს, რომელსაც აქვს სოკეტი. დაკავშირებულია დაკავშირების ფუნქციის გამოყენებით. ეს მონაცემთა გრამები შეიძლება მიეწოდოს სხვა UDP სოკეტს ჰოსტზე. თუ შემომავალი დატაგრამის სხვა შესატყვისი სოკეტი არ არის, UDP უგულებელყოფს მას და წარმოქმნის ICMP პორტის მიუწვდომელ შეტყობინებას.

ზემოაღნიშნულის შესაჯამებლად, შეგვიძლია განვაცხადოთ, რომ UDP კლიენტს ან სერვერს შეუძლია დარეკოს დაკავშირების ფუნქცია მხოლოდ იმ შემთხვევაში, თუ ეს პროცესი იყენებს UDP სოკეტს მხოლოდ ერთ თანამოსაუბრესთან კომუნიკაციისთვის. როგორც წესი, ეს არის UDP კლიენტი, რომელიც იძახებს დაკავშირების ფუნქციას, მაგრამ არის აპლიკაციები, რომლებშიც UDP სერვერი ურთიერთობს ერთ კლიენტთან დიდი ხნის განმავლობაში (როგორიცაა TFTP), ამ შემთხვევაში კლიენტიც და სერვერიც უწოდებს დაკავშირებას. ფუნქცია.

გრძელვადიანი ურთიერთქმედების კიდევ ერთი მაგალითია DNS (სურათი 8.8).

ბრინჯი. 8.8. DNS კლიენტებისა და სერვერების მაგალითი და დაკავშირების ფუნქცია

DNS კლიენტის კონფიგურაცია შესაძლებელია ერთი ან მეტი სერვერის გამოსაყენებლად, როგორც წესი, სერვერების IP მისამართების ჩამოთვლით /etc/resolv.conf ფაილში. თუ ამ ფაილში მხოლოდ ერთი სერვერია ჩამოთვლილი (ეს კლიენტი არის ფიგურის მარცხენა ოთხკუთხედი), კლიენტს შეუძლია დარეკოს დაკავშირების ფუნქცია, მაგრამ თუ ჩამოთვლილია მრავალი სერვერი (სურათზე მეორე მარჯვენა მართკუთხედი), კლიენტი ვერ დარეკავს. დაკავშირების ფუნქცია. როგორც წესი, DNS სერვერი ასევე ამუშავებს კლიენტის ნებისმიერ მოთხოვნას, ამიტომ სერვერები ვერ გამოიძახებენ დაკავშირების ფუნქციას.

ახლა ჩვენ შევამოწმებთ, თუ რა გავლენას ახდენს აპლიკაციაზე UDP-ში რაიმე ნაკადის კონტროლის არარსებობა. პირველ რიგში ჩვენ შევცვლით ჩვენს dg_cli ფუნქციას ისე, რომ ის აგზავნის მონაცემთა გრაფიკების ფიქსირებულ რაოდენობას. ის აღარ იკითხება სტანდარტული შეყვანიდან. ჩამონათვალი 8.9 აჩვენებს ფუნქციის ახალ ვერსიას. ეს ფუნქცია სერვერზე აგზავნის 1400 ბაიტის 2000 UDP მონაცემთა გრამას.

ჩამონათვალი 8.9. dg_cli ფუნქცია სერვერზე აგზავნის მონაცემთა გრამების ფიქსირებულ რაოდენობას

//udpcliserv/dgcliloop1.c

1 #include "unp.h"

2 #define NDG 2000 /* გასაგზავნად მონაცემთა გრამების რაოდენობა */

3 #define DGLEN 1400 /* თითოეული მონაცემთაგრამის სიგრძე */

5 dg_cli (FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

8char გაგზავნის ხაზი;

10 Sendto (sockfd, sendline, DGLEN, 0, pservaddr, servlen);

შემდეგ ჩვენ ვცვლით სერვერს, რომ მივიღოთ დატაგრამები და ვითვლით მიღებული დატაგრამების რაოდენობას. სერვერი აღარ ასახავს მონაცემთა გრამას კლიენტს. ჩამონათვალი 8.10 აჩვენებს ახალ dg_echo ფუნქციას. როდესაც ჩვენ ვასრულებთ სერვერის პროცესს ტერმინალზე შეწყვეტის ღილაკის დაჭერით (რაც იწვევს პროცესზე SIGINT სიგნალის გაგზავნას), სერვერი ბეჭდავს მიღებული დატაგრამების რაოდენობას და გამოდის.

ჩამონათვალი 8.10. dg_echo ფუნქცია, რომელიც ითვლის მიღებულ დატაგრამებს

//udpcliserv/dgecholoop1.c

1 #include "unp.h"

2 სტატიკური void recvfrom_int(int);

3 სტატიკური ინტ რაოდენობა;

5 dg_echo (int sockfd, SA *pcliaddr, socklen_t clilen)

7 socklen_t len;

8 char mesg;

9 სიგნალი(SIGINT, recvfrom_int);

11 len = კლილენ;

12 Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);

17 recvfrom_int(int signo)

19 printf("\nმიღებულია %d მონაცემთაგრამა\n", რაოდენობა);

ჩვენ ახლა ვმართავთ სერვერს node freebsd-ზე, რომელიც არის ნელი SPARCStation კომპიუტერი. ჩვენ ვმართავთ კლიენტს ბევრად უფრო სწრაფ RS/6000 სისტემაზე aix ოპერაციული სისტემით. ისინი პირდაპირ კავშირშია ერთმანეთთან 100 Mbit/s Ethernet ბმულის საშუალებით. გარდა ამისა, ჩვენ ვაწარმოებთ netstat -s სერვერის კვანძზე კლიენტისა და სერვერის გაშვებამდეც და შემდეგაც, რადგან სტატისტიკის გამომავალი გვიჩვენებს რამდენი დატაგრამა დავკარგეთ. ჩამონათვალი 8.11 აჩვენებს სერვერის გამომავალს.

ჩამონათვალი 8.11. გამომავალი სერვერის კვანძზე

freebsd % netstat -s -p udp

მიღებულია 71208 დატაგრამა

0 არასრული სათაურით

0 ცუდი მონაცემთა სიგრძის ველით

0 ცუდი გამშვები ჯამით

0 საკონტროლო ჯამის გარეშე

832 დაეცა სოკეტის არქონის გამო

16 მაუწყებლობის/მულტიკასტის მონაცემთა გრამა დაეცა სოკეტის არარსებობის გამო

1971 დაეცა სრული სოკეტის ბუფერების გამო

0 არა ჰეშირებული PCB-სთვის

137685 datagrams გამომავალი

freebsd % udpserv06გაუშვით ჩვენი სერვერი

კლიენტი აგზავნის მონაცემთა გრამას

^ Cკლიენტის მუშაობის დასასრულებლად, შეიყვანეთ ჩვენი შეწყვეტის სიმბოლო

freebsd % netstat -s -p udp

მიღებულია 73208 დატაგრამა

0 არასრული სათაურით

0 ცუდი მონაცემთა სიგრძის ველით

0 ცუდი გამშვები ჯამით

0 საკონტროლო ჯამის გარეშე

832 დაეცა სოკეტის არქონის გამო

16 მაუწყებლობის/მულტიკასტის მონაცემთა გრამა დაეცა სოკეტის არარსებობის გამო

3941 დაეცა სრული სოკეტის ბუფერების გამო

0 არა ჰეშირებული PCB-სთვის

137685 datagrams გამომავალი

კლიენტმა გაგზავნა 2000 დატაგრამა, მაგრამ სერვერის აპლიკაციამ მიიღო მათგან მხოლოდ 30, რაც ნიშნავს ზარალის 98%-ს. არც სერვერი და არც კლიენტი არ იღებენ შეტყობინებას, რომ ეს მონაცემთა გრამები დაიკარგა. როგორც ვთქვით, UDP-ს არ აქვს ნაკადის კონტროლის შესაძლებლობები - ის არასანდოა. როგორც ვაჩვენეთ, UDP გამგზავნისთვის ადვილია მიმღების ბუფერის გადატვირთვა.

თუ გადავხედავთ netstat-ის გამომავალს, დავინახავთ, რომ სერვერის კვანძის (არა სერვერის აპლიკაციის) მიერ მიღებული დატაგრამების საერთო რაოდენობა არის 2000 (73,208 - 71,208). სრული სოკეტის ბუფერების გამორიცხული მრიცხველი გვიჩვენებს, თუ რამდენი დატაგრამა იქნა მიღებული UDP-ის მიერ და იგნორირებული იყო, რადგან მიმღები სოკეტის მიღების ბუფერი სავსე იყო. ეს მნიშვნელობა არის 1970 (3941 - 1971), რომელიც, როდესაც დაემატება აპლიკაციის მონაცემთა გრაფიკების გამომავალს, მიღებული რაოდენობა (30) იწვევს კვანძის მიერ მიღებულ 2000 დატაგრამას. სამწუხაროდ, netstat-ის მიერ სრული ბუფერის გამო გაუქმებული მონაცემთა გრამები სისტემური მასშტაბითაა. არ არსებობს გზა, რათა დადგინდეს, რომელ აპლიკაციებზე (მაგ. რომელ UDP პორტებზე) არის დაზარალებული.

ამ მაგალითში სერვერის მიერ მიღებული დატაგრამების რაოდენობა არადეტერმინისტულია. ეს დამოკიდებულია ბევრ ფაქტორზე, როგორიცაა ქსელის დატვირთვა, კლიენტის კვანძი და სერვერის კვანძის დატვირთვა.

თუ ჩვენ გავუშვით ერთი და იგივე კლიენტი და ერთი და იგივე სერვერი, მაგრამ ამჯერად კლიენტი არის ნელი Sun სისტემაზე და სერვერი არის სწრაფ RS/6000 სისტემაზე, არც ერთი დატაგრამა არ იკარგება.

aix % udpserv06

^? მას შემდეგ, რაც კლიენტი დაასრულებს მუშაობას, შეიყვანეთ ჩვენი შეწყვეტის სიმბოლო

მიიღო 2000 დატაგრამა

8.9 განყოფილების ბოლოს, ჩვენ აღვნიშნეთ, რომ ასინქრონული შეცდომები არ ბრუნდება UDP სოკეტზე, თუ სოკეტი არ არის მიმაგრებული. ჩვენ შეგვიძლია რეალურად ვუწოდოთ დაკავშირების ფუნქცია UDP სოკეტზე (იხ. განყოფილება 4.3). მაგრამ ეს არ გამოიწვევს რაიმე TCP კავშირის მსგავსს: არ არსებობს სამმხრივი ხელის ჩამორთმევა. ბირთვი უბრალოდ ამოწმებს, არის თუ არა ცნობილი დანიშნულების ადგილი მიუწვდომელი, შემდეგ ჩაწერს თანატოლის IP მისამართს და პორტის ნომერს, რომლებიც შეიცავს სოკეტის მისამართების სტრუქტურას, რომელიც გადაეცემა დაკავშირების ფუნქციას და დაუყოვნებლივ უბრუნებს კონტროლს დარეკვის პროცესს.

ᲨᲔᲜᲘᲨᲕᲜᲐ

დაკავშირების ფუნქციის გადატვირთვა ამ ახალი ფუნქციით UDP სოკეტებისთვის შეიძლება დამაბნეველი იყოს. თუ კონვენცია არის ის, რომ sockname არის ადგილობრივი პროტოკოლის მისამართი და peername არის დისტანციური პროტოკოლის მისამართი, მაშინ ამ ფუნქციას უკეთესად ეწოდოს setpeername. ანალოგიურად, bind ფუნქციას უკეთესად ეწოდოს setsockname.

ამის გათვალისწინებით, აუცილებელია გავიგოთ განსხვავება ორ ტიპის UDP სოკეტს შორის.

გ– შეუერთებელი UDP სოკეტი არის ნაგულისხმევი UDP სოკეტი შექმნილი.

c– დაკავშირებული UDP სოკეტი არის UDP სოკეტზე დაკავშირების ფუნქციის გამოძახების შედეგი.

დაკავშირებულ UDP სოკეტს აქვს სამი განსხვავება დაუმაგრებელი სოკეტისგან, რომელიც იქმნება ნაგულისხმევად.

1. ჩვენ აღარ შეგვიძლია დავაყენოთ დანიშნულების IP მისამართი და პორტი გამომავალი ოპერაციისთვის. ანუ, sendto ფუნქციის ნაცვლად ვიყენებთ ჩაწერის ან გაგზავნის ფუნქციას. დაკავშირებულ UDP სოკეტში ჩაწერილი ყველაფერი ავტომატურად იგზავნება დაკავშირების ფუნქციით მითითებულ მისამართზე (როგორიცაა IP მისამართი და პორტი).

ᲨᲔᲜᲘᲨᲕᲜᲐ

TCP-ის მსგავსად, ჩვენ შეგვიძლია გამოვიძახოთ sendto ფუნქცია მიმაგრებულ UDP სოკეტზე, მაგრამ დანიშნულების მისამართს ვერ დავაზუსტებთ. sendto ფუნქციის მეხუთე არგუმენტი (სოკეტის მისამართის სტრუქტურის მაჩვენებელი) უნდა იყოს null მაჩვენებელი, ხოლო მეექვსე არგუმენტი (სოკეტის მისამართის სტრუქტურის ზომა) უნდა იყოს null. POSIX სტანდარტი განსაზღვრავს, რომ როდესაც მეხუთე არგუმენტი არის null მაჩვენებელი, მეექვსე არგუმენტი იგნორირებულია.

2. recvfrom ფუნქციის ნაცვლად ვიყენებთ read ან recv ფუნქციას. ბირთვის მიერ დაბრუნებული მხოლოდ მონაცემთა გრამები შეყვანის ოპერაციისთვის დაკავშირებულ UDP სოკეტზე არის მონაცემთა გრამები, რომლებიც მოდის დაკავშირების ფუნქციაში მითითებული მისამართიდან. მონაცემთა გრამები, რომლებიც განკუთვნილია დაკავშირებული UDP სოკეტის ლოკალური პროტოკოლის მისამართისთვის (როგორიცაა IP მისამართი და პორტი), მაგრამ მოდის პროტოკოლის მისამართიდან, გარდა იმისა, რომელზედაც იყო დაკავშირებული სოკეტი დაკავშირების ფუნქციის გამოყენებით, არ იგზავნება დაკავშირებულ სოკეტში. ეს ზღუდავს მიმაგრებულ UDP სოკეტს, რათა მას შეეძლოს გაცვალოს მონაცემთა გრამები ერთ და მხოლოდ ერთ თანატოლთან.

ᲨᲔᲜᲘᲨᲕᲜᲐ

უფრო ზუსტად, დატაგრამები გაცვლა ხდება მხოლოდ ერთი IP მისამართით, და არა ერთ თანამოსაუბრესთან, რადგან ეს შეიძლება იყოს მულტიკასტის IP მისამართი, რითაც წარმოადგენს თანამოსაუბრეთა ჯგუფს.

3. ასინქრონული შეცდომები პროცესს უბრუნდება მხოლოდ მიმაგრებულ UDP სოკეტზე ოპერაციებისთვის. შედეგად, როგორც უკვე ვთქვით, დაუმაგრებელი UDP სოკეტი არ იღებს ასინქრონულ შეცდომებს.

მაგიდაზე 8.2 აერთიანებს პირველ აბზაცში ჩამოთვლილ თვისებებს, როგორც გამოიყენება 4.4BSD.

ცხრილი 8.2. TCP და UDP სოკეტები: შესაძლებელია თუ არა დანიშნულების პროტოკოლის მისამართის მითითება

ᲨᲔᲜᲘᲨᲕᲜᲐ

POSIX განსაზღვრავს, რომ პინის ოპერაცია, რომელიც არ აკონკრეტებს დანიშნულების მისამართს მიუერთებელ UDP სოკეტზე, უნდა დააბრუნოს ENOTCONN შეცდომა და არა EDESTADDRREQ შეცდომა.

Solaris 2.5 საშუალებას აძლევს sendto ფუნქციას, რომელიც განსაზღვრავს მიმაგრებული UDP სოკეტის დანიშნულების მისამართს. POSIX განსაზღვრავს, რომ EISCONN შეცდომა უნდა დაბრუნდეს ამ სიტუაციაში.

ნახ. სექცია 8.7 აჯამებს ინფორმაციას მიმაგრებული UDP სოკეტის შესახებ.

ბრინჯი. 8.7. UDP მიმაგრებული სოკეტი

აპლიკაცია იძახებს დაკავშირების ფუნქციას, რომელშიც მითითებულია თანამოსაუბრის IP მისამართი და პორტის ნომერი. შემდეგ ის იყენებს წაკითხვისა და ჩაწერის ფუნქციებს მეორე მხარესთან მონაცემების გასაცვლელად.

ნებისმიერი სხვა IP მისამართიდან ან პორტიდან (რომელსაც ჩვენ აღვნიშნავთ როგორც "???" სურათზე 8.7) არ იგზავნება მიმაგრებულ სოკეტში, რადგან წყაროს IP მისამართი ან UDP პორტი არ ემთხვევა პროტოკოლის მისამართს, რომელსაც აქვს სოკეტი. დაკავშირებულია დაკავშირების ფუნქციის გამოყენებით. ეს მონაცემთა გრამები შეიძლება მიეწოდოს სხვა UDP სოკეტს ჰოსტზე. თუ შემომავალი დატაგრამის სხვა შესატყვისი სოკეტი არ არის, UDP უგულებელყოფს მას და წარმოქმნის ICMP პორტის მიუწვდომელ შეტყობინებას.

ზემოაღნიშნულის შესაჯამებლად, შეგვიძლია განვაცხადოთ, რომ UDP კლიენტს ან სერვერს შეუძლია დარეკოს დაკავშირების ფუნქცია მხოლოდ იმ შემთხვევაში, თუ ეს პროცესი იყენებს UDP სოკეტს მხოლოდ ერთ თანამოსაუბრესთან კომუნიკაციისთვის. როგორც წესი, ეს არის UDP კლიენტი, რომელიც იძახებს დაკავშირების ფუნქციას, მაგრამ არის აპლიკაციები, რომლებშიც UDP სერვერი ურთიერთობს ერთ კლიენტთან დიდი ხნის განმავლობაში (როგორიცაა TFTP), ამ შემთხვევაში კლიენტიც და სერვერიც უწოდებს დაკავშირებას. ფუნქცია.

გრძელვადიანი ურთიერთქმედების კიდევ ერთი მაგალითია DNS (სურათი 8.8).

ბრინჯი. 8.8. DNS კლიენტებისა და სერვერების მაგალითი და დაკავშირების ფუნქცია

DNS კლიენტის კონფიგურაცია შესაძლებელია ერთი ან მეტი სერვერის გამოსაყენებლად, როგორც წესი, სერვერების IP მისამართების ჩამოთვლით /etc/resolv.conf ფაილში. თუ ამ ფაილში მხოლოდ ერთი სერვერია ჩამოთვლილი (ეს კლიენტი არის ფიგურის მარცხენა ოთხკუთხედი), კლიენტს შეუძლია დარეკოს დაკავშირების ფუნქცია, მაგრამ თუ ჩამოთვლილია მრავალი სერვერი (სურათზე მეორე მარჯვენა მართკუთხედი), კლიენტი ვერ დარეკავს. დაკავშირების ფუნქცია. როგორც წესი, DNS სერვერი ასევე ამუშავებს კლიენტის ნებისმიერ მოთხოვნას, ამიტომ სერვერები ვერ გამოიძახებენ დაკავშირების ფუნქციას.

ახლა ჩვენ შევამოწმებთ, თუ რა გავლენას ახდენს აპლიკაციაზე UDP-ში რაიმე ნაკადის კონტროლის არარსებობა. პირველ რიგში ჩვენ შევცვლით ჩვენს dg_cli ფუნქციას ისე, რომ ის აგზავნის მონაცემთა გრაფიკების ფიქსირებულ რაოდენობას. ის აღარ იკითხება სტანდარტული შეყვანიდან. ჩამონათვალი 8.9 აჩვენებს ფუნქციის ახალ ვერსიას. ეს ფუნქცია სერვერზე აგზავნის 1400 ბაიტის 2000 UDP მონაცემთა გრამას.

ჩამონათვალი 8.9. dg_cli ფუნქცია სერვერზე აგზავნის მონაცემთა გრამების ფიქსირებულ რაოდენობას

//udpcliserv/dgcliloop1.c

1 #include "unp.h"

2 #define NDG 2000 /* გასაგზავნად მონაცემთა გრამების რაოდენობა */

3 #define DGLEN 1400 /* თითოეული მონაცემთაგრამის სიგრძე */

5 dg_cli (FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

8char გაგზავნის ხაზი;

10 Sendto (sockfd, sendline, DGLEN, 0, pservaddr, servlen);

შემდეგ ჩვენ ვცვლით სერვერს, რომ მივიღოთ დატაგრამები და ვითვლით მიღებული დატაგრამების რაოდენობას. სერვერი აღარ ასახავს მონაცემთა გრამას კლიენტს. ჩამონათვალი 8.10 აჩვენებს ახალ dg_echo ფუნქციას. როდესაც ჩვენ ვასრულებთ სერვერის პროცესს ტერმინალზე შეწყვეტის ღილაკის დაჭერით (რაც იწვევს პროცესზე SIGINT სიგნალის გაგზავნას), სერვერი ბეჭდავს მიღებული დატაგრამების რაოდენობას და გამოდის.

ჩამონათვალი 8.10. dg_echo ფუნქცია, რომელიც ითვლის მიღებულ დატაგრამებს

//udpcliserv/dgecholoop1.c

1 #include "unp.h"

2 სტატიკური void recvfrom_int(int);

3 სტატიკური ინტ რაოდენობა;

5 dg_echo (int sockfd, SA *pcliaddr, socklen_t clilen)

7 socklen_t len;

8 char mesg;

9 სიგნალი(SIGINT, recvfrom_int);

11 len = კლილენ;

12 Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);

17 recvfrom_int(int signo)

19 printf("\nმიღებულია %d მონაცემთაგრამა\n", რაოდენობა);

ჩვენ ახლა ვმართავთ სერვერს node freebsd-ზე, რომელიც არის ნელი SPARCStation კომპიუტერი. ჩვენ ვმართავთ კლიენტს ბევრად უფრო სწრაფ RS/6000 სისტემაზე aix ოპერაციული სისტემით. ისინი პირდაპირ კავშირშია ერთმანეთთან 100 Mbit/s Ethernet ბმულის საშუალებით. გარდა ამისა, ჩვენ ვაწარმოებთ netstat -s სერვერის კვანძზე კლიენტისა და სერვერის გაშვებამდეც და შემდეგაც, რადგან სტატისტიკის გამომავალი გვიჩვენებს რამდენი დატაგრამა დავკარგეთ. ჩამონათვალი 8.11 აჩვენებს სერვერის გამომავალს.

ჩამონათვალი 8.11. გამომავალი სერვერის კვანძზე

freebsd % netstat -s -p udp

მიღებულია 71208 დატაგრამა

0 არასრული სათაურით

0 ცუდი მონაცემთა სიგრძის ველით

0 ცუდი გამშვები ჯამით

0 საკონტროლო ჯამის გარეშე

832 დაეცა სოკეტის არქონის გამო

16 მაუწყებლობის/მულტიკასტის მონაცემთა გრამა დაეცა სოკეტის არარსებობის გამო

1971 დაეცა სრული სოკეტის ბუფერების გამო

0 არა ჰეშირებული PCB-სთვის

137685 datagrams გამომავალი

freebsd % udpserv06გაუშვით ჩვენი სერვერი

კლიენტი აგზავნის მონაცემთა გრამას

^ Cკლიენტის მუშაობის დასასრულებლად, შეიყვანეთ ჩვენი შეწყვეტის სიმბოლო

freebsd % netstat -s -p udp

მიღებულია 73208 დატაგრამა

0 არასრული სათაურით

0 ცუდი მონაცემთა სიგრძის ველით

0 ცუდი გამშვები ჯამით

0 საკონტროლო ჯამის გარეშე

832 დაეცა სოკეტის არქონის გამო

16 მაუწყებლობის/მულტიკასტის მონაცემთა გრამა დაეცა სოკეტის არარსებობის გამო

3941 დაეცა სრული სოკეტის ბუფერების გამო

0 არა ჰეშირებული PCB-სთვის

137685 datagrams გამომავალი

კლიენტმა გაგზავნა 2000 დატაგრამა, მაგრამ სერვერის აპლიკაციამ მიიღო მათგან მხოლოდ 30, რაც ნიშნავს ზარალის 98%-ს. არც სერვერი და არც კლიენტი არ იღებენ შეტყობინებას, რომ ეს მონაცემთა გრამები დაიკარგა. როგორც ვთქვით, UDP-ს არ აქვს ნაკადის კონტროლის შესაძლებლობები - ის არასანდოა. როგორც ვაჩვენეთ, UDP გამგზავნისთვის ადვილია მიმღების ბუფერის გადატვირთვა.

თუ გადავხედავთ netstat-ის გამომავალს, დავინახავთ, რომ სერვერის კვანძის (არა სერვერის აპლიკაციის) მიერ მიღებული დატაგრამების საერთო რაოდენობა არის 2000 (73,208 - 71,208). სრული სოკეტის ბუფერების გამორიცხული მრიცხველი გვიჩვენებს, თუ რამდენი დატაგრამა იქნა მიღებული UDP-ის მიერ და იგნორირებული იყო, რადგან მიმღები სოკეტის მიღების ბუფერი სავსე იყო. ეს მნიშვნელობა არის 1970 (3941 - 1971), რომელიც, როდესაც დაემატება აპლიკაციის მონაცემთა გრაფიკების გამომავალს, მიღებული რაოდენობა (30) იწვევს კვანძის მიერ მიღებულ 2000 დატაგრამას. სამწუხაროდ, netstat-ის მიერ სრული ბუფერის გამო გაუქმებული მონაცემთა გრამები სისტემური მასშტაბითაა. არ არსებობს გზა, რათა დადგინდეს, რომელ აპლიკაციებზე (მაგ. რომელ UDP პორტებზე) არის დაზარალებული.

ამ მაგალითში სერვერის მიერ მიღებული დატაგრამების რაოდენობა არადეტერმინისტულია. ეს დამოკიდებულია ბევრ ფაქტორზე, როგორიცაა ქსელის დატვირთვა, კლიენტის კვანძი და სერვერის კვანძის დატვირთვა.

თუ ჩვენ გავუშვით ერთი და იგივე კლიენტი და ერთი და იგივე სერვერი, მაგრამ ამჯერად კლიენტი არის ნელი Sun სისტემაზე და სერვერი არის სწრაფ RS/6000 სისტემაზე, არც ერთი დატაგრამა არ იკარგება.

aix % udpserv06

^? მას შემდეგ, რაც კლიენტი დაასრულებს მუშაობას, შეიყვანეთ ჩვენი შეწყვეტის სიმბოლო

მიიღო 2000 დატაგრამა



გაქვთ შეკითხვები?

შეატყობინეთ შეცდომას

ტექსტი, რომელიც გაეგზავნება ჩვენს რედაქტორებს: