프로그래밍/2024 네트워크 프로그래밍

7. read를 사용한 에코 클라이언트 최적화

데일리 백수 2025. 1. 8. 21:25

에코 클라이언트 코드

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <arpa/inet.h> 
#include <sys/socket.h> 
#define BUFFER_SIZE 1024 
void handle_error(char *); 
int main(int argc, char *argv[]) 
{ 
	int sockFd; 
	struct sockaddr_in serverAddress;
    
    char buffer[BUFFER_SIZE]; 
	int nRead;
    
    if(argc != 3){ 
		printf("Usage: %s <IP> <port>\n", argv[0]); 
		exit(1); 
	} 

	if((sockFd = socket(PF_INET, SOCK_STREAM, 0)) < 0) 
		handle_error("socket() error.");
        
    memset(&serverAddress, 0, sizeof(serverAddress)); 
 	serverAddress.sin_family = AF_INET; 
    serverAddress.sin_addr.s_addr = inet_addr(argv[1]); 
 	serverAddress.sin_port = htons(atoi(argv[2])); 
    
    if(connect(sockFd, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0) 
 		 handle_error("connect() error.");
         
    for( ; ; ) {  
 		fputs("Type a message (Q/q to quit): ", stdout); 
  		fgets(buffer, BUFFER_SIZE, stdin); 
  
  		if(!strcmp(buffer, "Q\n") || !strcmp(buffer, "q\n")) 
   			break; 
 
  		write(sockFd, buffer, strlen(buffer)); 
  		printf("Message sent to server: %ld bytes, %s", strlen(buffer), buffer); 
 
 		nRead = read(sockFd, buffer, BUFFER_SIZE - 1); 
  		buffer[nRead] = 0;  // Necessary for printf() 
  		printf("Message received from server: %d bytes, %s", nRead, buffer); 
 	} 
    close(sockFd); 
 
	 return 0;
         
 }

커스템 헤더 mylib.h

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
 
#include <unistd.h>  
#include <arpa/inet.h> 
#include <sys/socket.h> 
 
#include <errno.h> 
 
ssize_t read_n(int, void *, size_t); 
ssize_t write_n(int, const void *, size_t); 
ssize_t read_line(int, void *, size_t);

 

커스텀  mylib.c

 

#include "mylib.h" 
 
ssize_t read_n(int fd, void *vptr, size_t n) 
{ 
 size_t nleft; 
 ssize_t nread; 
 char *ptr; 
 
 ptr = vptr; 
 nleft = n; 
 while(nleft > 0) { 
  if((nread = read(fd, ptr, nleft)) < 0) { 
   if(errno == EINTR) 
    nread = 0; 
   else 
    return(-1);  
  } else if(nread == 0) 
   break; 
 
  nleft -= nread; 
  ptr += nread; 
 } 
 
 return(n - nleft); 
} 
 
ssize_t write_n(int fd, const void *vptr, size_t n) 
{ 
 size_t nleft; 
 ssize_t nwritten; 
 const char *ptr; 
 
 ptr = vptr; 
 nleft = n; 
 while(nleft > 0) { 
  if((nwritten = write(fd, ptr, nleft)) <= 0) { 
   if(errno == EINTR) 
    nwritten = 0; 
   else 
    return(-1); 
  } 
  nleft -= nwritten; 
  ptr += nwritten; 
 } 
 
 return(n); 
} 
 
ssize_t read_line(int fd, void *vptr, size_t maxlen) 
{ 
 ssize_t n, rc; 
 char c, *ptr; 
 
 ptr = vptr; 
 for(n = 1; n < maxlen; n++) { 
  again: 
  if((rc = read(fd, &c, 1)) == 1) { 
   *ptr++ = c; 
   if(c == '\n') 
    break;  /* newline is stored, like fgets() */ 
  } else if(rc == 0) { 
   if(n == 1) 
    return(0);  /* EOF, no data read */ 
   else 
    break;  /* EOF, some data was read */ 
  } else { 
   if(errno == EINTR) 
    goto again; 
   return(-1);  /* error, errno set by read() */ 
  } 
 } 
 
 *ptr = 0;  /* null terminate like fgets() */ 
 
 return(n); 
}

 

먼저 echo_client2 에서 서버와 통신하는 코드 전문 내용입니다.

위의 주소 초기화 및 connect 부분은 생략하겠습니다.

 

 먼저 미리 선언한 메시지 버퍼에 사용자 입력을 받아, 서버에 전송한 후, 

Read 함수를 사용해, 내가 전송한 write에서 반환해주는 str_len만큼 전송받을때까지 무한으로 read를 실행시키고 있습니다. 

Read_n으로 교체하기에 앞서, 기존 read와 read_n의 차이를 알아보겠습니다.

기존 read는 버퍼사이즈-1만큼 계속하여 데이터를 읽고 있습니다. 이는 언제 멈춰야 할지 기준 자체가 명확하지 않으며, 불필요한 데이터까지 읽을 가능성이 있습니다.

Read_n을 통하여 남은 바이트 수를 추적하여, read호출이 반환한 바이트 수를 누적해서 정해진 바이트 수를 다 채워질 때까지 반복합니다. 이러한 구조로 필요한 바이트 수만큼의 읽기를 진행합니다. 또한 EINRE처리를 통해 오류를 감지하여 수신에 유연한 핸들링이 가능하게 합니다.

read_n을 사용하여 변경한 코드입니다. 서버로부터 데이터를 수신할 때 read_n()을 사용하여 지정된 바이트(str_len)만큼 정확히 읽어옵니다. 이를 통해 부분적으로 수신된 데이터에 대해 여러 번 read()를 호출할 필요 없이 read_n()이 필요한 바이트를 모두 읽어줄 때까지 반복합니다.