C++ TCP/IP 네트워킹 맛 보기
카테고리: Cpp Network
태그: Cpp Network Programming
인프런에 있는 홍정모 교수님의 홍정모의 따라 하며 배우는 C++ 강의를 듣고 정리한 필기입니다. 😀
🌜 [홍정모의 따라 하며 배우는 C++]강의 들으러 가기!
chapter 20.5 TCP/IP 네트워킹 맛 보기
서버
클라이언트와 서버가 서로 데이터를 주고 받음
- 서버가 처음 하는 일
- 서버를 키고
- 클라이언트를 기다림
- 서버와 클라이언트가 서로 주고 받는게 빈번한 게임같은 경우에는 TCP 말고 UDP를 쓰기도 한다.
- 클라이언트와 서버를 ‘소켓’ 으로 연결
서버 코드
#include <iostream>
#include <vector>
#include <utility>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
int main()
{
try
{
boost::asio::io_service io_service;
tcp::endpoint endpoint(tcp::v4(), 13); // 통신을 하는 양 끝점 endpoint. 13은 port # 임의의 숫자 가능
tcp::acceptor acceptor(io_service, endpoint); // endpoint에서 io_service를 accept할 acceptor를 생성
std::cout << "Server started" << std::endl;
for (;;) // 무한 루프
{
const std::string message_to_send = "Hello From Server";
boost::asio::ip::tcp::iostream stream; // 일반 iostream이 아니라 오버로드된 tcp::iostream이다.
std::cout << "check 1" << std::endl; // 네트워킹에서도 cout을 쓸 수 있다.
boost::system::error_code ec;
acceptor.accept(*stream.rdbuf(), ec); // 클라이언트 접속을 받아들인다.
std::cout << "check 2" << std::endl;
if (!ec) //TODO: How to take care of multiple clients? Multi-threading?
{ // 클라이언트가 제대로 접속이 됐다면,
// receive message from client
std::string line;
std::getline(stream, line); // 입출력 스트림에서 클라이언트로부터 메세지를 받는다.
std::cout << line << std::endl; // 클라이언트 입장에서는 여기서 데이터를 보내야 서버가 받는다.
// send message to client
stream << message_to_send;
stream << std::endl; // 클라이언트 입장에서는 여기서 데이터를 읽어야 서버가 보낸다.
}
}
}
catch (std::exception& e) // 예외 발생시
{
std::cout << e.what() << std::endl;
}
}
- #include <boost/asio.hpp>
- 서버는 자기 자신이 구동이 되면서 자기 자신의 ip주소를 가지고 시작한다.
- 서버 스스로 자기 ip를 알고 있기 때문에 서버 시작을 준비하는 아래 과정에선 ip다루는 부분 없다.
- using boost::asio::ip::tcp;
- 네임 스페이스 👉 boost::asio::ip::tcp;
- boost 안에 있는 asio 안에 있는 ip 안에 있는 tcp
- 네임 스페이스 👉 boost::asio::ip::tcp;
- try - catch
- 네트워크 프로그래밍을 할 때에는 예외처리를 기본으로 해놔야한다. 불안정적인 상황 고려.
- boost::asio::io_service
io_service
; - tcp::endpoint
endpoint
(tcp::v4(), 13);- 통신을 하는 양 끝점. 종이컵 전화기의 양 끝 종이컵이라고 생각하며 됨. endpoint.
- tcp::v4()
- tcp 버전 4 를 사용
- 13
- 포트 넘버
- 필수
- 한 회선 안에서 마치 여러개의 회선이 있는 것 처럼 채널로 나눠주는 것
- TV 회선은 1개지만 채널이 여러개인 것처럼
- 포트 넘버
- tcp::acceptor acceptor(io_service, endpoint);
어셉터
를 만든다.- endpoint에서 io_service를 accept할 acceptor를 생성
- using boost::asio::ip::tcp;
- 서버 스스로 자기 ip를 알고 있기 때문에 서버 시작을 준비하는 아래 과정에선 ip다루는 부분 없다.
- for (;;)
- 서버는 무한 루프로 돌린다.
- const std::string
message_to_send
= “Hello From Server”;- 서버인 나에게 접속해 온 클라이언트에게 이 string을 보여줄 것!
- boost::asio::ip::tcp::iostream
stream
;- 일반 iostream이 아니라 오버로드된 tcp::iostream이다.
- std::cout « “check 1” « std::endl;
- 네트워킹에서도 cout을 쓸 수 있다.
- « »가 오버로딩 되어있어서 !
- stream은 매 반복마다 다시 선언되므로 for문 끝에 도달하면 비워진다 ! 지역변수니까. 매 반복마다 새로 생성됨
- boost::system::error_code
ec
;- 에러코드로 사용할 객체
- acceptor.accept(*stream.rdbuf(), ec);
- 클라이언트 접속을 받아들인다.( = appecptor가 appept 하다.)
- acceptor는 좀전에 위에서 io_service와 endpoint를 이용해 만들었다.
- *stream.rdbuf()
- 클라이언트가 접속을 해오면 stream으로부터 rdbuf를 읽어 옴.
- ec
- 받아 들여진게 없거나 에러가 나면 ec = NULL 로 만들어준다.
- if (!ec)
- 에러코드가 NULL 값이 아니라면
- accpector로부터 서버에 클라이언트가 들어온게 확인되면
- 즉 클라이언트가 서버에 제대로 접속이 됐다면
receive message from client
클라이언트로부터 메세지 받기- std::string
line
; - std::getline(stream, line);
- 한 줄 통째로 받기
- 입출력 스트림에서 클라이언트로부터 메세지를 받는다.
- 클라이언트 입장에서는 여기서 데이터를 보내야 서버가 받는다.
- 여기서 stream은 아까 tcp::iostream
- std::cout « line « std::endl;
- 입력 받은걸 그대로 출력
- std::string
send message to client
클라이언트에게 메세지 보내기- stream « message_to_send;
- “Hello From Server”;
- stream « std::endl;
- getline으로 클러이언트에게서 메세지를 받고 있기 때문에
- 개행 문자를 버퍼에서 없애기 위해 꼭 필요한 과정
- getline으로 클러이언트에게서 메세지를 받고 있기 때문에
- stream « message_to_send;
- const std::string
- 서버는 무한 루프로 돌린다.
클라이언트
클라이언트 솔루션과 서버코드의 솔루션은 다르다. 별개의 파일.
#include <iostream>
#include <string>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
int main(int argc, char** argv)
{
try
{
if (argc != 2)
{
std::cerr << "Usage : Client <host>\n";
return EXIT_FAILURE;
}
tcp::iostream stream(argv[1], std::to_string(int(13))); // port num = 13
if (!stream)
{
std::cout << "No address. Unable to connect: " << stream.error().message() << std::endl;
return EXIT_FAILURE;
}
// send message to server
stream << "Hello from client";
stream << std::endl;
// receive message from server
std::string line;
std::getline(stream, line);
std::cout << line << std::endl;
}
catch(std::exception & e)
{
std::cout << e.what() << std::endl;
}
}
- int main(int argc, char** argv)
- 서버 주소를 명령 인수에서 입력을 받는다.
- 프로젝트 - 우클 - 속성 - 디버깅 - 명령 인수
-
- 여기에 서버 주소를 입력한다.
- ex) 127.0.0.1(내 PC안에서 두개 다돌림) , 다른 컴퓨터의 ip주소
- 여기에 서버 주소를 입력한다.
-
- 프로젝트 - 우클 - 속성 - 디버깅 - 명령 인수
- 서버 주소를 명령 인수에서 입력을 받는다.
- if (argc != 2)
- 즉 서버 주소를 명령 인수에 입력 안했다면 !
- 그냥 종료 return EXIT_FAILURE;
- tcp::iostream
stream
(argv[1], std::to_string(int(13)));- tcp::iostream 타입인 stream 객체를 생성한다. ( 매개변수 2개 생성자 )
- argv[1]
- 입력한 서버 주소
- std::to_string(int(13))
- 포트 넘버를 문자열로 변환
- argv[1]
- tcp::iostream 타입인 stream 객체를 생성한다. ( 매개변수 2개 생성자 )
- if (!stream)
- stream이 안만들어지는 오류가 발생했다면
- 그냥 종료 return EXIT_FAILURE;
send message to server
서버에게 메세지 보내기- stream « “Hello from client”;
- 서버에게 보낼 스트림에 메세지 넣기
- 서버는 getline으로 stream을 받기 때문에 개행 문자 꼭 넣어주기
- stream « std::endl;
- stream « “Hello from client”;
receive message from server
서버로부터 메세지 받기- std::string
line
;- line에 받을 것
- std::getline(stream, line);
- stream으로부터 메세지를 받는다.
- std::cout « line « std::endl;
- 받은거 출력
- std::string
실행해보기
- 서버를 먼저 실행하였다.
- check1 밑에서 깜빡거리며 클라이언트가 accept 되기를 기다리는 중이다.
- 이때 클라이언트를 실행하면
- 왼쪽 창 → 클라이언트 / 오른쪽 창 → 서버
- 클라이언트가 실행되자마자 서버로부터 “Hello From Server” 메세지를 받는다.
- 동시에 서버창에서도 check2가 출력되고 “Hello From Server” 가 출력됐다.
- 무한 루프에 따라 다시 새로운 클라이언트가 실행되어 accpect되기를 기다린다. (check 1)
- 서로 stream을 주고 받고 하는 과정 !
채팅 프로그램 구현시 고려해볼 점
- 사용자 둘이서 언제나 번갈아가면서 얘기하진 않음
- How to take care of multiple clients? → Multi-threading 사용하기
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기