소켓 프로그래밍

EEEFFEE·2023년 11월 20일
0

네트워크

목록 보기
7/9

23.11.20 최초 작성

1. 네트워크 응용 개요

1.1 client-server 방식

  • 클라이언트가 요청하면 서버가 응답하는 방식으로 작동하는 통신 방식
  • client : 서비스를 요청하는 시스템
  • server : 서비스를 제공하는 시스템 (데이터베이스, 파일서버 등)
  • server의 용량에 의해 서비스할 수 있는 client의 수가 제한 됨

1.2 P2P(Peer-to-Peer) 방식

  • 사용자 간 직접 통신하는 형태의 통신 방식
  • 다른 Peer의 상황을 고려해 운영해야 함

2. socket

  • socket : 컴퓨터 간 통신을 가능하게 하는 end-point
  • stream socket
    • tcp 기반의 소켓으로 연결지향적, 신뢰성 있는 데이터 전송 보장
    • 웹 브라우징, 이메일 전송 등에 적용 됨
  • datagram socket
    • udp기반의 소켓으로 속도가 빠름
    • 음성 및 동영상, 온라인 게임 등에 적용 됨

2.1 socket 구조체


struct sockaddr{
	__SOCKADDR_COMMON (sa_);		//주소 체계를 나타내며 TCP/IP 사용 시 AF_INET
    char sa_data[14];				//실제 주소 데이터
}


#include <netinet/in.h>

struct cokaddr_in{
	short		sin_family;			//(sa_)에 해당하는 부분
    
    //sa_data[14]를 3부분으로 나눈 것
    unsigned short sin_port;		//포트 번호 나타냄
    struct in_addr sin_addr;		//IP 주소 나타냄
    char		sin_zero[8];		//실제로 사용하지 않으나 0으로 채움
}

struct in_addr{
	unsigned long s_addr;			//load with init_aton()
}

2.2 socket 관련 함수

2.2.1 socket 조작 및 통신 함수

  • socket() : 소켓을 생성하는 함수

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

///
domain : 인터넷을 통해 통신할 지, 같은 시스템 내에서 프로세스 끼리 통신할 지의 여부
type : 데이터의 전송 형태 지정
protocol : 특정 프로토콜을 사용을 지정, 보통 0

Return : 소켓 디스크립터
-1 : Fail

  • getsockopt(), setsockopt : 소켓의 설정 확인 / 변경

#include <sys/types.h>
#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);

										//optname값 의미
										//SOL_SOCKET : socket API level
                                        //IPPROTO_IP : IP protocol level
                                        //IPPROTO_TCP : TCP protocol level


  • bind : 소켓에 주소를 할당

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen);

///
sockfd : socket() 함수를 통해 배정받은 디스크립터 번호 serv_sock
addr : IP주소와 PORT번호를 지정한 serv_addr 구조체 (serv_addr은sockaddr_in 이므로 sockaddr 구조체로 변환해준다.)
addrlen : 주소정보를 담은 변수의 길이

Return
(0 : Success, -1 : Fail)

  • listen() : 연결요청을 대기

int listen(int sock, int backlog);

///
sock : 소켓 디스크립터 번호
backlog : 연결요청을 대기하는 큐의 크기

Return
(0 : Success, -1 : Fail)

  • accept() : 연결 요청을 수락

int accept(int sock, struct sockaddr*addr, socklen_t *addrlen);

///
sock : 서버소켓(리스닝소켓)의 디스크립터 번호
addr : 대기 큐를 참조해 얻은 클라이언트의 주소정보
addrlen : addr변수의 크기

반환값
0 이상 : Success, 소켓 디스크립터
-1 : Fail

2.2.2 메시지 송수신 함수

  • recv, recvfrom, recvmsg : 켓에서 메시지를 수신

#include <sys/socket.h>

int recv(int Socket, void *Buffer, size_t Length, int Flags);

ssize_t recvfrom(int Socket, void *Buffer, size_t Length, int Flags, struct sockaddr *From, socklen_t * FromLength);

int recvmsg(int Socket, struct msghdr Message,  int Flags);

///
Socket : 소켓 디스크립터 
Buffer : 메시지가 저장될 공간의 주소
Length : Buffer의 길이
Flag : 메시지 수신을 제어하는 값
From : 메시지를 보내는 주소가 들어간 구조체
FromLegnth : 송신자의 길이 지정
Message : msghdr 구조의 주소

Flag 옵션
	MSG_OOB : 대역 외 데이터를 처리
    MSG_PEEK : 수신 데이터에 있는 Peeks를 참조, 데이터는 읽지 않은 것으로 계속 처리되며 다음 수신 기능을 하는 함수에게 읽혀짐
    MSG_WAITALL : 요청된 바이트 수를 읽을 때까지 함수가 리턴되지 않도록 요청

Return : 수신한 메시지의 (recv, recvfrom : 바이트 수, recvmsg : 메시지의 수)
-1 : Fail

  • send, sendto, sendmsg : 소켓에서 메시지를 보냄

#include <sys/types.h>
#include <sys/socketvar.h>
#include <sys/socket.h>

int send(int Socket, const void *Message, size_t Length, int Flags);

int sendto(int Socket, const void *Message, size_t Length, int Flags, const struct sockaddr *To, socklen_t * ToLength)

int sendmsg(int Socket, struct msghdr Message, int Flags);


///
Socket : 소켓 디스크립터 
Message : 메시지가 저장된 공간의 주소
Length : Message의 길이
Flag : 메시지 송신을 제어하는 값
To : 메시지의 수신자 주소를 지정
ToLength : 수신자 주소의 크기를 지정

Flag 옵션
	MSG_OOB : SOCK_STREAM 통신을 지원하는 소켓의 대역 외 데이터를 처리
    MSG_DONTROUTE : 라우팅 테이블을 사용하지 않고 전송
    MSG_MPEG2 : 이 블록이 MPEG2 블록임을 표시
    
Return : 송신한 메시지의 (send, sendto : 바이트 수, sendmsg : 메시지의 수)
-1 : Fail

2.2.3 IP 형식 변경 함수

  • inet_aton() : Dotted-Decimal Notation 형식을 네트워크 바이트 오더(Big Endian 32bit) 값으로 변환
    struct sockaddr구조체의 struct in_addr에 IP 주소를 입력할 때 사용

#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);

///
cp : Dotted-Decimal Notation형식의 IP 주소
inp : IP 주소를 입력할 in_addr 구조체

Return 
0이 아닌 값 : Success
0 : Fail

  • inet_pton() : 네트워크 바이트 오더(Big Endian 32bit)형식을 Dotted-Decimal Notation값으로 변환

#include <arpa/inet.h>

int inet_pton(int af, const char *src, void *dst);

///
af : address family를 지정
src : 문자열 형태의 IP주소
dst : src를 binary형태로 변환 후 복사된 메모리의 포인터를 저장할 공간

Return 
1 : Success
0 : address family의 유효한 값이 아닌 경우
-1 : Fail

  • inet_netof, inet_ntoa, inet_lnaof : unsigned long형식의 IP 주소를 Dotted-Decimal Notation형식으로 변환

#include <sys/socket.h>
#include <sys/socketvar.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_netof(struct in_addr InternetAddr);
int inet_Inaof (struct in_addr InternetAddr);

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char *inet_ntoa (struct in_addr InternetAddr);

///
InternetAddr : unsigned long형식의 IP 주소

Return : 인터넷 주소 (lnaof의 경우 Class A, B, C에 따른 리턴값 다름)
-1 : Fail

  • inet_network, inet_addr : Dotted-Decimal Notation형식의 IP 주소를 unsiged long값으로 변환

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_network (register const char *CharString);


#include <sys/socket.h>
#include <sys/socketvar.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_addr (register const char *CharString);

///
CharString : Dotted-Decimal Notation형식의 IP 주소

Return
unsigned Integer : Success
-1 : Fail

  • inet_makeaddr : 네트워크 IP호스트 IP를 기반으로 주소가 포함된 in_addr구조체를 리턴

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

struct in_addr inet_makeaddr (int Net, int LocalNetAddr);

///
Net : 인터넷 IP
LocalNetAddr : 로컬 네트워크 IP

Return : IP 주소가 포함도니 in_addr 구조체
-1 : Fail

  • htonl, htons, ntohl, ntohs : 호스트와 네트워크에 맞는 형식으로 변경

#include <netinet/in.h>

uint32_t htonl(uint32_t long data);		//host형식을 network short로
uint16_t htons(uint16_t char data);		//host형식을 network long으로
uint32_t ntohl(uint32_t long data);		//network형식을 host short로
uint16_t ntohs(uint16_t char data);		//network형식을 host long으로

2.2.4 호스트 이름 조작

  • 네트워크 데이터베이스는 etc/hosts/ 파일에 위치하며 아래 함수들은 해당 값과 관련된 것들임
  • gethostbyname, gethostbyaddr : 호스트 이름/ IP 주소를 바탕으로 네트워크 데이터베이스에서 값을 찾아 hostent구조체에 저장해 포인터를 반환
  • gethostent, sethostent, endhostent : 네트워크 호스트 파일을 open/읽는 위치 결정/닫는 함수 (libnsl.so include 해줘야 함)

#include <netdb.h>

struct hostent *gethostbyname(char *Name);

struct hostent *gethostbyaddr(const void *Address, size_t Length, int Type);

struct hostent *gethostent(void);	//호스트명과 IP 주소를 읽어서 hostent 구조체에 저장하고 그 주소를 리턴

int sethostent(int StayOpen);		//IP 주소 데이터베이스의 현재 읽기 위치를 시작 부분으로 재설정

int endhostent(void);			//IP 주소 데이터베이스를 닫음

///
Name : 호스트 이름

Address : 호스트 주소
Length : 호스트 주소 길이
Type : 호스트 주소 도메인 유형 (AF_INET, AF_INET6)

StayOpen : IP 주소 데이터베이스를 열어둘지 여부를 나타내는 값
			 (0이 아니면 데이터베이스가 열린 채로 둠)

struct hostent{
		char *h_name;
		char **h_aliases;
		int h_addrtype;
		int h_length;
		char **h_addr_list;
};

/*
* h_name : 호스트명 저장
* h_aliases : 호스트를 가리키는 다른 이름들 저장
* h_addrtype : 호스트 주소의 형식 지정
* h_length : 주소의 길이 저장
* h_addr_list : 해당 호스트의 주소 목록을 저장
*/

Return 
gethostbyname, gethostbyaddr, gethostent : 해당 구조체를 반환
NULL : Fail

sethostent endhostent 
0 : Success, -1 : Fail

3. TCP 서버 & 클라이언트

3.1 TCP 서버

  • 클라이언트에게서 온 문자열을 대문자로 바꿔 다시 보내주는 코드

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h>

int main(int argc, char *argv[]) {
	struct sockaddr_in server, remote;
	int request_sock, new_sock;						//요청을 받는 소켓, 요청을 받고 실제 연결을 넘겨받을 소켓
	int bytesread, addrlen;
	int i;
	char buf[BUFSIZ];
    
    // port 번호를 argv에 받아야 함
	if (argc != 2) {
		(void) fprintf(stderr,"usage: %s port\n", argv[0]);
		exit(1);
	}
    
    // 소켓 생성
	if ((request_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {								
		perror("socket");
		exit(1);
	}
	
    // 소켓과 메모리상의 주소를 바인드 해 줌 
	memset((void *) &server, 0, sizeof (server));
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	printf("%d\n", INADDR_ANY);
	server.sin_port = htons((u_short)atoi(argv[1]));	//호스트 타입으로 지정된 short 타입을
														//네트워크에서 사용하는 형식으로 바꿔 줌

	if (bind(request_sock, (struct sockaddr *)&server, sizeof (server)) < 0) {
		perror("bind");
		exit(1);
	}
    
    //받은 메시지를 저장하는 queue의 크기 지정
	if (listen(request_sock, SOMAXCONN) < 0) {
		perror("listen");
		exit(1);
	}
    
    //요청 대기 &
	for (;;) {
		addrlen = sizeof(remote);
        
        //연결 요청 올 때 까지 대기
		new_sock = accept(request_sock,
			(struct sockaddr *)&remote, (socklen_t *)&addrlen);	
		if (new_sock < 0) {
			perror("accept");
			exit(1);
		}
		printf("connection from host %s, port %d, socket %d\n",
			inet_ntoa(remote.sin_addr), ntohs(remote.sin_port), new_sock);
u_short ntohs(u_short netshort);
		// inet_ntoa : 네트워크 바이트 순서의 32비트 값을 Dotted-Demical-Notation의 
        //				주소값으로 변환해 문자열 포인터를 반환
		// ntohs : 네트워크 바이트 정렬 방식의 2바이트 데이터를 
        //			호스트 바이트 정렬 방식으로 변환
		
        
        //소켓에서 데이터를 읽어서 내용을 대문자로 바꿔 다시 보내 줌
		for (;;) {
			bytesread = read(new_sock, buf, sizeof (buf) - 1);
			if (bytesread<=0) {
				printf("server: end of file on %d\n", new_sock);
				if (close(new_sock)) 
					perror("close");
				break;
			}
			buf[bytesread] = '\0';
			printf("%s: %d bytes from %d: %s\n", 
				argv[0], bytesread, new_sock, buf);
			for(i = 0; i < bytesread; i++)
				buf[i] = toupper(buf[i]);
			/* echo it back */
			if (write(new_sock, buf, bytesread) != bytesread)
				perror("echo");
		}
	}
}

3.2 TCP 클라이언트

  • 키보드로 데이터 입력하고 서버로부터 온 데이터 모니터로 출력하는 코드

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>

int main(int argc, char *argv[])
{
	struct hostent *hostp;			//호스트의 정보 저장
	struct sockaddr_in server;		//서버의 주소를 저장
	int sock;						//socket 디스크립터 저장

	char buf[BUFSIZ];				//보낼 데이터
	int bytesread;					//읽은 데이터의 크기 저장

	//호스트 IP or Domain 이름, 호스트 이름, 포트번호 인자로
	if(argc != 3)
	{
		(void) fprintf(stderr,"usage: %s host port\n", argv[0]);
		exit(1);
	}
	
    //소켓 생성
	if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
		perror("socket");
		exit(1);
	}

	if ((hostp = gethostbyname(argv[1])) == 0) {
		fprintf(stderr,"%s: unknown host\n",argv[2]);
		exit(1);
	}
	
    // 소켓과 메모리상의 주소를 바인드 해 줌 
	memset((void *) &server, 0, sizeof (server));
	server.sin_family = AF_INET;
	memcpy((void *) &server.sin_addr, hostp->h_addr, hostp->h_length);
	server.sin_port = htons((u_short)atoi(argv[2]));
	
    //서버와 연결 생성
	if (connect(sock, (struct sockaddr *)&server, sizeof (server)) < 0) {
		(void) close(sock);
		fprintf(stderr, "connect");
		exit(1);
	}
    
    
	for (;;) {
		/* data from keyboard */
		if (!fgets(buf, sizeof buf, stdin)) {
			close(sock);
			exit(0);
		}
        
        // 소켓에 데이터를 쓰고 서버에 전송
		if (write(sock, buf, strlen(buf)) < 0) {
			perror("write");
			exit(1);
		}
        
        // 서버로부터 온 데이터 읽음
		bytesread = read(sock, buf, sizeof buf - 1);
		buf[bytesread] = '\0';
		printf("%s: got %d bytes: %s\n", argv[0], bytesread, buf);
	}
} 

4. UDP server & client

4.1 UDP server


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


int main(int argc, char *argv[])
{
   int sockid, nread, addrlen;
   int i;
   struct sockaddr_in my_addr, client_addr;
   char msg[50];
   if(argc != 2)
   {
	printf("%s port\n", argv[0]);
	return 0;
   }
	
    
    //소켓 생성
   printf("Server: creating socket\n");
   if ( (sockid = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
      { printf("Server: socket error: %d\n",errno); exit(0); }
	
    // 소켓과 메모리상의 주소를 바인드 해 줌 
   printf("Server: binding my local socket\n");
   memset((char *) &my_addr, 0, sizeof(my_addr));
   my_addr.sin_family = AF_INET;
   my_addr.sin_addr.s_addr = INADDR_ANY;
   my_addr.sin_port = htons(atoi(argv[1]));
   if ( (bind(sockid, (struct sockaddr *) &my_addr, 
      sizeof(my_addr)) < 0) )
      { printf("Server: bind fail: %d\n",errno); exit(0); }   

   while(1) {
     printf("Server: starting blocking message read\n");
     addrlen = sizeof(client_addr);
     
     //클라이언트에서 데이터를 읽음 (UDP)
     nread = recvfrom(sockid, msg, sizeof (msg) -1 ,0,
                 (struct sockaddr *) &client_addr, (socklen_t *)&addrlen);
     msg[nread] = '\0';
     printf("Server: retrun code from read is %d\n",nread);
     printf("msg from host %s, port %d, socket %d\n",
                        inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), sockid);

     if (nread >0) printf("Server: message is: %s\n",msg);
     for(i = 0; i < nread; i++)
       msg[i] = toupper(msg[i]);
     sendto(sockid, msg, strlen(msg), 0, (struct sockaddr *) &client_addr, sizeof(client_addr));
   }

   close(sockid);
 }

4.2 UDP client


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
   int sockid, retcode;
   int nread, addrlen;
   struct hostent *hostp;

   struct sockaddr_in my_addr, server_addr;
   char msg[128];
   if(argc != 4)
   {
	printf("%s myport serveraddr serverport\n", argv[0]);
        return 0;
   }
   
   // 소켓 생성
   printf("Client: creating socket\n");
      if ( (sockid = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
      { printf("Client: socket failed: %d\n",errno); exit(0); }
	
   // 소켓과 메모리상의 주소를 바인드 해 줌 
   printf("Client: binding my local socket\n");
   memset((char *) &my_addr, 0, sizeof(my_addr));
   my_addr.sin_family = AF_INET;
   my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
   my_addr.sin_port = htons(atoi(argv[1]));

   if ( (bind(sockid, (struct sockaddr *) &my_addr, 
              sizeof(my_addr)) < 0) )
      { printf("Client: bind fail: %d\n",errno); exit(0); }   

    if ((hostp = gethostbyname(argv[2])) == 0) {
        fprintf(stderr,"%s: unknown host\n",argv[2]);
        exit(1);
    }


   printf("Client: creating addr structure for server\n");
   bzero((char *) &server_addr, sizeof(server_addr));
   server_addr.sin_family = AF_INET;
   server_addr.sin_addr.s_addr = inet_addr(argv[2]);
	memcpy((void *) &server_addr.sin_addr, hostp->h_addr, hostp->h_length);
   server_addr.sin_port = htons((u_short)atoi(argv[3]));

   printf("Client: initializing message and sending\n");

   /* data from keyboard */
   if (!fgets(msg, sizeof (msg) - 1, stdin)) {
          close(sockid);
	exit(0);
   }

	//서버에 데이터를 보냄
   retcode = sendto(sockid,msg,strlen(msg),0,(struct sockaddr *) &server_addr,
 		    sizeof(server_addr));
   if (retcode <= -1)
     {printf("client: sendto failed: %d\n",errno); exit(0); }   

   printf("Client: Successful\n");

   addrlen = sizeof(server_addr);
   nread = recvfrom(sockid, msg, sizeof (msg) - 1 ,0,
                 (struct sockaddr *) &server_addr, (socklen_t *)&addrlen);

   printf("Msg: %s\n", msg);
   /* close socket */
   close(sockid);
 }

0개의 댓글

관련 채용 정보