layered
간단한 서버/클라이언트 구현 본문
Server.c
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <winsock2.h>
#include <WS2tcpip.h>;
#include <string.h>
#include <stdlib.h>
#define BUF_SIZE 1024
#pragma comment(lib, "Ws2_32.lib")
void error_handling(const char*);
int main() {
WSADATA wsadata;
if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
error_handling("WSAStartup() error!");
SOCKET serv_sock, clnt_sock;
struct sockaddr_in serv_addr, clnt_addr;
char message[BUF_SIZE + 1];
int clnt_addr_size;
int port = 9000;
int clnt_num = 1;
int recv_len = 0;
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == INVALID_SOCKET)
error_handling("socket() error!");
else
printf("socket created\n");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
error_handling("bind() error!");
else
printf("socket binded\n");
if (listen(serv_sock, 5) == SOCKET_ERROR)
error_handling("listen() error!");
else
printf("server status: LISTENING\n");
while (1) {
clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if (clnt_sock == INVALID_SOCKET)
error_handling("accept() error!");
else {
printf("request accepted: connected to client %d\n", clnt_num);
printf("IP: %s, PORT: %d\n", inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));
}
while (1) {
recv_len = recv(clnt_sock, message, BUF_SIZE, 0);
if (recv_len == 0 || recv_len == SOCKET_ERROR) {
printf("%d\n", WSAGetLastError());
break;
}
printf("message from client: %s", message);
}
closesocket(clnt_sock);
printf("client %d unconnected\n", clnt_num);
clnt_num++;
}
closesocket(serv_sock);
WSACleanup();
return 0;
}
void error_handling(const char* message) {
printf("%s\n", message);
}
Client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#define BUF_SIZE 1024
#pragma comment(lib, "Ws2_32.lib")
void error_handling(const char*);
int main() {
WSADATA wsadata;
if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
error_handling("WSAStartup() error!");
SOCKET serv_sock;
struct sockaddr_in serv_addr;
char message[BUF_SIZE + 1];
int port = 9000;
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == INVALID_SOCKET)
error_handling("socket() error!");
else
printf("socket created\n");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
//serv_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
if (connect(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) {
error_handling("connect() error!");
printf("%d", WSAGetLastError());
}
else {
printf("connected to server!\n");
while (1) {
printf("Input message(Q to quit): ");
fgets(message, BUF_SIZE, stdin);
if (message[0] == 'Q')
break;
if (send(serv_sock, message, BUF_SIZE, 0) == SOCKET_ERROR)
error_handling("send() failed!");
else {
printf("message sended\n");
}
}
printf("unconnected\n");
}
closesocket(serv_sock);
WSACleanup();
return 0;
}
void error_handling(const char* message) {
printf("%s\n", message);
}
클라이언트가 메시지를 보내면 서버에서 출력하는 프로그램이다. 서버에 연결된 클라이언트는 순차적으로 번호를 부여받는다.
아직 멀티스레드를 몰라서 두 개의 클라이언트를 실행시키게 되면, 먼저 연결된 클라이언트가 종료되어야 다음 클라이언트가 연결된다.
겪었던 시행착오들.
1 WSAEADDRNOTAVAIL 10049
클라이언트에서 서버의 주소를 설정하는 부분에서 발생한 오류이다.
serv_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
처음에는 이렇게 했는데 자꾸 connect가 실패했다. 그런데 telnet localhost로 했을 때에는 또 잘 되어서 보니까 INADDR_ANY 부분이 문제였다. 그래서 127.0.0.1(= localhost)로 했더니 잘 실행된다.
찾아보니 INADDR_ANY는 서버처럼, 들어오는 연결에 대한 바인드를 나타내는 데 사용된다고 한다.
메모
INADDR_ANY는 컴퓨터에 여러 IP가 존재할 때 IP 주소를 자동으로 선택한다. 쉽게 말하자면 여러 IP를 통칭한다고 보면 된다.
그래서 서버를 작성할 때 주소를 INADDR_ANY로 설정하면, 컴퓨터의 여러 IP 주소로 들어오는 데이터는 모두 서버에게 전달되게 된다.
하지만 클라이언트는 조금 다르다. 주소를 정확히 알아야 찾아갈 수 있다. 그러니 찾아가고자 하는 서버의 주소를 INADDR_ANY로 뭉뚱그리게 되면 클라이언트는 '거기가 어딘데?'가 되는 것이다.
2 WSAECONNRESET 10054
서버에서 클라이언트로부터 데이터를 받을 때, 처음에는 받은 데이터의 크기가 0이면 연결을 끊도록 했다.
그런데 클라이언트를 종료시킬 때 Q를 입력하는 게 아니라 그냥 프롬프트를 꺼 버리면 서버도 같이 종료가 되었다.
그래서 데이터를 받는 부분에 SOCKET_ERROR도 검사하도록 했더니 해결이 되었다.
메모
소켓을 정상적으로 종료하거나, 강제로 종료하거나에 상관없이 서버가 받는 데이터의 크기는 0일 거라고 생각했다. 어차피 둘 다 소켓이 없는 건 마찬가지이기 때문이다.
하지만 위에서 보듯이 강제로 종료할 경우 오류를 발생시킨다. 이는 서버에서 클라이언트와의 연결을 적절하게 끊는 과정이 필요하기 때문이다. 이를 테면 '나 연결 끊을게!' '어 알겠어~' 하는 것이다.
그래서 클라이언트가 예기치 않게 종료된다면 서버는 '얘 어디 갔어?'가 되어서 데이터를 받지 못하고 오류를 발생시키게 되는 것 같다.
(이 프로그램은 TCP 기반이니까 closesocket 함수는 4 way handshake를 실행하게 되는 건가...? 이건 더 찾아봐야겠다.)
사실 클라이언트를 강제로 종료시킨다고 해도 운영체제에서 알아서 소켓을 종료시킬 줄 알았다.
그런데 오류가 나왔고, 실제로 closesocket(serv_sock)을 printf()와 scanf_s() 사이에 넣고 실행해 보니 입력을 하기 전에 클라이언트가 정상적으로 종료되었다.
3 텔넷에서 메시지 문제
텔넷으로 접속하면 데이터를 한 글자씩 받는다... 라인 모드 때문이라고 하는데, 다른 서버를 실행하고 접속하면 엔터를 입력하기 전까지의 글자만큼 잘 받는다.
뭐가 문제인지 모르겠어서 일단은 그대로 두었다ㅠ
으아 힘들당
'네트워크 > 열혈 TCP IP 소켓 프로그래밍' 카테고리의 다른 글
5 TCP 기반 서버/클라이언트 2 (0) | 2023.05.01 |
---|---|
4 TCP 기반 서버/클라이언트 1 (0) | 2023.04.30 |
3 주소 체계와 데이터 정렬 (0) | 2023.04.29 |
2 소켓의 타입과 프로토콜의 설정 (0) | 2023.04.28 |
1 네트워크 프로그래밍과 소켓의 이해 (1) | 2023.04.27 |