minhui study

소켓 프로그래밍1 - server, client 본문

네트워크/TCP스터디 및 프로젝트

소켓 프로그래밍1 - server, client

minhui 2020. 2. 29. 11:16

소켓 인터페이스 기반 통신 구조는 다음과 같다.

여기서 server와 client에서 send()와 recv() 순서는 누가 먼저 send()하던 상관없다.

기본 Server, Client 예제에 조금 더 과정을 추가할 예정이다.

 

순서는 대략 이렇다.

1. 연결되면 Server측에서 Hello World!보내기 (1번까지는 열혈 TCP/IP 책에 수록된 예제 코드이다.)

2. 클라이언트가 사용자의 입력을 받아서 send 
3. 서버는 recv 받은 값을 print
4. 서버는 제대로 받았다는 문자열을 클라이언트에게 send

5. 2,3,4번 반복하다 Ctrl+C를 누르면 종료

 

[server.cpp]

(1) 전처리기 선언 및 전역변수, 함수 선언과 초기화(1~10행)

 

1~5행

기본적으로 사용하는 헤더와 라이브러리 링킹

windoxk2.h에 우리가 사용할 소켓 프로그래밍에 관련된 함수가 선언되어 있음.

 

(2) 지역 변수 선언 및 인자 관련 필터링(11~25행)

 

16행 int strLen; : 클라이언트로부터 온 메세지 길이를 담는 변수

 

19행 char message[]="Hello World!" : 클라이언트와 연결 성공 시 보낼 메세지

 

21~24행 

인자를 정확하게 받았는지 필터링 진행.

정확한 값이 아닐 경우 사용법을 출력하고 프로그램 종료

 

(3) 소켓 생성 및 초기화(26~37행)

 

27행

WSAStartup(MAKEWORD(2, 2), &wsaData)

-> WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData)

WSADATA는 윈도우 소켓 초기화 호출에 반환 된 정보를 저장하는데 사용되는 전역함수이다.

윈속 초기화하고 윈도우 버전 등에 대한 초기화 정보를 wsaData에 담는다.

wVersionRequested : 프로그램이 요구하는 원속의 최상위 버전

lpWSAData : WSADATA 타입 변수의 포인터를 인자로 전달. 일반적으로 많이 사용하지 않음

 

30~32행 소켓 생성(socket함수)

33~37행 소켓 정보 초기화(SOCKADDR_IN 구조체)

socket함수와 SOCKADDR_IN구조체.pptx
0.06MB

 

(4) 소켓 바인딩 bind() (40,41행)

bind( hServSock, (SOCKADDR)&servAddr, sizeof( servAddr ) )

hServSock // 주소 정보(IP, PORT)를 할당한 소켓의 파일 디스크립터

(SOCKADDR)&servAddr // 할당하고자 하는 주소정보를 지닌 구조체 변수의 값

sizeof( servAddr ) // 두 번째 인자로 전달된 구조체 변수의 길이 정보
-> bind()함수를 통해서 생성한 소켓에 주소 정보를 할당한다.

(5) 연결 요청 대기 listen() (43, 44행)

listen() 함수 호출 후에 connect()를 받을 수 있음(연결 요청 수락 가능) -> server코드를 먼저 실행해야 하는 이유

listen(hServSock, 5)

hServSock // 주소 정보(IP, PORT)를 할당한 소켓의 파일 디스크립터

5 // 클라이언트 연결요청 대기 큐의 크기 전달, 5라면 연결 요청을 5개까지 대기시킬 수 있다.

 

(6) 연결 요청 수락 accept() (46~49행)

accept() 함수 호출 성공 시 내부적으로 데이터 입출력에 사용할 소켓 생성 후 클라이언트 소켓에 연결

accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);

hServSock // 서버 소켓의 파일 디스크립터 전달

(SOCKADDR*)&clntAddr // 연결을 요청한 클라이언트의 주소정보를 담을 변수 주소

&szClntAddr // 두 번째 인자로 전달된 구조체 변수의 길이 정보

 

(7) 데이터 송신 send() (51, 58행)

소켓을 통해 데이터 송신을 진행

send(SOCKET s, const char * buf, int len, int flags);

SOCKET s //데이터 전송 대상과 연결하는 소켓 핸들 값

const char * buf // 전송할 데이터를 저장하는 버퍼 주소

int len // 전송할 바이트 길이

int flags // 데이터 전송 시 적용할 다양한 옵션 정보

51행 : 클라이언트와 accept()까지의 과정을 통해 연결이 되었을 때 Hello World!를 보낸다.

58행 : 클라이언트에서 보낸 메세지를 잘받았다는 의미로 I got it!을 보낸다.

 

(8) 데이터 받기 recv() (55, 56행)

strLen = recv(hClntSock, m, sizeof(m), 0);
if (strLen == -1) ErrorHandling("read() error!"); //넘어온 메세지가 없으면 에러!

recv(SOCKET s, const char * buf, int len, int flags)

SOCKET s // 소켓에 대한 디스크립터

const char * buf // 수신받은 데이터를 저장하는 버퍼 주소

int len // 버퍼 길이

int flags // 어떤 작업을 할 것인지에 대한 명시

 

(9) 연결 종료 후 소켓 닫기 closesocket()

closesocket()을 통해 소켓을 닫아 통신 마무리

 

서버 측 코드의 주요 함수들의 기능에 대해서 알아보았으니 이제 예제코드에서 2,3,4번의 기능을 추가하기 위해 쓴 서버 측의 while(1)문 코드를 살펴보자.

 

1. 연결되면 Server측에서 Hello World!보내기 (1번까지는 열혈 TCP/IP 책에 수록된 예제 코드이다.)

2. 클라이언트가 사용자의 입력을 받아서 send 
3. 서버는 recv 받은 값을 print 
4. 서버는 제대로 받았다는 문자열을 클라이언트에게 send

5. 2,3,4번 반복하다 Ctrl+C를 누르면 종료

 

여기서 while(1)무한 루프를 쓴 이유는 5번처럼 Ctrl+C를 누르기 전까지 2,3,4번을 계속 반복하게 하기 위해서이다. 

처음에는 클라이언트가 사용자의 입력을 받아서 send한 값을 서버가 받아야 하므로 recv()함수로 메세지를 받아온다. 그리고 그 메세지의 길이를 확인하여 제대로 왔다는게 if문을 통해 확인이 되면 client로부터 온 메세지를 출력하고 제대로 받았다는 메세지를 보낸다.(send()) 이때 msg에는 "I got it!"이라는 문자열이 들어있다. 

 

이번에는 client.cpp를 살펴보자

서버와 다른 점은 클라이언트에서는 bind()와 listen(), accept()과정이 없고 connect()함수가 있다.

 

 

[client.cpp] 

 

server.cpp와 겹치는 코드부분의 설명은 생략하겠다.

 

(1) 전처리기 선언 및 전역변수, 함수 선언과 초기화(1~10행)

 

(2) 지역 변수 선언 및 인자 관련 필터링(12~24행)

 

(3) 소켓 생성 및 초기화(29~37행)

 

(4) 연결 요청 connect() (38행)

서버 측에서 bind(), listen()이 실행되었을 때 클라이언트가 서버 측에 연결 요청을 하는 과정이다.

connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr))

hSocket   // 소켓에 대한 파일 디스크립터

servAddr // 연결 요청을 보낼 서버 주소 정보를 지닌 구조체 변수의 포인터

addrlen  // servAddr 변수의 크기

 

(5) 데이터 받기 recv() (41~43행, 50행)

41행 : 서버과 연결이 성공적으로 이루어졌을 때 서버 측에서 보낸 Hello World 메세지 받아와서 출력하기

50행 : 

 

(6) 데이터 송신 send() (49행)

사용자의 입력을 받아서 서버로 메세지를 보낸다.

 

(9) 연결 종료 후 소켓 닫기 closesocket()

closesocket()을 통해 소켓을 닫아 통신 마무리

 

클라이언트 측 코드의 주요 함수들의 기능에 대해서 알아보았으니 이제 예제코드에서 2,3,4번의 기능을 추가하기 위해 쓴 클라이언트 측의 while(1)문 코드를 살펴보자.

1. 연결되면 Server측에서 Hello World!보내기 (1번까지는 열혈 TCP/IP 책에 수록된 예제 코드이다.)

2. 클라이언트가 사용자의 입력을 받아서 send 
3. 서버는 recv 받은 값을 print 
4. 서버는 제대로 받았다는 문자열을 클라이언트에게 send

5. 2,3,4번 반복하다 Ctrl+C를 누르면 종료

 

처음에는 클라이언트가 사용자의 입력을 받아서 메세지를 send한다.

scanf 대신 fgets을 사용한 이유는 띄어쓰기 때문이다. 

그리고 그 메세지의 길이를 확인하여 제대로 왔다는 것을 체크한 서버 측에서 보내는 "I got it!"메세지를 받아서 출력한다. 

 

자 이제 그러면 실습 화면을 보자

먼저 cmd 창에 다음과 같이 명령어를 입력한다. 여기서 listen()이 먼저 실행되어야 하기 때문에 server를 먼저 실행시킨다. 포트번호는 자신이 원하는 걸로 설정하되 client와 server가 연결되기 위해서는 같은 포트 번호를 입력해야 되고 client는 ipconfig를 통해 자신의 ip를 확인한 다음 그 ip를 입력해야 한다. 

 

server cmd창
client cmd창

 

 

서버와 클라이언트 연결이 성공하고 나면 다음과 같이 client창에 다음과 같이 뜬다.

client cmd창

 

 

그리고 다음과 같이 'hello my name is mini'라고 메세지를 입력했을 때 잘 전송이 되면 server로부터 I got it!이라는 문자를 받게 된다.

client cmd창

 

 

서버 측에 클라이어트로부터 온 메세지가 그대로 뜬다.

server cmd창

 

한번 더 해보자

client cmd창
server cmd창

이렇게 하다가 ctrl+c를 누르면 종료가 된다.

client cmd창
server cmd창

끝~!! :)

Comments