[Socket Programing] 기본 서버 프로그램 만들어보기

J_JEON·2023년 5월 16일
0

Socket Programing 이란?

모든 컴퓨터는 소통을 하게되는데 이를 위해 데이터를 요청을 하는 클라이언트 역할과 데이터를 내려주는 서버 역할이 생겨남
네트워크를 통해 데이터를 주고받게 해주는것이 네트워크 프로그래밍, 즉 소켓 프로그래밍임

Socket 이란?

물리적인 네트워크 연결 위에서 소프트웨어적인 데이터 송수신 방법을 모든 운영체제에서 제공하는데 이것이 소켓임
데이터를 주고받기위해 소켓 디스크립터라는 파일 시스템을 사용하는데 이것을 소켓이라고 생각하면 됨

서버를 만들기위한 절차


1. Socket()
다른 클라이언트와 통신을 하기위한 소켓을 생성해줌
2. Bind()
내가 만드려는 서버의 ip와 포트번호를 소켓에 할당해줌
3. listen()
다른 클라이언트에서 접근할때 응답할 수 있도록 대기함
4. accept()
다른 클라이언트로부터 연결 요청이 왔을 때 수락해줌
5. read(), write()
연결된 클라이언트와 데이터를 주고 받음
6. close()
연결된 클라이언트와 연결을 종료함

필요 함수

Socket()

  • 소켓을 생성하는 함수
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
  • 인자값

    domain
    인터넷을 통해 통신할지, 같은 시스템 내에서의 프로세스끼리 통해 통신할지 설정.
    PF_INET은 IPv4, PF_INET6는 IPv6, AF_UNIX는 같은 시스템내의 프로세스끼리 통신
    type
    데이터의 전송 형태 지정
    SOCK_STREAM은 TCP/IP 프로토콜 사용,SOCK_DGRAM은 UDP/IP 프로토콜 사용
    protocol
    통신에서 사용할 프로토콜을 지정, 보통 0 사용

  • 리턴값

    소켓 디스크립터를 반환, 소켓 생성 실패시 -1

Bind()

  • 소켓과 서버의 정보를 묶어주는 함수
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
  • 인자값

    sockfd
    bind할 소켓의 소켓디스크립터
    *myaddr
    서버의 IP주소
    addrlen
    주소의 길이

  • 리턴값

    성공시 0, 실패시 -1

listen()

  • 다른 클라이언트에서 접근할때 응답할 수 있도록 대기
#include <sys/socket.h>
int listen(int sockfd, int backlog);
  • 인자값

    sockfd
    소켓의 소켓 디스크립터
    backlog
    연결 대기열의 크기를 지정

  • 리턴값

    성공시 0, 실패시 -1

accept()

  • 서버 소켓에 클라이언트를 연결해주는 함수
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t addrlen);
  • 인자값

    sockfd
    소켓의 소켓 디스크립터
    *addr
    클라이언트 주소 정보를 담고있는 구조체
    addrlen
    2번째 인자값의 길이

  • 리턴값

    성공시 클라이언트 소켓 디스크립터, 실패시 -1

코드

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

void error_handling(char *message);

int main(int argc, char **argv)
{
	int serv_sock;
	int clnt_sock;

	int str_len;
	int sender;
	char message[500];

	// AF_INET의 경우 소켓 주소의 틀을 저장하는 sockaddr_in 구조체 사용
	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr; // accept에서 사용
	socklen_t clnt_addr_size;

	char msg[] = "Hello this is server!\n";

	// TCP 프로토콜을 사용하고 IPv4 도메인을 위한 소켓 생성
	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; // 타입을 IPv4로
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip주소를 host바이트에서 network바이트로 변환하여 저장
	serv_addr.sin_port = htons(atoi(argv[1])); //port번호를 host바이트에서 network바이트로 변환하여 저장

	//소켓과 서버 주소를 바인딩
	if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
		error_handling("bind error");

	//연결 대기열 5개 생성
	if (listen(serv_sock, 5) == -1)
		error_handling("listen error");
	//pollfd 배열 구조체 생성
	struct pollfd fd_list[100];
	int fd_count = 0;

	fd_list[0].fd = serv_sock; //0번째 배열에 listen 지정
	fd_list[0].events = POLLIN; //읽도록
	fd_list[0].revents = 0; //처음엔 0으로 초기화
	fd_count++;

	//나머지 fd_list에는 아직 fd가 없기때문에 -1 저장
	for(int i = 1; i < 100; i++)
		fd_list[i].fd = -1;

	while(1) // 무한정 대기
	{
		int result = poll(fd_list, fd_count, -1);

		if (result > 0)
		{
			if (fd_list[0].revents == POLLIN) // 서버에 이벤트 발생
			{
				// 클라이언트에게 연결이 올 시 수락
				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");
				fd_list[fd_count].fd = clnt_sock;
				fd_list[fd_count].events = POLLIN;
				printf("port: %d is conneted\n", ntohs(clnt_addr.sin_port));
				write(fd_list[fd_count].fd, msg, sizeof(msg));
				fd_count++;
			}
			else // 클라이언트에 이벤트 발생
			{
				for (int i = 1; i < fd_count ; i++)
				{
					switch (fd_list[i].revents)
					{
						case 0:
							break;
						case POLLIN:
						{
							str_len = read(fd_list[i].fd, message, 500);
							message[str_len] = 0;
							fputs(message, stdout); // 서버에 받은 메시지 출력
							fflush(stdout); // 버퍼 비우기
							sender = fd_list[i].fd;
							for(int i = 1 ; i < fd_count ; i++)
							{
								if (sender != fd_list[i].fd)
								{
									// 전송 클라이언트를 제외한 나머지에 모두 전송
									write(fd_list[i].fd, message, strlen(message));
								}
							}
						}
					}
				}
			}
		}
		else
			error_handling("poll error");
	}

	//소켓 닫기
	for(int i = fd_count - 1 ; fd_count != 0 ; i--)
		close(fd_list[i].fd);

	return (0);
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

사용방법

gcc main.c -o server 로 컴파일
./server 4242로 4242포트 사용하여 서버를 열어줌
telnet 127.0.0.1 4242 사용하여 telnet으로 localhost의 4242포트로 접속

출처

https://jhnyang.tistory.com/251

profile
늅늅

0개의 댓글