목차
Python을 통한 서버 문서 가져오기
- 목표
- 표준 함수 input()을 사용하여 www 사이트의 주소를 읽고 지정된 사이트의 루트 문서를 가져오는 프로그램을 작성.
- 프로그램은 문서를 화면에 출력
- 이 프로그램은 TCP를 사용하여 HTTP 서버에 연결
- 다음 단계를 수행
- TCP 기반 연결 지향 전송을 처리할 수 있는 소켓을 생성
- 소켓을 주어진 주소의 HTTP 서버에 연결
- 서버에 요청을 보내기
- 서버에 응답을 받기
- 소켓을 닫기(연결 종료)
1. 소켓을 사용하기 위한 모듈 import
import socket
2. 사용자 입력 받기
import socket
server_address = input("어떤 서버와 연결할건가요 ? :")
이때 사용자는 두가지의 형태로 입력할 수 있다.
- https:// 가 없는 경우
- 서버의 IP 주소 일 수 있지만 이 경우 모호할 수 있다. 동일한 IP 주소에 여러 개의 HTTP 서버가 있을 수 있기 때문이다.
즉 연결하려는 서버가 아닐 수 있다. 가상 호스트(Virtual Host) 기능을 사용하여 여러 개의 독립적인 웹사이트(도메인)를 운영할 수 있다.드물지만, 하나의 IP 주소에서 서로 다른 포트 번호를 사용하여 여러 HTTP 서버를 운영할 수도 있다.
사용자들이 두 가지 방법 중 어떤 것을 선택하던 우리의 문제가 아니다, 사용자들은 더 잘 알고 있다.
3. 소켓 생성
import socket
server_address = input("어떤 서버와 연결할건가요 ? :")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
TCP/IP 작동방식과 REST의 매개체로의 socket 에 대해 알아보자.
TCP/IP는 HTTP 트래픽을 전송할 수 있다는 점 HTTP는 REST의 중개자 역할을 할 수 있다.
'socket' 모듈은 'socket' 클래스르 제공하는데, 이 클래스는 실제 소켓 동작과 관련된 속성과 액티비티를 캡슐화 한다.
생성을 위해서 두개의 인수를 받는데, 두 인수 모두 소켓 모듈 내에 선언되어 있다.
- 첫번째 인자 : 인터넷 소켓 도메인을 지정한다.
- socket.AF_INET : 네트워크 파트의 정리된 것을 살펴보면 , Unix 와 INET 도메인에 대해 설명되어 있는데 이와 같고,
다른 도메인에는 완전히 다른 소켓 구조가 필요하므로, 대상 도메인을 알고 있어야 한다.
IPv4 주소 패밀리를 사용하는 소켓을 생성하겠다고 지정하는 것이다. 이는 대부분의 인터넷 통신에서 사용한다. - socket.AF_INET6 : IPv6 인터넷 도메인 소켓.
- socket.AF_UNIX (또는 AF_LOCAL) : 동일한 시스템 내 프로세스 간 통신을 위한 유닉스 도메인 소켓을 의미한다.
- socket.AF_INET : 네트워크 파트의 정리된 것을 살펴보면 , Unix 와 INET 도메인에 대해 설명되어 있는데 이와 같고,
- 두번째 인자 : 소켓 유형 코드
- socket.SOCK_STREAM : 문서 장치로 작동할 수 있는 고급 소켓. 연결 지향(connection-oriented)이며 신뢰성 있는(reliable) 바이트 스트림 통신을 제공한다. 데이터는 순서대로, 중복 없이, 오류 없이 전송된다. 이는 TCP 프로토콜이 제공하는 서비스와 정확히 일치한다.바이트 단위 데이터 전송에 관심이 있고, 스트림 소켓이 데이터의 경계(record boundaries)를 유지하지 않고 연속적인 바이트 흐름으로 데이터를 처리한다.
이러한 socket.AF_INET과 socket.SOCK_STREAM의 조합은 거의 항상 TCP 프로토콜을 기반으로 동작하도록 준비되어 있고, 기본 소켓의 구성이다.
UDP 같은 다른 프로토콜의 경우 다른 생성자 구문을 사용해야한다.
2025.07.09 - [Computer Science] - [Network] Network 통신
[Network] Network 통신
RESTREST 는 약어로 이루어져 있고, 세 단어로 유래되었다.REpresentational 표현State 상태Transfer 옮기다representational 표현기계가 표현을 저장하고, 전송하고 수신한다는 것을 의미하고,표현이라는 용어
radaonmommy.tistory.com
4. 서버 연결
import socket
server_address = input("어떤 서버와 연결할건가요 ? :")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((server_address, 80))
클라이언트 측에서 소켓을 사용하면 바로 사용이 가능하다.
하지만 서버는 몇가지 단계를 거치게 된다.
일반적으로 서버는 하나의 서버가 여러 클라이언트를 동시에 지원하기 때문에 클라이언트보다 더 복잡하다.
connect()를 통해 소켓을 지정된 주소와 포트 번호의 서비스에 연결하려고 한다.
두 값이 튜플의 요소로 메서드에 전달되는 변형을 사용하고, 이것이 두 쌍의 괄호가 있는 이유이다.
괄호가 두 쌍이 아니라면 오류가 발생한다.
대상 서비스의 주소 형식(튜플로 구성된 주소와 포트 번호)은 INET 도메인에 따라 다르다.
다른 도메인에서 동일하게 표시되지 않을 수 있다.
연결 실패할 가능성이 있을까 ?
당연히 서비스 주소가 잘못되거나, 서버가 존재하지 않거나, 연결 오류가 발생할 수 있다.
문제가 발생하면 coonect() 메서드에서 예외가 발생한다.
이부분은 이후에 보완하도록 한다.
연결이 준비되었고, 우리가 서버에 하고싶은 말은 무엇일까?
HTTP 서버가 우리의 말을 이해하도록 하려면 어떻게 해야할까 ?
당연히 HTTP로 통신해야 한다.
5. 서버에 요청하기
GET method
HTTP 서버와의 대화는 요청(requests)과 응답(responses)으로 구성한다.
HTTP는 허용되는 요청 집합을 정의한다.
이를 request methods , HTTP words 라고 한다.
GET의 경우서버에 주어진 이름의 특정 문서를 보내달라고 요청하는 메서드를 호출한다.
예를 들면 www.google.com 사이트에서 루트 문서를 얻기 위해서는 클라이언트는 올바르게 구성된 요청을 보내야한다.
GET / HTTP/1.1\r\n
Host: www.google.com\r\n
Connection: close\r\n
\r\n
- Method : GET 메서드로 보내겠다는 의미.
- Path : / 루트 문서를 의미.
- Protocol version : HTTP/1.1
- \r\n : 같은 줄 형식을 이용해 줄을 끝내야 한다.
사내망 이슈로 인한 테스트 불가 및 colab 또한 동작하지 않음.
자료 정리 후 개인 노트북에서 실행후 보완할 것
import socket
server_address = input("어떤 서버와 연결할건가요 ? :")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((server_address, 80))
sock.sendall(b'GET / HTTP/1.0\r\nHost:' +
bytes(server_address, "utf8") +
b"\rnConnection: close\r\n\r\n")
send()의 경우 기본적으로 문자열을 허용하지 않아서
요청 문자열 앞에 'b' 접두사를 사용하여 문자열을 바이트로 자동으로 변환한다.
또한 bytes() 함수도 같은 방식으로 변환한다.
bytes()는 두번째 인수로 서버 이름을 저장하는데 사용되는 인코딩을 지정하고 최신의 대부분 OS의 경우 UTF8이 가장 적합하다.
이러한 방식으로 수행되는 send()의 작업은 매우 복잡하다.
OS의 여러 계층 뿐 아니라 클라이언트와 서버간 경로에 배치된 많은 네트웤 장비와 당연히 서버 자체도 관여하게되는데
우리는 다행히도 걱정할 필요가 없다.
물론 복잡한 메커니즘 중 어떤 부분이 실패가 된다면, send()도 실패할 것이고, 예외가 발생한다.
6. 응답을 나눠서 받기
import socket
server_address = input("어떤 서버와 연결할건가요 ? :")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((server_address, 80))
sock.sendall(b'GET / HTTP/1.0\r\nHost:' +
bytes(server_address, "utf8") +
b"\rnConnection: close\r\n\r\n")
reply = sock.recv(10000)
해당 메서드는 receive의 약어로 서버의 응답을 기다렸다가 새로 생성된 bytes 타입에 객체를 저장한다.
해당 인수는 데이터의 최대 허용 길이를 지정하는 거싱고
서버의 응답이 이 제한보다 길다면 한번에 전체 데이터가 수신되지 못한다.
recv()는 항상 요청한 버퍼 크기 내에서 사용 가능한 만큼의 데이터를 반환한다.
나머지 데이터를 수신하기 위해 recv()를 데이터가 모두 수신될 때까지 반복적으로 호출해야 한다.
어디에서나 예외는 발생할 수 있다.
7. 연결 종료
import socket
server_address = input("어떤 서버와 연결할건가요 ? :")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((server_address, 80))
sock.sendall(b'GET / HTTP/1.0\r\nHost:' +
bytes(server_address, "utf8") +
b"\rnConnection: close\r\n\r\n")
reply = sock.recv(10000)
sock.shutdown(socket.SHUT_RDWR)
sock.close()
sock.shutdown을 통해 "우리는 이렇게(SHUT_RDWR) 할꺼고 더이상 말 안할꺼야 " 라는 여자친구의 선언과 같은 메세지이다.
서버는 그에 대해 의도를 알아차릴 수 있다.
socket.SHUT_RD | 우리는 더이상 서버의 메세지를 읽지 않을 것이다.(수신 X) |
socket.SHUT_WR | 우리는 아무 말도 하지 않을 것이다.(송신 X) |
socket.SHUT_RDWR | 우리는 말도 안하고 읽지도 않을 거야.(수신, 송신 X) |
shutdown()은 연결 종료 절차를 시작하고 의사를 전달하는 것으로 그 즉시 연결이 완전히 해제되는 것은 아니다.
shutdown()은 소켓의 특정 방향(쓰기, 읽기 또는 둘 다)으로의 데이터 흐름을 중단하겠다는 의사를 상대방에게 알린다. 특히 SHUT_WR이나 SHUT_RDWR은 FIN 패킷을 전송하여 상대방에게 "더 이상 보낼 데이터가 없다"고 알립니다
그리고 소켓의 close() 메서드를 사용하여 연결을 명확하게 닫는다.
close()만 호출해도 대부분의 경우 운영체제가 나머지 종료 절차를 알아서 처리하지만,
shutdown()을 먼저 호출하면 상대방에게 종료 의사를 명확히 알리고 나머지 데이터를 안전하게 처리할 기회를 준다.
특히 송신 버퍼에 남아있는 데이터가 모두 전송되도록 기다린 후 연결을 닫는 등의 동작을 보장한다.
파이썬의 "명시적인 것이 암시적인 것보다 낫다(Explicit is better than implicit)"는 철학과도 일맥상통한다.
종료 의사를 명시적으로 알리는 shutdown()을 사용하는 것이 더 '파이썬스러운' 방식이라고 볼 수 있다.
8. 응답 출력
import socket
server_address = input("어떤 서버와 연결할건가요 ? :")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((server_address, 80))
sock.sendall(b'GET / HTTP/1.0\r\nHost:' +
bytes(server_address, "utf8") +
b"\rnConnection: close\r\n\r\n")
reply = sock.recv(10000)
sock.shutdown(socket.SHUT_RDWR)
sock.close()
print(repr(reply))
repr() 함수는 모든 객체를 거의 텍스트처럼 명확하게 표현한다.
서버에 응답이 모든것이 성공적으로 진행되었다면
(1. 사용자가 유효한 주소 입력,
2. 인터넷이 예상대로 작동,
3. 서버가 협조할 의향이 있었다면)
다음과 같이 표시될 수 있다.
b'HTTP/1.1 200 OK\r\nDate: Fri, 10 July 2025 23:24:41 GMT\r\nServer: UltraDNS Client Redirection Server ....
응답 헤더를 통해 200 이라는 성공 메세지를 확인할 수 있다.
exception 확인
1. 잘못된 주소나 존재하지 않는 주소를 입력한 경우
socket.gaierror: [Errno -2] Name or service not known
connect() 함수는 해당 이름의 예외를 발생시키는데,
함수 이름 gaierror는 OS 커널에서 제공된 저수준의 함수 이름(getaddrinfo())에서 따왔다.
2. 포트 번호가 잘못되었거나 해당 서버에서 포트를 열어놓지 않은 경우
CoonectionRefusedError: [Errono 111] Connection refused
예외 이름만 봐도 서버가 연결을 거부했음을 알 수 있다.
3. 서버의 반응이 적절한 시간 내에 이루어 지지 않은 경우.
socket.timeout
socket.timeout 예외는 socket 객체에 타임아웃이 설정되어 있고,
해당 시간 내에 connect(), recv(), send() 등의 작업이 완료되지 않았을 때 발생한다.
'Developer > Python' 카테고리의 다른 글
[python] XML , XML 모듈 (0) | 2025.07.13 |
---|---|
[python] JSON, JSON 모듈 (6) | 2025.07.12 |
[python] GUI programming tkInter (Canvas) (0) | 2025.07.10 |
[python] GUI programming tkInter (메인창 구성 및 messagebox) (6) | 2025.07.10 |
[python] GUI programming tkInter (menu) (3) | 2025.07.09 |