getsockname(sfd, (struct sockaddr *)&saddr, &len_saddr);
Thursday, June 5, 2008
Socket, getsockname
이미 binding 된 socket으로 부터 주소정보를 받아올 때 getsockname을 사용한다. 아래와 같이 사용하면 된다.
Wednesday, June 4, 2008
Socket, thundering herd problem
network 프로그래밍시 주의해야할 문제 중에 하나로 thundering herd problem이 있다. 천둥이 치면 소떼가 뛴다...는 상황을 비교해서 쓰는 말인데, 여러 thread/process 간에 공유된 자원이 있을 때 자원이 사용가능하게 되지마자 block되어 있던 모든 프로세스들이 깨어나지만 한 프로세스만 자원을 점유하고 나머지는 다시 block되어야 하므로 CPU time을 낭비하게 된다. Mutex 같은 lock을 통해 이런 문제를 해결할 수 있다고 하는데,.. 그런가 ㅡ.ㅡ;,..
Monday, June 2, 2008
Socket, code example
간단히 ... 잊지 않을 정도의 코드 예제를 적어본다.
server side
client side
server side
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
int sfd, rsfd;
struct sockaddr saddr; // listener
struct sockaddr rsaddr;
sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
saddr.sin_family = AF_INET;
saddr.sin_port = 10000;
saddr.sin_addr.s_addr = inet_addr("100.100.100.100");
// or inet_aton
bind(sfd, (struct sockaddr*)&saddr, sizeof(saddr));
listen(sfd, BACKLOG);
rsfd = accept(sfd, (struct sockaddr *)&saddr, &len_rsaddr);
recv(rsfd, rbuf, sizeof(rbuf), 0);
send(rsfd, sbuf, sizeof(sbuf), 0);
shutdown(rsfd, SHUT_RDWR); // or close
client side
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int sfd;
struct sockaddr saddr;
sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
saddr.sin_family = AF_INET;
saddr.sin_port = 10000;
inet_aton("100.100.100.100", &saddr.sin_addr);
// or inet_addr
connect(sfd, (struct sockaddr*)saddr, sizeof(saddr));
send(sfd, sbuf, sizeof(sbuf), 0);
recv(sfd, rbuf, sizeof(rbuf), 0);
close(sfd); // or shutdown
Thread, mutex - deadlock/race condition avoidance
여러개의 thread가 같은 메모리에 접근할 때 발생할 수 있는 문제가 deadlock, race condition이다. deadlock을 만드는 가장 간단한 방법은 같은 mutex에 두번 lock을 거는 것이다. 혹은 두 thread A,B가 있고 mutex X,Y가 있을 때 A가 X를 lock하고 Y자원을 사용하고자 기다릴때 B가 Y를 lock하고 X자원을 사용하고자하는 경우 이런 상황이 발생한다. thread에서 이런 상황을 피하는 방법으로는 아래의 두가지가 있다.
linux에서 사용가능한 mutex관련 함수로는 아래 것들이 있다.
mutex를 사용하기 위해서는 ..._init(), ..._destroy() 함수들을 사용해야한다. ..._init()함수 사용시 mutex의 종류를 정해줄 수 있는데, 아래와 같은 매크로로 지정할 수 있다.
위에서 설명한 "timed mutex"는 기본 mutex인데, 중복으로 lock을 걸면 deadlock에 걸리고, 다른 thread가 lock한 상태에서 내가 풀려고 하면 undefined 상태가 된다. errorcheck 타입은 데드락의 경우 에러를 리턴해준다. 그 외에 adaptive mutex가 있는데, 표준은 아니고, 플랫폼에 따라 최적의 매커니즘으로 동작하게 된다. linux에서 SMP가 지원되는 경우 spinlock을 사용하게 되는데, 이때 adaptive mutex를 사용하게 되면 짧은 시간동안의 lock, unlock에 대해 최적의 성능을 지원한다. 대신 비 표준이라 PTHREAD_MUTEX_ADAPTIVE_NP를 통해 초기화 한다.
이런 mutex 타입을 mutex attribute 함수들을 통해 설정하여 mutex init시 사용하면 된다.
- 여러 mutex를 사용하는 경우 lock하는 순서를 항상 동일하게 한다. (위의 예라면 항상 X를 lock한 후 Y를 lock하는 식으로 정해서 deadlock을 피할 수 있다)
- 자신이 사용하고자 하는 자원이 다른 thread에 의해 사용중인 경우 자신이 점유하고 있는 자원을 해제한 후 기다린다.
linux에서 사용가능한 mutex관련 함수로는 아래 것들이 있다.
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
mutex를 사용하기 위해서는 ..._init(), ..._destroy() 함수들을 사용해야한다. ..._init()함수 사용시 mutex의 종류를 정해줄 수 있는데, 아래와 같은 매크로로 지정할 수 있다.
- PTHREAD_MUTEX_NORMAL : linux에서는 "timed" mutex이다.
- PTHREAD_MUTEX_RECURSIVE : 여러번 잠글 수 있는 mutex
- PTHREAD_MUTEX_ERRORCHECK : 에러 체크해주는 mutex
- PTHREAD_MUTEX_MUTEX_DEFAULT : linux에서는 첫번째의 ..._NORMAL과 같다.
위에서 설명한 "timed mutex"는 기본 mutex인데, 중복으로 lock을 걸면 deadlock에 걸리고, 다른 thread가 lock한 상태에서 내가 풀려고 하면 undefined 상태가 된다. errorcheck 타입은 데드락의 경우 에러를 리턴해준다. 그 외에 adaptive mutex가 있는데, 표준은 아니고, 플랫폼에 따라 최적의 매커니즘으로 동작하게 된다. linux에서 SMP가 지원되는 경우 spinlock을 사용하게 되는데, 이때 adaptive mutex를 사용하게 되면 짧은 시간동안의 lock, unlock에 대해 최적의 성능을 지원한다. 대신 비 표준이라 PTHREAD_MUTEX_ADAPTIVE_NP를 통해 초기화 한다.
이런 mutex 타입을 mutex attribute 함수들을 통해 설정하여 mutex init시 사용하면 된다.
#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutex_gettype(pthread_mutexattr_t *restrict attr);
int pthread_mutex_settype(pthread_mutexattr_t *attr, int type);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
Thursday, May 29, 2008
Thread, basic II
thread에 사용되는 함수들은 아래와 같다.
- pthread_create()
thread를 만든다. - pthread_exit()
종료할때 return 대신 쓰면 cleanup함수들이 실행된다. - pthread_join()
다른 thread가 종료될 때까지 현재 코드를 block한다. 종료될 때 그 thread의 리턴값을 받을 수 있다. - pthread_cancel()
다른 thread에게 종료 신호를 보낸다. 종료하는 건 신호를 받은 thread 맘이다. 종료하게 되면 pthread_exit()가 호출된 것처럼 동작한다. - pthread_cleanup_push()
종료시 실행할 함수를 등록한다 like atexit(). 종료시 stack처럼 최근 등록된 순서대로 pop되면서 실행된다. - pthread_cleanup_pop()
위의 ..._push()함수로 등록한 것을 최근 순서로 해제(pop)한다. 단 인자가 0이 아니면 실행하면서 pop한다.
Socket, connect()
client에서 서버로 접속할 때는 connect()를 사용한다.
보다시피 사용방법은 아주 간단하다. 먼저 socket을 만들고, 만들 소켓을 접속한 주소가 담긴 sockaddr와 함께 인자로 주면 된다. 입력한 주소가 invalid한 경우에 0을 리턴하기 때문에 아래와 같이 코드를 만들게 된다.
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addr_len);
보다시피 사용방법은 아주 간단하다. 먼저 socket을 만들고, 만들 소켓을 접속한 주소가 담긴 sockaddr와 함께 인자로 주면 된다. 입력한 주소가 invalid한 경우에 0을 리턴하기 때문에 아래와 같이 코드를 만들게 된다.
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int sock_fd;
int saddr;
sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
saddr.sin_family = AF_INET;
saddr.sin_port = 80; // port
if (inet_aton("100.100.100.100"), &(saddr.sin_addr) == 0)
fprintf(stderr, "error : inet_aton\n");
if (connect(sfd, (struct sockaddr *)&saddr, sizeof(saddr)) == -1)
perror("connect");
Tuesday, May 27, 2008
Socket, bind(), listen() and accept()
socket()을 통해 소켓을 생성했다면 다음은 bind()이다. bind()는 아래와 같다.
sockfd는 앞서 만든 socket이고, sockaddr은 IP/Port 등의 주소 정보를 가진 구조체다. addrlen은 이 구조체의 크기이다. Unix socket을 사용하는 경우 sockaddr_un을 사용하지만, Network socket을 사용하는 경우 sockaddr_in을 사용한다. 두 구조체 모두 bind에 넣을 때는 (sockaddr *)로 캐스팅해서 넣어준다(bind내부에서 sockaddr로 캐스팅된 구조체의 앞부분을 보고 network socket인지 unix socket인지 알수 있다). 구조체를 잠깐 살펴 보자.
특별한 건 없고, AF_INET/AF_UNIX, 주소, 포트 번호 등이 들어가 있다. 주소는 in_addr 구조체로 들어가 있는데, unsigned long 타입으로 되어 있다. IP주소가 x.x.x.x 식으로 되어 있고 x < 256 이므로(8비트), 32bit 만 있으면 충분하다. 그래서 in_addr의 s_addr이 unsinged long (32bit) 이다. 여기에 INADDR_ANY (0)을 넣으면 서버에 지정된 모든 IP에 해당된다. 여기에 주소를 넣을 때는 inet_addr(), inet_aton()을 사용할 수 있는데, 전자의 경우에는 unix 표준이지만, 리턴되는 -1값이 INADDR_NONE(255.255.255.255) 인 경우와 에러인 경우를 모두 포함하므로 구별하기가 모호하다. inet_aton은 구분이 가능하지만 표준에 들어있지 않아.. 거시기 하다ㅡㅡ; 암튼 대충 이렇고, 실제로 주소를 넣는 코드는 아래와 같다.
bind는 성공시 0을, 실패시 -1을 리턴하고 errno를 설정해준다. 위처럼 설정한 sockaddr_in을 아래처럼 binding하면된다.
bind된 socket으로 실제 TCP 요청을 받기 위해(TCP 접속을 위해) listen()을 사용한다. 인자로 backlog 값이 사용되는데, 커널에서 큐로 구현되어 있으며 default로 1024 로 되어 1024개 까지 TCP 접속을 허용할 수 있게된다.
생성된 소켓으로 외부 요청을 받을 수 있도록 주소를 binding하고 listen 하기 시작했다면, 요청을 기다리다가 들어온 요청을 accept() 하여 받을 수 있다. accept 함수는 bind 소켓과 client의 주소정보를 담을 sockaddr 구조체를 인자로 주면 요청이 들어올 때까지 코드를 block 한다.
위 코드의 실행시 client의 요청에 대해 아래와 같이 출력된다.
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
sockfd는 앞서 만든 socket이고, sockaddr은 IP/Port 등의 주소 정보를 가진 구조체다. addrlen은 이 구조체의 크기이다. Unix socket을 사용하는 경우 sockaddr_un을 사용하지만, Network socket을 사용하는 경우 sockaddr_in을 사용한다. 두 구조체 모두 bind에 넣을 때는 (sockaddr *)로 캐스팅해서 넣어준다(bind내부에서 sockaddr로 캐스팅된 구조체의 앞부분을 보고 network socket인지 unix socket인지 알수 있다). 구조체를 잠깐 살펴 보자.
struct sockaddr_in {
unsigned short sin_family; // AF_INET
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8]; // not used
}
struct sockaddr_un {
short sun_family;
char sun_path[108];
}
struct in_addr {
unsigned long s_addr;
}
특별한 건 없고, AF_INET/AF_UNIX, 주소, 포트 번호 등이 들어가 있다. 주소는 in_addr 구조체로 들어가 있는데, unsigned long 타입으로 되어 있다. IP주소가 x.x.x.x 식으로 되어 있고 x < 256 이므로(8비트), 32bit 만 있으면 충분하다. 그래서 in_addr의 s_addr이 unsinged long (32bit) 이다. 여기에 INADDR_ANY (0)을 넣으면 서버에 지정된 모든 IP에 해당된다. 여기에 주소를 넣을 때는 inet_addr(), inet_aton()을 사용할 수 있는데, 전자의 경우에는 unix 표준이지만, 리턴되는 -1값이 INADDR_NONE(255.255.255.255) 인 경우와 에러인 경우를 모두 포함하므로 구별하기가 모호하다. inet_aton은 구분이 가능하지만 표준에 들어있지 않아.. 거시기 하다ㅡㅡ; 암튼 대충 이렇고, 실제로 주소를 넣는 코드는 아래와 같다.
struct sockaddr_in saddr_svr;
saddr_svr.sin_family = AF_INET;
saddr_svr.sin_port = htons(12000); //port number
// 방법1
saddr_svr.sin_addr.s_addr =
inet_addr ("192.168.100.20");
// 방법2
if (!inet_aton("192.168.100.20", &(saddr_svr.sin_addr.s_addr)))
{
fprintf (stderr, "inet_aton");
}
bind는 성공시 0을, 실패시 -1을 리턴하고 errno를 설정해준다. 위처럼 설정한 sockaddr_in을 아래처럼 binding하면된다.
if (bind(sock_fd, (sockaddr *)saddr_svr, sizeof(saddr_svr)) == -1)
perror("bind");
bind된 socket으로 실제 TCP 요청을 받기 위해(TCP 접속을 위해) listen()을 사용한다. 인자로 backlog 값이 사용되는데, 커널에서 큐로 구현되어 있으며 default로 1024 로 되어 1024개 까지 TCP 접속을 허용할 수 있게된다.
int listen (int s, int backlog);
생성된 소켓으로 외부 요청을 받을 수 있도록 주소를 binding하고 listen 하기 시작했다면, 요청을 기다리다가 들어온 요청을 accept() 하여 받을 수 있다. accept 함수는 bind 소켓과 client의 주소정보를 담을 sockaddr 구조체를 인자로 주면 요청이 들어올 때까지 코드를 block 한다.
int client_sockfd;
struct sockaddr_in client_saddr;
socklen_t client_socklen;
client_socklen = sizeof(client_saddr);
client_sockfd = accept(sock_fd, (struct sockaddr *)&client_saddr, &client_socklen);
printf("client : %s:%d\n", inet_ntoa(client_saddr.sin_addr), ntohs(client_saddr.sin_port));
close(...);
위 코드의 실행시 client의 요청에 대해 아래와 같이 출력된다.
./server 127.0.0.1 21650
Client : fd(4) 127.0.0.1:35130
Subscribe to:
Posts (Atom)