#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
# define PORT 10000
int main(int argc, char const *argv[]) {
int server_fd, new_socket, valread;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
char *hello = (char*)"Hello from server";
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsocket");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons( PORT );
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
perror("accpet");
exit(EXIT_FAILURE);
}
valread = read( new_socket, buffer, 1024 );
printf("%s\n", buffer);
send(new_socket, hello , strlen(hello), 0 );
printf("Hello message sent\n");
return 0;
}
stdlib은 c표준 유틸리티함수의 모음이고, stdio는 표준 입출력 라이브러리이다. cpp에서는 iostream으로 쓰기도 하는데 stdio도 먹힌다. (속도도 더 빠르다고 한다.)
윈도우에서는 사용할 수 없는(vs 환경이라면 io.h헤더로 바꾸거나, vs를 안쓰면 된다고는 한다) 헤더파일이다. POSIX 운영체제 (이식가능한 운영체제) API를 제공하는 헤더파일이다.
기능으로는 read, write가 있는데
버퍼에서 데이터를 읽고/쓰고 성공하면 바이트수를 반환, 실패하면 -1을 반환한다.
ssize_t read(int fd, void* buf, size_t nbytes);
ssize_t write(int fd, void* buf, size_t nbytes);
socklen_t 타입을 가능하게 만들어주는 헤더이다.
socklen_t 는 unsigned opaque integral type of length of at least 32 bits. 이라고 되어있다.
해석해보면 부호없는 불투명한 최소 32비트의 길이를 가진 정수타입 이라는 뜻이다...
이 헤더는 소켓관련 핵심기능을 수행하는데, 일단 sockaddr struct를 정의한다. 이는 잠시후에 설명하겠다. 이 밖에도 msghdr, cmsghdr등을 정의해준다고 한다.
또한 우리가 쓰는 accept, bind, connect, send, recv, listen 등의 함수도 이 헤더안에 구현되어 있다.
이 헤더는 in_port_t, in_addr_t 타입을 정의한다.
in_addr, sockaddr_in를 정의한다. 즉, ip나 포트 관련 헤더파일이다.
sa_family_t | sin_family | AF_INET.
in_port_t | sin_port | Port number.
struct in_addr | sin_addr | IP address.
이 멤버들을 정의하는 헤더이다.
헤더를 다 봤으니 이제 코드를 하나씩 살펴보자
int server_fd, new_socket, valread;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
char *hello = (char*)"Hello from server";
server_fd는 파일 디스크립터, socket은 소켓, valread는 받은 메세지를 읽어서 저장하는 역할을 한다.
파일 디스크립터(File Descriptor)란 리눅스 혹은 유닉스 계열의 시스템에서 프로세스(process)가 파일(file)을 다룰 때 사용하는 개념으로, 프로세스에서 특정 파일에 접근할 때 사용하는 추상적인 값이다. 파일 디스크럽터는 일반적으로 0이 아닌 정수값을 갖는다.
opt는 몰?루
addrlen은 ip주소의 길이, buffer는 메세지를 담은 버퍼를 선언한것이고, hello는 보낼 메세지를 미리 저장해둔것이다.
#include <sys/socket.h>
int socket( int domain, int type, int protocol );
소켓을 생성하는 역할을 하는 함수이다. 생성이 정상적으로 수행되면 fd를 반환하고 실패한경우 -1을 반환한다.
int socket은 위에서 보다싶이, domain, type protocol을 가진다.
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
실제로 작성하면 이렇게 쓰이는데 안에 들어간 인자들을 알아보자.
소켓코드를 생성할 때 사용하는 프로토콜이 두개가 있는데 하나는 af이고 하나는 pf이다. INET은 internet이다.
과거 소켓프로그래밍이 설계될 때, 확장성을 위해 주소체계가 여러 프로토콜을 지원할 수 있도록 설계하였다. 따라서 이 개념들을 분리하기 위해 af와 pf로 구분해서 작성하기 시작한 것이다.
sockaddr_in 구조체처럼 소켓의 주소와 주소체계를 지정하기 위해서는 AF를 가진다. (Address Family)이다.
실제로 연결하기 위한 포로토콜을 지정할떄는, Protocol Family라는 의미의 PF를 사용한다고 한다.
하지만 실제로 AF, PF를 구분하는일은 생기지 않았고, 리눅스에서도 그냥 둘을 만대로 써도 작동한다고 한다. 다만 햇갈릴수 있으니 의도대로 구분해서 작성해주기를 권장한다고 한다.
INET 이외에도 INET6, LOCAL, PACKET, IPX같은 것들이 있다.
데이터 전송 타입을 지정해주는 역할을 한다.
SOCK_STREAM은 연결 지향성 소켓으로 에러나 데이터 손실없이 무사히 전달하는 타입이다. (TCP)
SOCK_DGRAM은 비연결 지향형 소켓으로 일단 순서고 뭐고 빨리 보내는걸 목적으로 한다. (UDP)
마지막은 통신 프로토콜 정보를 전달하는 공간이다.
같이 직접 지정해줘도 되지만, 0을 써줘도 된다. 0은 SP의 방식을 그대로 따르겠다는 뜻이다.
perror는 오류처리용, exit는 종료용 함수이다.
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
파일 디스크립터 sockfd가 참조하고있는 소켓을 조작하는데 도움을 주는 기능을 한다. 안해도 작동은 되는데 address already in use 같은 치명적 오류 방지 기능이 있다한다.
이제 소켓을 만들었으니 메모리에 이를 지정해주고, bind 할 준비를 해야한다.
이는 다음글에 써보겠다