0. 서론
쓰레드는 전역변수를 공유한다. 고로 전역변수가 있는 함수를 통해 버퍼링을 하면 안된다. -> 멀티 프로세스를 활용.
서버는 명령어를 분석해야 하기 때문에 readline을 사용-> \n 문자열을 받아야 하기 때문. (line 단위 read : readline)
length 단위 read : readn ( 커널은 자신이 정해둔 기본 단위 만큼 전송하기 때문에(헤더에 있는 값이 기준인 것 처럼 보임-확인필요), 정확한 길이만큼 데이터를 받고 사용하려면 길이만큼 작동하는 함수 - readn, writen이 필요 )
클라이언트는 데이터를 받아야 하기 때문에 readn을 사용
1. ftp server 구현
1. ls\n -> readline()
2. readn()
[ftp_server]
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 4096
static ssize_t
my_read(int fd, char *ptr)
{
static int read_cnt = 0;
static char *read_ptr;
static char read_buf[MAXLINE];
if (read_cnt <= 0) {
again:
if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
if (errno == EINTR)
goto again;
return(-1);
} else if (read_cnt == 0)
return(0);
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return(1);
}
ssize_t readline(int fd, void *vptr, size_t maxlen)
{
int n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ( (rc = my_read(fd, &c)) == 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
return(-1); /* error, errno set by read() */
}
*ptr = 0; /* null terminate like fgets() */
return(n);
}
/* end readline */
void my_sig(int signo )
{
printf("signo=%d\n", signo );
while( waitpid(-1, 0, WNOHANG) > 0 )
;
}
int main()
{
int sd, new_sd, ret;
int optval, optlen, addrlen;
struct sockaddr_in s_addr, c_addr;
char buff[1024];
signal( SIGCHLD, my_sig );
sd = socket( AF_INET, SOCK_STREAM, 0 );
printf("sd=%d\n", sd );
if( sd < 0 )
printf("%s\n", strerror(errno) );
// memset( &s_addr, 0, sizeof(s_addr));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(7777);
s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bzero( &s_addr.sin_zero, 8 );
optval = 1;
setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));
ret = bind( sd, (struct sockaddr*)&s_addr, sizeof(s_addr) );
if( ret < 0 )
{
perror("bind");
exit(0);
}
listen( sd, 10 );
addrlen = sizeof(c_addr);
while(1)
{
new_sd = accept( sd, 0, 0 );
// ...
printf("new_sd=%d\n", new_sd );
getpeername( new_sd, (struct sockaddr*)&c_addr, &addrlen );
printf("새로운 클라이언트 접속 : ip=%s, port=%u\n",
inet_ntoa(c_addr.sin_addr), ntohs( c_addr.sin_port ) );
if( fork()==0 )
{
int fd, f_size;
char f_name[100];
close(sd);
while( 1 )
{
ret = readline( new_sd, f_name, sizeof f_name );
f_name[ret-1] = 0; // "a.c\n\0" =>"a.c\0" gets();
fd = open( f_name, O_RDONLY);
f_size = lseek( fd, 0, SEEK_END );
lseek( fd, 0, SEEK_SET );
write(new_sd, &f_size, sizeof f_size );
while( ret = read( fd, buff, sizeof buff ) )
write( new_sd, buff, ret );
ret = readline( new_sd, buff, sizeof buff );
if( strncmp( buff, "quit\n", ret ) == 0 )
break;
}
close(new_sd);
exit(0);
}
close(new_sd);
}
close(sd);
return 0;
}
[ftp_client]
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
ssize_t /* Read "n" bytes from a descriptor. */
readn(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; /* and call read() again */
else
return(-1);
} else if (nread == 0)
break; /* EOF */
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
int main()
{
int sd, fd, ret, f_size=-1, len;
struct sockaddr_in s_addr;
char buff[10], f_name[100];
sd = socket( AF_INET, SOCK_STREAM, 0 );
printf("sd=%d\n", sd );
if( sd < 0 )
printf("%s\n", strerror(errno) );
// memset( &s_addr, 0, sizeof(s_addr));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(7777);
s_addr.sin_addr.s_addr = inet_addr("192.168.56.102");
bzero( &s_addr.sin_zero, 8 );
connect( sd, (struct sockaddr*)&s_addr, sizeof(s_addr) );
while(1)
{
ret = read( 0, f_name, sizeof f_name );
write( sd, f_name, ret );
f_name[ret-1] = 0;
fd = open( f_name, O_WRONLY | O_CREAT | O_TRUNC, 0666 );
readn( sd, &f_size, sizeof f_size );
while( f_size>0 )
{
len = (sizeof(buff) < f_size ) ? sizeof(buff) : f_size ;
ret = readn( sd, buff, len );
write( fd, buff, ret );
f_size -= ret;
}
close(fd);
ret = read( sd, buff, sizeof buff );
if( ret == 0 )
break;
}
close(sd);
return 0;
}
2 thread ( 쓰래드 )
* pthread_create : 쓰래드 생성 함수 [서드파티 라이브러리]
커널은 프로세스와 쓰래드를 구분하지 않는다.(동일한 스케줄링 구조를 갖게 된다. 둘다 그저 task) 쓰래드 또한 좀비가 될 수 있음. 이를 방지하는 함수가 pthread_join. 나눠줬던 쓰래드를 합쳐주는(?) 역할을 한다. 쓰래드는 pid로 부모와 자식을 구분 할 수 없고, 주어진 함수로 확인 된다.(인자)
-> 이렇게 보면 프로세스와 쓰래드는 동일(둘가 task)
[프로세스와 쓰래드의 차이점은?]
* 쓰래드 : 전역변수를 함께 사용
부모와 자식이 동일한 가상메모리(mm)를 보게 됨.
do_fork에서 주소만 복사
* 프로세스 : 서로 다른 전역변수를 사용
자식이 부모의 데이터를 복사해서 그곳을 바라봄.
새로운 가상메모리 공간 주소를 mm*이 갖고 있게 됨.
do_fork에서 메모리 자체를 복사.
* 쓰레드와 프로세스의 가상메모리 할당 방식 추가 내용: text, data, heap, tack 영역 *
text 영역 : 프로세스와 쓰래드 둘다 부모 자식이 동일한 곳을 본다.
data영역 : 프로세스도 처음에는 자식이 부모의 data를 보고 있다가, 자식 프로세스가 새로운 곳에 data를 만듬. ( 효율을 위해 잠시 copy를 늦춤.)
[쓰레드의 자원관리]
한 쓰레드에 주어지는 시간은 20ms정도. A에서 제어권을 갖다가 변수에 대한 처리를 끝내지 못하고 B로 옮겨질 경우 변수에 이상한 값이 들어 갈 수 있다. 각 전역변수에 대한 값의 locking이 필요하게 된다.
이를 해결하기 위한 방법이 필요. Locking이 필요. -> 어셈블리를 동원
- 방법
1. mutex사용
2. semaphore 사용
자세한 내용은 아래 블로그들을 참고
https://mangkyu.tistory.com/104
- 원론적인 이야기
https://heeonii.tistory.com/14
- 예제가 좀 더 쉬움
소스는 안드로이드 오픈소스를 확인해 볼 것.
결론 => 공유하는 변수는 lock 변수(mutex, semaphore)로 묶어야 한다.
3. tls를 활용하는 방식
각 쓰레드 마다 tls라는 내부 공간을 주어 활용하는 방식. 동적 메모리 할당.
* 문제점
동적 메모리임으로 해제의 문제가 존재한다.
해제 함수는 쓰래드가 소멸 될 때 호출되어야 한다.
아래 예시 소스로 보면 해제 함수는 readline_destructor(void *ptr) 이다. 인자인 ptr은 해당 동적메모리 위치를 주게 됨.
예시 - unpv12e의 소스(/unpv12e/threads/readline.c)*
/* include readline1 */
#include "unpthread.h"
static pthread_key_t rl_key;
static pthread_once_t rl_once = PTHREAD_ONCE_INIT;
static void
readline_destructor(void *ptr)
{
free(ptr);
}
static void
readline_once(void)
{
Pthread_key_create(&rl_key, readline_destructor);
}
typedef struct {
int rl_cnt; /* initialize to 0 */
char *rl_bufptr; /* initialize to rl_buf */
char rl_buf[MAXLINE];
} Rline;
/* end readline1 */
/* include readline2 */
static ssize_t
my_read(Rline *tsd, int fd, char *ptr)
{
if (tsd->rl_cnt <= 0) {
again:
if ( (tsd->rl_cnt = read(fd, tsd->rl_buf, MAXLINE)) < 0) {
if (errno == EINTR)
goto again;
return(-1);
} else if (tsd->rl_cnt == 0)
return(0);
tsd->rl_bufptr = tsd->rl_buf;
}
tsd->rl_cnt--;
*ptr = *tsd->rl_bufptr++;
return(1);
}
ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
int n, rc;
char c, *ptr;
Rline *tsd;
Pthread_once(&rl_once, readline_once);
if ( (tsd = pthread_getspecific(rl_key)) == NULL) {
tsd = Calloc(1, sizeof(Rline)); /* init to 0 */
Pthread_setspecific(rl_key, tsd);
}
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ( (rc = my_read(tsd, fd, &c)) == 1) {
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
if (n == 1)
return(0); /* EOF, no data read */
else
break; /* EOF, some data was read */
} else
return(-1); /* error, errno set by read() */
}
*ptr = 0;
return(n);
}
/* end readline2 */
ssize_t
Readline(int fd, void *ptr, size_t maxlen)
{
ssize_t n;
if ( (n = readline(fd, ptr, maxlen)) < 0)
err_sys("readline error");
return(n);
}
참고
* 가상메모리 : 순서대로 메모리가 자리한다.
* 물리메모리 : 랜덤으로 메모리 순서가 잡힌다.
* $ mkfifo mufifo -> mkfifo : fifo 만드는 쉘 명령어
* mm은 task_struct의 멤버변수 , 가상메모리의 위치를 갖고 있는다(간단, 사실 가상메모리의 위치를 포함한 어떤 struct를 갖고 있음).
* 가상메모리에 대한 내용참고 : https://hyunalee.tistory.com/108
* 전역변수 영역은 bss로 0초기화
* 참고하면 좋은 소스(unpv12e) - wget http://kohala.com/start/unpv12e/unpv12e.tar.gz
'IT > Linux(Unix)' 카테고리의 다른 글
남들은 자주 안쓰지만 나는 자주쓰는 VI 꿀팁들 (0) | 2024.10.19 |
---|---|
리눅스 프로그래밍 - 커널 소스 분석 ( 소켓, 서버 ) (0) | 2022.07.21 |
리눅스 프로그래밍 - 커널 소스 분석 ( 네트워크 기초 ) (0) | 2022.07.20 |
[VI] Xshell 에러 해결 모음집 (0) | 2021.05.04 |