본문 바로가기

컴퓨터 네트워크/소켓 프로그래밍

Socket Programming (소켓 프로그래밍) (2)

지난 글에서는 server와 client의 연결, 그리고 메시지를 보내고 받는 함수들에 대해서 알아보았다.

본 글에서는 앞서 배운 과정과 함수들을 이용하여 직접 통신해보는 프로그램을 구현한다.

 

지난 글에서 알아본 과정은 다음과 같다.

 

만약 과정이 이해가 안된다면 이전글(링크)를 통해 이해하고 오자.

TCP


 

 

 

다음 코드들은 메시지를 보내는 예시코드이다.

 

TCP 서버코드

#define BUFSIZE 1024
void error_handling(char *message);
int main(int argc, char **argv){
    int serv_sock;
    int clnt_sock;
    char message[BUFSIZE];
    int str_len;
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    int clnt_addr_size;
    if(argc!=2){
    	printf("Usage : %s <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");
    }
    /* 데이터 수신 및 전송 */
	while( (str_len=read(clnt_sock,message, BUFSIZE)) != 0){
    	write(clnt_sock, message, str_len);
    	write(1, message, str_len);
    }
    
    close(clnt_sock);
    return 0;
}

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

 

TCP 클라이언트 코드

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 1024

void error_handling(char *message);
int main(int argc, char **argv) {

    int sock;
    char message[BUFSIZE];
    int str_len;
    struct sockaddr_in serv_addr;
    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_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!");
    }
    while(1) {
    /* 메세지 입력, 전송 */
        fputs("전송할 메시지를 입력 하세요 (q to quit) : ", stdout);
        fgets(message, BUFSIZE, stdin);
        if(!strcmp(message,"q\n")) break;
        write(sock, message, strlen(message));
        /* 메세지 수신, 출력 */
        str_len=read(sock, message, BUFSIZE-1);
        message[str_len]=0;
        printf("서버로부터 전송된 메시지 : %s \n", message);
        
    }
    close(sock);
    return 0;
}
void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

 

위 코드를 실행시 다음과 같은 결과가 나타난다.

만약 클라이언트에서 q를 입력해 while문을 빠져나오면 close를 요청하니 연결이 해제될 것이다.

Concurrent server vs Iterative server


다중 클라이언트에 대한 서버의 방식이 다르다.

 

Concurrent server

TCP에서의 방식이다. 

TCP는 1:1통신만을 지원하기 때문에 하나의 프로세스당 하나의 소켓만 처리할 수 있다.

때문에 하나의 서버로 여러 클라이언트들의 요청이 오게되면 그때마다 fork()를 통해 자식프로세스를 생성 후 생성한 자식 프로세스에서 새로운 응답을 받게끔 해야한다.

 

fork()는 자식프로세스를 생성하는 시스템콜이다.

 

int sockfd, newsockfd;
if ( (sockfd = socket( … )) < 0 )
	Err_sys( “socket error” );
if ( bind(sockfd, …) < 0 )
	Err_sys( “bind error” );
if ( listen(sockfd, 5 )
	Err_sys( “listen error” );
for ( ; ; ) {
    newsockfd = accept( sockfd, … );
    If ( newsockfd < 0 )
    Err_sys( “accept error” );
    If ( fork() == 0 ) {   //자식 프로세스에서 부모의 socket을 닫은 후 새로운 newsockfd로 진행
        Close( sockfd );
        Doit( newsockfd );
        Exit( 0 );
    } 	
    else close( newsockfd ); //부모 프로세스라면 새로운 newsockfd를 닫음
}

 

 

Iterative server

UDP에서의 방식이다.

UDP는 1:다 통신을 지원하기 때문에  하나의 프로세스에서 여러개의 소켓을 처리할 수 있다.

 

int sockfd, newsockfd;
if ( (sockfd = socket( … ) < 0 )
    Err_sys( “socket error” );
if ( (bind( sockfd, … ) < 0 )
    Err_sys( “bind error” );
if ( (listen( sockfd, 5 ) < 0 )
    Err_sys( “listen error” )
for ( ; ; ) {
    newsockfd = accept( sockfd, … );
    if ( newsockfd < 0 )
    Err_sys( “accept error” );
    doit( newsockfd );
    close( newsockfd );
}

 

 

UDP


UDP의 과정에는 커넥션 셋업을 하는 과정이 없기 때문에 다음과 같은 그림으로 표현될 수 있다.

 

다음은 클라이언트가 세개의 메시지를 서버에게 보내고 서버가 그에대한 응답을 보내는 코드이다.

 

UDP 서버 코드

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 30
void error_handling(char *message);
int main(int argc, char **argv){
    int serv_sock;
    char message[BUFSIZE];
    int str_len, num=0;
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    int clnt_addr_size;
    if(argc!=2){
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
    serv_sock=socket(PF_INET, SOCK_DGRAM, 0);
    if(serv_sock == -1) {
        error_handling("UDP 소켓 생성 오류");
    }
    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");
    }
    sleep(5);
    while(1) {
        clnt_addr_size=sizeof(clnt_addr);
        sleep(1);
        str_len = recvfrom(serv_sock, message, BUFSIZE, 0, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
        printf("수신 번호 : %d \n", num++);
        sendto(serv_sock, message, str_len, 0, (struct sockaddr*)&clnt_addr,
        sizeof(clnt_addr));
    }
    return 0;
}
void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

 

 

UDP 클라이언트 코드

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 30
void error_handling(char *message);
int main(int argc, char **argv){
    int sock;
    char message[BUFSIZE];
    int str_len, addr_size, i;
    char MSG1[] = "Good ";
    char MSG2[] = "Evening ";
    char MSG3[] = "Everybody!";
    struct sockaddr_in serv_addr;
    struct sockaddr_in from_addr;
    if(argc!=3){
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
    sock=socket(PF_INET, SOCK_DGRAM, 0);
    if(sock == -1) error_handling("UDP 소켓 생성 오류");
    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]));
    sendto(sock, MSG1, strlen(MSG1), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    sendto(sock, MSG2, strlen(MSG2), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    sendto(sock, MSG3, strlen(MSG3), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    for(i=0; i<3; i++)
    {
        addr_size = sizeof(from_addr);
        str_len = recvfrom(sock, message, BUFSIZE, 0, (struct sockaddr*)&from_addr, &addr_size);
        message[str_len]=0;
        printf("서버로부터 수신된 %d차 메시지 : %s \n", i, message);
    }
    close(sock);
    return 0;
}
void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

 

위 코드를 실행시키면 결과는 다음과 같다.