저번 주에는 서버와 클라이언트들의 소켓을 열고 통신하는 실습을 해보았다.
이번 주에는 그 때 사용했던 socket 함수에 대하여 더 자세히 알아보도록 하자.
socket
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
- domain
소켓이 사용할 프로토콜 체계의 정보
- type
소켓 데이터 전송방식에 대한 정보 전달(TCP, UDP)
- protocol
두 컴퓨터간 통신에 사용되는 프로토콜 정보
return: file descriptor(success), -1(error)
여기에 들어가는 인자들을 하나씩 알아보자.
프로토콜 체계
프로토콜 체계는 Protocol Family로 줄여서 PF라고도 부른다.
프로토콜도 종류에 따라서 부류가 나뉘는데, 그 부류들을 가리켜 프로토콜 체계라고 한다.
많이 사용하는 프로토콜 쳬계들이다.
이름 | 프로토콜 체계(Protocol Family) |
PF_INET | IPv4 인터넷 프로토콜 체계 |
PF_INET6 | IPv6 인터넷 프로토콜 체계 |
PF_LOCAL | 로컬 통신을 위한 UNIX 프로토콜 체계 |
PF_PACKET | Low Level 소켓을 위한 프로토콜 체계 |
PF_IPX | IPX 노벨 프로토콜 체계 |
우리는 이 중에서 IPv4에 해당하는 PF_INET을 사용해 학습할 것이다.
소켓 타입
소켓의 타입은 데이터 전송 방식을 의미한다.
socket 함수로 소켓을 생성할 때 소켓의 타입도 같이 결정이 되어야 한다.
우리가 사용하는 PF_INET의 대표적인 소켓 타입은 TCP와 UDP에 해당하는 연결 지향형 소켓 타입과 비 연결 지향형 소켓 타입이 있다.
연결지향형(SOCK_STREAM) - TCP | 비 연결지향형(SOCK_DGRAM) - UDP |
중간에 데이터가 소멸되지 않는다. | 순서 상관없이 빠르 속도로 전송한다. |
전송 순서대로 데이터가 수신된다.(순서가 유지된다.) | 데이터 손실 및 파손의 우려가 있다. |
데이터의 경계가 존재하지 않는다. | 데이터의 경계가 존재한다. |
소켓: 소켓의 연결은 1:1의 구조로 이루어진다. | 한번에 전송할 수 있는 데이터의 크기가 제한된다. |
연결지향형 소켓을 사용시
int SOCKET = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
int SOCKET = socket(PF_INET, SOCK_STREAM, 0);
비 연결지향형 소켓을 사용시
int SOCKET = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
int SOCKET = socket(PF_INET, SOCK_DGRAM, 0);
첫 번째, 두 번째 인자로 사실상 소켓이 결정되기 때문에 세 번째 인자는 0으로 전달 해도 된다.
2주차에는 1주차에서 실습했던 내용에 기능을 추가하려 한다.
1. Server에서 메시지를 전송하는 것이 아닌, Client에서 메시지를 전송
2. Server는 수신한 Message를 출력
3. 수신한 문자열의 길이를 strlen() 함수를 이용하여 출력
4. Server는 수신한 Message에 ("-> from Server")라는 문자열을 추가하여 다시 클라이언트로 전송
5. Client는 수신한 Message를 출력
그렇게 작성한 Server 프로그램의 코드이다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
void error_handling(char *message);
int main(int argc, char *argv[])
{
int serv_sock;
int clnt_sock;
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size;
char message[BUFSIZ];
if(argc!=2){
printf("Usage : %s port\n", argv[0]);
exit(1);
}
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;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); //IP 주소 할당(자기 자신의 IP 주소로)
serv_addr.sin_port=htons(atoi(argv[1])); //PORT 번호 할당
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1) //bind 함수로 IP, PORT 할당
error_handling("bind() error");
if(listen(serv_sock, 5)==-1)
error_handling("listen() error");
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");
//client로 부터 데이터를 읽어옴
read(clnt_sock, message, BUFSIZ);
printf("Received message: %s\n", message);
//BUFSIZ의 버퍼 중에서 문자열의 길이를 구함
printf("Length of message : %lu\n", strlen(message));
strcat(message, "-> from Server");
//write 함수를 이용해 descriptor에 문자열을 작성
write(clnt_sock, message, strlen(message) + 1);
close(clnt_sock);
close(serv_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
진짜 파일 입출력 하던 것처럼 read, write를 사용했다.
이번엔 client 코드이다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
void error_handling(char *message);
int main(int argc, char* argv[])
{
int sock;
struct sockaddr_in serv_addr;
char sendMSG[BUFSIZ], recvMSG[BUFSIZ];
if(argc!=3){
printf("Usage : %s IP port\n", argv[0]);
exit(1);
}
printf("Please send message: ");
scanf("%s", sendMSG);
sock= socket(PF_INET, SOCK_STREAM, 0);
if(sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]); //IP 주소 할당
serv_addr.sin_port=htons(atoi(argv[2])); // PORT 번호 할당
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
error_handling("connect() error!");
//write 함수를 이용하여 해당 descriptor에 문자열을 작성
write(sock, sendMSG, strlen(sendMSG));
//read 함수를 이용하여 해당 descriptor에서 버퍼로 문자열을 읽어옴
read(sock, recvMSG, BUFSIZ);
printf("Receive message: %s", recvMSG);
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
이렇게 잘 작동하는 것을 볼 수 있다.
여기서 server 코드를 작성하면서 socket과 accept의 리턴값이 같은지 다른지 궁금해졌다.
그래서 어차피 int 값의 descriptor들이기 때문에 %d로 출력해보았다.
출력 값들을 보면 다른 것을 볼 수 있다.
서버에서 socket 함수의 리턴 값은 서버 자체의 소켓이고
그 소켓을 이용해서 client들과 통신할 수 있는 개별 소켓들을 열어주어야 한다.
'학교 생활 > 네트워크 프로그래밍' 카테고리의 다른 글
네트워크 프로그래밍 3주차 - 2 (0) | 2023.04.03 |
---|---|
네트워크 프로그래밍 3주차 - 1 (0) | 2023.04.03 |
네트워크 프로그래밍 1주차 (0) | 2023.03.20 |