미션
1. 고객의 요청을 대기중인 서버를 만들어보자
(hint. 고객의 요청을 복사하는 서버/고객 코드)
https://koyo.kr/post/c-socket-example/
이 프로젝트를 따라해보았당
block
기다리는 행위. 어디까지 진행하고 클라이언트가 접속할때까지 기다린다? 그렇다면 블락된 것.
socket()
소켓은 일종의 통신창구. 객체 내 통신의 종단점. 이 서버가 다른 서버와 통신하고 싶다면 개설하는 통로.
int socket(int domain, int type, int protocol);
입력
domain : 인터넷 프로토콜 결정
type : 데이터 전송 프로토콜 결정.
protocol : 소켓에 특별한 프로토콜이 필요할 경우. '소통 도메인'을 지정.
출력
새 소켓의 파일디스크립터.
실패시 -1 그리고 에러넘버
domain | 내용 |
PF_INET, AF_INET | IPv4 인터넷 프로토콜을 사용합니다. |
PF_INET6 | IPv6 인터넷 프로토콜을 사용합니다. |
PF_LOCAL, AF_UNIX | 같은 시스템 내에서 프로세스 끼리 통신합니다. |
PF_PACKET | Low level socket 을 인터페이스를 이용합니다. |
PF_IPX | IPX 노벨 프로토콜을 사용합니다. |
domain | 내용 |
SOCK_STREAM | TCP/IP 프로토콜을 이용합니다. |
SOCK_DGRAM | UDP/IP 프로토콜을 이용합니다. |
bind
OS에게 이 포트를 사용하겠다고 선언하는 것
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen)
socket, socket address (ip, port, 전송프로토콜) 입력.
성공시 0반환
실패시 -1반환, 그리고 errno를 제공한다.
listen
커넥션을 위해 소켓이 듣고있는 상태로 바꿔줌
int listen(int sockfd, int backlog);
입력
소켓fd
backlog : 소켓 대기열이 커질 수 있는 최대 길이를 의미.
출력
0, -1, errno
accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
이 소켓이 고객의 주소에 대해 연결되는 것을 허용. client의 connect와 쌍으로 쓰임.
입력
인가할 서버 소켓, 고객의 주소, 주소길이
출력
accept 된 소켓. 아니면 -1.
receive
int recv(int sockfd, void *buf, size_t len, int flags);
socket으로부터 메시지를 받음.
입력
소켓, 버퍼, flag
출력
받아진 byte 개수를 출력. 에러시 -1과 에러코드.
flag 종류
MSG_DONTWAIT : 블락(기다리는 작업)을 하지 않음.
MSG_ERRQUEUE : 대기중인 에러가 소켓 오류 대기열에서 수신되어야 함.
MSG_OOB : 일반 데이터수신에서 수신되지 않는 대역 외 데이터 수신을 요청함.
MSG_PEEK : 수신을 할 때 기존 데이터를 제거하지 않고 수신 대기열의 시작부분에서 데이터를 반환함.
MSG_TRUNC : buffer 보다 긴 경우에도 실제 데이터 길이를 반환.
MSG_WAITALL : 설정한 buffer 길이만큼 들어올때까지 대기.
send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
소켓으로 메시지를 보냄.
입력
소켓, 버퍼(보내는 문자열), 사이즈(문자열 길이), flag
출력
보내진 byte의 개수 출력. 에러시 -1과 에러코드.
flag
MSG_CONFIRM : 링크 계층에 성공적인 응답을 받았다고 알려줌. 만약 얻지 못하면 주기적으로 재탐색함.
MSG_DONTROUTE : 패킷을 보낼때 게이트웨이를 통과하지 않고 직접 연결된 네트워크의 호스트에게만 보냄.
MSG_DONTWAIT : 비차단작업.
MSG_EOR : 레코드 중단.
MSG_MORE : 발신자가 보낼 데이터가 더 있다는 뜻.
MSG_NOSIGNAL : connection을 닫은경우 신호를 보내지 말라는 뜻.
MSG_OOB : 이 개념의 수신자에게 송신함.
pid_t pid = fork();
이건 fork 를 하게되면 두 인스턴스가 동시에 돌아가게 된다.
그래서 if pid == 0 을 하고 else 를 한다면
else 에서는 parent인 서버가 돌아가게 되고, if 에서는 client 가 돌아가게 된다.
서버와 클라이언트를 동시에 돌릴때 사용됨.
inet_pton
af에 맞추어 dst 에 src 를 변환한 후 복사해 넣는 함수이다.
int inet_pton(int af, const char *src, void *dst);
af
address family. 통신의 종류를 나타낸다.
IPv4 주소인지, IPv6 주소인지, 내부 시스템망인지를 판단
src
문자열 형태의 IP주소를 넣는다.
dst
src를 binary 형태로 변환한 후 복사한 메모리의 포인터.
소스코드는 다음과 같다.
ip바꾸고 1 눌러서 서버를 2 눌러서 클라이언트를 하면 된다
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#define MSG_MAX 128
#define BUF_LEN 128
int main(void){
unsigned int cmd;
int r;
int opt = 1;
fputs("원하는 기능을 선택하세요(1 : 서버대기 2 : 클라이언트) : ", stdout);
fgets((char *)&cmd, 2, stdin);
while(getchar() != '\n');
if(cmd == '1') {
int server_sock;
struct sockaddr_in sockaddr;
/*create server socket*/
server_sock = socket(AF_INET, SOCK_STREAM, 0);
if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, (socklen_t)sizeof(opt)) < 0) printf("socket set opt failed");
/*set server socket address*/
memset(&sockaddr, 0, sizeof(struct sockaddr_in));
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(5000);
sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
printf("socket address\n");
/* bind */
r = bind(server_sock, (struct sockaddr *)&sockaddr, sizeof(struct sockaddr_in));
if (r < 0) { printf("bind failed Error code: %d\n", errno); return -1;}
printf("bind\n");
/* listen */
r = listen(server_sock, 5);
if (r < 0) { printf("listen failed\n"); }
printf("listen\n");
{
int client_sock;
struct sockaddr_in client_addr;
int socklen, recv_len;
char buf[BUF_LEN];
/* accept */
memset(&client_addr, 0, sizeof(struct sockaddr_in));
client_sock = accept(server_sock, (struct sockaddr *)&client_addr, (socklen_t *)&socklen);
if (client_sock < 0) {printf("S accept failed\n"); return -1;}
printf("S accept\n");
/*recv*/
recv_len = recv(client_sock, buf, BUF_LEN, 0);
if (recv_len < 0) { printf("S receive failed errno : %d\n", errno); return -1; }
printf("S receive \n");
printf("S buf %s\n", buf);
/*send*/
r = send(client_sock, buf, recv_len, 0);
printf("S send\n");
close(client_sock);
}
close(server_sock);
}
else if (cmd == '2'){
/*fputs("메시지를 입력하세요 : ", stdout);
fgets(msg, MSG_MAX, stdin);
printf("%s\n", msg);*/
int client_sock;
struct sockaddr_in client_addr;
char buf[BUF_LEN];
int recv_len;
/* client socket */
client_sock = socket(AF_INET, SOCK_STREAM, 0);
opt = 1;
setsockopt(client_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (client_sock < 0) { printf("C socket failed\n"); return -1;}
printf("C socket\n");
/* client_sockaddr */
memset(&client_addr, 0, sizeof(struct sockaddr_in));
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(5000);
inet_pton(AF_INET, "172.16.3.80", &client_addr.sin_addr.s_addr);
printf("C socket address\n");
/* connect */
r = connect(client_sock, (struct sockaddr *)&client_addr, sizeof(struct sockaddr_in));
if (r < 0) { printf("C connect failed errno : %d\n", errno); return -1; }
printf("C connect\n");
/* send */
r = send(client_sock, "hello", strlen("hello"), 0);
printf("C send\n");
/*recv*/
recv_len = recv(client_sock, buf, BUF_LEN, 0);
if (recv_len < 1) { printf("C recv failed\n"); return -1; }
printf("C recv\n");
printf("C buf %s \n", buf);
close(client_sock);
}
else printf("1, 2만 입력하세요");
return 0;
}
'개발 > C' 카테고리의 다른 글
[C] buffer memory flush 하는법 (fgets) (0) | 2022.12.26 |
---|---|
[C] 형식 지정자 (ex. %d, %s, %o...) (0) | 2022.12.26 |
[C] fgets와 strcmp 를 같이 쓸때 주의할점 (0) | 2022.12.23 |
[네트워크] [C 소켓통신 #2] buffer 크기 이상으로 받은 내용을 output (0) | 2022.12.23 |
[C] scanf() fgets() fscanf() sscanf() 차이점 (0) | 2022.12.22 |