Computer >> 컴퓨터 >  >> 네트워킹 >> 인터넷

인터넷이 말하는 방식

커뮤니케이션 이야기

인터넷이 실제로 어떻게 말하는지 궁금해 한 적이 있습니까? 한 컴퓨터는 인터넷을 통해 다른 컴퓨터와 어떻게 "대화"합니까?

사람들이 서로 의사 소통할 때 우리는 겉보기에 의미 있는 문장으로 묶인 단어를 사용합니다. 이 문장의 의미에 동의했기 때문에 문장이 의미가 있습니다. 말하자면 우리는 통신 프로토콜을 정의했습니다.

밝혀진 바에 따르면 컴퓨터는 인터넷을 통해 비슷한 방식으로 서로 대화합니다. 그러나 우리는 우리 자신보다 앞서 가고 있습니다. 사람들은 입으로 의사소통을 하는데, 먼저 컴퓨터의 입이 무엇인지 알아봅시다.

소켓 입력

소켓은 컴퓨터 과학에서 가장 기본적인 개념 중 하나입니다. 소켓을 사용하여 상호 연결된 장치의 전체 네트워크를 구축할 수 있습니다.

컴퓨터 과학의 다른 모든 것과 마찬가지로 소켓은 매우 추상적인 개념입니다. 따라서 소켓이 무엇인지 정의하는 것보다 소켓이 무엇을 하는지 정의하는 것이 훨씬 쉽습니다.

그렇다면 소켓은 무엇을 합니까? 두 대의 컴퓨터가 서로 통신하는 데 도움이 됩니다. 어떻게 합니까? send()라는 두 가지 메서드가 정의되어 있습니다. 및 recv() 각각 보내기와 받기를 위해.

좋습니다. 하지만 send()는 및 recv() 실제로 보내고 받습니까? 사람들은 입을 움직일 때 말을 주고받습니다. 소켓이 메서드를 사용할 때 비트와 바이트를 교환합니다.

예제를 통해 방법을 설명하겠습니다. A와 B라는 두 대의 컴퓨터가 있다고 가정해 보겠습니다. 컴퓨터 A는 컴퓨터 B에게 무언가를 말하려고 합니다. 따라서 컴퓨터 B는 컴퓨터 A가 말하는 것을 들으려고 합니다. 다음과 같습니다.

인터넷이 말하는 방식
버퍼가 있는 소켓

버퍼 읽기

조금 이상해 보이지 않나요? 하나는 두 컴퓨터 모두 '버퍼'라는 제목의 가운데 막대를 가리키고 있습니다.

버퍼란? 버퍼는 메모리 스택입니다. 각 컴퓨터의 데이터가 저장되는 곳이며 커널에 의해 할당됩니다.

다음으로, 왜 둘 다 동일한 버퍼를 가리키고 있습니까? 글쎄, 그것은 실제로 정확하지 않습니다. 각 컴퓨터에는 자체 커널에 의해 할당된 자체 버퍼가 있으며 네트워크는 두 개의 개별 버퍼 간에 데이터를 전송합니다. 하지만 여기서는 네트워크 세부 정보를 다루고 싶지 않으므로 두 컴퓨터가 "사이의 빈 공간 어딘가에" 배치된 동일한 버퍼에 액세스할 수 있다고 가정합니다.

자, 이제 시각적으로 어떻게 생겼는지 알았으니 코드로 추상화해 보겠습니다.

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

이 코드 조각은 위의 이미지가 나타내는 것과 똑같은 작업을 수행합니다. 한 가지 호기심을 제외하고는 computerB.recv(data)이라고 말하지 않습니다. . 대신 데이터 대신 겉보기에 임의의 숫자를 지정합니다.

이유는 간단합니다. 네트워크를 통한 데이터는 비트로 전송됩니다. 따라서 컴퓨터 B에서 수신할 때 비트 수를 지정합니다. 우리는 주어진 시간에 언제든지 받을 의향이 있습니다.

한 번에 수신할 1024바이트를 선택한 이유는 무엇입니까? 특별한 이유는 없습니다. 일반적으로 수신할 바이트 수를 2의 거듭제곱으로 지정하는 것이 가장 좋습니다. 저는 2¹⁰인 1024를 선택했습니다.

그렇다면 버퍼는 이것을 어떻게 알아낼까요? 글쎄요, 컴퓨터 A는 저장된 데이터를 버퍼에 쓰거나 보냅니다. 컴퓨터 B는 해당 버퍼에 저장된 것의 처음 1024바이트를 읽거나 받기로 결정합니다.

좋아, 굉장해! 그러나 이 두 컴퓨터가 서로 대화하는 것을 어떻게 압니까? 예를 들어, 컴퓨터 A가 이 버퍼에 쓸 때 컴퓨터 B가 버퍼를 선택한다는 것을 어떻게 알 수 있습니까? 다시 말해서 두 컴퓨터 간의 연결에 고유한 버퍼가 있음을 어떻게 보장할 수 있습니까?

IP로 이식

인터넷이 말하는 방식
컴퓨터의 포트 및 IP

위의 이미지는 우리가 작업한 동일한 두 대의 컴퓨터와 하나의 세부 사항이 추가된 것을 보여줍니다. 막대 길이를 따라 각 컴퓨터 앞에 나열되는 많은 숫자가 있습니다.

각 컴퓨터 앞에 있는 긴 막대를 특정 컴퓨터를 인터넷에 연결하는 라우터로 간주합니다. 각 막대에 나열된 숫자를 포트라고 합니다. . 귀하의 컴퓨터에는 현재 수천 개의 포트가 있습니다. 각 포트는 소켓 연결을 허용합니다. 위 이미지에는 6개의 포트만 표시되어 있지만 이해가 되실 것입니다.

255 미만의 포트는 일반적으로 시스템 호출 및 저수준 연결용으로 예약되어 있습니다. 일반적으로 8000과 같이 높은 4자리의 포트에서 연결을 여는 것이 좋습니다. 위 이미지에서는 버퍼를 그리지 않았지만 각 포트에 자체 버퍼가 있다고 가정할 수 있습니다.

막대 자체에도 연관된 번호가 있습니다. 이 번호를 IP 주소라고 합니다. IP 주소에는 여러 포트가 연결되어 있습니다. 다음과 같이 생각하십시오.

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

좋습니다. 컴퓨터 A와 컴퓨터 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)

다음은 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'))

코드 면에서 조금 앞서는 것 같지만 단계별로 살펴보겠습니다. A와 B라는 두 대의 컴퓨터가 있다는 것을 알고 있습니다. 따라서 하나는 데이터를 보내고 다른 하나는 데이터를 수신해야 합니다.

데이터를 보낼 A와 데이터를 받을 B를 임의로 선택했습니다. 이 줄에서 computerA.connect((‘127.0.0.1’, 8000) , 컴퓨터 A를 IP 주소 127.0.0.1의 포트 8000에 연결합니다.

참고:127.0.0.1은 일반적으로 컴퓨터를 참조하는 localhost를 의미합니다.

그런 다음 컴퓨터 B의 경우 IP 주소 127.0.0.1의 포트 8000에 바인딩합니다. 이제 두 대의 다른 컴퓨터에 대해 동일한 IP 주소를 사용하는 이유가 궁금할 것입니다.

제가 속이기 때문입니다. 소켓을 사용하는 방법을 보여주기 위해 한 대의 컴퓨터를 사용하고 있습니다(간단함을 위해 기본적으로 동일한 컴퓨터에서 연결하고 있음). 일반적으로 두 대의 다른 컴퓨터에는 두 개의 서로 다른 IP 주소가 있습니다.

우리는 이미 비트만 데이터 패킷의 일부로 보낼 수 있다는 것을 알고 있기 때문에 문자열을 보내기 전에 인코딩합니다. 마찬가지로 컴퓨터 B에서 문자열을 디코딩합니다. 위의 두 파일을 로컬에서 실행하기로 결정했다면 computerB.py를 실행해야 합니다. 먼저 파일. computerA.py을 실행하면 파일을 먼저 실행하면 연결 거부 오류가 발생합니다.

고객 서비스

인터넷이 말하는 방식
클라이언트와 서버 간 데이터 전송

제가 지금까지 설명한 것이 매우 단순한 클라이언트-서버 모델이라는 것을 많은 분들이 분명히 이해하셨으리라 확신합니다. 실제로 위의 이미지에서 볼 수 있듯이 컴퓨터 A를 클라이언트로, 컴퓨터 B를 서버로 교체한 것뿐입니다.

클라이언트와 서버 간에 지속적인 통신 흐름이 있습니다. 이전 코드 예제에서 데이터 전송의 원샷을 설명했습니다. 대신, 우리가 원하는 것은 클라이언트에서 서버로 지속적으로 데이터를 전송하는 것입니다. 그러나 데이터 전송이 완료되는 시점도 알고 싶으므로 수신을 중지할 수 있습니다.

유추를 사용하여 더 자세히 살펴보겠습니다. 다음 두 사람의 대화를 상상해 보세요.

인터넷이 말하는 방식

두 사람이 자기소개를 하려고 합니다. 그러나 그들은 동시에 말하려고 하지 않을 것입니다. Raj가 먼저 간다고 가정해 봅시다. John은 Raj가 자신을 소개하기 시작하기 전에 자기 소개를 마칠 때까지 기다릴 것입니다. 이것은 학습된 경험적 방법을 기반으로 하지만 일반적으로 위의 내용을 프로토콜로 설명할 수 있습니다.

우리의 클라이언트와 서버는 유사한 프로토콜이 필요합니다. 아니면 데이터 패킷을 보낼 차례인지 어떻게 알 수 있습니까?

이를 설명하기 위해 간단한 작업을 수행합니다. 문자열 배열인 데이터를 보내고 싶다고 가정해 봅시다. 배열이 다음과 같다고 가정해 봅시다:

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

위는 클라이언트에서 서버로 쓸 데이터입니다. 다른 제약 조건을 만들어 보겠습니다. 서버는 그 순간에 전송될 문자열이 차지하는 데이터와 정확히 동일한 데이터를 수락해야 합니다.

예를 들어 클라이언트가 'random' 문자열을 통해 전송하려고 하고 각 문자가 1바이트를 차지한다고 가정하면 문자열 자체는 6바이트를 차지합니다. 6바이트는 6*8 =48비트와 같습니다. 따라서 'random' 문자열이 소켓을 통해 클라이언트에서 서버로 전송되기 위해서는 서버가 특정 데이터 패킷에 대해 48비트에 액세스해야 한다는 것을 알아야 합니다.

이것은 문제를 해결할 좋은 기회입니다. 먼저 파악해야 할 몇 가지 사항이 있습니다.

문자열이 차지하는 바이트 수는 어떻게 알아낼 수 있나요? 파이썬?

글쎄, 우리는 먼저 문자열의 길이를 알아내는 것으로 시작할 수 있습니다. 간단합니다. len()를 호출하면 됩니다. . 그러나 길이뿐만 아니라 문자열이 차지하는 바이트 수를 알아야 합니다.

먼저 문자열을 바이너리로 변환한 다음 결과 바이너리 표현의 길이를 찾습니다. 그러면 사용된 바이트 수를 알 수 있습니다.

len(‘random’.encode(‘utf-8’)) 우리가 원하는 것을 줄 것입니다

각 바이트가 차지하는 바이트 수를 보내는 방법 문자열을 서버에?

쉽습니다. 바이트 수(정수)를 해당 숫자의 이진 표현으로 변환하고 서버로 보냅니다. 이제 서버는 문자열 자체를 수신하기 전에 문자열의 길이를 수신할 것으로 예상할 수 있습니다.

클라이언트가 모든 문자열?

대화의 유추에서 기억하십시오. 데이터 전송이 완료되었는지 알 수 있는 방법이 필요합니다. 컴퓨터에는 의존할 수 있는 고유한 휴리스틱이 없습니다. 따라서 무작위 규칙을 제공합니다. 우리는 'end' 문자열을 보낼 때 서버가 모든 문자열을 수신했으며 이제 연결을 닫을 수 있음을 의미한다고 말할 것입니다. 물론 이것은 배열의 맨 끝을 제외하고는 다른 부분에서 'end' 문자열을 사용할 수 없음을 의미합니다.

지금까지 설계한 프로토콜은 다음과 같습니다.

인터넷이 말하는 방식
단순 프로토콜

문자열의 길이는 2바이트이고 그 뒤에 가변 길이가 될 실제 문자열 자체가 옵니다. 이전 패킷에서 보낸 문자열 길이에 따라 달라지며 문자열 길이와 문자열 자체를 번갈아 전송합니다. EOT는 End Of Transmission의 약자로 'end'라는 문자열을 보낸다는 것은 더 이상 보낼 데이터가 없다는 의미입니다.

참고:계속하기 전에 지적하고 싶은 것이 있습니다. 이것은 매우 간단하고 어리석은 프로토콜입니다. 잘 설계된 프로토콜이 어떤 모습인지 알고 싶다면 HTTP 프로토콜을 살펴보십시오.

이것을 코딩해 봅시다. 설명이 필요하지 않도록 아래 코드에 주석을 포함했습니다.

좋습니다. 실행 중인 클라이언트가 있습니다. 다음으로 서버가 필요합니다.

위의 요지에서 몇 가지 특정 코드 줄을 설명하고 싶습니다. 첫 번째, clientSocket.py 파일.

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

위의 작업은 숫자를 바이트로 변환하는 것입니다. to_bytes 함수에 전달된 첫 번째 매개변수는 len_of_string 변환 결과에 할당된 바이트 수입니다. 이진 표현으로.

두 번째 매개변수는 Little Endian 형식을 따를지 Big Endian 형식을 따를지 결정하는 데 사용됩니다. 여기에서 자세한 내용을 읽을 수 있습니다. 지금은 해당 매개변수에 대해 항상 약간만 사용한다는 점만 알아두세요.

살펴보고 싶은 다음 코드 줄은 다음과 같습니다.

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

‘utf-8’을 사용하여 문자열을 바이너리 형식으로 변환합니다. 인코딩.

다음으로 serverSocket.py에서 파일:

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

위 코드의 첫 번째 줄은 클라이언트로부터 2바이트의 데이터를 받습니다. clientSocket.py에서 문자열의 길이를 이진 형식으로 변환할 때 , 우리는 결과를 2바이트에 저장하기로 결정했습니다. 이것이 동일한 데이터에 대해 여기에서 2바이트를 읽는 이유입니다.

다음 줄에는 이진 형식을 정수로 변환하는 작업이 포함됩니다. byteorder 여기 byteorder과 일치하는 "작은"이 있습니다. 클라이언트에서 사용했습니다.

계속해서 두 개의 소켓을 실행하면 서버가 클라이언트가 전송하는 문자열을 인쇄하는 것을 볼 수 있습니다. 커뮤니케이션을 구축했습니다!

결론

자, 지금까지 꽤 많이 다루었습니다. 즉, 소켓이 무엇인지, 소켓을 사용하는 방법과 매우 간단하고 어리석은 프로토콜을 설계하는 방법입니다. 소켓 작동 방식에 대해 더 알고 싶다면 Beej의 네트워크 프로그래밍 가이드를 읽는 것이 좋습니다. 그 전자책에는 훌륭한 내용이 많이 들어 있습니다.

물론 지금까지 이 기사에서 읽은 내용을 사용하여 RaspberryPi 카메라에서 컴퓨터로 이미지를 스트리밍하는 것과 같은 더 복잡한 문제에 적용할 수 있습니다. 즐거운 시간 보내세요!

원하는 경우 Twitter 또는 GitHub에서 나를 팔로우할 수 있습니다. 여기에서 내 블로그를 확인할 수도 있습니다. 저에게 연락을 원하시면 언제든지 연락할 수 있습니다!

2019년 2월 14일 https://redixhumayun.github.io/networking/2019/02/14/how-the-internet-speaks.html에 원래 게시되었습니다.