TCP/IP (소켓 프로그래밍에 대한 개념 위주)

Hamji·2022년 2월 25일
3

TCP/IP (소켓 프로그래밍에 대한 개념 위주)

목차

  1. 개요 (네트워크에 관한 개념 정리)
    1. Packets and Protocol
    2. OSI 7 Layer vs TCP/IP
    3. 주소에 관하여
    4. Port Number
    5. Socket
    6. Client / Server
  2. TCP/UDP
  3. Implementation of TCP Server/Client
  4. Implementation of UDP Server/Client
  5. Socket Option
  6. Multiprocess Server
  7. Multithread Server
  8. IO MultiPlexing
  9. Broadcast & Multicast

1.개요

1. Packt and Protocol

  1. Protocol
    1. 사람과 사람이 대화할 땐 두 사람이 공용된 언어를 사용해야 대화를 했다고 볼 수 있다. 이때 언어가 프로토콜의 역할을 한다.
    2. 컴퓨터와 컴퓨터 간에 통신을 할 때 원활히 통신하기 위하여 정한 규약이다.
    3. e.g) TCP, UDP, IP, HTTP
    4. protocol
  2. packet
    1. 네트워크 상에서 패킷이란 더 큰 메시지 중 작은 조각(Segment)라는 뜻을 가지고 있다.
    2. TCP/IP 네트워크 상에서 작은 양의 data를 뜻한다.

2. osi7 layer vs TCP/IP

  1. OSI 7 Layer

    1. ISO 표준으로 계층구조를 가짐으로써 데이터 자체의 흐름을 구간별로 움직임을 알 수있다.
    2. troubleshooting 과 다른 벤더와의 호환성 면에서 좋다.
    이름역할예제
    ApplicationEnd user LayerHTTP, HTP, IRC
    PresentaionSyntax LayerSSL, SSH, IMAP, FTP
    SessionSynch & send to PORTAPI's Sockets
    TransportEnd-to-End ConnectionTCP, UDP
    NetworkPacketsIP, ICMP, IPSec
    Data LinkFramesEthernet, Switch, Bridge
    PhysicalPhysical structureCoax, Fiber, Wireless
  <br>

  1. TCP/IP

    1. 현재 인터넷에서 컴퓨터들이 사용하는 통신 규약의 모임
    2. H/W, OS, 접속매체에 관계없이 동작할 수있는 개방성을 가진다.
    이름역할예제
    ApplicationTo allow access to network resourcesHTTP, FTP, DNS
    Transportto provide reliable process to process message delivery and error deleveryTCP, UDP
    InternetTo move Packet src to destARP, ICMP
    Network InterfaceTransmission for two device on the same networkEthernet, ATM ...
  1. 결론
  1. 네트워크 작업의 데이터 흐름을 추상화 및 계층화해서 보면 좋은 이유
     - 문제가 일어나면 다른 계층에는 관여하지 않고 해당 문제가 일어나는 특정한 계층만 고칠 수 있다.
       - 예를 들어 상하 구조를 가지고 있기 때문에 1-4단계까지 완벽하고 5단계만 문제가 있다면 5단계만 이슈가 나는 부분을 보면 된다.
     - 통신이 일어나는 과정을 단계별로 파악할 수 있다.     

OSITCP/IP

3. 주소에 관하여...

  • 우리가 누군가에게 편지를 보낼 때 그 사람이 어디에 사는지 주소를 알아야 한다.
  • 마찬가지로 컴퓨터에서도 End User 간에 통신을 하기 위해서는 이 End User가 어디에 있는지, 그리고 어떤 경로로 가야하는 지에 대한 정보가 있어야 할 것이다.
  • 위에서 나오다 싶이 network의 데이터 흐름을 계층화 한다고 했는데 그래서 각 계층별로 가지고 있는 주소가 있다.
  • 계층별 Address
계층ADDRESS예제
DatalinkMac AddressE0-15-4D-90-7A-DA
NetworkIP Address172.168.42.3
TransportPort Number22
  • 그리고 이를 통해 네트워크가 어디로 갈지 길을 찾는데 도움을 준다.

4. PORT number

  1. 운영 체제 통신에서의 종단점을 의미한다.
  2. OSI 7 Layer에서 Transport 계층에서 동작하는 TCP/UDP 프로토콜에서 application이 상호 구분하기 위하여 사용하는 번호이다.
  3. IP 내에서 프로세스를 구분하기 위해 사용하며, 각 프로토콜의 데이터가 내부의 논리적 통로를 따라 흐른다.
  4. 네트워크를 통해 데이터를 주고받는 프로세스를 식별하기 위해 호스트 내부적으로 프로세스가 할당받는 고유한
  5. 16 bit
    1. 총 65536 개의 포트번호가 존재 가능
    2. 3가지로 분류된다.
      1. Well-known ports : 0 ~ 1023
      2. Registered Ports : 1024 ~ 49151
      3. Dynamic Ports : 49152 ~ 65535
  6. 사용법
    1. IP address 뒤 콜론을 붙이고 적는다
    2. e.g) 192.168.100.1:2222
  7. 포트 포워딩
    1. 외부에서 공유기 내부의 IP에 특정 PC를 지정할 수 있도록 정의해주는 작업

5. Socket

  1. 컴퓨터 네트워크를 경유하는 프로세스 간 통신의 종착점이다 .
  2. 네트워크 응용 프로그램은 소켓을 통하여 통신망으로 데이터를 송수신한다.
    1. 두 소켓이 연결되면 서로 다른 프로세스끼리 데이터를 전달할 수 있다.
  3. 응용프로그램과 TCP/IP 간 인터페이스 역할
  4. 보통 소켓은 TCP 혹은 UDP을 이용하여 통신한다.
  5. Socket의 종류
    1. Stream
      1. TCP를 사용
      2. 양방향으로 바이트 스트림을 전송할 수 있는 연결 지향형 소켓
      3. 확실히 보냈다는 보장성 있음
      4. 대용량 데이터 전송에 적합
      5. 약간의 오버헤드 존재
    2. Datagram
      1. UDP를 사용
      2. 비 연결형 소켓
      3. 메시지 크기에 약간의 제한이 있음
      4. 보장성 X
      5. 데이터를 잃어 버려도 오류가 나지 않는다.
    3. RAW
      1. Transport 계층을 우회하여 바로 애플리케이션으로 송신하는 소켓
      2. 필터를 거치지 않으므로 원형 그대로의 패킷 확인 가능

2. TCP / UDP

1. UDP

1. 개념

  1. User DatagramProtocol

  2. 비 연결성

  3. 오류 검사를 거의 수행하지 않는다.(check sum 정도만 함)

  4. 프로세스 간 통신을 제공 이외에 IP 서비스에는 아무것도 추가하지 않는다.

  5. 최소한의 오버헤드

  6. 소규모 메시지, 신뢰성이 중요하지 않은 경우(속도가 중요한 서비스)에 주로 사용

  7. 멀티캐스팅 애플리케이션에 편함

  8. 헤더

    UDP Header



2. TCP

1. 개념

  1. 전송 제어 프로토콜 (TransMission Control Protocol)

  2. IP에 연결 지향 기능 및 신뢰성을 추가

  3. 신뢰할 수 있는 스트림 전달 서비스

  4. 손실이나 중복없이 데이터 전송 보장

  5. 클라이언트(대화를 시작할 프로세스)와 서버(피어가 시작될 때까지 기다리는)가 지정되어야 함

  6. 통신 과정

    1. 3-Way Handshaking
    2. 4-Way Handshaking

    TCP sequence

  7. 헤더 구조

    TCP Header

2. TCP 프로그램 흐름

  • TCP는 보통 서버와 클라이언트간의 통신이 주로 이루어짐
  • 서버 실행 흐름
    • Socket 생성
    • Socket Binding
    • Socket Listen
    • Socket Connect
    • Send or Receive Data
  • 클라이언트 실행 흐름
    • Socket 생성
    • Socket Connect
    • Send or Receive Data


3. UDP server implementation


1. 개요

  • 위에서 공부한 UDP 개념과 C언어의 라이브러리를 이용하여 UDP를 이용한 간단한 서버, 클라이언트를 만들어 볼 것이다.
  • UDP 에서는 TCP와 달리 연결 요청 및 그에 따른 수락요청이 없기 때문에 서버 클라이언트라는 표현은 그냥 서비스를 제공한다는 의미에서 서버라고 했다
  • 멀티 프로세스와 멀티 쓰레드를 이용한 서버는 6 장 7장에서 TCP 를 이용하여 만들 것이기에 일단 이 장에서는 1:1 서버 클라이언트 프로그램으로 작성하고자 한다.

2. 서버 코드


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

#define BUF_SIZE 128

void error_handling(char *message);

int main(int argc, char*argv[]){
    
     //소켓 디스크립터
    int serv_sock;
    
    //보낼 메시지를 담는 배열형태의 변수
    char message[BUF_SIZE];
    
    //클라이언트로 부터 수신 받은 문자열 길이
    int str_len;
    
    socklen_t clnt_adr_sz;
    
    struct sockaddr_in serv_adr, clnt_adr;
    
    //실행파일 경로 /port 번호 입력받기
    if (argc != 2) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
    
    //udp 소켓 생성
    serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
    
    if (serv_sock == -1) {
        error_handling("udp socket creation error");
    }
    
    //서버 주소 정보 초기화
    memset(&serv_adr , 0 , sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));
    
    //서버 주소 정보 할당
    if (bind(serv_sock , (struct sockaddr *)&serv_adr , sizeof(serv_adr)) == -1) {
        error_handling("bind creation error");
    }
    
    
    //
    while (1) {
        
        clnt_adr_sz = sizeof(clnt_adr);
        //클라이언트로부터 널 문자를 제외하고 문자열 수신
        str_len = recvfrom(serv_sock, message , BUF_SIZE, 0,
                           (struct sockaddr*)&clnt_adr , &clnt_adr_sz);
		// write 를 이용해 받은 만큼 출력
		write(1, message, str_len); 
    }
    
    //udp 소켓 종료
    close(serv_sock);
    
    return 0;
}

//에러처리
void error_handling(char *message){
    
    fputs(message , stderr);
    fputc('\n' , stderr);
    exit(1);
    
}


3. 클라이언트 코드


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

#define BUF_SIZE 128
void error_handling(char *message);

int main (int argc, char *argv[]){
    
    //소켓 디스크립터
    int sock;
    
    //보낼 메시지를 담는 배열형태의 변수
    char message[BUF_SIZE];
    
    //서버로 부터 받은 메시지 길이
    int str_len;
    
    socklen_t adr_sz;
    
    //주소 구조체
    struct sockaddr_in serv_adr, from_adr;
    
    //인자로 연결할 ip 주소 / port 번호
    if(argc!=3){
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
    
    //UDP 소켓 생성
    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock == -1) {
        error_handling("socket() error");
    }
    
    //서버주소 정보 초기화
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_adr.sin_port=htons(atoi(argv[2]));
    
    
    
    while (1) {
        
        fputs("insert message(q to quit) :",stdout);
        
        fgets(message , sizeof(message), stdin);
        
        
        if (!strcmp(message,  "q\n") || !strcmp(message, "Q\n")) {
            break;
        }
        
        /*
            클라이언트의 주소가 자동으로 할당됨
            입력받은 문자열을 서버로 널문자를 제외하고 송신
            데이터를 전송할 때마다 반드시 목적지의 주소 정보를 별도로 추가해야한다.
            (tcp 처럼 연결된 상태가 아니기 때문에.)
         */
        sendto(sock, message, strlen(message) , 0  ,
               (struct sockaddr*)&serv_adr , sizeof(serv_adr));
    }
    //udp 소켓 종료
    close(sock);
    
    return 0;
    
    
    
}


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

4. 결과창

결과



4. TCP server implementation


1. 개요

  • 위에서 공부한 TCP 개념과 C언어의 라이브러리를 이용하여 TCP를 이용한 간단한 서버, 클라이언트를 만들어 볼 것이다.
  • 멀티 프로세스와 멀티 쓰레드를 이용한 서버는 6 장 7장에서 TCP 를 이용하여 만들 것이기에 일단 이 장에서는 1:1 서버 클라이언트 프로그램으로 작성하고자 한다.

2. 서버 코드


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>

#define MAXBUF 256
#define EQ ==

int main()
{
        int ssock, csock; // 소켓 스크립트 정의
        int clen;
        struct sockaddr_in client_addr, server_addr; // 주소 구조체
        char buf[MAXBUF] = "Message from Server"; // 클라이언트에 보내줄 문자

        // 서버 소켓 생성

        if ((ssock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        {
                perror("socket error :");
                exit(1);
        }

        clen = sizeof(client_addr);
        // 주소 구조체에 주소 지정
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        server_addr.sin_port = htons(1000);

        // 사용할 포트로 1000번 포트 사용
        //bind() 사용해서 서버소켓의 주소 설정

        if (bind(ssock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
        {
                perror("bind error: ");
                exit(1);
        }

        //위에서 지정한 주소로 크라이언트 접속을 기다림
        if (listen(ssock, 8) < 0)
        {
                perror("listen error : ");
                exit(1);
        }

        while (1)
        {
                // 클라이언트가 접속하면 접속을 허용하고 , 클라이언트 소켓을 생성
                csock = accept(ssock, (struct sockaddr *)&client_addr, &clen);
                //클라이언트로 buf에 있는 문자열 발송
                if (write(csock, buf, MAXBUF) <= 0)
                        perror("write error: ");
                // 클라이언트 소켓을 닫음
                close(csock);
        }

        return 0;
}

3. 클라이언트 코드


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>

#define MAXBUF 256
#define EQ ==


int main()
{
        int ssock;
        int clen;

//소켓 생성
        struct sockaddr_in server_addr;
        char buf[MAXBUF];
        if((ssock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) <0){
                perror("socket error : ");
                exit(1);
        }

        clen = sizeof(server_addr);
//소켓에 접속할 주소 지정
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        server_addr.sin_port = htons(1000);

// 지정한 주소로 접속
        if(connect(ssock, (struct sockaddr *)&server_addr, clen)<0){
                perror("connect error : ");
                exit(1);
        }

        memset(buf, 0, MAXBUF);

//서버에서 전송하는 문자열을 받음
        if(read(ssock, buf, MAXBUF) <= 0){
                perror("read error : ");
                exit(1);
        }

// 소켓 닫음
         close(ssock);
// 받아온 문자열 출력

        printf("\nread : %s\n\n", buf);
        return 0;

}

출처

4. 결과창

결과창



5. Socket Option


1. 개요

  • 네트워크 환경은 복잡 다양하기 때문에 예측하기 힘들다 그래서 네트워크 프로그램 종류에 따라서 소켓 세부사항을 조절할 때가 있다.
  • 이 때 getsocketopt() 와 setsocketopt() 두개의 함수를 사용하여 소켓값을 조정한다 get은 값을 불러오고 set은 값을 설정한다.

#include <sys/types.h>
#include <sys/socket.h>

int getsockopt(int  s, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int s, int  level,  int  optname,  const  void  *optval, socklen_t optlen);
  • s: 소켓 번호
  • level : 소켓의 레벨러 어떤 레벨의 소켓정보를 가져오거나 변경할 것인지 명시
    • SOL_SOCKET
    • IPPROTO_TCP
    • 이 둘중 하나를 사용한다.
  • optname : 설정을 위한 소켓옵션 번호
  • optval : 설정값 저장을 위한 버퍼 포인터
  • optlen : optval 버퍼 크기

6. Multiprocess Server


1. 개요

  • 서버는 보통 둘 이상의 클라이언트에게 서비스를 제공한다. 그래서 보통 아래와 같은 구조를 가지고 있다.

다중접속서버

  • 그리고 아래와 같은 순서로 일을 처리하게 된다.
    1. 서버(부모 프로세스) accept 를 이용해 연결 요청을 수락
    2. 이 떄 얻은 소켓의 파일 디스크럽터를 자식 프로세스를 생성해 넘긴다.
    3. 자식 프로세스는 전달 받은 파일 디스크립터를 바탕으로 서비스를 제공한다.
      • 클라이언트로 부터 받은 데이터를 읽고 다시 클라이언트에게 보낸다.

2. 서버 코드


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30

//에러처리
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

//시그널 핸들러
void read_childproc(int sig){
    pid_t pid;
    int status;
    //임의의 자식 프로세스가 종료되길 기다림
    pid = waitpid(-1, &status , WNOHANG);
    
    //자식 프로세스가 정상 종료될때 true 반환
    if (WIFEXITED(status)) {
        printf("removed proc id : %d \n" , pid);                    //종료된 자식프로세스
        printf("removed proc send : %d \n" , WEXITSTATUS(status));  //전달된 값
    }
}

int main(int argc, char *argv[])
{
    //서버, 클라이언트 소켓
    int serv_sock, clnt_sock;
    //소켓에 할당할 주소 정보 구조체
    struct sockaddr_in serv_adr, clnt_adr;
    
    //프로세스 id
    pid_t pid;
    //시그널 정보 구조체
    struct sigaction act;
    
    //클라이언트 소켓 길이
    socklen_t adr_sz;
    
    int str_len , state;
    //전달할 데이터를 담을 char 배열
    char buf[BUF_SIZE];
    
    
    //실행파일의 경로 + port 를 인자로 받는다.
    if (argc != 2) {
        printf("usage : %s <port> \n" , argv[0]);
        exit(1);
    }
    
    //sigaction 구조체 초기화
    act.sa_handler = read_childproc;    //시그널 핸들러는 read_childproc 함수포인터
    sigemptyset(&act.sa_mask);          //0으로 초기화
    act.sa_flags = 0 ;                  //0으로 초기화
    
    //자식 프로세스 종료시 read_childproc 함수 호출되게 운영체제에게 등록
    state = sigaction(SIGCHLD,&act,0);  //운영체제에 시그널 등록
    
    //IPv4, TCP 소켓 생성
    serv_sock = socket(PF_INET,SOCK_STREAM,0);
    
    
    //서버주소 정보 초기화
    memset(&serv_adr , 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));
    
    
    //서버 주소정보를 기반으로 주소 할당
    if (bind(serv_sock, (struct sockaddr*)&serv_adr , sizeof(serv_adr)) == -1) {
        error_handling("bind() error...");
    }
    
    
    //서버가 클라이언트의 연결 요청 준비를 완료
    if (listen(serv_sock , 5) == -1) {
        error_handling("listen() error...");
    }
    
// <-------------------- 서버 준비 완료 --------------------------->


    while (true) {
        
        adr_sz = sizeof(clnt_adr);
        clnt_sock = accept(serv_sock , (struct sockaddr*)&clnt_adr , &adr_sz);
  
        if (clnt_sock == -1) {
            continue;
        }else{
            
            puts("new client connected...");
            
            //자식 프로세스 생성
            pid = fork();
            
            //프로세서 생성 오류 처리
            if (pid == -1) {
                continue;
            }
            
            //자식프로세스 인경우
            if (pid == 0) {
                close(serv_sock);   //자식 프로세스는 서버소켓 파일 디스크립터 필요없음.
                while (1) {    
                    //클라이언트 에서 보낸 데이터 읽어서
                   str_len =  read(clnt_sock, buf, BUF_SIZE);
                    if (str_len == 0) {
                        printf("str_len == 0 \n" );
                        break;
                    }
                    //클라이언트에 다시 에코..(전달..)
                    write(clnt_sock , buf, str_len);
                }
                
                //클라이언트에 대한 에코 서비스를 모두 완료 했으므로 소켓 연결 종료
                close(clnt_sock);
                puts("client disconnected...");
                //자식 프로세스 종료
                return 0;
                
            }else{
                //부모 프로세스 경우 클라이언트 소켓은 필요없다. - 자식만 필요
                close(clnt_sock);
            }     
        }
    }
    
    //부모 프로세스에서 서버 소켓 종료
    close(serv_sock);
	return 0;
}

3. 클라이언트 코드


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

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sock;
	char message[BUF_SIZE];
	int str_len;
	struct sockaddr_in serv_adr;

	if(argc!=3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);   
	if(sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));
	
	if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("connect() error!");
	else
		puts("Connected...........");
	
	while(1) 
	{
		fputs("Input message(Q to quit): ", stdout);
		fgets(message, BUF_SIZE, stdin);
		
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
			break;

		write(sock, message, strlen(message));
		str_len=read(sock, message, BUF_SIZE-1);
		message[str_len]=0;
		printf("Message from server: %s", message);
	}
	
	close(sock);
	return 0;
}

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

4. 결과창

멀티프로세스 결과창



7. Multithread Server


1. 개요

  • 멀티 프로세스 기반의 서버는 아래와 같은 단점을 가지고 있다.

    1. 프로세스 생성이라는 운영체제에서 부담스러운 작업과정
    2. 두 프로세스 사이에서의 데이터 교환을 위해 별도의 IPC 기법을 적용해야함
    3. 프로세스가 많아지면 초당 적게는 수십번에서 많게는 수천번까지 일어나는 문맥교환 부담

  • 쓰레드의 사용

    1. 멀티프로세스의 특징을 유지하면서 단점을 극복하기 위해 사용
    2. 멀티프로세스보다 경량화
    3. 쓰레드 사이에서 데이터 교환에 특별한 기법이 필요치 않음
    4. 프로세스는 운영체제 관점에서 별도의 실행 흐름을 구성 단위 이지만
      , 쓰레드는 프로세스 관점에서 별도 실행흐름을 구성하는 단위임
  • 멀티쓰레드의 메모리 구조와 멀티프로세스의 메모리 구조

    • 멀티프로세스

      멀티프로세스구조


  • 멀티 쓰레드

    멀티쓰레드 구조


2. 중요 개념

  • 쓰레드의 생성과 실행 흐름 구성

    • 쓰레드는 쓰레드만의 main 함수를 별도로 정의해야 한다.

    • 그리고 이함수를 시작으로 별도의 실행 흐름을 형성해 줄것을 OS 에세 요청해야 하는데

      • pthread_create함수를 이용한다.
        • thread : 생성할 쓰레드 ID 장을 위한 변수 주소 값전달
        • attr : 쓰레드에 부여할 특성 정보를 전달을 위한 매개변수 NULL 전달 시 기본적인 특성의 쓰레드 생성
        • start_routine : 쓰레드의 main 함수 역할을 하는 별도 실행흐름의 시작이 되는 함수 주소값 전달
        • arg : 세번째 인자를 통해 등록된 함수가 호출될 때 전달할 인자의 정보를 담고 있는 변수의 주소 값 전달
    • 쓰레드의 실행 형태를 그림으로 표현하면 아래와 같다.
      쓰레드의 실행 흐름

    • 쓰레드는 메인 함수가 종료하여 프로세스가 전체 소멸되어 버리면 쓰레드도 함께 종료된다.
      쓰레드 종료

    • 이럴 땐 thread_join 함수를 이용하여 쓰레드의 종료를 대기해준다.
      thread_join


  • mutex
    • Mutual Exclusion 의 줄임말로 쓰레드의 동시 접근을 허용하지 않는다는 의미가 있다.
    • 쓰레드의 동기 접근에 대한 해결책으로 주요 사용된다.
    • 아래의 함수들을 이용하여 임계영역의 보호를 위해 자물쇠 시스템을 적용한다.
    • lock 과 unlock 함수를 이용하여 임계영역의 시작과 끝을 감싼다.
    • 이것이 임계영역에 자물쇠 역할을 하면서 해당 임계영역에 대해 둘 이상의 쓰레드가 접근을 허용하지 못하게 만든다.
#include <pthread.h>

// 뮤택스 생성시
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); 
// 뮤택스 소멸시
int pthread_mutex_destroy(pthread_mutex_t *mutex)
// 임계영역 잠그기, 풀기 
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);  // 성공 시 0, 실패 시 0 이외의 값 반환

3. 서버 코드


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>

#define BUF_SIZE 100
#define MAX_CLNT 256

void * handle_clnt(void * arg);
void send_msg(char * msg, int len);
void error_handling(char * msg);

int clnt_cnt=0;            // 서버에 접속한 클라이언트의 소켓 관리를 위한 변수와 배열
int clnt_socks[MAX_CLNT];  // 이 둘은 접근과 관련있는 코드가 임계영역을 구성하게 됨에 주목하자.
pthread_mutex_t mutx;

int main(int argc, char *argv[])
{
   int serv_sock, clnt_sock;
   struct sockaddr_in serv_adr, clnt_adr;
   int clnt_adr_sz;
   pthread_t t_id;
   if(argc!=2) {
      printf("Usage : %s <port>\n", argv[0]);
      exit(1);
   }
  
   pthread_mutex_init(&mutx, NULL);
   serv_sock=socket(PF_INET, SOCK_STREAM, 0);

   memset(&serv_adr, 0, sizeof(serv_adr));
   serv_adr.sin_family=AF_INET; 
   serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
   serv_adr.sin_port=htons(atoi(argv[1]));
   
   if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
      error_handling("bind() error");
   if(listen(serv_sock, 5)==-1)
      error_handling("listen() error");
  while(1)
   {
      clnt_adr_sz=sizeof(clnt_adr);
      clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr,&clnt_adr_sz);
      
      pthread_mutex_lock(&mutx);
      clnt_socks[clnt_cnt++]=clnt_sock;
      pthread_mutex_unlock(&mutx);
   
      pthread_create(&t_id, NULL, handle_clnt, (void*)&clnt_sock);
      pthread_detach(t_id);
      printf("Connected client IP: %s \n", inet_ntoa(clnt_adr.sin_addr));
   }
   close(serv_sock);
   return 0;
}
   
void * handle_clnt(void * arg)
{
   int clnt_sock=*((int*)arg);
   int str_len=0, i;
   char msg[BUF_SIZE];
   
   while((str_len=read(clnt_sock, msg, sizeof(msg)))!=0)
      send_msg(msg, str_len);
   
   pthread_mutex_lock(&mutx);
   for(i=0; i<clnt_cnt; i++)   // remove disconnected client
   {
      if(clnt_sock==clnt_socks[i])
      {
         while(i++<clnt_cnt-1)
            clnt_socks[i]=clnt_socks[i+1];
         break;
      }
   }
   clnt_cnt--;
   pthread_mutex_unlock(&mutx);
   close(clnt_sock);
   return NULL;
}
void send_msg(char * msg, int len)   // send to all
{
   int i;
   pthread_mutex_lock(&mutx);
   for(i=0; i<clnt_cnt; i++)
      write(clnt_socks[i], msg, len);
   pthread_mutex_unlock(&mutx);
}
void error_handling(char * msg)
{
   fputs(msg, stderr);
   fputc('\n', stderr);
   exit(1);
}


4. 클라이언트 코드


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> 
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
   
#define BUF_SIZE 100
#define NAME_SIZE 20
   
void * send_msg(void * arg);
void * recv_msg(void * arg);
void error_handling(char * msg);
   
char name[NAME_SIZE]="[DEFAULT]";
char msg[BUF_SIZE];
   
int main(int argc, char *argv[])
{
   int sock;
   struct sockaddr_in serv_addr;
   pthread_t snd_thread, rcv_thread;
   void * thread_return;
   if(argc!=4) {
      printf("Usage : %s <IP> <port> <name>\n", argv[0]);
      exit(1);
    }
   
   sprintf(name, "[%s]", argv[3]);
   sock=socket(PF_INET, SOCK_STREAM, 0);
   
   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");
   
   pthread_create(&snd_thread, NULL, send_msg, (void*)&sock);
   pthread_create(&rcv_thread, NULL, recv_msg, (void*)&sock);
   pthread_join(snd_thread, &thread_return);
   pthread_join(rcv_thread, &thread_return);
   close(sock);  
   return 0;
}
   
void * send_msg(void * arg)   // send thread main
{
   int sock=*((int*)arg);
   char name_msg[NAME_SIZE+BUF_SIZE];
   while(1) 
   {
      fgets(msg, BUF_SIZE, stdin);
      if(!strcmp(msg,"q\n")||!strcmp(msg,"Q\n")) 
      {
         close(sock);
         exit(0);
      }
      sprintf(name_msg,"%s %s", name, msg);
      write(sock, name_msg, strlen(name_msg));
   }
   return NULL;
}
   
void * recv_msg(void * arg)   // read thread main
{
   int sock=*((int*)arg);
   char name_msg[NAME_SIZE+BUF_SIZE];
   int str_len;
   while(1)
   {
      str_len=read(sock, name_msg, NAME_SIZE+BUF_SIZE-1);
      if(str_len==-1) 
         return (void*)-1;
      name_msg[str_len]=0;
      fputs(name_msg, stdout);
   }
   return NULL;
}
   
void error_handling(char *msg)
{
   fputs(msg, stderr);
   fputc('\n', stderr);
   exit(1);
}

5. 결과창

멀티쓰레드결과



8. IO MultiPlexing


개요

  • 멀티 프로세스 서버의 단점으로부터 출발
  • 하나의 통신 채널을 통해서 둘 이상의 데이터를 전송하는데 사용하는 기술
  • 최소한의 물리적인 요소만 사용해서 최대한의 데이터를 전달해야한다.

select

  • select 함수를 사용하면 한 곳에 여러개의 파일 디스크립터를 모아놓고 동시에 이들을 관찰 가능하다.
    • 수신한 데이터를 지니는 소켓이 있는가?
    • 블로킹 되지않고 데이터 전송 가능한 소켓은?
    • 예외 상황이 발생한 소켓은?
  • select 함수의 호출 방법과 순서는 아래와 같다고 한다.
    select
  • select 함수를 사용하면 여러개의 파일 디스크립터를 관찰할 수 있단 의미는 여러개의 소켓 관찰로 해석할 수도 있다.
  • select 함수의 ft_set 변수를 이용하여 관찰 대상임을 알 수 있다.

#include <sys/select.h>
#include <sys/time.h>

int select( int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
// 성공 : 0 반환
// 실패 : -1 반환
  • maxfd : 검사 대상이 되는 파일 디스크립터 수
  • readset : fd_set형 변수에 수신된 데이터의 존재 여부에 관심있는 파일 디스크립터 정보를 모두 등록하여 그 변수의 주소 값을 전달
  • writeset ; fd_set 형 변수에 블로킹 없는 데이터 전송 가능여부에 관심있는 파일 디스크립터 정보를 모두 등록해 그 변수 주소값을 전달
  • exceptset : fd_set 형 변수에 예외 상황의 발생 여부에 관심있는 파일 디스크립터 정보를 모두 등록해서 그 변수의 주소 값을 전달
  • timeout : select 함수 호출 이후 무한정블로킹 상태 빠지지 않도록 타임아웃을 설정하기 위한 인자를 전달

서버 코드


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>

#define BUFSIZE 100

void errorHandling(char *buf);

int main(int argc, char *argv[])

{

    int servSock, clntSock;
    struct sockaddr_in servAddr, clntAddr;
    struct timeval timeout;
    fd_set reads, cpyReads;
    socklen_t addrSz;
    int fdMax, strLen, fdNum, i;
    char buf[BUFSIZE];

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

// 소켓 초기화
    servSock = socket(PF_INET, SOCK_STREAM, 0);
    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]));

// 소켓 bind, listen
    if (bind(servSock, (struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
        errorHandling("bind() error");
    if (listen(servSock, 5) == -1)
        errorHandling("listen() error");

// read 모든 비트 0으로 초기화
    FD_ZERO(&reads);
// server socket에 파일디스크립터 정보 등록
    FD_SET(servSock, &reads);
    fdMax = servSock;

    while (1)
    {
        cpyReads = reads;
        // 타임아웃 설정
        timeout.tv_sec = 5;
        timeout.tv_usec = 5000;

        // 오류가 나면 바로 꺼준다.
        if ((fdNum = select(fdMax + 1, &cpyReads, 0, 0, &timeout)) == -1)
            break;
        if (fdNum == 0)
            continue;
        for (i = 0; i < fdMax + 1; i++)
        {
            // fdset으로 전달된 주소의 변수에 매개변수 fd 로 전달된 파일디스크립터 정보가 있으면 양수 반환
            if (FD_ISSET(i, &cpyReads))
            {
                // 서버 소켓이라면
                if (i == servSock)
                {
                    addrSz = sizeof(clntAddr);
                    // accept
                    clntSock = accept(servSock, (struct sockaddr *)&clntAddr, &addrSz);
                    FD_SET(clntSock, &reads);
                    if (fdMax < clntSock)
                        fdMax = clntSock;
                    printf("connected client : %d\n", clntSock);
                }
                else
                {
                    strLen = read(i, buf, BUFSIZE);
                    // close request
                    if (strLen == 0)
                    {
                        FD_CLR(i, &reads);
                        close(i);
                        printf("close client : %d\n", i);
                    }
                    else
                    {
                        // echo
                        write(i, buf, strLen);
                    }
                }
            }
        }
    }

    close(servSock);
    return 0;
}

출처

9. Broadcast & Multicast


1. 개요

  • 전송이란 한 호스트이 패킷을 다른 네트워크에 연결된 다른 호스트로 전달하는 프로세스이다.
  • 멀티 캐스트, 브로드 캐스트, 유니 캐스트는 네트워크의 기본적인 개념 중 하나 이니 숙지하는 편이 좋다.

2. 유니 캐스트

  • 1:1 통신 (LAN 통신에서 송신자의 MAC과 수신자의 MAC 주소를 알 때 메시지 전달)
  • 개인적이거나 고유한 리소스가 필요한 모든 네트워크 프로세스에서 사용
  • CPU 성능에 큰 문제를 주지 않는다.
  • 전송 비용이 매우 높다.

3. 브로드캐스트

  • 로컬 네트워크에 연결이 되어 있는 모든 시스템에게 프레임을 보내는 방식이다.
  • 이 방식은 브로드캐스트용 주소가 미리 정해져 있고, 수신을 받은 시스템은 이 주소가 오면 패킷을 자신의 cpu에 전송, CPU가 패킷을 처리하는 방식이다.
  • 이 방식은 자신이 통신하고자 하는 시스템의 MAC주소도 알지 못하는 경우, 시스템에 알리는 경우, 라우터끼리 정보를 교환하거나 새로운 정보를 찾는 경우에 이용된다.

4. 멀티캐스트

  • 네트워크에 연결되어 있는 시스템 중 일부에게만 정보를 전송하는 것

  • 특정 그룹에 속해있는 시스템에게만 한 번에 정보를 전달하는 방법

    • 라우터가 멀티캐스트를 지원해야만 사용 가능하다.
  • 특징

    • 멀티캐스트 그룹 단위로 묶어 그 그룹 Host들이 동시에 데이터를 받을 수 있다.
    • UDP 를 사용하여 신뢰성을 보장하지는 않는다.
    • 하나의 Client가 여러개의 멀티캐스트 주소를 수용할 수 있다.
    • Server가 멀티 캐스트 주소로 데이터를 전송 중에 있을 때 중간에 클라이언트가 중간에 끼어도 데이터를 처음부터 받을 수 없고 중간부터 데이터를 받게 된다.
  • 멀티캐스트 IP 주소 체계

    IP설명
    224.0.0.0 ~ 224.0.0.225IETF에서 관리용으로 사용되는 영역
    224.0.1.0 ~ 238.255.255.255실제 인터넷에서 멀티캐스트를 사용하는 기관이나 기업에게 할당하는 내역
    232.0.0.0 ~ 232.255.255.255PIM 기술을 위해 사용하는 대역
    233.0.0.0 ~ 233.255.255.255하나의 AS 내에 전파를 원할 때 사용하는 대역
    239.0.0.0 ~ 239.255.255.255기관이나 기업 내부에서 사용할 수 있는 사설 멀티캐스트 주소

    이 중 주요 IP 두개는 다음과 같은 역할을 한다.

  • 224.0.0.1 : 현재 서브넷에 존재하는 멀티캐스트가 가능한 모든 호스트 지칭

  • 224.0.0.2 : 현재 서브넷에 존재하는 멀티캐스트가 가능한 모든 라우터를 지칭

  • 출처

5. 비교 사진

비교사진

profile
얕고 작은 내 지식 옹달샘

0개의 댓글