TCP 서버의 함수 호출 순서
: socket() → bind() → listen() → accept() → read()/write() → close()
TCP 클라이언트의 함수 호출 순서
: socket() → connect() → read()/write() → close()
* 각 함수 별 상세 설명
2020/12/11 - [열심히 살기/Network] - 소켓 프로그래밍(Socket Programming)
Echo Server
//TCP echo server
#include <stdio.h> //표준 입출력
#include <stdlib.h> //표준 라이브러리
#include <string.h> //문자열 처리 함수
#include <unistd.h> //유닉스 표준
#include <arpa/inet.h> //인터넷 프로토콜
#include <sys/socket.h> //소켓 통신 함수
#define BUF_SIZE 1024 //1024바이트 버퍼 사용
void error_handling(char *message);
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
char message[BUF_SIZE]; //메시지 수신용 1024바이트 버퍼
int str_len, i; //수신한 데이터 길이
//주소 정보 구조체
struct sockaddr_in serv_adr;
struct sockaddr_in clnt_adr;
socklen_t clnt_adr_sz; //클라이언트 주소 구조체의 바이트 크기
//쉘에 입력된 인자 개수 체크
if(argc!=2) {
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
serv_sock=socket(PF_INET, SOCK_STREAM, 0); //TCP통신용 서버 소켓 생성. ipv4
if(serv_sock==-1)
error_handling("socket() error");
//주소 설정을 위한 메모리 0으로 초기화. ip, port 설정
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[1]); //프로그램당 포트 1개 사용
//소켓과 주소 바인딩
if(bind(serv_soxk, (struct sockaddr*(&serv_adr, sizeof(serv_adr))==-1)
error_handling("bind() error");
//소켓을 리스닝소켓으로 만듦. 대기상태.
if(listen(serv_sock, 5)==1)
error_handling("listen() error");
clnt_adr_sz=sizeof(clnt_adr); //클라이언트 주소 구조체 크기
//클라이언트의 요청을 5번 받아들임
//서버는 클라이언트의 요청이 있을때까지 Block된 상태로 기다림
for(i=0;i<5;i++)
{
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
if(clnt_sock==-1)
error_handling("accept() error");
else //accept가 성공하면 클라이언트 소켓(clnt_sock)이 만들어짐
printf("Connected client %d \n", i+1);
//서버에서 클라이언트가 보낸 메시지를 읽은 후 다시 클라이언트로 전송
while(str_len=read(clnt_sock, messagem BUF_SIZE))!=0) //클라이언트가 소켓을 닫으면 while문 종료
write(clnt_sock, message, str_len); //문자열 끝 널문자 제외하고 에코
close(clnt_sock); //클라이언트와 접속이 끊기면 클라이언트 소켓 close
}
close(serv_sock); //클라이언트를 5번 받아주고나면 서버 소켓 close
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
Echo Client
//TCP echo client
#include <stdio.h> //표준 입출력
#include <stdlib.h> //표준 라이브러리
#include <string.h> //문자열 처리 함수
#include <unistd.h> //유닉스 표준
#include <arpa/inet.h> //인터넷 프로토콜
#include <sys/socket.h> // 소켓 함수
#define BUF_SIZE 1024 //소켓 통신 문자열 버퍼(1024 바이트)
void error_handling(char *message);
int main(int argc, char *argv[]) // 쉘에서 IP와 PORT 번호를 입력받음
{
int sock; //서버에 접속할 소켓
char message[BUF_SIZE] //서버에 보낼 메시지를 저장할 문자열 버퍼(1024 바이트)
int str_len; //송수신 메시지의 문자열 길이
struct sockadr_in serv_adr; //접속할 서버의 주소
//인자 개수 체크
if(argc!=3) {
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_STREAM, 0); //TCP통신용 소켓 생성. ipv4
if(sock==-1)
error_handling("socket() error");
//주소 설정, ip, port
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]);
//연결요청
if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==1)
error_handling("connect() error");
else
puts("Connected....");
while(1)
{
fputs("Input messate(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin);
if(!strcmp(message, "q\n") || !strcmp(message, "!\n"))
break;
write(sock, message, strlen(message)); //소켓을 이용해 메시지 전송
str_len=read(sock, message, BUF_SIZE-1); //에코되어 돌아오는 메시지 수신
message[str_len]=0;
printf(Message from server: %s", message);
}
close(sock); //소켓 close
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
코드(Echo Server, Echo Client) 실행 결과
- Echo Server: Client가 connect할 때까지 대기상태.
Client의 접속을 5번 받아주고 난 후 종료
여러 Client의 접속을 동시에 받지 못하고, 한 Client가 종료되면 다른 Client의 접속 허용
- Echo Client: 수신 대기중인 Server에 접속.
Server로 문자열(널문자 포함) 전송하고, 서버에서 에코된 문자열 수신 후 출력
Echo Client 코드상 문제
- TCP 소켓은 데이터의 경계가 없으므로 서버가 에코한 문자열의 완전한 수신을 보장하지 못함(문자열의 일부만 전송)
- 하지만, 앞서 작성된 Echo Client 코드는 '한 번의 read() 호출로 서버로부터 에코된 문자열 전체를 읽어들일 수 있다'는 가정이 존재
→ 클라이언트가 모든 문자열을 수신할 때까지 read()를 반복 호출하여 해결
Echo Client 코드 수정
TCP echo client 수정
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock; //소켓 파일디스크립터 변수
char message[BUF_SIZE];
int str_len, recv_len, recv_cnt; //서버로 보낸 데이터와 서버로부터 바든 바이트 수 체크 변수 추가
struct sockaddr_in serv_adr;
if(argc!=3){
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_STREAM, 0); //TCP통신용 소켓 생성. ipv4
if(sock==-1)
error_handling("socket() error");
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]));
if(connet(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
error_handling("connect() error!");
else
puts("Connected....");
//데이터 전송
while(1)
{
fputs("Input message(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin);
if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break;
str_len=write(sock, message, strlen(message)); //Echo 서버에 데이터 전송 후 사이즈 저장
//클라이언트가 수신해야 할 데이터의 크기(str_len)를 미리 알고 있음
//str_len만큼의 데이터를 수신할 때까지 반복해서 read() 호출
recv_len=0;
while(recv_len<str_len)
{
recv_cnt=read(sock, &message[recv_len], BUF_SIZE-1); //서버로부터 에코된 데이터 수신
if(recv_cnt==-1)
error_handling("read() error!");
recv_len+=recv_cnt; //서버로부터 받은 데이터를 읽은 바이트 수 만큼 더함
}
//서버에서 널문자를 제외하고 보내므로 서버로부터 수신한 문자열 맨 뒤에 널문자 추가
message[recv_len]=0;
printf("Message from server: %s", message);
}
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
공부하면서 학습 목적으로 작성한 포스팅이므로 내용이 완전하지 않습니다ㅠ
계속해서 학습 후 지식이 좀 더 쌓이면 수시로 수정해나갈 예정입니다!
틀린 내용은 둥글게 댓글 달아주시면 빠른 확인 후 수정하겠습니다. :)
코드 출처
윤성우 저 '윤성우의 열혈 TCP/IP 소켓 프로그래밍'
참고
* qteveryday.tistory.com/49?category=921416
'열심히 살기 > Network' 카테고리의 다른 글
소켓 프로그래밍 - TCP 소켓(입출력 버퍼, Half-close) (0) | 2020.12.13 |
---|---|
소켓 프로그래밍 - 어플리케이션 프로토콜 (0) | 2020.12.13 |
소켓 프로그래밍 - 바이트 순서(호스트, 네트워크) (0) | 2020.12.12 |
소켓 프로그래밍 - 연결지향형 소켓(TCP 소켓) (0) | 2020.12.12 |
소켓 프로그래밍 - 파일 디스크립터 (0) | 2020.12.12 |
댓글