Computer >> Máy Tính >  >> Kết nối mạng >> Internet

Internet nói như thế nào

Câu chuyện về giao tiếp

Bạn đã bao giờ tự hỏi Internet thực sự nói như thế nào? Làm thế nào để một máy tính “nói chuyện” với một máy tính khác qua Internet?

Khi mọi người giao tiếp với nhau, chúng ta sử dụng các từ được xâu chuỗi thành những câu có vẻ có nghĩa. Các câu chỉ có ý nghĩa vì chúng tôi đã thống nhất ý nghĩa cho những câu này. Chúng tôi đã xác định một phương thức giao tiếp, có thể nói như vậy.

Hóa ra, các máy tính nói chuyện với nhau theo cách tương tự qua Internet. Nhưng, chúng tôi đang vượt lên chính mình. Mọi người sử dụng miệng để giao tiếp, trước tiên hãy tìm hiểu xem miệng của máy tính là gì.

Nhập Ổ cắm

Ổ cắm là một trong những khái niệm cơ bản nhất trong khoa học máy tính. Bạn có thể xây dựng toàn bộ mạng các thiết bị được kết nối với nhau bằng cách sử dụng ổ cắm.

Giống như tất cả những thứ khác trong khoa học máy tính, ổ cắm là một khái niệm rất trừu tượng. Vì vậy, thay vì định nghĩa socket là gì, việc xác định socket làm gì sẽ dễ dàng hơn nhiều.

Vì vậy, một ổ cắm làm gì? Nó giúp hai máy tính giao tiếp với nhau. Làm thế nào nó làm điều này? Nó có hai phương thức được định nghĩa, được gọi là send()recv() để gửi và nhận tương ứng.

Được rồi, điều đó thật tuyệt, nhưng phải làm sao send()recv() thực sự gửi và nhận? Khi mọi người cử động miệng, họ trao đổi lời nói. Khi các socket sử dụng các phương thức của chúng, chúng sẽ trao đổi các bit và byte.

Hãy minh họa các phương pháp bằng một ví dụ. Giả sử chúng ta có hai máy tính, A và B. Máy tính A đang cố gắng nói điều gì đó với Máy tính B. Do đó, Máy tính B đang cố gắng lắng nghe những gì Máy tính A đang nói. Đây là những gì sẽ trông như thế nào.

Internet nói như thế nào
Ổ cắm có bộ đệm

Đọc bộ đệm

Trông hơi kỳ lạ, phải không? Đối với một, cả hai máy tính đều trỏ đến một thanh ở giữa, có tên là ‘buffer’.

Bộ đệm là gì? Bộ đệm là một ngăn xếp bộ nhớ. Đó là nơi dữ liệu của mỗi máy tính được lưu trữ và được phân bổ bởi hạt nhân.

Tiếp theo, tại sao cả hai đều trỏ đến cùng một bộ đệm? Thực ra điều đó không hoàn toàn chính xác. Mỗi máy tính có bộ đệm riêng được cấp phát bởi nhân riêng của nó và mạng vận chuyển dữ liệu giữa hai bộ đệm riêng biệt. Tuy nhiên, tôi không muốn đi sâu vào chi tiết mạng ở đây, vì vậy chúng tôi sẽ giả định rằng cả hai máy tính đều có quyền truy cập vào cùng một bộ đệm được đặt ở "một nơi nào đó trong khoảng trống giữa".

Được rồi, bây giờ chúng ta đã biết trực quan cái này trông như thế nào, hãy tóm tắt nó thành mã.

#Computer A sends data computerA.send(data) 
#Computer B receives data computerB.recv(1024)

Đoạn mã này thực hiện điều tương tự như hình ảnh ở trên đại diện. Ngoại trừ một sự tò mò, chúng tôi không nói computerB.recv(data) . Thay vào đó, chúng tôi chỉ định một số dường như ngẫu nhiên ở vị trí của dữ liệu.

Lý do rất đơn giản. Dữ liệu qua mạng được truyền thành các bit. Do đó, khi chúng tôi recv trong computerB, chúng tôi chỉ định số lượng bit chúng tôi sẵn sàng nhận vào bất kỳ thời điểm nào tại một thời điểm nhất định.

Tại sao tôi chọn 1024 byte để nhận cùng một lúc? Không có lý do cụ thể. Thông thường, tốt nhất là chỉ định số byte bạn sẽ nhận được với lũy thừa là 2. Tôi đã chọn 1024 là 2¹⁰.

Vì vậy, làm thế nào để bộ đệm tìm ra điều này? Máy tính A ghi hoặc gửi bất kỳ dữ liệu nào được lưu cùng với nó vào bộ đệm. Máy tính B quyết định đọc hoặc nhận 1024 byte đầu tiên của những gì được lưu trữ trong bộ đệm đó.

Được rồi, tuyệt vời! Nhưng, làm thế nào để hai máy tính này biết nói chuyện với nhau? Ví dụ, khi Máy tính A ghi vào bộ đệm này, làm thế nào nó biết Máy tính B sẽ lấy nó? Để diễn đạt lại điều đó, làm thế nào nó có thể đảm bảo rằng kết nối giữa hai máy tính có một bộ đệm duy nhất?

Chuyển vào IP

Internet nói như thế nào
Cổng và IP của máy tính

Hình ảnh trên cho thấy hai máy tính giống nhau mà chúng tôi đang làm việc cùng với một chi tiết khác được thêm vào. Có một loạt các con số được liệt kê phía trước mỗi máy tính dọc theo chiều dài của một thanh.

Hãy coi thanh dài phía trước mỗi máy tính là bộ định tuyến kết nối một máy tính cụ thể với internet. Những con số được liệt kê trên mỗi thanh được gọi là cổng . Máy tính của bạn hiện có hàng nghìn cổng. Mỗi cổng cho phép một kết nối ổ cắm. Tôi chỉ hiển thị 6 cổng trong hình ảnh trên, nhưng bạn hiểu rõ.

Các cổng dưới 255 thường được dành cho các cuộc gọi hệ thống và các kết nối cấp thấp. Thông thường, bạn nên mở kết nối trên một cổng có 4 chữ số cao, như 8000. Tôi không vẽ bộ đệm trong hình trên, nhưng bạn có thể giả định rằng mỗi cổng có bộ đệm riêng.

Bản thân thanh cũng có một số liên kết với nó. Số này được gọi là địa chỉ IP. Địa chỉ IP có một loạt các cổng được liên kết với nó. Hãy nghĩ về nó theo cách sau:

                          127.0.0.1                             / | \                            /  |  \                           /   |   \                        8000  8001 8002

Tuyệt vời, hãy thiết lập kết nối trên một cổng cụ thể giữa Máy tính A và Máy tính B.

# computerA.pyimport socket 
computerA = socket.socket() 
# Connecting to localhost:8000 computerA.connect(('127.0.0.1', 8000)) string = 'abcd' encoded_string = string.encode('utf-8') computerA.send(encoded_string)

Đây là mã cho computerB.py

# computerB.py import socket 
computerB = socket.socket() 
# Listening on localhost:8000 computerB.bind(('127.0.0.1', 8000)) computerB.listen(1) 
client_socket, address = computerB.accept() data = client_socket.recv(2048) print(data.decode('utf-8'))

Có vẻ như chúng tôi đã đi trước một chút về mã, nhưng tôi sẽ vượt qua nó. Chúng tôi biết chúng tôi có hai máy tính, A và B. Do đó, chúng tôi cần một máy tính để gửi dữ liệu và một máy tính để nhận dữ liệu.

Tôi đã tự ý chọn A để gửi dữ liệu và B để nhận dữ liệu. Trong dòng này computerA.connect((‘127.0.0.1’, 8000) , Tôi đang đặt máy tínhA kết nối với cổng 8000 trên địa chỉ IP 127.0.0.1.

Lưu ý:127.0.0.1 thường có nghĩa là localhost, tham chiếu đến máy của bạn

Sau đó, đối với computerB, tôi đang đặt nó liên kết với cổng 8000 trên địa chỉ IP 127.0.0.1. Bây giờ, bạn có thể tự hỏi tại sao tôi có cùng một địa chỉ IP cho hai máy tính khác nhau.

Đó là bởi vì tôi đang gian lận. Tôi đang sử dụng một máy tính để chứng minh cách bạn có thể sử dụng các ổ cắm (về cơ bản tôi đang kết nối từ và đến cùng một máy tính vì mục đích đơn giản). Thông thường, hai máy tính khác nhau sẽ có hai địa chỉ IP khác nhau.

Chúng ta đã biết rằng chỉ có thể gửi các bit như một phần của gói dữ liệu, đó là lý do tại sao chúng ta mã hóa chuỗi trước khi gửi đi. Tương tự, chúng tôi giải mã chuỗi trên Máy tính B. Nếu bạn quyết định chạy cục bộ hai tệp trên, hãy đảm bảo chạy computerB.py nộp hồ sơ trước. Nếu bạn chạy computerA.py đầu tiên, bạn sẽ gặp lỗi từ chối kết nối.

Phục vụ khách hàng

Internet nói như thế nào
Truyền dữ liệu giữa máy khách và máy chủ

Tôi chắc chắn rằng nhiều người trong số các bạn đã hiểu khá rõ rằng những gì tôi đang mô tả cho đến nay là một mô hình máy khách-máy chủ rất đơn giản. Trên thực tế, bạn có thể thấy rằng từ hình ảnh trên, tất cả những gì tôi đã làm là thay thế Máy tính A làm máy khách và Máy tính B làm máy chủ.

Có một luồng giao tiếp liên tục diễn ra giữa máy khách và máy chủ. Trong ví dụ mã trước đây của chúng tôi, chúng tôi đã mô tả một lần chuyển dữ liệu. Thay vào đó, những gì chúng tôi muốn là một luồng dữ liệu liên tục được gửi từ máy khách đến máy chủ. Tuy nhiên, chúng tôi cũng muốn biết khi nào quá trình truyền dữ liệu hoàn tất, vì vậy chúng tôi biết rằng chúng tôi có thể ngừng lắng nghe.

Hãy thử sử dụng phép loại suy để xem xét thêm điều này. Hãy tưởng tượng cuộc trò chuyện sau đây giữa hai người.

Internet nói như thế nào

Hai người đang cố gắng giới thiệu bản thân. Tuy nhiên, họ sẽ không cố gắng nói chuyện cùng một lúc. Giả sử rằng Raj đi trước. John sau đó sẽ đợi cho đến khi Raj giới thiệu xong bản thân trước khi bắt đầu giới thiệu bản thân. Điều này dựa trên một số kinh nghiệm đã học nhưng chúng ta có thể mô tả chung ở trên như một giao thức.

Máy khách và máy chủ của chúng tôi cần một giao thức tương tự. Hoặc nếu không, làm sao họ biết được khi nào đến lượt họ gửi các gói dữ liệu?

Chúng tôi sẽ làm điều gì đó đơn giản để minh họa điều này. Giả sử chúng tôi muốn gửi một số dữ liệu có thể là một mảng các chuỗi. Giả sử mảng như sau:

arr = ['random', 'strings', 'that', 'need', 'to', 'be', 'transferred', 'across', 'the', 'network', 'using', 'sockets']

Trên đây là dữ liệu sẽ được ghi từ máy khách đến máy chủ. Hãy tạo một ràng buộc khác. Máy chủ cần chấp nhận dữ liệu chính xác tương đương với dữ liệu bị chiếm bởi chuỗi sẽ được gửi qua ngay thời điểm đó.

Vì vậy, ví dụ:nếu khách hàng sẽ gửi qua chuỗi 'ngẫu nhiên' và giả sử mỗi ký tự chiếm 1 byte, thì bản thân chuỗi đó sẽ chiếm 6 byte. 6 byte sau đó bằng 6 * 8 =48 bit. Do đó, để chuỗi 'ngẫu nhiên' được chuyển qua các ổ cắm từ máy khách đến máy chủ, máy chủ cần biết rằng nó phải truy cập 48 bit cho gói dữ liệu cụ thể đó.

Đây là cơ hội tốt để chia nhỏ vấn đề. Có một số điều chúng ta cần tìm hiểu trước.

Làm cách nào để tìm ra số byte mà một chuỗi chiếm trong Python?

Chà, chúng ta có thể bắt đầu bằng cách tìm ra độ dài của một chuỗi trước. Thật dễ dàng, đó chỉ là một cuộc gọi đến len() . Tuy nhiên, chúng ta vẫn cần biết số byte được chiếm bởi một chuỗi, không chỉ độ dài.

Trước tiên, chúng tôi sẽ chuyển đổi chuỗi thành nhị phân, sau đó tìm độ dài của biểu diễn nhị phân kết quả. Điều đó sẽ cung cấp cho chúng tôi số byte được sử dụng.

len(‘random’.encode(‘utf-8’)) sẽ cung cấp cho chúng tôi những gì chúng tôi muốn

Làm cách nào để chúng tôi gửi số byte bị chiếm bởi mỗi chuỗi vào máy chủ?

Thật dễ dàng, chúng tôi sẽ chuyển đổi số byte (là một số nguyên) thành một biểu diễn nhị phân của số đó và gửi nó đến máy chủ. Bây giờ, máy chủ có thể nhận được độ dài của một chuỗi trước khi nhận chính chuỗi đó.

Làm cách nào để máy chủ biết khi nào máy khách đã gửi xong tất cả dây?

Hãy nhớ rằng từ sự tương tự của cuộc trò chuyện, cần phải có một cách để biết liệu quá trình truyền dữ liệu đã hoàn tất hay chưa. Máy tính không có phương pháp phỏng đoán riêng mà chúng có thể dựa vào. Vì vậy, chúng tôi sẽ cung cấp một quy tắc ngẫu nhiên. Chúng tôi sẽ nói rằng khi chúng tôi gửi qua chuỗi ‘end’, điều đó có nghĩa là máy chủ đã nhận được tất cả các chuỗi và hiện có thể đóng kết nối. Tất nhiên, điều này có nghĩa là chúng ta không thể sử dụng chuỗi "end" trong bất kỳ phần nào khác của mảng ngoại trừ phần cuối.

Đây là giao thức chúng tôi đã thiết kế cho đến nay:

Internet nói như thế nào
Giao thức đơn giản của chúng tôi

Độ dài của chuỗi sẽ là 2 byte, tiếp theo là chính chuỗi thực sẽ có độ dài thay đổi. Nó sẽ phụ thuộc vào độ dài chuỗi được gửi trong gói trước đó và chúng tôi sẽ luân phiên gửi độ dài chuỗi và chính chuỗi đó. EOT là viết tắt của End Of Transmission và việc gửi chuỗi ‘end’ có nghĩa là không có thêm dữ liệu để gửi.

Lưu ý:Trước khi chúng ta tiếp tục, tôi muốn chỉ ra một số điều. Đây là một giao thức rất đơn giản và ngu ngốc. Nếu bạn muốn xem giao thức được thiết kế tốt trông như thế nào, không cần tìm đâu xa hơn giao thức HTTP.

Hãy mã này ra. Tôi đã bao gồm các nhận xét trong mã bên dưới để nó tự giải thích.

Tuyệt vời, chúng tôi có một khách hàng đang chạy. Tiếp theo, chúng tôi cần máy chủ.

Tôi muốn giải thích một vài dòng mã cụ thể trong các ý chính ở trên. Đầu tiên, từ clientSocket.py tệp.

len_in_bytes = (len_of_string).to_bytes(2, byteorder='little')

Những gì ở trên làm là chuyển đổi một số thành byte. Tham số đầu tiên được truyền cho hàm to_bytes là số byte được phân bổ cho kết quả chuyển đổi len_of_string sang biểu diễn nhị phân của nó.

Tham số thứ hai được sử dụng để quyết định xem theo định dạng Little Endian hay Big Endian. Bạn có thể đọc thêm về nó ở đây. Hiện tại, chỉ cần biết rằng chúng tôi sẽ luôn chú trọng đến thông số đó.

Dòng mã tiếp theo mà tôi muốn xem qua là:

client_socket.send(string.encode(‘utf-8’))

Chúng tôi đang chuyển đổi chuỗi thành định dạng nhị phân bằng ‘utf-8’ mã hóa.

Tiếp theo, trong serverSocket.py tệp:

data = client_socket.recv(2) str_length = int.from_bytes(data, byteorder='little')

Dòng mã đầu tiên ở trên nhận 2 byte dữ liệu từ máy khách. Hãy nhớ rằng khi chúng tôi chuyển đổi độ dài của chuỗi thành định dạng nhị phân trong clientSocket.py , chúng tôi quyết định lưu trữ kết quả trong 2 byte. Đây là lý do tại sao chúng tôi đang đọc 2 byte ở đây cho cùng một dữ liệu.

Dòng tiếp theo liên quan đến việc chuyển đổi định dạng nhị phân thành số nguyên. byteorder đây là "little", để khớp với byteorder chúng tôi đã sử dụng trên máy khách.

Nếu bạn tiếp tục và chạy hai ổ cắm, bạn sẽ thấy rằng máy chủ sẽ in ra các chuỗi mà máy khách gửi qua. Chúng tôi đã thiết lập thông tin liên lạc!

Kết luận

Được rồi, chúng tôi đã đề cập khá nhiều cho đến nay. Cụ thể, các socket là gì, cách chúng ta sử dụng chúng và cách thiết kế một giao thức rất đơn giản và ngu ngốc. Nếu bạn muốn tìm hiểu thêm về cách hoạt động của ổ cắm, tôi thực sự khuyên bạn nên đọc Hướng dẫn lập trình mạng của Beej. Cuốn sách điện tử đó có rất nhiều thứ tuyệt vời trong đó.

Tất nhiên, bạn có thể lấy những gì bạn đọc trong bài viết này cho đến nay và áp dụng nó cho các vấn đề phức tạp hơn như truyền hình ảnh từ máy ảnh RaspberryPi sang máy tính của bạn. Hãy vui vẻ với nó!

Nếu bạn muốn, bạn có thể theo dõi tôi trên Twitter hoặc GitHub. Bạn cũng có thể kiểm tra blog của tôi ở đây. Tôi luôn sẵn sàng nếu bạn muốn liên hệ với tôi!

Được xuất bản lần đầu tại https://redixhumayun.github.io/networking/2019/02/14/how-the-internet-speaks.html vào ngày 14 tháng 2 năm 2019.