본문 바로가기

CS지식/컴퓨터네트워크

소켓 프로그래밍 과제 1번

1번 문제: Client에서 문자를 서버에 보내면 서버는 클라이언트의 문자를 받아서 아스키코드로 리턴. 클라이언트의 화면에 다시 보여라. 

 

<개발 환경 구현>

Window에서 리눅스 프로그래밍을 구현하기 위해 아래의 블로그를 참고하여 리눅스 환경을 구현하였다.

https://jhnyang.tistory.com/441

 

[WSL]윈도우에서 리눅스 설치해 배시 사용하기 - 윈도우 우분투

안녕하세요 양햄찌 블로그 주인장입니다. 보니까 g++이 리눅스 기반이라서 소스가 여러개일 경우 다중컴파일이 안되더라고요 ㅎㅎ 터미널을 윈도우에서 bash로 바꿔줘야하는 일이 생겨,, 오늘은

jhnyang.tistory.com

https://jhnyang.tistory.com/entry/VScode-CC%EA%B0%9C%EB%B0%9C-%EB%B9%84%EC%A3%BC%EC%96%BC%EC%8A%A4%ED%8A%9C%EB%94%94%EC%98%A4%EC%BD%94%EB%93%9C-Cmake%EB%A1%9C-%EB%B9%8C%EB%93%9C%ED%95%98%EA%B8%B0

 

VSCode 비주얼스튜디오코드 리눅스 환경 개발 - WSL 확장플러그인 사용하기

안녕하세요 양햄찌 블로그 주인장입니다. ㅎㅎ 요새 계속 비주얼스튜디오코드 세팅 관련 포스팅을 올리고 있는데 언제쯤 끝이 날지... VSCode에서 리눅스 개발 환경 세팅하기 오늘은 VSCode에서 윈

jhnyang.tistory.com

<소켓 프로그래밍 함수 이해>

 

 소켓 프로그래밍 절차는 위와 같다. 클라이언트 소켓은 socket() -> connect() -> send()/recv() -> close() 절차로 통신을 하고 서버소켓은 socket() -> bind() -> listen() -> accept() -> send/recv() -> close() 의 단계를 거친다. 클라이언트 소켓에 비교했을 때 listen와 accept 함수가 추가된다.

 

1. 주소 설정

//client.c
    struct sockaddr_in csa;
    memset(&csa, 0, sizeof(csa));
    csa.sin_family = AF_INET;
    csa.sin_addr.s_addr = inet_addr(IPADDR);
    csa.sin_port = htons(11234);
 
//server.c
    struct sockaddr_in ssa;
    memset(&ssa, 0, sizeof(ssa));
    ssa.sin_family = AF_INET;
    ssa.sin_addr.s_addr = htonl(INADDR_ANY);
    ssa.sin_port = htons(11234);

sockaddr_in은 IP 주소, 포트 번호와 같은 주소 정보를 가지는 구조체이다. memset 함수를 통해 구조체를 초기화 한다.

 

.sin_family:

AF_UNIX(유닉스 도메인 소켓) : 같은 호스트에서 통신한다. 따라서 TCP/IP를 사용하지 않고 실행파일의 경로로 통신

AF_INET(인터넷 소켓) : 인터넷을 통해 다른 호스트와 통신하는 소켓

 

.sin_addr.s_addr : 통신할 대상의 IP 주소를 저장한다.

.sin_port :  통신할 대상의 포트번호를 저장한다.

 

- 소켓을 통해 데이터를 송수신할 때, 호스트 시스템의 바이트 순서를 네트워크에서 사용하는 바이트 형태로 변환해주는 함수

htonl() : host to network long // ip주소와 같이 4바이트의 경우 사용

htons() : host to network short // 포트 주소와 같이 2바이트인 경우 사용

 

 서버에서는 클라이언트의 주소를 알 수 없기 때문에 htonl(INADDR_ANY)라고 지정한다. INADDR_ANY는 어떤 주소이든 상관없다는 의미이다.

 

 클라이언트 프로그래밍에서 서버의 주소를 127.0.0.1로 설정하였다. 이는 루프백 주소라고 한다. 소켓 프로그래밍에서 서버 코드는 클라이언트 코드와 다른 컴퓨터에서 실행된다. 그러나 루프백 주소를 사용하면 컴퓨터가 고정 ip 주소를 가진 것 처럼 만들어 준다. 클라이언트와 서버를 같은 컴퓨터에서 실행하여도 인터넷을 통해 데이터를 주고 받는 것 처럼 만들어 준다. 

 

2. Socket() : 소켓을 여는 기본함수

 sys/socket.h 라이브러리에 있다. int socket(int domain, int type, int protocol) 형태로 사용한다.

 
  ss = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
 

domain : 도메인, 유닉스 도메인(PF_UNIX) 과 인터넷 도메인 (PF_INET)

type : 통신 방식, TCP(SOCK_STREAM)와 UDP(SOCK_DGRAM)

protocol : 프로토콜, TCP(IPPROTO_TCP)와 UDP(IPPROTO_UDP)

 

리턴 값 : 소켓 생성에 성공하면 소켓 디스크립터를 반환한다. 실패하면 -1 반환

 

3. bind() : 서버에서 생성된 소켓을 포트와 연결할 때 사용하는 함수

 sys/socket.h 라이브러리에 있다. 형식은 int bind(int s, const struct sockaddr *addr, socklen_t addrlen)

 
 bind(ss,(struct sockaddr *) &ssa, sizeof(ssa))
 

s : 소켓 기술자. s =socket()이 성공하는 경우에 사용할 수 있다.

addr : 접속하려는 호스트의 IP 주소 및 포트번호가 들어있는 sockaddr 구조체

addrlen : sockaddr 구조체의 길이

 

반환값 : 0(성공), -1(실패)

 

4. listen() 함수 : 소켓들을 사용할 수 있는 상태로 활성화시키는 함수

sys/socket.h 라이브러리에 있다. 형식은 int listen(int s, int q_len)

 
 listen(ss,10)
 

s : 소켓 기술자

q_len : 소켓 대기 큐queue 의 길이를 나타낸다. 즉, 접속 가능한 클라이언트 수를 의미한다. q_len의 길이 만큼 소켓 접속을 허용한다. 

 

반환값 : 성공하면 0, 실패하면 -1 반환

 

5. connect() 함수 : 클라이언트가 서버에 연결을 시도할 때 사용하는 함수

 

sys/socket.h 라이브러리에 있다. 형식은 int connect(int s, const struct sockaddr *addr, socklen_t addrlen)

 
  connect(cs, (struct sockaddr *) &csa, sizeof(csa))
 

s : 소켓 기술자. s = socket() 이 성공하는 경우에 사용할 수 있다.

addr : 접속하려는 IP 주소와 포트번호가 들어있는 sockaddr 구조체

addrlen : sockaddr 구조체의 길이

 

리턴 값 : 0(성공), -1(실패)

 

6. accept() 함수 : 클라이언트의 연결을 받아들이는 함수.

 

sys/socket.h 라이브러리에 있으며 형식은 int_accept(int s, const struct sockaddr *addr, socklen_t addrlen)

 
 sa = accept(ss,0,0);
 

s : 소켓 기술자

addr : 연결된 클라이언트의 주소및 포트번호가 저장된다.

addrlen : addr 의 크기

 

반환값 : 성공하는 경우 기존의 소켓 기술자 s와 다른 새로운 소켓 기술자 new_s 가 반환된다. 실패하면 -1 반환

 

7. send() 함수 : 소켓을 통해 데이터를 보낼 때 사용하는 함수

 sys/socket.h 라이브러리에 있다. 형식은 int send(int s, const void *buf,size_t len, int flags) 이다.

   
 send(sa,"test", 5, 0);
 

s : 소켓 기술자

buf : 전송할 데이터를 담고 있는 메모리 주소

len : 데이터의 크기

flags : 데이터를 보내는 방법. 0을 사용하면 일반적인 데이터 전송

 

반환값 : 실제로 보낸 데이터의 크기를 반환 len 보다 적은 값이 반환된다면 모든 데이터를 보내지 못햇다는 의미이다. 데이터를 아예 못보내면 -1 반환.

 

8. recv() 함수 : 소켓을 통해 데이터를 받을 때 사용하는 함수

sys/socket.h 라이브러리에 있다. 형식은 int recv(int s, const void *buf, size_t len, int flags)

 
  recv(cs, buf, 5, 0);
 

s: 소켓 기술자 

buf : 받은 데이터를 저장할 메모리 주소

len : buf의 길이

flag : 데이터를 주고받는 방법

 

반환값 : 받은 데이터의 크기를 반환한다. 수신 오류일 경우 -1 반환

 

 

<문제1 해결 코드>

char a에 문자 하나를 사용자의 입력을 받아 저장한다. a를 서버에 전송하고 서버는 받은 a를 int b에 저장하여 아스키 코드 값으로 변환한다. 즉, b에는 문자 a의 아스키코드 값이 저장된다. 서버는 b를 다시 클라이언트에게 전송하고, 클라이언트는 데이터를 받아 화면에 출력한다.

- 서버 코드

//sol1_server.c

#include <stdio.h>
#include <string.h>// memset 등
#include <unistd.h> //exit, sockaddr_in 등 사용
#include<stdlib.h>
#include <sys/socket.h> // 소켓 관련 라이브러리
#include <arpa/inet.h> // 아파넷 관련 함수

// 에러 발생 시 내용 출력하는 에러 핸들러
void error_handling(char * message){
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}

void main(){
    int ss, sa;
    char a;
    int b;
    struct sockaddr_in ssa;

// ssa 구조체 초기화 후 통신타입, ip주소, 포트번호 지정
    memset(&ssa, 0, sizeof(ssa));
    ssa.sin_family = AF_INET;
    ssa.sin_addr.s_addr = htonl(INADDR_ANY); // 어떤 주소이든 상관없다.
    ssa.sin_port = htons(11234); // 포트번호 11234

// tcp 통신 소켓 생성
    ss = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(ss == -1) // 소켓 생성 실패할 경우 에러 메세지 출력
        error_handling("socket error");

//소켓과 서버 주소를 연결한다. 실패할 경우 에러 메세지 출력
    if(bind(ss,(struct sockaddr *) &ssa, sizeof(ssa))==-1){
        error_handling("bind error");
    }
   
// 서버는 무한루프를 돌면서 클라이언트의 요청이 있을 때 마다 서비스해준다.

    while(1){
   
    // 소켓을 사용가능한 상태로 활성화. 소켓 대기열의 크기는 10.
        if(listen(ss,10)==-1){
            error_handling("listen error");
        }

//클라이언트로부터 요청이 오면 연결을 수락한다.
        sa = accept(ss,0,0);
        if(sa ==-1){
            error_handling("accept error");
        }

// 클라이언트로부터 문자열 데이터를 받아 a에 저장한다.
        recv(sa, &a, sizeof(a), 0);
        b = a; // char 데이터를 int 데이터에 저장하여 아스키코드값으로 변환
       
        // 아스키코드 값이 담긴 데이터를 클라이언트에게 전송한다.
        send(sa,&b,sizeof(b),0);
        close(sa); // 클라이언트와의 통신 소켓을 닫는다.
    }
   
    }

 

- 클라이언트 코드

//sol1_client.c

#include <stdio.h>
#include <string.h> // memset 등
#include <unistd.h>//exit, sockaddr_in 등 사용
#include<stdlib.h>
#include <sys/socket.h> // 소켓 관련 라이브러리
#include <arpa/inet.h> // 아파넷 관련 라이브러리

#define IPADDR "127.0.0.1"

// 에러 발생 시 내용 출력하는 에러 핸들러
void error_handling(char * message){
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}

void main(){
    int cs;
    char a;
    int b;
    struct sockaddr_in csa;

//csa 구조체 초기화 후 통신타입, ip주소, 포트번호 지정
    memset(&csa, 0, sizeof(csa));
    csa.sin_family = AF_INET;
    csa.sin_addr.s_addr = inet_addr(IPADDR); // 루프백 주소인 127.0.0.1 지정
    csa.sin_port = htons(11234); // 포트번호 11234

// tcp 통신 소켓 생성
    cs = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(cs ==-1){ // 소켓 생성 실패할 경우 에러 메세지 출력
        error_handling("client socket error");
    }
    // 서버와 소켓 연결한다. 연결 실패 시 에러 메세지 출력
    if(connect(cs, (struct sockaddr *) &csa, sizeof(csa))==-1){
        error_handling("connect error");
    }

    printf("enter char : "); // 문자를 입력받는다.
    scanf("%c", &a);

    // 입력받은 문자를 서버에 전송한다.
    send(cs,&a,sizeof(a),0);

    // 아스키코드 값으로 변환된 데이터를 받아 int b에 저장한다.
    recv(cs,&b,sizeof(b),0);

    printf("client recieve [%d]\n", b); // 정상적으로 왔는지 출력

    // 소켓을 닫는다.
    close(cs);
}

 

 

<실행 화면>