본문 바로가기

CS/유닉스프로그래밍

14. 유닉스 - 소켓

Internet model (TCP/IP protocol suite)

 

 

소켓은 tcp ip를 사용하기 위한 인터페이스 툴임

네트워크는 osi 7계층 하드웨어레이어, 데이터링크계층, 네트워크계층, 전송계층, 세션계층, 표현계층, 응용계층
tcp ip에서는 전송계층 위에 응용계층 하나만 있음 응용 계층에서 프로세스가 돌아감
그 밑에 전송계층에 tcp 프로토콜 udp 프로토콜 이 있고 그 밑에 ip프로토콜이 있고 그 밑에 데이터링크 레이어에
이더넷 토큰링 토큰버스 이렇게 있음 보통 이더넷을 사용함

 

 

 

우리는 컴퓨터에서 돌아가는 여러 개의 프로세스가 있는데 그 중에서 한 개의 프로세스로 인터넷에 연결된 원격진행이 되는 여러개의 프로세스 중에서 한 개의 프로세스와 소통하기를 원함
내 프로세스가 왼쪽이고 통신할 컴퓨터의 프로세스 하나와 통신하기를 원함
그러면 결국은 프로세스와 프로세스 통신을 위해서 하부 구조에서 그 프로세스에서 돌아가는 컴퓨터와 컴퓨터의
통신이 이루어져야함 

그것이 호스트 투 호스트 레이어가 됨 그러면 pc와 pc 사이에 인터넷 망에는 중간에 라우터가 있음
노드와 노드 사이에 통신이 있어야함 그게 데이터 링크 레이어임 호스트와 호스트 사이는 네트워크 레이어임
프로세스와 프로세스 는 전송계층에서 이루어짐
전송 계층에서 tcp udp가 있는 거임 

 

 

 

네트워크 레이어에 ip프로토콜이 있음 네트워크 레이어에 대해서는 pc대pc간 통신에 관여하기 때문에
이 pc의 주소만 지정하면 됨 그래서 pc 주소를 지정하는데 ip주소가 사용됨 
그런데 pc에서 돌아가는 프로세스는 한개만 있는게 아니라 여러개 있음 
그러면 tcp와 udp를 사용해서 프로세스 간의 커뮤니케이션을 지원해주기 위해서는 ip가 가리키는 pc 여러개 프로세스 중 에서 한 개를 지정할 수 있어야 함

pc에서 돌아가는 여러 개 프로세스 중 한 개의 특정 프로세스를 특정해서 지정하기 위해서 필요한 게 포트번호임

프로세스간 통신을 위해서는 pc를 지정하는 ip 그리고 포트넘버 필요
소켓 인터페이스를 사용하는데 여기에 들어가는 주소는 ip와 port 번호 두 개가 필요함

 

UNIX Networking

유닉스의 역사를 보면 AT&T에서 발전한 시스템파이브와 버클리대학에서 BSD버전을 만들고 새로운 기능추가됨
4.1 버전에서 최초로 소켓 인터페이스가 구현됨 system V 는 4년뒤에 TLI라는 인터페이스를 만듦
소켓인터페이스가 표준이 됨 2001년에 포식스 스탠다드
소켓이 표준이 되었음 XTI 또다른 XTI버전도 있음


Sockets

이 소켓은 커뮤니케이션 마지막 단자의 인터페이스의 abstraction임
프로세스 간에 tcp ip 프로토콜을 사용해서 나와있는 단자 끝에 소켓 인터페이스라는 표준 인터페이스를 제공
이 프로세스가 이걸 통해서 커뮤니케이션하는 아주 편리한 프로그래밍을 제공하고 있음
유닉스에서 모든 걸 파일로 간주함 디바이스도 파일로 간주함 


커뮤니케이션 채널도 파일로 간주함 그럼 좋은 점은 파일처럼 open할수있고 그럼 파일디스크립터가 나옴
파일 이름으로 지정할 수 있음 파일디스크립터가 나와서 read랑 write를 할 수 있음 실제로 디스크레귤러파일이나
io나 소켓으로 io하는 거 모두 똑같은 read write 시스템콜을 이용해서 할 수 있음


파일디스크립터로 io를 한다는거, integer로 하는거임 그리고 모든걸 파일로 하고 디스크 터미널 파이프 다 파일임
그래서 상대방과 원격에 있는 인터넷 멀리 떨어진 프로그램과 커뮤니케이션 원할 때 소켓을 그냥 파일디스크립터
얻어서 read write만 하면됨 그 밑에 하부 네트워크에서 일어나는 일은 전혀 신경안쓰고 believe해도 됨

 

 

전송계층에서는 크게 두개의 프로토콜이 있음 tcp와 udp가 있음
udp에서는 user datagram protocol이라고 함 tcp는 transmission control protocol이라고 함
간단히 얘기해서 udp는 connection less, tcp는 connection oriented 임 


두 프로세스 간에 통신을 할 때 이 사이에 물리적 노드들이 굉장히 많이 있는데
connection oriented는 두 프로세스 통신시 connection이 완전히 setup되고 통신이 이루어짐
connection less 는 두 프로세스 통신 시 하나의 완전한 연결이 이루어지지 않고 데이터가 보내질 때마다
자기가 길을 찾아서 감 갈때마다 다른길을 가는거임 tcp는 reliable 하고 flow control, congeston control 도 해줌 전화
udp는 우편 편지 같은거 email unreliable 흐름제어 혼잡제어 안해줌 broadcast multicast 적합

 

소켓 구현

#include <sys/socket.h>

//소켓 구현
int socket(int domain, int type, int protocol);
// returns : file (socket) descriptor if ok, -1 on error

//주소 할당, ip port
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
// returns : 0 if ok, -1 on error

//연결 요청 대기 상태로 진입
int listen(int sockfd, int backlog);
// returns : 0 if ok, -1 on error

//연결 요청 수락
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
// returns : file(socket) descriptor if ok, 1 on error

//클라이언트 연결 요청
int conncet(int sockfd, const struct sockaddr *addr, socklen_t len);
// returns : 0 if ok, -1 on error

 

#include <stdio.h>  
#include <stdlib.h>     
#include <string.h>  
#include <unistd.h> 
#include <arpa/inet.h> 
#include <sys/types.h>
#include<sys/socket.h>

int main(int argc, char **argv){
  int serv_sock, clnt_sock;
  struct sockaddr_in serv_addr, clnt_addr;

  int clnt_addr_size;
  char message[]="Hello World!\n";

  if(argc!=2){ printf("Usage : %s <port>\n", argv[0]); exit(1); }

  serv_sock=socket(PF_INET, SOCK_STREAM, 0); /* 서버 소켓 생성 */  if(serv_sock == -1) error_handling("socket() error");

  memset(&serv_addr, 0, sizeof(serv_addr));
  serv_addr.sin_family=AF_INET;
  serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
  serv_addr.sin_port=htons(atoi(argv[1]));
  
  if( bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1 )
  error_handling("bind() error"); 

  if( listen(serv_sock, 5)==-1 ) error_handling("listen() error");
  clnt_addr_size=sizeof(clnt_addr);  
  clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size); 
  if(clnt_sock==-1) error_handling("accept() error");  
  write(clnt_sock, message, sizeof(message)); /* 데이터 전송 */  
  close(clnt_sock); /* 연결 종료 */
  return 0;
}

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

int main(int argc, char **argv){
  int sock;
  struct sockaddr_in serv_addr;
  char message[30];
  int str_len;
  if(argc!=3){ printf("Usage : %s <IP> <port>\n", argv[0]); exit(1); }

  sock=socket(PF_INET, SOCK_STREAM, 0); 
  if(sock == -1) error_handling("socket() error");
  memset(&serv_addr, 0, sizeof(serv_addr));
  serv_addr.sin_family=AF_INET;
  serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
  serv_addr.sin_port=htons(atoi(argv[2]));

  if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)   error_handling("connect() error!");

  str_len=read(sock, message, sizeof(message)-1); /* 데이터 수신 */

  if(str_len==-1) error_handling("read() error!");
  message[str_len]=0;
  printf("Message from server : %s \n", message);  
  close(sock); /* 연결 종료 */
  return 0;
}

 

UDP

 

Internet addressing

#include <arpa/inet.h>

int inet_aton (const char *strptr, struct in_addr *addrptr);
// returns : 1 if string was valid, 0 on error

in_addr_t inet_addr (const char *strptr);
// returns : 32 bits binary network byte ordered IPv4 address, INADDR_NONE if error

char *inet_nota (struct in_addr inaddr);
// returns : pointer to dotted-decimal string

 

net_aton, inet_addr, inet_ntoa 
a는 string 을 나타내고 n은 binary 값을 의미함 a->n 이라는 의미

in_addr_t는 primitive system data type임 int랑 같은 거임 

inet_addr은 리턴 값으로 binary 주소가 나옴 이런 차이가 있음 위에는 addrptr이 아웃풋인데 두번째 꺼는 리턴값

ntoa는 2진수로된 네 바이트 아이피 주소를 문자열로 된 아이피주소로 바꿔주는게 inet_ntoa
output이 문자열로된 포인터 - string 얘기임 이게 나옴 이런 주소 변환 라이브러리가 준비되어 있음

 

Main Structure

struct sockaddr {
	u_short sa_family;
    char sa_data[14];
};

struct in_addr {
	u_long s_addr;
};

struct sockaddr_in {
	u_short sa_family;
    u_short sin_port;
    struct in_addr sin_addr;
    char sin_zero[8];
};

 

Network byte order

어떤 변수 값이 있을 때 0x는 핵사 값임 네바이트 값임 맨 뒤에 78이 low order 값이고 12가 high order 값임
12345678 이게 메모리에 저장될 때는 메모리에 연결되어있는 네바이트에 저장됨

12는 little 15는 big? 12 34 56 78 low order 값이 big 주소에 저장되고 high가 little에 저장되면

low order를 기준으로 big에 주소에 저장되어 있다고 해서 big에서 끝났다고해서 big endian임

 

하드웨어 업체에따라 다른데 빅엔디안이냐 리틀엔디안이냐가 중요함 데이터를 저장할 때
이런 차이가 있어서 발생하는 문제가 있는데 이런 것들을 편리하게 시스템에서 하드웨어의 차이를 숨겨야함
밑에있는 로우 아키텍쳐에있는 차이를 프로그램에서 드러나지 않고 숨기게하기 위해서 숨기는 일종의 라이브러리임

 

The byte order functions

#include <arpa/inet.h>

uint32_t htonl (uint32_t hostint32);
// returns : 32 bits integer in network byte order

uint16_t htons (uint16_t hostint16);
// returns : 16 bits integer in network byte order

uint32_t ntohl (uint32_t netint32);
// returns : 32 bits integer in host byte order

uint16_t ntohn (uint16_t netint16);
// returns : 16 bits integer in host byte order

network to host short /long 해서 네트워크에서 받은걸 호스트거로 변환해서 받음
그럼 내 컴퓨터가 리틀인지 빅인지 잘 몰라도 알아서 변환해주는거임
system dependent하게 알아서 되어 있음

 

host to network 호스트에서 네트워크로 변환할 때, 호스트에 32비트 4바이트 값이 리턴됨 
호스트의 값이 네트워크의 값이 나오는 건데 호스트가 little인지 big인지 모르는 상태에서 network 꺼로 바꿔서 보냄

uint32이 값을 보냄 uint16_t가 네트워크에 보낼거고 hostin16이 호스트꺼임
네트워크로 부터 받은 값이 netint32 16임 그 값이 들어가면 네바이트나 두바이트 해가지고 
uint32 16이게 호스트에서 사용하는거임 실제 프로그램에서 쓰는 값


The socket(2) system call

#include <sys/socket.h>
int socket (int domain, int type, int protocol);
// returns : file (socket) descriptor if ok, -1 on error

역사적으로 원래 의도는 하나의 프로토콜 패밀리가 여러 개의 어드레스 패밀리들이 지원되도록 하는 것이 의도였음.

PF는 소켓을 생성하기 위해 사용되고, AFsocket address structure들을 설정하는데 사용.

그러나 현재 프로토콜 패밀리는 여러 개의 어드레스 패밀리를 전혀 지원하고 있지 않는다. <sys/socket.h>PFAF는 동일하게 설정되어 있다.

 


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

The listen(2) system call

#include <sys/socket.h>
int listen (int sockfd, int backlog);
// returns : 0 if ok, -1 on error

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

#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;
  }

 /* 연결을 처리할 자식을 하나 낳는다. 만일 자식이면 클라이언트와 정보를 보내고 받는다 */
 }

}

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

 

/* 클라이언트 프로세스 */
#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); 
 }

 /* 서버와 정보를 보내고 받는다. */

}

Sending and receivng data

#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

 

flag가 0이면 read나 write랑 같음

 

그 소켓을 ip 주소를 바인드했음 그리고 이제 서버가 write할 때 client가 read를 close하면
connection oriented에서 read가 close할 때 write하면 sigpipe 날라와서 서버가 죽음
근데 서버는 항상 켜져 있어야 함 그래서 sigpipe 날라와도 죽으면 안됨

그래서 sigpipe에 대해서 catch해서 handle하도록 action을 미리 지정해둠
catcher는 새롭게 accept해서 return 된 거임 그걸 close 하고 exit하면 그런 핸들러라고 한다면 여기서는 exit함 
이 handler를 실행하면 시그널이 못들어오게 하는거임

 

#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);
 }

}

#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);
   }
 }
}

UDP sending and receiving message

#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 는 연결이 없기 때문에 받으면 어디서 받는지, 보낸 쪽의 주소를 알아야 함
보낸 다음에는 보낸 쪽 주소를 또 알아야 함 파일디스크립터 버퍼 사이즈는 read랑 같음 그리고 flag 이건 recv랑 같음
그 다음 주소가 더 있음 두 개가 더 있음 어디서 받았는지, 보낸쪽의 주소가 send_addr 그리고 보낸다하면
앞에까지는 write랑 같고 flag까지해서 send랑 같음 그 밑에 주소는 내가 보내려고 하는 쪽의 주소 두개가 더 있음

 


#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;
   }
 }
}

  

 

#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);
 }
}

'CS > 유닉스프로그래밍' 카테고리의 다른 글

유닉스  (0) 2020.12.11
13. 유닉스 IPC - semaphore, shared memory  (0) 2020.12.10
12. 유닉스 IPC - message queue, semaphore  (0) 2020.12.08
11. 유닉스 IPC - IO multiplexing  (0) 2020.12.08
10. 유닉스 IPC - PIPE, FIFO  (0) 2020.12.08