상세 컨텐츠

본문 제목

UNIX - Socket Programming TCP / UDP

Computer Science/UNIX

by 2021. 12. 3. 14:25

본문

반응형

Selecting the Protocol

socket을 통해 소켓을 생성시 다음과 같은 방법으로 TCP / UDP를 선택할 수 있다.
 
Connection oriented (streams)
sd = socket(AF_INET, SOCK_STREAM, 0);
Connectionless (datagrams)
sd = socket(AF_INET, SOCK_DGRAM, 0);

 

Programming the connection oriented model

connection oriented model에서 connect과정에 필요한 나머지 system call 을 알아보자.

The bind(2) system call

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);

// Returns: 0 if OK, -1 on error

socket으로 생성한 socket file descriptor를 sockfd에 넣어 bind한다.

 

The listen(2) system call

#include <sys/socket.h>
int listen(int sockfd, int backlog);

// Returns: 0 if OK, -1 on error

backlog로 연결요청 대기 큐의 개수를 설정한다.

 

The accept(2) system call

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *restrict addr,
				socklen_t *restrict len);

// Returns: file (socket) descriptor if OK, -1 on error

addr과 len argument의 경우 이미 connect된 상태에서는 상대방의 주소를 넣을 필요없이 NULL을 입력해도 된다. 

 

example - Server process

#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SIZE sizeof (struct sockaddr_in)
int newsockfd;

main(){
	int sockfd;
	struct sockaddr_in server = {AF_INET, 7000, INADDR_ANY);

	if ( (sockfd = socket (AF_INET, SOCK_STREAM, 0)) == -1){
		perror ("socket call failed"); exit (1);
	}
	if ( bind(sockfd, (struct sockaddr *)&server, SIZE) == -1){
		perror ("bind call failed");	exit (1);
	}
	if ( listen(sockfd, 5) == -1){
		perror ("listen call failed"); exit (1);
	}
	for ( ; ;){
		if ( (newsockfd = accept(sockfd, NULL, NULL)) == -1){
			perror ("accept call failed"); continue;
		}
	/* 연결을 처리할 자식을 하나 낳는다. 만일 자식이면 클라이언트와 정보를 보내고 받는다 */
	}
}

socket을 통해 소켓을 지정하고 얻은 sockfd를 이용하여 bind한다. 

sockaddr_int server에는 family, port, IP주소가 들어있어, 해당 주소를 bind 할당한다. 

listen으로 connect 준비를 하고, accept하여 client의 connect를 wait한다. accept에서 client의 주소는 NULL로 비워놓는다. accept가 성공할 경우 fork로 자식을 생성하여 정보를 주고받는 역할을 한다. 

 

The connect(2) system call

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);

// Returns: 0 if OK, -1 on error

server로 connect할 때 사용한다. addr에는 server의 주소가 들어간다.

 

example - Client process

/* 클라이언트 프로세스 */
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SIZE sizeof (struct sockaddr_in)

main(){
	int sockfd;
	struct sockaddr_in server = {AF_INET, 7000};

	/* 서버의 IP 주소를 변환하여 저장한다. */
	server.sin_addr.s_addr = inet_addr ("206.45.10.2");

	/* 트랜스포트 엔드 포인트를 구축한다. */
	if ( (sockfd = socket (AF_INET, SOCK_STREAM, 0)) == -1){ 
		perror ("socket call failed"); exit( 1);
	}

	/* 소켓을 서버의 주소에 연결한다. */
	if ( ( connect (sockfd, (struct sockaddr *) &server, SIZE) == -1){
		perror ("connect call failed");
		exit (1); 
	}
	/* 서버와 정보를 보내고 받는다. */
}

server의 s_addr에 서버의 IP주소를 변환하여 저장한다. 문자열 IP주소를 inet_addr로 binary 4byte로 변환시켜 저장한다. 

socket을 통해 소켓을 생성하고, connect로 서버의 주소에 연결한 후 connect에서 사용한 sockfd로 정보를 주고받는다. 

 

Sending and receiving data

TCP에서 send / receive에 사용되는 함수는 다음과 같다.

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
	
// Returns: length of message in bytes, 0 if no messages are available 		     
		// and peer has done an orderly shutdown, or -1 on error

ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);

// Returns: number of bytes sent if OK, -1 on error

flags argument를 제외하면 read / write와 같은 방식으로 사용된다. 

 

Closing the connection

TCP에서 상대방의 연결이 끊어지는 것에는 민감하다.

끊어진 socket으로 write나 send를 시도하게 되면 SIGPIPE signal이 발생한다. 

connection이 끊어진 socket으로 read나 recv를 시도하면 0을 return한다. 

 

example - Server process

#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#define SIZE  sizeof (struct sockaddr_in)

int newsockfd;
void catcher (int sig){
	close (newsockfd);
	exit (0);
}
main(){
	int sockfd;
	char c;
	struct sockaddr_in server = {AF_INET, 7000, INADDR_ANY};
	static struct sigaction act;
	act.sa_handler = catcher;
	sigfillset (&(act.sa_mask));
	sigaction (SIGPIPE, &act, NULL);
	if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
		perror ("socket call failed");  exit (1);
	}
	if (bind (sockfd, (struct sockaddr *) &server, SIZE) == -1) {
		perror ("bind call failed");	 exit (1);  
	}
	if ( listen(sockfd, 5) == -1 ){/* 들어오는 연결을 듣기 시작한다. */ 
		perror ("listen call failed"); exit (1); 
	}
    
	for (;;){
		if ( (newsockfd = accept (sockfd, NULL, NULL)) == -1){
			perror ("accept call failed");
			continue;
		}
		/* 연결을 처리할 자식을 하나 낳는다. */
		if ( fork() == 0){
			while (recv (newsockfd, &c, 1, 0) > 0){
			c = toupper(c);
			send (newsockfd, &c, 1, 0);
		}
		close (newsockfd);
		exit (0);
		}
		/* 부모는 newsockfd를 필요로 하지 않는다. */
		close (newsockfd);
	}
}

sockaddr_in server에 internet type, port, IP를 할당하여 socket을 생성한다. 

발생할 SIGPIPE에 대한 handler act를 설정한다.

server는 accept로 client의 request에 대한 응답을 대기한다. 

 

fork를 통해 연결을 처리할 child를 생성한다. child가 recv / send를 통해 client와 정보를 주고받고,

parent는 다음 client를 accept하기 위해 이전에 accept한 socket을 close한다. 

child는 recv의 return이 0이 될 경우 client에서 close한 것이므로 exit한다. 

 

example - Client process

#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SIZE sizeof (struct sockaddr_in)

main(){
	int sockfd;
	char c, rc;
	struct sockaddr_in server = {AF_INET, 7000};

	server.sin_addr.s_addr = inet_addr("197.45.10.2");
	if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
		perror ("socket call failed"); exit (1);
	}
	if ( connect(sockfd, (struct sockaddr *) &server, SIZE) == -1){
		perror ("connect call failed");
		exit (1);
	} 
	for (rc = '\n‘; ;){
		if (rc == '\n') printf ("Input a lower case character\n");
		c = getchar();
		send (sockfd, &c, 1, 0);
		if (recv(sockfd, &rc, 1, 0)>0) printf ("%c", rc);
		else {
			printf ("server has died\n");
			close (sockfd);
			exit (1);
		}
	}
}

server주소를 sockaddr_in server에 저장하고, socket을 생성한다. 

connect를 성공하면, server와 send / recv를 통해 정보를 주고받다가 exit한다.

 

 

Programming the connectionless  model

이번에는 connectionless model의 연결과정을 알아보자.

 

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags,
			struct sockaddr *restrict send_addr, socklen_t *restrict addrlen);
// Returns: length of message in bytes, 0 if no messages are available 		   
	// and peer has done an orderly shutdown, or -1 on error
 
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags,
			const struct sockaddr *dest_addr, socklen_t destlen);
// Returns: number of bytes sent if OK, -1 on error

connectionless model은 별도의 연결과정이 없으므로 recv / send에 상대방 socket의 주소가 필요하다.

sockfd, buf, flags는 recv / send와 같지만, send_addr, addrlen / dest_addr, destlen과 같은 주소 정보 argument가 추가로 필요하다.

 

example - Server process

#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SIZE sizeof (struct sockaddr_in)

main(){
	int sockfd;
	char c;
	struct sockaddr_in server = {AF_INET, 7000, INADDR_ANY};
	struct sockaddr_in client;
	int client_len = SIZE;
	if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1){
		perror ("socket call failed");  exit(1);
	} 
 
	if ( bind(sockfd, (struct sockaddr *) &server, SIZE)== -1){
		perror ("bind call failed"); exit (1);
	}
	for( ; ;){
		if (recvfrom(sockfd, &c, 1, 0,&client, &client_len)== -1){
			perror ("server: receiving"); continue;
		}
		c = toupper(c);
	   
		if ( sendto(sockfd, &c, 1, 0, &client, client_len) == -1){
			perror ("server: sending"); continue;
		}
	}
}

 UDP server 코드에서는 sockaddr_in server 에 서버 정보를 할당하고, socket 및 bind만을 거치고 바로 recvfrom / sendto를 통해 데이터를 주고 받을 수 있다. 

 

example - Client process

#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SIZE sizeof (struct sockaddr_in)

main(){
	int sockfd;
	char c;
	struct sockaddr_in client = {AF_INET, INADDR_ANY, INADDR_ANY};
	struct sockaddr_in server = {AF_INET, 7000};
	server.sin_addr.s_addr = inet_addr("197.45.10.2");

	if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1){
		perror ("socket call failed"); exit (1);
	} 
	if ( bind(sockfd, (struct sockaddr *) &client, SIZE)== -1) {
		perror ("bind call failed"); exit (1);
	}
	while ( read(0, &c, 1) != 0){
		if ( sendto (sockfd, &c, 1, 0, &server, SIZE) == -1){
			perror ("client: sending"); continue;
		}
		if (recv(sockfd, &c, 1, 0)== ―1){
			perror ("client: receiving"); continue;
		}
		write (1, &c, 1);
	}
}

client에서는 sendto에서는 server주소를 지정하지만, recv에서는 recvfrom을 통해 서버를 한정하지 않고 데이터를 받는다. 

반응형

관련글 더보기