학교 과제(2022년 네트워크 수업)로 한 TCP프로토콜 소켓 프로그래밍(정확히는 TCP 에코 서버를 프로그래밍)
* 2022년 네트워크 수업을 수강하며 작성한 글입니다.
기본 server, client코드는 제공하며, 추가적으로 과제에서 해야하는 목표는 다음과 같다.
1. client가 보낸 메세지를 server가 수신하여 출력
2. 수신한 메세지를 그대로 server에서 client로 송신
TCP소켓 프로그래밍에서는 아주아주 쉬운 정도라 코드가 별로 길거나 어렵지는 않지만 해당 수업을 통해 소켓 프로그래밍을 처음 접해보기에 코드에서 이해해야 하는 부분들이 존재하였다.
client.c는 모든 코드가 제공되었기에 수정할 것이 없었다.
해당 코드는 client가 소켓을 열고 server에게 connection을 요청하며, server에 전달할 메세지를 입력하는 코드로 구성되어있다.
client.c
int n, rc;
char c;
for(n=1; n<maxlen; n++){
if ( (rc = read(fd, &c, 1)) == 1 ){
*ptr ++ = c;
if ( c == '\n' ) break;
} else if ( rc == 0 ){
if ( n == 1 ) return 0;
else break;
}
}
*ptr = 0;
return n;
}
int main(int argc, char *argv[]) {
if ( argc < 2 ){
printf("Input : %s port number\n", argv[0]);
return 1;
}
int SERVER_PORT = atoi(argv[1]);
const char* server_name = "localhost"; // 127.0.0.1
struct sockaddr_in server_address; // Create socket structure
memset(&server_address, 0, sizeof(server_address)); // Initialize memory space with zeros
server_address.sin_family = AF_INET; // IPv4
inet_pton(AF_INET, server_name, &server_address.sin_addr); // Convert IP addr. to binary
server_address.sin_port = htons(SERVER_PORT);
// open a stream socket
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
printf("Could not create socket\n");
return 1;
}
if (connect(sock, (struct sockaddr*)&server_address,
sizeof(server_address)) < 0) {
printf("Could not connect to server\n");
return 1;
}
int n = 0;
int maxlen = 1024;
char buffer[maxlen];
char data_to_send[maxlen+1];
while(1){
if ( readline(0, data_to_send, maxlen) > 0 ){
send(sock, data_to_send, strlen(data_to_send), 0);
}
if ((n = recv(sock, buffer, maxlen, 0)) > 0) {
buffer[n] = '\0'; // Null
printf("%s", buffer);
}
}
close(sock);
return 0;
server.c
//server
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if ( argc < 2 ){
printf("Input : %s port number\n", argv[0]);
return 1;
}
int SERVER_PORT = atoi(argv[1]);
struct sockaddr_in server_address;
memset(&server_address, 0, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_port = htons(SERVER_PORT);
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
int listen_sock;
if ((listen_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
printf("Could not create listen socket\n");
return 1;
}
if ((bind(listen_sock, (struct sockaddr *)&server_address,
sizeof(server_address))) < 0) {
printf("Could not bind socket\n");
return 1;
}
int wait_size = 16;
if (listen(listen_sock, wait_size) < 0) {
printf("Could not open socket for listening\n");
return 1;
}
struct sockaddr_in client_address;
int client_address_len = 0;
int sock;
int maxlen = 1024;
int n = 0;
char buffer[maxlen];
while (1) {
if ((sock = accept(listen_sock, (struct sockaddr *)&client_address, &client_address_len)) < 0) {
printf("Could not open a socket to accept data\n");
return 1;
}
printf("Client connected with ip address: %s\n", inet_ntoa(client_address.sin_addr));
while (1) {
//1. 클라이언트가 보낸 메시지 수신
n = read(sock, buffer, maxlen - 1);
//2. 수신한 메시지를 출력
buffer[n] = '\0';
printf("%s", buffer);
//3. 수신한 메시지를 클라이언트에게 송신
send(sock, buffer, strlen(buffer), 0);
}
}
close(sock);
close(listen_sock);
return 0;
}
server.c 살펴보기
소켓 프로그래밍의 스켈레톤 코드인 부분을 제외하고 이번 과제에서 server.c에 입력한 부분만 살펴보면,
while (1) {
//1. 클라이언트가 보낸 메시지 수신
n = read(sock, buffer, maxlen - 1);
//2. 수신한 메시지를 출력
buffer[n] = '\0';
printf("%s", buffer);
//3. 수신한 메시지를 클라이언트에게 송신
send(sock, buffer, strlen(buffer), 0);
}
- read를 통해 클라이언트가 보낸 메세지를 수신하여 buffer에 저장
- buffer에 저장한 내용을 server에서 출력
- server가 client로 수신 받아 저장한 내용을 다시 client에 송신
이때 과제에 핵심은 read, recv, write, send 함수를 통해 받은 메세지를 저장하고 보내는 부분이다.(내 의견이지만....)
과제제출을 마치고 다른 친구들의 코드도 살펴보았더니
서버가 클라이언트에서 보낸 메세지를 받아 버퍼에 저장하는 부분은 read혹은 recv 두 가지 중 하나만 사용해도 구현이 되었고,
서버가 클라이언트에게 다시 메세지를 보낼 때는 write 혹은 send 두 가지 중 하나만 사용해도 상관 없었다.
다만, read와 write/recv와 send는 각각의 쌍으로 함수에서 받아야 하는 매개변수의 수가 다르기 때문에 이 점을 주의해야 한다.
보통은 read/write, recv/send를 한 쌍으로 사용하는 것 같다.