본문 바로가기
열심히 살기/Network

소켓 프로그래밍 - TCP 기반(서버, 클라이언트)

by yeni03o_o 2020. 12. 12.

TCP 서버의 함수 호출 순서

: socket() → bind() → listen() → accept() → read()/write() → close()

TCP 클라이언트의 함수 호출 순서

: socket() → connect() → read()/write() → close()

 

 * 각 함수 별 상세 설명

2020/12/11 - [열심히 살기/Network] - 소켓 프로그래밍(Socket Programming)

 

 

Echo Server

//TCP echo server

#include <stdio.h>		//표준 입출력
#include <stdlib.h>		//표준 라이브러리
#include <string.h>		//문자열 처리 함수
#include <unistd.h>		//유닉스 표준
#include <arpa/inet.h>		//인터넷 프로토콜
#include <sys/socket.h>		//소켓 통신 함수

#define BUF_SIZE 1024		//1024바이트 버퍼 사용


void error_handling(char *message);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    char message[BUF_SIZE];					//메시지 수신용 1024바이트 버퍼
    int str_len, i;						//수신한 데이터 길이
    
    //주소 정보 구조체
    struct sockaddr_in serv_adr;
    struct sockaddr_in clnt_adr;
    socklen_t clnt_adr_sz;					//클라이언트 주소 구조체의 바이트 크기
    
    //쉘에 입력된 인자 개수 체크
    if(argc!=2) {
    	printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }
    
    serv_sock=socket(PF_INET, SOCK_STREAM, 0);			//TCP통신용 서버 소켓 생성. ipv4
    if(serv_sock==-1)
    	error_handling("socket() error");
    
    //주소 설정을 위한 메모리 0으로 초기화. ip, port 설정
    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]);			//프로그램당 포트 1개 사용
    
    //소켓과 주소 바인딩
    if(bind(serv_soxk, (struct sockaddr*(&serv_adr, sizeof(serv_adr))==-1)
    	error_handling("bind() error");
    
    //소켓을 리스닝소켓으로 만듦. 대기상태.
    if(listen(serv_sock, 5)==1)
    	error_handling("listen() error");
    
    clnt_adr_sz=sizeof(clnt_adr);				//클라이언트 주소 구조체 크기
    
    //클라이언트의 요청을 5번 받아들임
    //서버는 클라이언트의 요청이 있을때까지 Block된 상태로 기다림
    for(i=0;i<5;i++)
    {
    	clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
        if(clnt_sock==-1)
        	error_handling("accept() error");
        else							//accept가 성공하면 클라이언트 소켓(clnt_sock)이 만들어짐
        	printf("Connected client %d \n", i+1);
        
        //서버에서 클라이언트가 보낸 메시지를 읽은 후 다시 클라이언트로 전송
        while(str_len=read(clnt_sock, messagem BUF_SIZE))!=0)	//클라이언트가 소켓을 닫으면 while문 종료
        	write(clnt_sock, message, str_len);		//문자열 끝 널문자 제외하고 에코
            
        close(clnt_sock);					//클라이언트와 접속이 끊기면 클라이언트 소켓 close          
    }
    
    close(serv_sock);						//클라이언트를 5번 받아주고나면 서버 소켓 close
    return 0;
}

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

 

Echo Client

//TCP echo client

#include <stdio.h>		//표준 입출력
#include <stdlib.h>		//표준 라이브러리
#include <string.h>		//문자열 처리 함수
#include <unistd.h>		//유닉스 표준
#include <arpa/inet.h>		//인터넷 프로토콜
#include <sys/socket.h>		// 소켓 함수

#define BUF_SIZE 1024		//소켓 통신 문자열 버퍼(1024 바이트)


void error_handling(char *message);

int main(int argc, char *argv[]) 			// 쉘에서 IP와 PORT 번호를 입력받음
{
    int sock;						//서버에 접속할 소켓
    char message[BUF_SIZE]				//서버에 보낼 메시지를 저장할 문자열 버퍼(1024 바이트)
    int str_len;					//송수신 메시지의 문자열 길이
    
    struct sockadr_in serv_adr;				//접속할 서버의 주소
    
    //인자 개수 체크
    if(argc!=3) {
    	printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
    
    sock=socket(PF_INET, SOCK_STREAM, 0);		//TCP통신용 소켓 생성. ipv4
    if(sock==-1)
    	error_handling("socket() error");
    
    //주소 설정, ip, port
    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 messate(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);
            
        if(!strcmp(message, "q\n") || !strcmp(message, "!\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);	//소켓 close
    return 0;
}

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

 

코드(Echo Server, Echo Client) 실행 결과

- Echo Server: Client가 connect할 때까지 대기상태. 

                  Client의 접속을 5번 받아주고 난 후 종료

                  여러 Client의 접속을 동시에 받지 못하고, 한 Client가 종료되면 다른 Client의 접속 허용

echo_server.c 실행 결과

- Echo Client: 수신 대기중인 Server에 접속.

                  Server로 문자열(널문자 포함) 전송하고, 서버에서 에코된 문자열 수신 후 출력

Client1
Client 2, 3, 4, 5

 


 

Echo Client 코드상 문제

- TCP 소켓은 데이터의 경계가 없으므로 서버가 에코한 문자열의 완전한 수신을 보장하지 못함(문자열의 일부만 전송)

- 하지만, 앞서 작성된 Echo Client 코드는 '한 번의 read() 호출로 서버로부터 에코된 문자열 전체를 읽어들일 수 있다'는 가정이 존재

→ 클라이언트가 모든 문자열을 수신할 때까지 read()를 반복 호출하여 해결

 

Echo Client 코드 수정

TCP echo client 수정

#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, recv_len, recv_cnt;		//서버로 보낸 데이터와 서버로부터 바든 바이트 수 체크 변수 추가
    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);		//TCP통신용 소켓 생성. ipv4
    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(connet(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;
            
        str_len=write(sock, message, strlen(message));	//Echo 서버에 데이터 전송 후 사이즈 저장
        
        //클라이언트가 수신해야 할 데이터의 크기(str_len)를 미리 알고 있음
        //str_len만큼의 데이터를 수신할 때까지 반복해서 read() 호출
        recv_len=0;
        while(recv_len<str_len)
        {
        	recv_cnt=read(sock, &message[recv_len], BUF_SIZE-1);	//서버로부터 에코된 데이터 수신
            if(recv_cnt==-1)
            	error_handling("read() error!");
            
            recv_len+=recv_cnt;			//서버로부터 받은 데이터를 읽은 바이트 수 만큼 더함
        }
        
        //서버에서 널문자를 제외하고 보내므로 서버로부터 수신한 문자열 맨 뒤에 널문자 추가
        message[recv_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);
}

 

 

공부하면서 학습 목적으로 작성한 포스팅이므로 내용이 완전하지 않습니다ㅠ 
계속해서 학습 후 지식이 좀 더 쌓이면 수시로 수정해나갈 예정입니다! 
틀린 내용은 둥글게 댓글 달아주시면 빠른 확인 후 수정하겠습니다. :)


코드 출처

윤성우 저 '윤성우의 열혈 TCP/IP 소켓 프로그래밍'

 

참고

* qteveryday.tistory.com/49?category=921416

 

댓글