* 네트워크 프로그램의 흐름
1. 서버 : 소켓 생성 → 포트 부여 → 상대편 연결 기다리기 → 통신 → 종료
2. 클라이언트 : 소켓 생성 → 포트 부여 → 상대편 IP/Port 주소로 연결 → 통신 → 종료
[ 서버 프로그램 만들기 ]
1. bind() : 소켓을 인터넷 주소와 포트 번호로 묶어준다.
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
sockfd : 파일 지정 번호
serv_addr : 연결할 인터넷 주소와 port 번호를 포함한 구조체
addrlen : 두번째 매개변수로 넘길 데이터의 크기
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 모든 클라이언트로부터 접속대기
addr.sin_port = htons(8080)
서버 입장에서는 클라이언트의 주소는 정해져 있지 않으므로 모든 클라이언트로 부터 통신을 받기 위해서는
INADDR_ANY를 사용한다. INADDR_ANY은 모든 클라이언트로부터 통신을 받겠다는 의미이다.
state = bind(sockfd , (struct sockaddr *)&addr, sizeof(addr));
bind 함수의 반환 값은 성공하면 0, 실패하면 -1이다.
2. listen() : 수신 대기열 생성
제일 첫번째로 들어온 클라이언트의 요청을 제외한 나머지 모든 클라이언트의 요청은 수신 대기열( FIFO queue )로
들어간다. 현재 접속 요청 중인 클라이언트 요청에 대한 처리가 끝나기 전에 새로운 클라이언트가 요청할 수 있도록
하기 위해서이다.
int listen(int queue_size);
3. accpet() : 연결 대기
서버 소켓에 수신 대기중인 연결 요청이 있는지 확인하고 대기열의 맨 앞에 있는 클라이언트 요청을 읽는다.
클라이언트 요청이 있다면 클라이언트와 통신을 담당할 소켓 지정 번호( 연결 소켓 혹은 클라이언트 소켓 )을 반환한다.
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
s : 서버 소켓의 소켓 지정 번호
addr : 클라이언트의 주소 정보
addrlen : 두번째 매개변수의 데이터 크기
accpet() 함수의 반환값은 소켓 지정 번호이다. 서버 소켓과는 별도로 연결된 클라이언트와의 통신을 위한
연결 소켓이다.
* 서버는 소켓 종류가 2가지이다.
① listening : 클라이언트와 소통을 하겠다는 소켓
② connected : 다른 소켓을 사용해서 직접적으로 클라이언트와 데이터를 주고 받는다.
→ 한 클라이언트와 소통을 하고 있는 도중에 다른 클라이언트와의 대화는 기다려야 한다.
1000명의 클라이언트와 통신을 할 경우, 서버는 1001개의 소켓을 사용한다. 1개의 listening 소켓과 1000개의
connected 소켓이다. 소켓은 각 클라이언트마다 하나씩 가지게 된다.
[ 리눅스 클라이언트와 서버 코드 분석하기 ]
1. server.c
#include <sys/socket.h> /* 소켓 관련 함수 */
#include <sys/stat.h>
#include <arpa/inet.h> /* 소켓 지원을 위한 각종 함수 */
#include <stdio.h> /* 표준 입출력 관련 */
#include <string.h> /* 문자열 관련 */
#include <stdlib.h>
#include <unistd.h> /* 각종 시스템 함수 */
#define MAXBUF 1024
int main(int argc, char **argv)
{
int server_sockfd, client_sockfd;
int client_len, n;
char buf[MAXBUF];
struct sockaddr_in clientaddr, serveraddr;
client_len = sizeof(clientaddr);
/* socket 함수를 사용해서 소켓을 생성한다. */
if ((server_sockfd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP )) == -1)
{
perror("socket error : ");
exit(0);
}
memset(&serveraddr, 0x00, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(atoi(argv[1]));
/* bind 함수를 통해 소켓을 인터넷 주소와 포트 번호로 묶는다. */
bind (server_sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
/* listen 함수를 통해 5 사이즈의 수신 대기열을 생성한다. */
listen(server_sockfd, 5);
while(1)
{
/* accept 함수를 통해 연결 요청을 받아들여 소켓간 연결을 수립한다.
client_sockfd : 리턴되서 나온 클라이언트 소켓
server_sockfd : 서버 소켓*/
client_sockfd = accept(server_sockfd, (struct sockaddr *)&clientaddr,
&client_len);
printf("New Client Connect: %s\n", inet_ntoa(clientaddr.sin_addr));
memset(buf, 0x00, MAXBUF);
/* 클라이언트로 부터 데이터를 읽는 부분
-> 클라이언트 소켓으로 부터 MAXBUF 크기만큼의 데이터를 읽어서 buf에 저장한다.*/
if ((n = read(client_sockfd, buf, MAXBUF)) <= 0)
{
/* 데이터를 read할 경우 데이터가 제대로 도착이 되었다면 바로 읽어올 수 있지만
전송이 완료되지 않을 경우 바로 읽어올 수 없어 read를 주도적으로 할 수 없다.
-> 소켓이 열려있다면 입력 대기 중이므로 소켓을 닫아줘야한다. */
close(client_sockfd);
continue;
}
/* 클라이언트로부터 받은 데이터를 다시 클라이언트로 전송한다.*/
if (write(client_sockfd, buf, MAXBUF) <=0)
{
perror("write error : ");
close(client_sockfd);
}
/* 더 이상 사용하지 않는 파일 닫아주기*/
close(client_sockfd);
}
close(server_sockfd);
return 0;
}
< 입출력 함수 read, write >
1. read( fd, buf, count ) : 소켓에서 count 크기 만큼의 데이터를 읽어서 buf에 저장한다.
fd : 소켓 지정 번호
① client : socket()로 생성된 소켓
② server : accept()로 생성된 소켓
buf : 읽어들인 데이터가 저장될 버퍼 함수
count : 읽어들일 데이터의 크기
read의 반환 값은 성공할 경우 읽어들인 데이터의 크기이고, 실패할 경우 -1이다.
2. write( fd, buf, count ) : buf에서 count 크기 만큼의 데이터를 파일 fd에 작성한다.
fd : 연결된 소켓 지정 번호
buf : 클라이언트로 전송할 데이터가 저장된 버퍼
count : 전송할 데이터의 크기
write의 반환 값은 성공할 경우 작성해서 전달할 데이터의 크기이고, 실패할 경우 -1이다.
< 메모리 채우기 함수 memset >
memset( void *dest, int fillChar, unsigned int count )
dest : 초기화될 또는 특정 문자가 채워질 버퍼
fillChar : 초기화할 특정 문자
count : dest에 몇 바이트의 문자를 채우는지에 대한 크기
memset은 메모리 주소 dest부터 시작해서 count만큼 fillChar로 메모리를 채운다. return 값은 메모리의 시작
주소이다. memset은 1 바이트 단위의 메모리를 세팅하므로 char형 배열을 사용한다.
memset은 문자열 버퍼 뿐만 아니라 구조체, 공용체 등에서도 다른 버퍼의 값을 0으로 초기화할 때 많이 사용한다.
* buffer : 속도 차가 큰 대상이 입출력을 수행할 때 효율성을 위해 사용하는 임시 저장 공간
2. client.c
#include <sys/socket.h> /* 소켓 관련 함수 */
#include <arpa/inet.h> /* 소켓 지원을 위한 각종 함수 */
#include <sys/stat.h>
#include <stdio.h> /* 표준 입출력 관련 */
#include <string.h> /* 문자열 관련 */
#include <unistd.h> /* 각종 시스템 함수 */
#define MAXLINE 1024
int main(int argc, char **argv)
{
struct sockaddr_in serveraddr;
int server_sockfd;
int client_len;
char buf[MAXLINE];
if ((server_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("error :");
return 1;
}
/* 연결요청할 서버의 주소와 포트번호 프로토콜등을 지정한다. */
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
serveraddr.sin_port = htons(3600);
client_len = sizeof(serveraddr);
/* 서버에 연결을 시도한다. */
if (connect(server_sockfd, (struct sockaddr *)&serveraddr, client_len) == -1)
{
perror("connect error :");
return 1;
}
memset(buf, 0x00, MAXLINE);
read(0, buf, MAXLINE); /* 키보드 입력을 기다린다. */
if (write(server_sockfd, buf, MAXLINE) <= 0) /* 입력 받은 데이터를 서버로 전송한다. */
{
perror("write error : ");
return 1;
}
memset(buf, 0x00, MAXLINE);
/* 서버로 부터 데이터를 읽는다. */
if (read(server_sockfd, buf, MAXLINE) <= 0)
{
perror("read error : ");
return 1;
}
printf("read : %s", buf);
close(server_sockfd);
return 0;
}
'2CHAECHAE 학교생활 > OSNW실습' 카테고리의 다른 글
[ OS/NW 실습 ] 7주차 - 바이트 순서( Byte Order ), 인터넷 주소와 도메인 (0) | 2022.10.29 |
---|---|
[ OS/NW 실습 ] 6주차 - 소켓 네트워크 프로그램 이해 (0) | 2022.10.29 |
[ OS/NW 실습 ] 6주차 - 소켓 네트워크 프로그램 개발 ① 네트워크 프로그램의 흐름, 클라이언트 프로그램 만들기 (0) | 2022.10.28 |
[ OS/NW 실습 ] 5주차 - 네트워크 OSI 모델 7 계층 구조 (0) | 2022.10.08 |
[ OS/NW 실습 ] gcc, 오브젝트 파일, 언어 계층 구조 보충 (0) | 2022.10.08 |