모든 컴퓨터는 소통을 하게되는데 이를 위해 데이터를 요청을 하는 클라이언트 역할과 데이터를 내려주는 서버 역할이 생겨남
네트워크를 통해 데이터를 주고받게 해주는것이 네트워크 프로그래밍, 즉 소켓 프로그래밍임
물리적인 네트워크 연결 위에서 소프트웨어적인 데이터 송수신 방법을 모든 운영체제에서 제공하는데 이것이 소켓임
데이터를 주고받기위해 소켓 디스크립터라는 파일 시스템을 사용하는데 이것을 소켓이라고 생각하면 됨
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
- 소켓과 서버의 정보를 묶어주는 함수
#include <sys/socket.h> int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
- 인자값
sockfd
bind할 소켓의 소켓디스크립터
*myaddr
서버의 IP주소
addrlen
주소의 길이
- 리턴값
성공시 0, 실패시 -1
- 다른 클라이언트에서 접근할때 응답할 수 있도록 대기
#include <sys/socket.h> int listen(int sockfd, int backlog);
- 인자값
sockfd
소켓의 소켓 디스크립터
backlog
연결 대기열의 크기를 지정
- 리턴값
성공시 0, 실패시 -1
- 서버 소켓에 클라이언트를 연결해주는 함수
#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포트로 접속