이 글은 학교 네트워크 프로그래밍 과목의 기말 시험을 공부하기 위한 글
Client와 Server는 스트리밍 소켓을 기반으로 하며, TCP를 사용했다.
TCP를 쓰는 대안으로 UDP도 있다. UDP는 datagram socket으로 알려져있다.
UDP는 패킷 전송을 보장하지 않는다.
UDP datagrams는 손실되어 순서가 틀릴 수도 있다.
UDP datagrams는 여러번 복사할 수 있으며, 수신 노드가 처리하는 속도보다 더 빠르게 전송할 수 있다.
: 받는 쪽에서 처리를 하든 못하든 그냥 막 전송한다. 전송해놓고 안본다. 받든말든
UDP 활용 예시 : DNS(Domain Name Service), NTP(Network Time Protocol), TFTP(Trivial File Transfer Protocol)
예를 들어, 간단한 DNS 조회 요청은 기본적으로 요청의 이름 또는 번호와 응답의 DNS 서버로부터의 해당 답변(오류일 수도 있음)의 두 가지 정보만으로 구성됩니다.
이렇게 간단한 통신을 위해 스트리밍 소켓을 열고 연결을 유지하는 데 드는 비용을 부담할 필요가 없습니다.
NTP 요청도 유사합니다. 질문("몇 시입니까?")과 서버의 답변으로 구성됩니다.
지속적인 커뮤니케이션이 필요하지 않습니다.
// bongbong@ssl:~$ nc -z -v -u kr.poolntp.org 123
// Connection to kr.pool.ntp.org 123 port [udp/ntp] succeeded!
// bongbong@ssl:~$ nc -z -v 223.194.7.95 21
// Connection to 223.194.7.95 21 port [tcp/ftp] succeeded!
// bongbong@ssl:~$ nc -z -v ssl.kw.ac.kr 80
// Connection to ssl.kw.ac.kr 80 port [tcp/http] succeeded!
// bongbong@ssl:~$ nc -z -v -u kns.kornet.net 53
// Connection to kns.kornet.net 53 port [udp/domain] succeeded!
// bongbong@ssl:~$ nc -z -v 223.194.7.95 1-255
// nc: connect to 223.194.7.95 port 1 (tcp) failed: Connection refused
// nc: connect to 223.194.7.95 port 2 (tcp) failed: Connection refused
.
.
.
// nc: connect to 223.194.7.95 7 port [tcp/echo] succeeded!
// nc: connect to 223.194.7.95 port 8 (tcp) failed: Connection refused
.
.
.
// bongbong@ssl:~$ nc -v -u 223.194.7.95 13
// Connection to 223.194.7.95 13 port [udp/daytime] succeeded!
// 17 MAY 2022 14:11:32 KST
// 17 MAY 2022 14:11:33 KST
// 17 MAY 2022 14:11:34 KST
// 17 MAY 2022 14:11:35 KST
// bongbong@ssl:~$ nc -v 223.194.7.95 13
// Connection to 223.194.7.95 13 port [tcp/daytime] succeeded!
// 17 MAY 2022 14:12:27 KST
// bongbong@ssl:~/Lecture/netprog$ cat | nc -v 223.194.7.95 7
// Connection to 223.194.7.95 7 port [tcp/echo] succeeded!
// Hi
// Hi
// bongbong@ssl:~/Lecture/netprog@ cat | nc -v -u 223.194.7.95 7
// Connection to 223.194.7.95 7 port [udp/echo] succeeded!
// XXXX1234
// 1234
// 5678
// 5678
서버 없이는 클라이언트 코드를 못만든다. 그래서 간단한 UDP 서버 코드부터 만들어보자
아래 헤더 파일들이 필요하다.
-- #include <sys/types.h>
-- #include <sys/socket.h>
-- #include <netdb.h>
-- #include <string.h>
-- #include <stdio.h>
그 다음, 우리는 1KB 사이즈의 상수를 정의할 것이다. 우리가 사용할 메세지 버퍼의 사이즈를 정의하기 위해서 상수를 사용한다.
-- #define MAXBUF 1024
int main(int argc, char* argv[]) {
int updSocket;
int returnStatus = 0;
int addrlen = 0;
struct sockaddr_in udpServer, udpClient;
char buf[MAXBUF];
}
이 프로세스는 SOCK_STREAM 대신 SOCK_DGRAM을 사용한다.
UDP 에서는 SOCK_DGRAM, TCP에서는 SOCK_STREAM 사용
// Create a Socket
udpSocket = socket(AF_INET, SOCK_DGRAM, 0);
if(udpSocket == -1) {
fprintf(stderr, "Could not create a socket!\n");
exit(1);
}
else
printf("Socket created.\n");
INADDR_ANY를 사용한다. - 구조체 세팅에 사용된다.
-- 소켓은 구성된 로컬 주소에 바인딩되며 인수로 전달된 포트 번호를 사용합니다.
port를 다룰때 htons()를 호출하는것을 꼭 명심하자!
: 포트번호 세팅
// setup the server address and port
// use INADDR_ANY to bind to all local addresses
udpServer.sin_family = AF_INET;
udpServer.sin_addr.s_addr = htonl(INADDR_ANY);
// 중요 ip주소 다룰때는 꼭 htonl
// use the port pased as argument
udpServer.sin_port = htons(atoi(argv[1]));
// argv는 프로그램에서 파라미터로 받는 것인데, 문자열이므로 atoi를 통해서 integer 타입으로 변환한 다음 htons를 적용하여 포트번호를 세팅한다.
// bind to the socket
returnStatus = bind(udpSocket, (struct sockaddr*)&udpServer, sizeof(udpServer));
if(returnStatus == 0)
fprintf(stderr, "Bind Completed!\n");
else {
fprintf(stderr, "Could not bind to address!\n");
close(udpSocket);
exit(1);
}
listen(), accept(), read() 함수들 대신에, recvfrom() 함수를 사용하는 것을 명심하자!
-- udp는 recvfrom, tcp는 listen, accept, read
recvfrom()은 accept()와 마찬가지로 blocking function이다.
이 함수는 통신이 수신될 때 까지 소켓에서 기다린다.
while(1) {
addrlen = sizeof(udpClient);
returnStatus = recvfrom(udpSocket, buf, MAXBUF, 0, (struct sockaddr*)&udpClient, &addrlen);
if(returnStatus == -1)
fprintf(stderr, "Could not receive message!\n");
else {
printf("Received: %s\n", buf);
// a message was received so send a confirmation
strcpy(buf, "OK");
returnStatus = sendto(udpsocket, buf, strlen(buf)+1, 0, (struct sockaddr*)&udpClient, sizeof(udpClient));
if(returnStatus == -1)
fprintf(stderr, "Could not send confirmation!\n");
else
printf("Confirmation sent.\n");
}
}
datagram socket을 생성하고, Client의 sockaddr_in 구조체에 우리가 가진 정보를 세팅한다.
우리는 Client를 위해 특정 포트 번호를 선언할 필요가 없다.
OS가 랜덤으로 하나를 할당할 것이다.
서버가 수신하는 UDP 패킷은 원본 IP 주소와 함께 헤더 필드를 포함하므로, 서버는 응답을 보낼 때 사용할 많은 정보를 가집니다.
일단 정보가 준비되면, socket에 bind 하고, 전송을 준비한다.
// Create a socket
updSocket = socket(AF_INET, SOCK_DGRAM, 0);
if(udpSocket == -1) {
fprintf(stderr, "Could not create a socket!\n");
}
else
printf("Socket Created!\n");
// Client address
// use INADDR_ANY to use all local addresses
udpClient.sin_family = AF_INET;
udpClient.sin_addr.s_addr = INADDR_ANY;
udpClient.sin_port = 0;
returnStatus = bind(udpSocket, (struct sockaddr*)&udpClient, sizeof(udpClient));
if(returnStatus == 0)
fprintf(stderr, "Bind Completed!\n");
else {
fprintf(stderr, "Could not bind to address!\n");
close(udpSocket);
exit(1);
}
전송하기 전에 메세지를 세팅해야 한다.
또한 연결할 서버를 나타내는 데 사용할 sockaddr_in 구조체를 입력해야 합니다.
// set up the message to be sent to the server
strcpy(buf, "For Professionals, By Professionals\n");
// server address
// use the command-line arguments
udpServer.sin_family = AF_INET;
udpServer.sin_addr.s_addr = inet_addr(argv[1]);
udpServer.sin_port = htons(atoi(argv[2]));
returnStatus = sendto(udpSocket, buf, strlen(buf)+1, 0, (struct sockaddr*)&udpServer, sizeof(udpServer));
if(returnStatus == -1)
fprintf(stderr, "Could not send message!\n");
else
printf("Message sent.\n");
// message sent: look for confirmation
addrlen = sizeof(udpServer);
returnStatus = recvfrom(udpSocket, buf, MAXBUF, 0, (struct sockaddr*)&udpServer, &addrlen);
if(returnStatus == -1)
fprintf("stderr, "Did not receive confirmation!\n");
else {
buf[returnStatus] = 0;
printf("Received: %s\n", buf);
}
// UDP Server
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#define MAXBUF 1024
int main(int argc, char* argv[]) {
int udpSocket;
int returnStatus = 0;
int addrlen = 0;
struct sockaddr_in udpServer, udpClient;
char buf[MAXBUF];
// check for the right number of arguments
if(argc < 2) {
fprintf(stderr, "Usage: %s <port>\n", argv[0]);
exit(1);
}
// create a socket
udpSocket = socket(AF_INET, SOCK_DGRAM, 0);
if(udpSocket == -1) {
fprintf(stderr, "Could not create a socket!\n");
exit(1);
}
else
printf("Socket created!\n");
// setup the server address and port
// use INADDR_ANY to bind to all local addresses
udpServer.sin_family = AF_INET;
udpServer.sin_addr.s_addr = htonl(INADDR_ANY);
// use the port pased as argument
udpServer.sin_port = htons(atoi(argv[1]));
// bind to the socket
returnStatus = bind(udpSocket, (struct sockaddr *)&udpServer, sizeof(udpServer));
if(returnStatus == 0)
fprintf(stderr, "Bind completed!\n");
else {
fprintf(stderr, "Could not bind to address!\n");
close(udpSocket);
exit(1);
}
while(1) {
addrlen = sizeof(udpClient);
returnStatus = recvfrom(udpSocket, buf, MAXBUF, 0, (struct sockaddr*)&udpClient, &addrlen);
if(returnStatus == -1)
fprintf(stderr, "Could not receive message!\n");
else {
printf("Received: %s\n", buf);
// a message was received so send a confirmation
strcpy(buf, "OK");
returnStatus = sendto(udpSocket, buf, strlen(buf)+1, 0, (struct sockaddr*)&udpClient, sizeof(udpClient));
if(returnStatus == -1)
fprintf(stderr, "Could not send confirmation!\n");
else
printf("Confirmation sent.\n");
}
}
// cleanup
close(udpSocket);
return 0;
}
// UDP Client
#include <sys.types.h>
#include <sys.socket.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#define MAXBUF 1024
int main(int argc, char* argv[]) {
int udpSocket;
int returnStatus;
int addrlen;
struct sockaddr_in udpClient, udpServer;
char buf[MAXBUF];
if(argc < 3) {
fprintf(stderr, "Usage: %s <ip address> <port>\n", argv[0]);
exit(1);
}
// create a socket
udpSocket = socket(AF_INET, SOCK_DGRAM, 0);
if(udpSocket == -1) {
fprintf(stderr, "Could not create a socket!\n");
exit(1);
}
else
printf("Socket created!\n");
// client address
// use INADDR_ANY to use all local addresses
udpClient.sin_family = AF_INET;
udpClient.sin_addr.s_addr = INADDR_ANY;
udpclient.sin_port = 0;
returnStatus = bind(udpSocket, (struct sockaddr*)&udpClient, sizeof(udpClient));
if(returnStatus == 0)
fprintf(stderr, "Bind Completed!\n");
else {
fprintf(stderr, "Could not bind to address!\n");
close(udpSocket);
exit(1);
}
// setup the message to be sent to the server
strcpy(buf, "For Professionals, By Professionals.\n");
// server address
// use the command line arguments
udpServer.sin_family = AF_INET;
udpServer.sin_addr.s_addr = inet_addr(argv[1]);
udpServer.sin_port = htons(atoi(argv[2]));
returnStatus = sendto(udpSocket, buf, strlen(buf)+1, 0, (struct sockaddr*)&udpServer, sizeof(udpServer));
if(returnStatus == -1)
fprintf(stderr, "Could not send message!\n");
else {
printf("Message sent.\n");
// message sent: look for confirmaiton
addrlen = sizeof(udpServer);
returnStatus = recvfrom(udpSocket, buf, MAXBUF, 0, (struct sockaddr*)&udpServer, &addrlen);
if(returnStatus == -1)
fprintf(stderr, "Did not receive confirmation\n");
else {
buf[returnStatus] = 0;
printf("Received: %s\n", buf);
}
}
// cleanup
close(udpSocket);
return 0;
}
파일 전송하는데는, 우리는 TCP를 소켓을 사용할 것이다.
server는 특정 포트를 bind 할 것이고, 연결을 위해 listen 할 것이다.
연결이 되면, client는 server에 파일 이름을 전송할 것이고, 그러면 server는 파일 이름을 읽을 것이고, 디스크로부터 파일을 검색할 것이고, client에게 파일을 다시 되돌려 줄 것이다.
한가지 주요한 차이점은 소켓을 두 가지 사용한다는 점이다.
소켓을 두 가지 사용하면, server는 다른 client로부터 더 많은 연결을 accept 하면서 client로부터의 요청을 다룰 수 있다.
연결은 선착순으로 queue에 배치될 것이다.
소켓 한 가지는 read/write 담당, 나머지 하나는 request 담당이다.
// File transfer server xferServer.c
#include <fcntl.h>
#include <sys.types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netdb.h>
#define SERVERPORT 8888
#define MAXBUF 1024
int main() {
int socket1, socket2;
int addrlen;
struct sockaddr_in xferServer, xferClient;
int returnStatus;
// Create a socket
socket1 = socket(AF_INET, SOCK_STREAM, 0);
if(socket1 == -1) {
fprintf(stderr, "Could not create socket!7n");
exit(1);
}
// bind to a socket, use INADDR_ANY for all local addresses
xferServer.sin_family = AF_INET;
xferServer.sin_addr.s_addr = INADDR_ANY
xferServer.sin_port = htons(SERVERPORT);
returnStatus = bind(socket1, (struct sockaddr*)&xferServer, sizeof(xferServer));
if(returnStatus == -1) {
fprintf(stderr, "Could not listen on socket!\n");
exit(1);
}
for(;;) {
int fd;
int i, readCounter, writeCounter;
char* bufptr;
char buf[MAXBUF];
char filename[MAXBUF];
// wait for an incoming connection
addrlen = sizeof(xferClient);
// use accept() to handle incoming connection requests
// and free up the original s ocket for other requests
socket2 = accept(socket1, (struct sockaddr*)&xferClient, &addrlen);
if(socket2 == -1) {
fprintf(stderr, "Could not accept connection!\n");
exit(1);
}
// get the filename from the client over the socket
i=0;
if((readCounter = read(socket2, filename+1, MAXBUF) > 0))
i += readCounter;
if(readCounter == -1) {
fprintf(stderr, "Could not read filename from socket!\n");
close(socket2);
continue;
}
filename[i+1] = '\0';
printf("Reading file %s\n", filename);
// open the file for reading
fd = open(filename, O_RDONLY);
if(fd == -1) {
fprintf(stderr, "Could not open file for reading!\n");
close(socket2);
continue;
}
// reset the read counter
readCounter = 0;
// read the file, and send it to the client in chunks of size MAXBUF
while((readCounter = read(fd, buf, MAXBUF)) > 0) {
writeCounter = 0;
bufptr = buf;
while(writeCounter < readCounter) {
readCounter -= writeCounter;
bufptr += write(socket2, bufptr, readCounter);
if(writeCounter == -1) {
fprintf(stderr, "Could not write file to client!\n");
continue;
}
}
}
close(socket2);
close(fd);
}
close(socket1);
return 0;
}
// File transfer client xferClient.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SERVERPORT 8888
#define MAXBUF 1024
int main(int argc, char* argv[]) {
int sockd;
int counter;
int fd;
struct sockaddr_in xferServer;
char buf[MAXBUF];
int returnStatus;
if (argc < 3) {
fprintf(stderr, "Usage: %s <ip address> <filename> [dest filename]\n", argv[0]);
exit(1);
}
// create a socket
sockd = socket(AF_INET, SOCK_STREAM, 0);
if(sockd == -1) {
fprintf(stderr, "Could not create socket!\n");
exit(1);
}
// set up the server information
xferServer.sin_family = AF_INET;
xferServer.sin_addr.s_addr = inet_addr.(argv[1]);
xferServer.sin_port = htons(SERVERPORT);
// connect to the server
returnStatus = connect(sockd, (struct sockaddr*)&xferServer, sizeof(xferServer));
if(returnStatus == -1) {
fprintf(stderr, "Could not connect to server!\n");
exut(1);
}
// send the name of the file we want to the server
returnStatus = write(sockd, argv[2], strlen(argv[2])+1);
if(returnStatus == -1) {
fprintf(stderr, "Could not send filename to server!\n");
exit(1);
}
// call the shutdown to set our socket to read only
shutdown(sockd, SHUT_WR);
// open up a handle to our destination file to receive the contents
// from the server
fd = open(argv[3], O_WRONLY | O_CREATE | O_APPEND);
if(fd == -1) {
fprintf(stderr, "Could not open destination file, using stdout.\n");
fd = 1;
}
// read the file from the socket as long as there is data
while((counter =read(sockd, buf, MAXBUF)) > 0) {
// send the contents to stdout
write(fd, buf, counter);
}
if(counter == -1) {
fprintf(stderr, "Could not read file from socket!\n");
exit(1);
}
close(sockd);
return 0;
}
// [user@host projects]$ cc -o xferServer xferServer.c
// [user@host projects]$ ./xferServer
// [user@host projects]@ cc -o xferClient xferClient.c
// [user@host projects]@ ./xferClient 127.0.0.1 filename new-filename
일반적으로 리턴값이 음수이면 오류이다.
그러나 가끔 안그런 경우도 있긴하다
그런 에러 코드들은 errno.h 파일에 정의되어있다.
/usr/include/asm/errno.h