윤성우의 열혈 TCP/IP Ch.1

KSH·2022년 7월 25일
0

열혈강의 TCP/IP

목록 보기
1/4

윤성우의 열혈 TCP/IP Ch.1

원본 도서
http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788996094036


chapter1 네트워크 프로그래밍과 소켓의 이해

시작하기 전에

간단한 구조를 먼저 파악해야 전체적인 내용을 파악할 때 유리하다.
1단계: 소켓 생성 => socket 함수 호출
2단계: IP주소와 PORT번호 할당 => bind 함수 호출
3단계: 연결 요청 가능 상태로 변경 => listen 함수 호출
4단계: 연결요청에 대한 수락 => accept 함수 호출

줄 안맞는거 굉장히 불편... 밑에 테이블은 너무 귀찮...


1. 네트워크와 소켓

I. 네트워크란
네트워크는 서로 다른 두 컴퓨터의 연결되어 데이터를 주고 받을 수 있게 한 것이다.

II. 소켓(socket)
네트워크 상에서 연결된 두 컴퓨터의 데이터 송 수신에 이용할 수 있는 소프트웨어적 장치로 일종의 규격화에 해당한다.


2. 소켓 생성 함수(LINUX)

! 공부를 위한 작성이다보니 내용이 이후 챕터에서 반복될 수 있습니다.
windows 환경에서는 헤더들을 찾을 수 없다는 오류가 발생합니다 wsl 혹은 linux 환경에서 진행해야 오류가 발생하지 않습니다.

I. int socket()

소켓의 생성을 담당하는 함수, 송수신자의 전화기처럼 없으면 송수신 자체가 안됀다.
이후 chapter2에서 더 자세한 내용이 나옵니다.

  • 헤더
#include<sys/socket.h>
  • 반환
    if 성공 : 파일 디스크립터 반환
    if 실패 : -1 반환

  • 파라미터

int socket(domain, int type, int protocol)
int domain 		: 사용할 프로토콜
int type 		: TCP, UDP등 사용할 통신 방식
int protocol 	: 두 컴퓨터간 통신에 사용되는 프로토콜의 전달, 적절한 flag를 사용하여 전달한다.

II. int bind

생성된 소켓의 목적지를 묶어주는 함수
bind함수의 경우는 각 파라미터 모두가 소켓 프로그래밍에서 다 중요한 내용이므로 차후 더 자세하게 다룹니다. 네트워크 공부가 선행되지 않은 상태에서 간료화된 설명으론 이해가 힘들 가능성이 높습니다
이후 chapter3에서 더 자세하게 나옵니다.

  • 헤더

  • 반환
    if 성공 : 0 반환
    if 실패 : -1 반환

  • 파라미터

int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen)
int 				sockfd  : 주소정보(IP+PORT)를 할당할 소켓의 파일 디스크립터
struct sockaddr* 	myaddr  : 할당하고자 하는 주소정보를 지니는 구조체 변수의 주소 값
socklen_t 			addrlen : 두번째 인자로 전달된 구조체 변수의 길이정보

III. int listen

연결요청을 위해 통신을 개통하는 함수, 쉽게 말해, 가게가 오픈했으니 손님을 기다리는 상태와 유사하다. 아직 손님은 안온 것이다.

  • 헤더
#include<sys/socket.h>
  • 반환
    if 성공 : 0 반환
    if 실패 : -1 반환

  • 파라미터

int listen(int sockfd, int backlog)
int sockfd 	: int socket()에서 초기화된 소켓 전달
int backlog : 연결 가능한 큐의 대기 수 

IV. int accept()

연결요청에 대한 수락

  • 헤더
#include<sys/socket.h>
  • 반환
    if 성공 : 파일 디스크립터 반환
    if 실패 : -1 반환

  • 파라미터

int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen)
int sockfd				: int socket()에서 초기화된 소켓을 전달
struct sockaddr* addr	: 소켓을 구성하는 구조체 
socklen_t* addrlen		: 소켓에 제공할 주소를 전달하는 파라미터

3. 네트워크 호출의 순서

시작하기 전에 했던 내용과 완전히 일치한다.
두번봐도 세번봐도 좋다. 생각보다 공부과정에서 자주 잊어버렸다.

1단계: 소켓 생성 socket 함수 호출
2단계: IP주소와 PORT번호bind 함수 호출
3단계: 연결 요청 가능 상태로 변경listen 함수 호출
4단계: 연결요청에 대한 수락 accept 함수 호출

4. Windows Server에서의 Socket

!주의사항
visual studio 환경으로 진행하면 외부 의존성 환경을 UI단위로 조정하라고(설정을 통해 조정) 책에서 가이드 하지만 본인은 작성중

실행 환경
Visual Studio 2022
개발자 PowerShell로 빌드 및 실행

#pragma comment(lib, "ws2_32.lib")

위와 같은 코드를 작성하여 쉽게 처리했다.

winsock의 초기화

I. WSAStartup()

  • 헤더
#include<winsock2.h>
  • 반환
    if 성공: 0 반환
    if 실패: -1 반환

  • 파라미터

WORD mVersionRequested : 프로그래머가 사용하는 winsock의 버전정보 전달
LPWSADATA lpWSAData	   : WSADATA라는 구조체 변수의 주소값 전달
  • MAKEWORD flag : mVersionRequested에 제공되는 flag
 MAKEWORD(1,2) -> 주버전 1, 부버전 2
 MAKEWORD(2,2) -> 주버전 2, 부버전 2

5. 예시 코드: 서버의 hello world를 클라이언트에서 출력하기

Linux

  1. hello_server.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

void error_handling(char *message);

int main(int argc, char *argv[]){
    int serv_sock;
    int clnt_sock;

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size;

    char message[] = "Hello World!";
    
    if(argc != 2){
        printf("Useage : <port>\n", argv[0]);
        exit(1);
    }
    
    serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    //여기서 소켓이 생성된다.

    if(serv_sock == -1){
        error_handling("socket() error");
    }
    memset(&serv_addr, 0, sizeof(serv_addr));
    
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1)
        error_handling("bind() error");

    if(listen(serv_sock, 5)==-1)
        error_handling("listen() error");
    
    clnt_addr_size=sizeof(clnt_addr);
    clnt_sock=accept(serv_sock, (struct sockaddr*) &clnt_addr, &clnt_addr_size);
    
    if(clnt_sock==1)
        error_handling("accept() error");

    write(clnt_sock, message, sizeof(message));
    close(clnt_sock);
    close(serv_sock);
    return 0;
}

void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n',stderr);
    exit(1);
}
  1. hello_client.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

void error_handling(char *message);

int main(int argc, char *argv[]){
    //argc: main함수에 전달되는 데이터의 길이
    //argv: main함수에 전달되는 실질적인 데이터, 동적 배열의 포인터로 제공된다.
    int sock;
    struct sockaddr_in serv_addr;
    char message[30];
    int str_len;

    if(argc!=3){
        printf("Useage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
    
    sock=socket(PF_INET, SOCK_STREAM, 0);
    if(sock==-1)
        error_handling("socket() error");
    
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_addr.sin_port=htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
        error_handling("connect() error");

    str_len=read(sock, message, sizeof(message)-1);
    if(str_len==-1)
        error_handling("read() error");
    
    printf("Message from server : %s\n", message);
    close(sock);
    return 0;
}

void error_handling(char *message){
    fputs(message, stderr);//fputs는 문자열 배열을 출력할 때 사용한다
    fputc('\n', stderr); // fputc는 파일 입출력시에 사용한다.
    exit(1);
}

windows(Winsock)

  1. hello_server_win.c
#pragma comment(lib, "ws2_32.lib")
#include<stdio.h>
#include<stdlib.h>
#include<winsock2.h>

void ErrorHandling(char* message);

int main(int argc, char* argv[]){
    WSADATA wsaData;
    SOCKET hServSock, hClntSock;
    SOCKADDR_IN servAddr, clntAddr;

    int szClntAddr;

    char message[] ="HelloWorld";
    if(argc!=2){
        printf("Useage : %s <port>\n", argv[0]);
        exit(1);
    }

    if(WSAStartup(MAKEWORD(2,2), &wsaData)!=0)
        ErrorHandling("WSAStartup() error!");
        //소켓 라이브러리를 초기화하고 있다.

    hServSock=socket(PF_INET, SOCK_STREAM, 0);
        //소켓을 생성하고 IP주소와 PORT번호를 할당하고 있다.
    if(hServSock==INVALID_SOCKET)
        ErrorHandling("bind() error");

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family=AF_INET;
    servAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    servAddr.sin_port=htons(atoi(argv[1]));

    if(bind(hServSock, (SOCKADDR*) &servAddr, sizeof(servAddr))==SOCKET_ERROR)
        ErrorHandling("bind() error");

    if(listen(hServSock, 5)==SOCKET_ERROR)
        ErrorHandling("listen() error");
    
    szClntAddr=sizeof(clntAddr);
    hClntSock=accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
    if(hClntSock == INVALID_SOCKET)
        ErrorHandling("accept() error");

    send(hClntSock, message, sizeof(message),0);
    //send 함수 호출을 통해서 accept
    closesocket(hClntSock);
    closesocket(hServSock);
    WSACleanup();
    //종료전 소켓 라이브러리 해제
    return 0;
}

void ErrorHandling(char* message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
/*
    memset(void* ptr, int value, size_t num) 함수
    메모리를 특정 값으로 세팅하는 함수
    ptr : 세팅하고자 하는 메모리의 시작 주소, 포인터의 위치
    value : 메모리에 할당하고자 하는 실질적 데이터, int이지만 저장시 unsigned char로 저장된다
    num : 길이, 바이트 단위
*/
  1. hello_client_win.c
#pragma comment(lib, "ws2_32.lib")
#include<stdio.h>
#include<stdlib.h>
#include<winsock2.h>

void ErrorHandling(char* message);

int main(int argc, char* argv[]){
    WSADATA wsaData;
    SOCKET hSocket;
    SOCKADDR_IN servAddr;

    char message[30];
    int strLen;
    if(argc!=3){
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    if(WSAStartup(MAKEWORD(2,2), &wsaData))
        ErrorHandling("WSAStartup() error!");
    
    hSocket=socket(PF_INET, SOCK_STREAM, 0);
    if(hSocket==INVALID_SOCKET)
        ErrorHandling("socket() error");

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family=AF_INET;
    servAddr.sin_addr.s_addr=inet_addr(argv[1]);
    servAddr.sin_port=htons(atoi(argv[2]));

    if(connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr))==SOCKET_ERROR)
        ErrorHandling("connect() error");

    strLen=recv(hSocket, message, sizeof(message)-1,0);
    if(strLen==-1)
        ErrorHandling("read() error");
    printf("Message from server : %s\n", message);

    closesocket(hSocket);
    WSACleanup();
    return 0;
}

void ErrorHandling(char* message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

6. 실행화면

windows 기준으로 실행합니다.

profile
대충 개발자 비슷한거

0개의 댓글