რა არის სოკეტი

ბოლო განახლება: 10/31/2015

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

მოდით შევხედოთ ამ კლასის ძირითად თვისებებს:

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

    • ინტერქსელური: IPv4 მისამართი

      InterNetworkV6: IPv6 მისამართი

      Ipx: IPX ან SPX მისამართი

      NetBios: NetBios მისამართი

    ხელმისაწვდომია: აბრუნებს წასაკითხად ხელმისაწვდომი მონაცემების რაოდენობას

    დაკავშირებულია: ბრუნდება true, თუ სოკეტი დაკავშირებულია დისტანციურ ჰოსტთან

    LocalEndPoint: აბრუნებს ლოკალურ წერტილს, საიდანაც სოკეტი იწყება და სადაც ის იღებს მონაცემებს

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

    • IPSecAuthenticationHeader (IPv6 AH Header)

      IPSecEncapsulatingSecurityPayload (IPv6 ESP სათაური)

      IPv6DestinationOptions (IPv6 Destination Options header)

      IPv6FragmentHeader (IPv6 Fragment Header)

      IPv6HopByHopOptions (IPv6 Hop by Hop Options სათაური)

      IPv6NoNextHeader (IPv6 შემდეგი სათაური არ არის)

      IPv6RoutingHeader

      უცნობი (უცნობი პროტოკოლი)

      დაუზუსტებელი (დაუზუსტებელი პროტოკოლი)

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

    RemoteEndPoint: აბრუნებს დისტანციური ჰოსტის მისამართს, რომელზეც არის დაკავშირებული სოკეტი

    SocketType: აბრუნებს სოკეტის ტიპს. წარმოადგენს ერთ-ერთ მნიშვნელობას SocketType ჩამოთვლიდან:

    • Dgram: სოკეტი მიიღებს და გაგზავნის მონაცემთა გრამას Udp პროტოკოლის გამოყენებით. სოკეტის ეს ტიპი მუშაობს პროტოკოლის ტიპთან - Udp და მნიშვნელობა AddressFamily.InterNetwork-თან ერთად.

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

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

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

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

      უცნობია: NetBios მისამართი

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

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

ან სოკეტი Udp პროტოკოლის გამოყენებით:

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

ამრიგად, სოკეტის შექმნისას, ჩვენ შეგვიძლია განვსაზღვროთ პროტოკოლების, სოკეტების ტიპების და მნიშვნელობების სხვადასხვა კომბინაციები AddressFamily ნომრიდან. თუმცა, ამავე დროს, ყველა კომბინაცია არ არის სწორი. ასე რომ, Tcp პროტოკოლით მუშაობისთვის, უნდა მივუთითოთ შემდეგი პარამეტრები: AddressFamily.InterNetwork, SocketType.Stream და ProtocolType.Tcp. Udp-სთვის პარამეტრების ნაკრები განსხვავებული იქნება: AddressFamily.InterNetwork, SocketType.Dgram და ProtocolType.Udp. სხვა პროტოკოლებისთვის მნიშვნელობების ნაკრები განსხვავებული იქნება. ამიტომ, სოკეტების გამოყენებამ შეიძლება მოითხოვოს გარკვეული ცოდნა იმის შესახებ, თუ როგორ მუშაობს ინდივიდუალური პროტოკოლები. მიუხედავად იმისა, რომ Tcp და Udp-თან დაკავშირებით ყველაფერი შედარებით მარტივია.

სოკეტის მუშაობის ზოგადი პრინციპი

სოკეტებთან მუშაობისას, არჩეული პროტოკოლების მიუხედავად, ჩვენ დავეყრდნობით Socket კლასის მეთოდებს:

    Accept() : ქმნის ახალ Socket ობიექტს შემომავალი კავშირის დასამუშავებლად

    Bind() : აკავშირებს Socket ობიექტს ლოკალურ ბოლო წერტილთან

    Close(): ხურავს სოკეტს

    Connect(): ამყარებს კავშირს დისტანციურ ჰოსტთან

    Listen(): იწყებს შემომავალი მოთხოვნების მოსმენას

    Poll(): განსაზღვრავს სოკეტის მდგომარეობას

    Receive(): იღებს მონაცემებს

    Send(): აგზავნის მონაცემებს

    Shutdown(): ბლოკავს სოკეტს მონაცემების მიღებასა და გაგზავნაში

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

პროტოკოლის გამოყენებისას, რომელიც მოითხოვს კავშირის დამყარებას, როგორიცაა TCP, სერვერმა უნდა გამოიძახოს Bind მეთოდი, რათა დააყენოს წერტილი შემომავალი კავშირების მოსასმენად და შემდეგ დაიწყოს კავშირების მოსმენა Listen მეთოდის გამოყენებით. შემდეგი, Accept მეთოდის გამოყენებით, შეგიძლიათ მიიღოთ შემომავალი კავშირის მოთხოვნები Socket ობიექტის სახით, რომელიც გამოიყენება დისტანციურ კვანძთან ურთიერთობისთვის. მიღებულ Socket ობიექტზე, Send და Receive მეთოდებს, შესაბამისად, გამოიძახებენ მონაცემების გასაგზავნად და მისაღებად. თუ საჭიროა სერვერთან დაკავშირება, გამოიძახება Connect მეთოდი. Send ან Receive მეთოდები ასევე გამოიყენება სერვერთან მონაცემთა გაცვლისთვის.

თუ თქვენ იყენებთ უკავშირო პროტოკოლს, როგორიცაა UDP, მაშინ არ გჭირდებათ Listen მეთოდის გამოძახება Bind მეთოდის გამოძახების შემდეგ. და ამ შემთხვევაში, ReceiveFrom მეთოდი გამოიყენება მონაცემების მისაღებად, ხოლო SendTo მეთოდი გამოიყენება მონაცემების გასაგზავნად.

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

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

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

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

TCP/IP სატრანსპორტო პროტოკოლებისთვის პირველ პარამეტრად ყოველთვის მივუთითებთ წინასწარ განსაზღვრულ მუდმივ AF_INET-ს (მისამართების ოჯახი - ინტერნეტი) ან მის სინონიმს PF_INET (პროტოკოლის ოჯახი - ინტერნეტი).

მეორე პარამეტრი მიიღებს წინასწარ განსაზღვრულ მნიშვნელობებს SOCK_STREAM ნაკადის სოკეტებისთვის და SOCK_DGRAM დატაგრამის სოკეტებისთვის.

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

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

სისტემის ზარი სოკეტის შესაქმნელად

სისტემური ზარის პროტოტიპი

#შეიცავს #შეიცავს int სოკეტი (int დომენი, int ტიპი, int პროტოკოლი);

სისტემური ზარის აღწერა

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

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

  • PF_INET – ამისთვის TCP/IP პროტოკოლის ოჯახი ;
  • PF_UNIX – შიდა UNIX პროტოკოლების ოჯახისთვის, სხვაგვარად ცნობილი როგორც UNIX დომენი.

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

  • SOCK_STREAM – დაწესებულების გამოყენებით კომუნიკაციისთვის ვირტუალური კავშირი ;
  • SOCK_DGRAM – ინფორმაციის გაცვლისთვის შეტყობინებების საშუალებით.

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

დაბრუნების ღირებულება

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

სოკეტის მისამართები. სოკეტის მისამართის დაყენება. bind() სისტემური ზარი

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

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

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

struct sockaddr (მოკლე sa_family; char sa_data; );

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

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

struct sockaddr _in( short sin_family; /* არჩეული პროტოკოლის ოჯახი ყოველთვის არის AF_INET */ ხელმოუწერელი მოკლე sin_port; /* 16-ბიტიანი პორტის ნომერი ქსელის ბაიტის მიხედვით */ struct in_addr sin_addr; /* ქსელის ინტერფეისის მისამართი */ char sin_zero; /* ეს ველი არ გამოიყენება, მაგრამ ყოველთვის უნდა იყოს შევსებული ნულებით */ );

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

სრული მისამართის დისტანციური ნაწილი - IP მისამართი - შეიცავს struct in_addr ტიპის სტრუქტურას, რომელსაც შევხვდით განყოფილებაში "IP მისამართის თარგმანის ფუნქციები". inet_ntoa(), inet_aton () " .

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

რომელი პორტის ნომერი შეიძლება გამოიყენოს მომხმარებელს ფიქსირებულ კონფიგურაციაში? პორტის ნომრები 1-დან 1023-მდე შეიძლება მიენიჭოს სოკეტებს მხოლოდ იმ პროცესებით, რომლებიც მიმდინარეობს სისტემის ადმინისტრატორის პრივილეგიებით. როგორც წესი, ეს ნომრები ენიჭება სისტემური ქსელის სერვისებს, მიუხედავად გამოყენებული ოპერაციული სისტემის ტიპისა, ასე რომ, მომხმარებლის კლიენტის პროგრამებს ყოველთვის შეუძლიათ მოითხოვონ მომსახურება იმავე ლოკალური მისამართებიდან. ასევე არსებობს მრავალი ფართოდ გამოყენებული ქსელური პროგრამა, რომლებიც აწარმოებენ პროცესებს მომხმარებლის ნორმალური ნებართვით (მაგალითად, X-Windows). ინტერნეტ კორპორაციის მიერ ასეთი პროგრამებისთვის სახელებისა და ნომრების მინიჭებისთვის (

ძალიან მომწონს სტატიების მთელი სერია, გარდა ამისა, ყოველთვის მინდოდა მთარგმნელად მესინჯა თავი. შესაძლოა, სტატია ზედმეტად აშკარად მოეჩვენოს გამოცდილი დეველოპერებისთვის, მაგრამ მეჩვენება, რომ ის ნებისმიერ შემთხვევაში სასარგებლო იქნება.
პირველი სტატია - http://habrahabr.ru/post/209144/

მონაცემთა პაკეტების მიღება და გადაცემა

შესავალი
გამარჯობა, მე მქვია გლენ ფიდლერი და მივესალმები ჩემს მეორე სტატიას "ქსელის პროგრამირება თამაშების დეველოპერებისთვის" სერიიდან.

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

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

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

BSD სოკეტები მუშაობენ მარტივი ფუნქციებით, როგორიცაა "socket", "bind", "sendto" და "recvfrom". რა თქმა უნდა, ამ ფუნქციებზე პირდაპირ წვდომა შეგიძლიათ, მაგრამ ამ შემთხვევაში თქვენი კოდი დამოკიდებული იქნება პლატფორმაზე, რადგან მათი დანერგვა შეიძლება ოდნავ განსხვავდებოდეს სხვადასხვა ოპერაციულ სისტემაში.

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

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

// პლატფორმის ამოცნობა #define PLATFORM_WINDOWS 1 #define PLATFORM_MAC 2 #define PLATFORM_UNIX 3 #if defined(_WIN32) #define PLATFORM PLATFORM_WINDOWS #elif defined(__APPLEe_PLATSE #fined PLATFORM_FORMA PLATFORM_UNIX #endif
ახლა მოდით ჩავრთოთ სათაურის ფაილები, რომლებიც საჭიროა სოკეტებთან მუშაობისთვის. ვინაიდან საჭირო სათაურის ფაილების ნაკრები დამოკიდებულია მიმდინარე OS-ზე, აქ ვიყენებთ ზემოთ დაწერილ #define კოდს, რათა განვსაზღვროთ რომელი ფაილები უნდა იყოს ჩართული.

#if PLATFORM == PLATFORM_WINDOWS #include #elif PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX #include #შეიცავს #შეიცავს #დაასრულე თუ
UNIX სისტემებში სოკეტებთან მუშაობის ფუნქციები შედის სისტემის სტანდარტულ ბიბლიოთეკებში, ამიტომ ამ შემთხვევაში არ გვჭირდება მესამე მხარის ბიბლიოთეკები. თუმცა, Windows-ზე, ამ მიზნებისთვის ჩვენ უნდა შევიტანოთ winsock ბიბლიოთეკა.

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

#if PLATFORM == PLATFORM_WINDOWS #pragma comment(lib, "wsock32.lib") #endif
მომწონს ეს ხრიკი, რადგან ზარმაცი ვარ. თქვენ შეგიძლიათ, რა თქმა უნდა, ბიბლიოთეკა პროექტში ან მაკიაფილში ჩართოთ.

სოკეტების ინიციალიზაცია
უმეტეს unix-ის მსგავს ოპერაციულ სისტემებზე (მათ შორის macosx) არ არის საჭირო სპეციალური ნაბიჯები სოკეტის ფუნქციონირების ინიციალიზაციისთვის, მაგრამ Windows-ზე ჯერ რამდენიმე ნაბიჯის გაკეთება გჭირდებათ - ნებისმიერი სოკეტის ფუნქციის გამოყენებამდე უნდა გამოიძახოთ "WSAStartup" ფუნქცია. და სამუშაოს დასრულების შემდეგ - დარეკეთ "WSACleanup".

მოდით დავამატოთ ორი ახალი ფუნქცია:

Inline bool InitializeSockets() ( #if PLATFORM == PLATFORM_WINDOWS WSADATA WsaData; დაბრუნება WSAStartup(MAKEWORD(2,2), &WsaData) == NO_ERROR; #else return true; #endif ) inline void Shutdows(=MOCketif) PLATFORM_WINDOWS WSACleanup(); #endif)
ჩვენ ახლა გვაქვს პლატფორმისგან დამოუკიდებელი სოკეტის ინიციალიზაცია და გამორთვის კოდი. პლატფორმებზე, რომლებიც არ საჭიროებენ ინიციალიზაციას, ეს კოდი უბრალოდ არაფერს აკეთებს.

შექმენით სოკეტი
ახლა ჩვენ შეგვიძლია შევქმნათ UDP სოკეტი. ეს კეთდება ასე:

Int სახელური = სოკეტი (AF_INET, SOCK_DGRAM, IPPROTO_UDP); თუ (სახელური<= 0) { printf("failed to create socket\n"); return false; }
შემდეგი, ჩვენ უნდა დავაკავშიროთ სოკეტი კონკრეტულ პორტის ნომერზე (მაგალითად, 30000). თითოეულ სოკეტს უნდა ჰქონდეს თავისი უნიკალური პორტი, რადგან ახალი პაკეტის ჩამოსვლისას პორტის ნომერი განსაზღვრავს რომელ სოკეტში გაიგზავნება. არ გამოიყენოთ 1024-ზე დაბალი პორტის ნომრები - ისინი რეზერვირებულია სისტემის მიერ.

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

Sockaddr_in მისამართი; მისამართი.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; მისამართი.sin_port = htons((ხელმოუწერელი მოკლე) პორტი); if (bind(handle, (const sockaddr*) &address, sizeof(sockaddr_in))< 0) { printf("failed to bind socket\n"); return false; }
ახლა ჩვენი სოკეტი მზად არის მონაცემთა პაკეტების გასაგზავნად და მისაღებად.

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

ამ სტატიაში კიდევ რამდენჯერმე იხილავთ "htons" ფუნქციას და მის 32-ბიტიან კოლეგას "htonl", ამიტომ ყურადღება მიაქციეთ.

სოკეტის გადართვა არადაბლოკვის რეჟიმში
ნაგულისხმევად, სოკეტები არის ის, რასაც "ბლოკირების რეჟიმში" უწოდებენ. ეს ნიშნავს, რომ თუ თქვენ ცდილობთ მისგან მონაცემების წაკითხვას „recvfrom“-ის გამოყენებით, ფუნქცია არ დაბრუნდება მანამ, სანამ სოკეტი არ მიიღებს პაკეტს მონაცემებით, რომელთა წაკითხვაც შესაძლებელია. ეს საქციელი საერთოდ არ გვიწყობს. თამაშები არის რეალურ დროში აპლიკაციები, რომლებიც მუშაობენ 30-დან 60 კადრამდე წამში და თამაში არ შეიძლება უბრალოდ გაჩერდეს და დაელოდო მონაცემთა პაკეტის ჩამოსვლას!

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

თქვენ შეგიძლიათ დააყენოთ სოკეტი არაბლოკირებულ რეჟიმში შემდეგნაირად:

#if PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX int nonBlocking = 1; if (fcntl(handle, F_SETFL, O_NONBLOCK, nonBlocking) == -1) ( printf("ვერ დაყენდა არადაბლოკვის სოკეტი\n"); return false; ) #elif PLATFORM == PLATFORM_WINDOWS DWORD nonBlocking = 1; if (ioctlsocket(handle, FIONBIO, &nonBlocking) != 0) (printf("ვერ დაყენდა არადაბლოკვის სოკეტი\n"); return false; ) #endif
როგორც ხედავთ, Windows-ში არ არის "fcntl" ფუნქცია, ამიტომ მის ნაცვლად ვიყენებთ "ioctlsocket".

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

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

Int sent_bytes = sendto(handle, (const char*)packet_data, packet_size, 0, (sockaddr*)&address, sizeof(sockaddr_in)); if (sent_bytes != packet_size) ( printf("პაკეტის გაგზავნა ვერ მოხერხდა: დაბრუნების მნიშვნელობა = %d\n", sent_bytes); return false; )
გთხოვთ გაითვალისწინოთ, რომ დაბრუნებული მნიშვნელობა "sendto" ფუნქციიდან მხოლოდ მიუთითებს, იყო თუ არა პაკეტი წარმატებით გაგზავნილი ადგილობრივი კომპიუტერიდან. მაგრამ ეს არ მიუთითებს, მიიღეს თუ არა პაკეტი დანიშნულების ადგილამდე! UDP-ს არ აქვს საშუალება, განსაზღვროს, მიაღწია თუ არა პაკეტმა დანიშნულების ადგილს.

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

ვთქვათ, გვინდა პაკეტის გაგზავნა ნომერზე 207.45.186.98:30000.

მოდით დავწეროთ მისამართი შემდეგი ფორმით:

ხელმოუწერელი int a = 207; ხელმოუწერელი int b = 45; ხელმოუწერელი int c = 186; ხელმოუწერელი int d = 98; ხელმოუწერელი მოკლე პორტი = 30000;
და თქვენ უნდა გააკეთოთ კიდევ რამდენიმე ტრანსფორმაცია, რომ მიიყვანოთ ისეთ ფორმამდე, რომელსაც "sendto" ესმის:

ხელმოუწერელი int destination_address = (ა<< 24) | (b << 16) | (c << 8) | d; unsigned short destination_port = port; sockaddr_in address; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(destination_address); address.sin_port = htons(destination_port);
როგორც ხედავთ, ჯერ გავაერთიანოთ a, b, c, d რიცხვები (რომლებიც არიან დიაპაზონში ) ერთ მთელ რიცხვში, რომელშიც თითოეული ბაიტი არის ერთ-ერთი თავდაპირველი რიცხვი. შემდეგ ჩვენ ვაყენებთ „sockaddr_in“ სტრუქტურის ინიციალიზაციას ჩვენი დანიშნულების მისამართით და პორტით, გვახსოვს ბაიტის რიგის კონვერტაცია „htonl“ და „htons“ ფუნქციების გამოყენებით.

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

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

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

პაკეტების მარყუჟში მიღების კოდი:

while (true) (unsigned char packet_data; unsigned int maximum_packet_size = sizeof(packet_data); #if PLATFORM == PLATFORM_WINDOWS typedef int socklen_t; #endif sockaddr_in from; socklen_t მიღებულია სიგრძის_სმით; (char *)packet_data, მაქსიმალური_packet_size, 0, (sockaddr*)&from, &fromLength); თუ (მიღებული_ბაიტი<= 0) break; unsigned int from_address = ntohl(from.sin_addr.s_addr); unsigned int from_port = ntohs(from.sin_port); // process received packet }
პაკეტები, რომლებიც აღემატება მიმღების ბუფერის ზომას, უბრალოდ ჩუმად წაიშლება რიგიდან. ასე რომ, თუ იყენებთ 256 ბაიტიან ბუფერს, როგორც ზემოთ მოცემულ მაგალითში, და ვინმე გამოგიგზავნით 300 ბაიტიან პაკეტს, ის გაუქმდება. თქვენ არ მიიღებთ მხოლოდ პირველ 256 ბაიტს პაკეტიდან.

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

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

#if PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX დახურვა(სოკეტი); #elif PLATFORM == PLATFORM_WINDOWS closesocket(socket); #დაასრულე თუ
ასე გააგრძელე, Windows!

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

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

ამიტომ, ჩვენ გავაკეთებთ შეფუთვის კლასს "Socket" ყველა ამ ოპერაციისთვის. ჩვენ ასევე შევქმნით "Address" კლასს IP მისამართებთან მუშაობის გასაადვილებლად. ეს საშუალებას მოგვცემს არ განვახორციელოთ ყველა მანიპულაცია „sockaddr_in“-ით ყოველ ჯერზე, როცა გვინდა პაკეტის გაგზავნა ან მიღება.

ასე რომ, ჩვენი სოკეტის კლასი:

Class Socket ( public: Socket(); ~Socket(); bool Open (ხელმოუწერელი მოკლე პორტი); void Close(); bool IsOpen() const; bool Send(const მისამართი და დანიშნულება, const void * მონაცემები, int ზომა); int Receive(მისამართი და გამგზავნი, void * მონაცემები, int ზომა); private: int handle; );
და მისამართების კლასი:

კლასის მისამართი ( საჯარო: მისამართი(); მისამართი (ხელმოუწერელი char a, ხელმოუწერელი char b, ხელმოუწერელი char c, ხელმოუწერელი char d, ხელმოუწერელი მოკლე პორტი); მისამართი (ხელმოუწერელი int მისამართი, ხელმოუწერელი მოკლე პორტი); ხელმოუწერელი int GetAddress() const; ხელმოუწერელი char GetA() const; ხელმოუწერელი char GetB() const; ხელმოუწერელი char GetC() const; ხელმოუწერელი char GetD() const; ხელმოუწერელი მოკლე GetPort() const; bool ოპერატორი == (const მისამართი და სხვა) const; bool ოპერატორი ! = (const მისამართი და სხვა) const; private: ხელმოუწერელი int მისამართი; ხელმოუწერელი მოკლე პორტი; );
თქვენ უნდა გამოიყენოთ ისინი მიღებისა და გადაცემისთვის შემდეგნაირად:

// სოკეტის შექმნა const int port = 30000; სოკეტის სოკეტი; if (!socket.Open(port)) ( printf("ვერ შეიქმნა სოკეტი!\n"); return false; ) // გაგზავნეთ პაკეტი const char data = "hello world!"; socket.Send(Address(127,0,0,1,port), data, sizeof(data)); // მიიღოს პაკეტები, ხოლო (true) (მისამართის გამგზავნი; ხელმოუწერელი ჩანაწერების ბუფერი; int bytes_read = სოკეტი. მიღება (გამომგზავნი, ბუფერი, ზომა (ბუფერი)); თუ (!bytes_read) გატეხილია; // დამუშავების პაკეტი)
როგორც ხედავთ, ეს ბევრად უფრო ადვილია, ვიდრე უშუალოდ BSD სოკეტებთან მუშაობა. და ასევე, ეს კოდი იგივე იქნება ყველა OS-სთვის, რადგან პლატფორმაზე დამოკიდებული ყველა ფუნქცია მდებარეობს Socket და Address კლასებში.

დასკვნა
ჩვენ ახლა გვაქვს პლატფორმისგან დამოუკიდებელი ინსტრუმენტი UDP პაკეტების გაგზავნისა და მისაღებად.

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

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

>კვანძი 30000
>კვანძი 30001
>კვანძი 30002
და ა.შ…

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

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

ტეგები: ტეგების დამატება

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

რა არის სოკეტი?

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

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

რამდენიმე პროგრამის პარალელურად შესრულება შესაძლებელია ერთ კომპიუტერზე. ვთქვათ, თქვენ გაქვთ 10 პროგრამა გაშვებული თქვენს კომპიუტერში და ისინი ყველა ელოდება სხვა კომპიუტერებს მათთან კომუნიკაციას. თქვენ შეგიძლიათ წარმოიდგინოთ ასე: 10 ადამიანი ხართ დიდ ოფისში 1 ტელეფონით, ყველა ელოდება ზარებს საკუთარი კლიენტებისგან. როგორ მოაგვარებ ამას? თქვენ, რა თქმა უნდა, შეგიძლიათ დანიშნოთ პასუხისმგებელი თანამშრომელი და ის მიიტანს ტელეფონს შესაბამის პირთან, რომელსაც დაურეკეს, მაგრამ შემდეგ სხვა კლიენტები ვერ შეძლებენ სხვა ადამიანებთან დაკავშირებას. გარდა ამისა, ძალიან რთული და უხერხულია გყავდეს ადამიანი, რომელიც პასუხისმგებელია ზარების მარშრუტიზაციაზე სწორ ადამიანებთან. თქვენ ალბათ მიხვდით, სად მივდივარ ამით - თუ ყველა ეს პროგრამა, რომელიც მუშაობს იმავე კომპიუტერზე, ამაყად სთხოვს კლიენტებს, დაუკავშირდნენ მათ კონკრეტული IP მისამართით, მაშინ მათი კლიენტები არ იქნებიან კმაყოფილი. იდეა არის... გქონდეთ ცალკე IP მისამართი თითოეული პროგრამისთვის, არა? ᲡᲘᲛᲐᲠᲗᲚᲔᲡ ᲐᲠ ᲨᲔᲔᲡᲐᲑᲐᲛᲔᲑᲐ! კითხვის არსი არ არის სწორი - ეს იგივეა, რომ გკითხოთ თითოეული თქვენგანისთვის ცალკე ოფისის შესახებ. აბა, მაშინ... იქნებ ცალკე ტელეფონის ნომრები საკმარისი იყოს? დიახ! ქსელურ ჟარგონში „ინდივიდუალურ ტელეფონის ნომრებს“ პორტები ეწოდება. პორტი მხოლოდ რიცხვია და თითოეულ პროგრამას, რომელიც მუშაობს კონკრეტულ კომპიუტერზე, შეუძლია აირჩიოს უნიკალური პორტის ნომერი გარე სამყაროსთვის საკუთარი თავის იდენტიფიცირებისთვის. დაიმახსოვრეთ – თქვენ ვერ იპოვით ამ პორტებს კომპიუტერის აპარატურას შორის (არც კი ეცადოთ მათი ძებნა). ეს რიცხვები ლოგიკურია. ახლა ყველაფერი ნათელია: არის IP მისამართი, რომლითაც სხვა კომპიუტერებს შეუძლიათ ამოიცნონ კონკრეტული კომპიუტერი ქსელში და პორტის ნომერი, რომელიც განსაზღვრავს კომპიუტერზე გაშვებულ გარკვეულ პროგრამას. ასევე ირკვევა, რომ სხვადასხვა კომპიუტერის ორ პროგრამას შეუძლია გამოიყენოს ერთი და იგივე პორტი (სხვადასხვა ქუჩაზე მდებარე ორ სახლს შეიძლება ჰქონდეს იგივე ნომერი, თუ არა?). ისე, ჩვენ თითქმის იქ ვართ, უბრალოდ რომ ცოტა შეგაშინოთ, მოდით გამოვიტანოთ ფორმულა:

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

თუ ზემოხსენებულ განტოლებებს დავუმატებთ, მივიღებთ:

IP მისამართი + პორტის ნომერი = _____

Სხვა სიტყვებით:

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

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

როგორ დავაპროგრამოთ სოკეტების გამოყენებით?

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

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

2) სერვერთან წარმატებით დაკავშირების შემდეგ კლიენტი ელოდება თქვენგან მონაცემების შეყვანას და აგზავნის ტექსტს სერვერზე.

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

4) კლიენტი აჩვენებს სერვერიდან მიღებულ ტექსტს, რათა გაჩვენოთ სერვერის აზრი თქვენს შესახებ. მზად ხართ განვითარების დასაწყებად? Მოდით დავიწყოთ. ნება მომეცით აღვნიშნო, რომ მე არ გასწავლით Java პროგრამირებას ნულიდან, არამედ აგიხსნით მხოლოდ კოდს, რომელიც ეხება სოკეტებს. შექმენით 2 ახალი Java პროგრამა და დაასახელეთ Server.java და Client.java. ქვემოთ მოყვანილი კოდი მაქვს, არ ინერვიულოთ, ყველაფერს აგიხსნით.


იმპორტი java.net.*;
იმპორტი java.io.*;
საჯარო კლასის სერვერი (
int პორტი = 6666; // შემთხვევითი პორტი (შეიძლება იყოს ნებისმიერი რიცხვი 1025-დან 65535-მდე)
სცადე (
ServerSocket ss = new ServerSocket(პორტი); // შექმენით სერვერის სოკეტი და დააკავშირეთ იგი ზემოთ მოცემულ პორტთან
System.out.println ("კლიენტის მოლოდინში...");

სოკეტის სოკეტი = ss.accept(); // აიძულეთ სერვერი დაელოდოს კავშირებს და აჩვენოს შეტყობინება, როდესაც ვინმე დაუკავშირდება სერვერს
System.out.println("მიიღე კლიენტი:) ... ბოლოს ვიღაცამ დამინახა მთელი ყდა!");
System.out.println();

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


სიმებიანი ხაზი = null;
ხოლო (მართალია) (
ხაზი = in.readUTF(); // დაველოდოთ კლიენტს ტექსტის სტრიქონის გაგზავნას.
System.out.println("სულმა კლიენტმა გამომიგზავნა ეს ხაზი: " + ხაზი);
System.out.println ("მე ვგზავნი უკან...");
out.writeUTF(ხაზი); // გაუგზავნეთ იგივე ტექსტის სტრიქონი კლიენტს.
System.out.println ("ველოდები შემდეგ ხაზს...");
System.out.println();
}
) catch(გამონაკლისი x) ( x.printStackTrace(); )
}
}

java.net.* იმპორტი;
იმპორტი java.io.*;

საჯარო კლასის კლიენტი (
საჯარო სტატიკური ბათილი მთავარი (სტრიქონი ar) (
int serverPort = 6666; // აქ თქვენ უნდა მიუთითოთ პორტი, რომელზეც სერვერია მიბმული.
სტრიქონის მისამართი = "127.0.0.1"; // ეს არის კომპიუტერის IP მისამართი, სადაც მუშაობს ჩვენი სერვერის პროგრამა.
// აქ არის იმავე კომპიუტერის მისამართი, სადაც კლიენტი შესრულდება.

სცადე (
InetAddress ipAddress = InetAddress.getByName(მისამართი); // შექმენით ობიექტი, რომელიც აჩვენებს ზემოთ აღწერილ IP მისამართს.
System.out.println("ნებისმიერს გსმენიათ სოკეტის შესახებ IP მისამართით " + მისამართი + " და პორტი " + serverPort + "?");
Socket socket = new Socket(ipAddress, serverPort); // შექმენით სოკეტი სერვერის IP მისამართისა და პორტის გამოყენებით.
System.out.println("დიახ! მე ახლახან მივიღე პროგრამა.");

// ვიღებთ სოკეტის შემავალ და გამომავალ ნაკადებს, ახლა შეგვიძლია კლიენტის მიერ მონაცემების მიღება და გაგზავნა.
InputStream sin = socket.getInputStream();
OutputStream sout = socket.getOutputStream();

// გადაიყვანეთ ნაკადები სხვა ტიპზე, ტექსტური შეტყობინებების დამუშავების გასაადვილებლად.
DataInputStream in = new DataInputStream(sin);
DataOutputStream out = new DataOutputStream(sout);

// კლავიატურიდან წასაკითხად ნაკადის შექმნა.
BufferedReader კლავიატურა = new BufferedReader(new InputStreamReader(System.in));
სიმებიანი ხაზი = null;
System.out.println("აკრიფეთ რაღაც და დააჭირეთ enter. გამოგიგზავნით სერვერს და გეტყვით რას ფიქრობს.");
System.out.println();

მიუხედავად იმისა, რომ (მართალია) (
line = keyboard.readLine(); // დაელოდეთ მომხმარებლის შეყვანას და დააჭირეთ ღილაკს Enter.
System.out.println("ამ ხაზის სერვერზე გაგზავნა...");
out.writeUTF(ხაზი); // გაგზავნეთ შეყვანილი ტექსტური სტრიქონი სერვერზე.
out.flush(); // აიძულეთ თემა დაასრულოს მონაცემების გადაცემა.
ხაზი = in.readUTF(); // დაელოდეთ სანამ სერვერი გამოგიგზავნით ტექსტის სტრიქონს.
System.out.println("სერვერი იყო ძალიან თავაზიანი. გამომიგზავნა ეს: " + ხაზი);
System.out.println("როგორც ჩანს, სერვერი კმაყოფილია ჩვენგან. განაგრძეთ და შეიყვანეთ მეტი ხაზი.");
System.out.println();
}
) დაჭერა (გამონაკლისი x) (
x.printStackTrace();
}
}
}

ახლა მოდით შევადგინოთ კოდი:

Javac Server.java Client.java

მოდით გავხსნათ ორი ბრძანების ფანჯარა (DOS). ერთ ფანჯარაში შევდივართ:

ჯავის სერვერი

და მეორეში:

ჯავის კლიენტი

აუცილებლად ამ თანმიმდევრობით.

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

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

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

განვიხილოთ სერვერის კოდის შემდეგი ნაწილი:

ServerSocket ss = new ServerSocket(პორტი);
სოკეტის სოკეტი = ss.accept();

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

შემდეგ ჩვენ გადავხედავთ Socket კლასს. თქვენ შეგიძლიათ შექმნათ Socket ობიექტი IP მისამართისა და პორტის მითითებით. IP მისამართის საჩვენებლად შეგიძლიათ გამოიყენოთ InetAddress კლასი (ეს არის სასურველი მეთოდი). InetAddress ობიექტის შესაქმნელად გამოიყენეთ შემდეგი მეთოდი:

InetAddress ipAddress = InetAddress.getByName(მისამართი);

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

მას შემდეგ რაც შევქმნით InetAddress-ს, შეგვიძლია შევქმნათ სოკეტი:

Socket socket = new Socket(ipAddress, serverPort);

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

InputStream sin = socket.getInputStream();
OutputStream sout = socket.getOutputStream();

შემდეგი სტრიქონები უბრალოდ გარდაქმნის ნაკადებს ნაკადის სხვა ტიპებად. ამის შემდეგ გაგვიადვილდება String ობიექტებთან მუშაობა. ეს კოდი არაფერს აკეთებს ქსელთან.

DataInputStream in = new DataInputStream(sin);
DataOutputStream out = new DataOutputStream(sout);

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



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

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

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