(처음 부터 만들면 되긴 하지만, 조금 귀찮다. 괜찮은 서버를 하나 찾아서 수정하면서 시작하자.)
찾는 샘플의 조건:
1. blocking socket : 서버가 단순해 진다.
2. fork 버전 : 서버 관리및 운영이 단순해 진다.
3. 언어불문 : 가능하면 C/C++
4. client 테스트 버전이 있으면 우대(?)
구글링으로부터 시작했다.
socket sample
역시나 많이 나온다.
첫번째 자료를 본다.
훌륭하다.
흠... 아직 서버를 준비 안했다.
내부 서버를 만들어서 소스를 붙여 넣고 컴파일 한다.
http://www.cs.uic.edu/~troy/fall99/eecs471/socksample/samples.html
몇가지 문제가 있다.
C++소스긴 한데, cerr밖에는 C++이지 않다.
과감하게 C로 바꾼다.
/*
NAME : sockser.C
TYPE : C++ SOURCE
AUTHOR : Arunkumar Elango
DESCRIPTION :
This file contains the source code for the server. It takes the following command line as input :$ rpiped [-m maxconnections] [-p portnumber]
It creates and binds a socket at the either the well known portnumber or at the portnumber given in the
commandline. Then, it waits for a connection request from a client. When the request arrives, it forks
a child process that handles the request. The child process first checks if the client has given the
correct password and if it does, processes the request and exits. The parent doesnot fork a child if
there are already maxconnections number of children.*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <memory.h>
#include <string.h>
#include <errno.h>
#include <sys/errno.h>
#include <sys/wait.h>
#include <signal.h>
const int MAXSTR=1024; // Maximum String length
const long PORTNUM=54715; // The "Well-known" port number
const int PASSWORD = 21; // The password that the server expects.void handleRequest(int); // Function to handle clients' request(s)
int numberChildren; // The current number of child processes.
void childExit(int); // signal handler for SIGCHLDmain( int argc, char *argv[])
{
int maxConnections,portNumber;
char executableName[MAXSTR];
struct sigaction action;// Validate and read from the command line
if ( (argc != 5) && (argc != 3) && (argc != 1) )
{
fprintf(stderr, "Invalid command line.\n");
exit(1);
}
if ( argc == 1 )
{
maxConnections = 5;
portNumber = PORTNUM;
}
else if (argc == 3 )
{
if (!strcmp(argv[1],"-m"))
{
maxConnections = atoi(argv[2]);
portNumber = PORTNUM;
}
else if (!strcmp(argv[1],"-p"))
{
portNumber = atoi(argv[2]);
maxConnections = 5;
}
else
{
fprintf(stderr, "Invalid command line.\n");
exit(1);
}
}
else
{
if ((!strcmp(argv[1],"-m")) && (!strcmp(argv[3],"-p")))
{
maxConnections = atoi(argv[2]);
portNumber = atoi(argv[4]);
}
else
{
fprintf(stderr, "Invalid command line.\n");
exit(1);
}
}if (portNumber < 10000)
{
fprintf(stderr, "Port number has to be atleast 10000.\n");
exit(1);
}// At this point, the command line has been validated.
int sockfd,newsockfd,clilen,childpid;
struct sockaddr_in serv_addr,cli_addr;// Set the signal handler for SIGCHLD.
sigemptyset(&action.sa_mask);
action.sa_handler = childExit;
action.sa_flags = 0;
if (sigaction(SIGCHLD,&action,0) == -1)
{
fprintf(stderr, " sigaction error.\n");
exit(1);
}// Create a new TCP socket...
if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
fprintf(stderr, "Cant open stream socket.\n");
exit(0);
}bzero((char *)&serv_addr,sizeof(serv_addr)) ;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(portNumber);// Bind the socket to the server's ( this process ) address.
if (bind(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
{
fprintf(stderr, "Cant bind local address.\n");
exit(1);
}// Listen for connections in this socket
listen(sockfd,5);
int status;
fprintf(stderr, "Max number of connections = %d connected to port : %d\n",
maxConnections, portNumber);char buf[MAXSTR],ack[MAXSTR];
// repeat forever..
while (1)
{
clilen = sizeof(cli_addr);
// Accept a client's request, and get the client's address info into the local variable cli_addr.
newsockfd = accept(sockfd,(struct sockaddr *)&cli_addr,(socklen_t *)&clilen);
// After accepting the connection, all transaction with this client would happen with the new
// socket descriptor - newsockfd.if ((newsockfd < 0) && (errno != EINTR))
fprintf(stderr, " server : accept error.\n");
else if (newsockfd > 0)
{
if (numberChildren < maxConnections)
{
// Fork a new child to serve this client.
if ((childpid = fork()) < 0)
fprintf(stderr, "server : fork error.\n");
else if (childpid == 0)
{
// The child server...first handles the client's request and then quits.
close(sockfd);
handleRequest(newsockfd);
exit(0);
}
// The parent server, in the meantime, goes about serving other clients.
numberChildren ++;
}
else
{
// There are already max no of clients being served. So send a negative ack to
//this client, refusing connection.
read(newsockfd,(void *)buf,MAXSTR);
sprintf(ack,"NO");
ack[strlen(ack)] = '\0';
write(newsockfd,ack,strlen(ack));
}
close(newsockfd);
}
waitpid(-1,&status,WNOHANG);
}}
void handleRequest(int newsockfd)
{
int nGuess; //
}#if 0
void handleRequest(int newsockfd)
{
char buf[MAXSTR],execName[MAXSTR],ack[MAXSTR];
char data[MAXSTR];
int i=0,code;
char ch='0';
int fd = open("dummy",O_RDWR | O_CREAT | O_TRUNC,0764);// Open a dummy file to store client's data, that would be needed to execute the requested
// command. For example, the text after a wc command.if ( fd == -1)
{
fprintf(stderr, "Error opening dummy file.\n");
exit(0);
}// Read from the socket, for the password and the requested command.
read(newsockfd,(void *)buf,MAXSTR);
sscanf(buf,"%d %s\n",&code,execName);if (code != PASSWORD)
{
// If password is not valid, send a negative acknowledgement - refusing connection.
fprintf(stderr, "Invalid Password\n");
sprintf(ack,"NO");
ack[strlen(ack)] = '\0';
write(newsockfd,ack,strlen(ack));
exit(0);
}// The client is authorized, so send a positive acknowledgement.
fprintf(stderr, "Processing %s for client.\n",execName);
sprintf(ack,"OK");
ack[strlen(ack)] = '\0';
write(newsockfd,ack,strlen(ack));// Get the addidtional data that the client has to send, like the text in a wc or cat command,
//and then write it to the dummy file.
read(newsockfd,(void *)data,MAXSTR);
write(fd,data,strlen(data));
close(fd);/* Duplicate this process's Standard output into the socket's stream.
Henceforth, anything that the process writes to the screen, will actually be written to
the socket, so that the client can read from the socket and print to its local screen.
Remember : This is the child server process, and hence only the child's STDOUT has been Duplicated.
The parent server, still continues to write to the screen.
*/dup2(newsockfd,STDOUT_FILENO);
char command[MAXSTR];
sprintf(command,"cat dummy | %s",execName);
command[strlen(command)] = '\0';// execlp is a system call that executes a shell command from within a process.
execlp("/bin/sh","sh","-c",command,0);
}
#endif// Signal Handler for SIGCHLD
void childExit(int signo)
{
// A child has just exitted, so just decrement the counter.
numberChildren --;
}
줄이 좀 안맞는 것은 용서~~
테스트를 위한 클라이언트 소스도 있다.
/*
NAME : rpipe.C
TYPE : C++ SOURCE
AUTHOR : Arunkumar Elango
DESCRIPTION :
This file contains the source code for the client. It takes the following command line as input :$ rpipe [-h hostname] [-p portnumber] executable_name
The client tries to connect to the server at the known portnumber and host. If it succeds in connecting, it sends
a password and the executable_name. The server checks the validity of the password, and if it is right, sends back
an acknowledgement. Else, the child server exits and the connection is lost. Once the client receives the
acknowledgement, it reads its standard input and sends the data to the server through the socket. The server
processes the request and sends the output back through the socket. The client just prints the output it recieved
onto the screen and exits.*/
//#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <memory.h>
const int MAXSTR=1024;
const long PORTNUM=54715;
const int PASSWORD = 21;main( int argc, char *argv[])
{
int portNumber;
char executableName[MAXSTR],hostName[MAXSTR];if ( (argc != 6) && (argc != 4) && (argc != 2) )
{
fprintf(stderr, "Invalid command line.\n");
exit(1);
}
if ( argc == 2 )
{
strcpy(hostName,"ernie.eecs.uic.edu");
portNumber = PORTNUM;
strcpy(executableName,argv[1]);
}
else if (argc == 4 )
{
if (!strcmp(argv[1],"-h"))
{
strcpy(hostName,argv[2]);
portNumber = PORTNUM;
}
else if (!strcmp(argv[1],"-p"))
{
portNumber = atoi(argv[2]);
strcpy(hostName,"ernie.eecs.uic.edu");
}
else
{
fprintf(stderr, "Invalid command line.\n");
exit(1);
}
strcpy(executableName,argv[3]);
}
else
{
if ((!strcmp(argv[1],"-h")) && (!strcmp(argv[3],"-p")))
{
strcpy(hostName,argv[2]);
portNumber = atoi(argv[4]);
}
else
{
fprintf(stderr, "Invalid command line.\n");
exit(1);
}
strcpy(executableName,argv[5]);
}if (portNumber < 10000)
{
fprintf(stderr, "Port number has to be atleast 10000.\n");
exit(1);
}
// cerr << hostName << " " << portNumber << executableName <<"\n";struct hostent *hostptr;
if ((hostptr = gethostbyname(hostName)) == NULL)
{
fprintf(stderr, "Error locating host : %s\n", hostName);
exit(1);
}
struct sockaddr_in serv_addr;bzero((char *)&serv_addr,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
memcpy((char *)&serv_addr.sin_addr,(char *)hostptr->h_addr,hostptr->h_length);
serv_addr.sin_port = htons(portNumber);int sockfd,newsockfd;
if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
fprintf(stderr, "client : socket error.\n");
exit(1);
}
if ( connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
{
fprintf(stderr, "client : connect error.\n");
exit(1);
}
char buf[MAXSTR],ack[MAXSTR],data[MAXSTR];
char answer[MAXSTR];
int i=0;
char ch='0';sprintf(buf,"%d %s\n",PASSWORD,executableName);
buf[strlen(buf)] = '\0';
write(sockfd,buf,strlen(buf));
read(sockfd,(void *)ack,MAXSTR);// cerr << "got back " << ack << "from server";
if (( ack[0] == 'N') && (ack[1] == 'O') )
{
fprintf(stderr, "Connect request refused.\n");
exit(1);
}
//cin.getline(data,MAXSTR,'\0');
strcpy(data, "TEST...");
data[strlen(data)] = '\0';
// cerr << "Client : sending.." << data << endl;
write(sockfd,data,strlen(data));
read(sockfd,(void *)answer,MAXSTR);//cerr << answer <<endl;
printf("%s\n", answer);close(sockfd);
}
여기에 Makefile만 있으면 컴파일 준비 완료
all:rpiped rpipe
rpiped:sockser.o
gcc -o rpiped sockser.o -lnsl
sockser.o:sockser.c
gcc -c sockser.c
rpipe:rpipe.o
gcc -o rpipe rpipe.o -lnsl
rpipe.o:rpipe.c
gcc -c rpipe.c
여기까지하면 기본적인 준비 완료.