/* Make the necessary includes and set up the variables. */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
int main()
{
int sockfd;
int len;
struct sockaddr_un address;
int result;
char ch = 'A';
/* Create a socket for the client. */
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
/* Name the socket, as agreed with the server. */
address.sun_family = AF_UNIX;
strcpy(address.sun_path, "server_socket");
len = sizeof(address);
/* Now connect our socket to the server's socket. */
result = connect(sockfd, (struct sockaddr *)&address, len);
if(result == -1) {
perror("oops: client1");
exit(1);
}
/* We can now read/write via sockfd. */
write(sockfd, &ch, 1);
read(sockfd, &ch, 1);
printf("char from server = %c\n", ch);
close(sockfd);
exit(0);
}
/* Make the necessary includes and set up the variables. */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
int main()
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_un server_address;
struct sockaddr_un client_address;
/* Remove any old socket and create an unnamed socket for the server. */
unlink("server_socket");
server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
/* Name the socket. */
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, "server_socket");
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
/* Create a connection queue and wait for clients. */
listen(server_sockfd, 5);
while(1) {
char ch;
printf("server waiting\n");
/* Accept a connection. */
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);
/* We can now read/write to client on client_sockfd. */
read(client_sockfd, &ch, 1);
ch++;
write(client_sockfd, &ch, 1);
close(client_sockfd);
}
}
struct sockaddr_un
{
sa_family_t sun_family; // set it = AF_UNIX
char sun_path[]; // filename
}
struct sockaddr_in
{
short int sin_family; // set it = AF_INET
unsigned short int sin_port; // set = port number
struct in_addr sin_addr; // Internet address
Let's look at the same program modified to run in the AF_INET world.
Before we go any further, we need to make sure that we all use unique Port numbers. So each student needs to use different port numbers. In my notes, I will specify port numbers like so:
If you login as c27513, then when you see the notes indicate:
17xx0, 17xx1, 17xx3, etc. You will actually use port numbers 17130, 17131, 17133, etc.
/* Make the necessary includes and set up the variables. */ #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> int main() { int sockfd; int len; struct sockaddr_in address; int result; char ch = 'A'; /* Create a socket for the client. */ sockfd = socket(AF_INET, SOCK_STREAM, 0); /* Name the socket, as agreed with the server. */ address.sin_family = AF_INET; address.sin_addr.s_addr = inet_addr("127.0.0.1"); address.sin_port = 17xx0; // replace the xx with your student number len = sizeof(address); /* Now connect our socket to the server's socket. */ result = connect(sockfd, (struct sockaddr *)&address, len); if(result == -1) { perror("oops: client2"); exit(1); } /* We can now read/write via sockfd. */ write(sockfd, &ch, 1); read(sockfd, &ch, 1); printf("char from server = %c\n", ch); close(sockfd); exit(0); }
/* Make the necessary includes and set up the variables. */ #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> int main() { int server_sockfd, client_sockfd; int server_len, client_len; struct sockaddr_in server_address; struct sockaddr_in client_address; /* Create an unnamed socket for the server. */ server_sockfd = socket(AF_INET, SOCK_STREAM, 0); /* Name the socket. */ server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); server_address.sin_port = 17xx0; // replace xx with your student number server_len = sizeof(server_address); bind(server_sockfd, (struct sockaddr *)&server_address, server_len); /* Create a connection queue and wait for clients. */ listen(server_sockfd, 5); while(1) { char ch; printf("server waiting\n"); /* Accept a connection. */ client_len = sizeof(client_address); client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len); /* We can now read/write to client on client_sockfd. */ read(client_sockfd, &ch, 1); ch++; write(client_sockfd, &ch, 1); close(client_sockfd); } }
In general to convert from the order of your host bytes (reversed on Intel products) to the order that the network wants it, you call one of the following routines:
#include <netinet/in.h> unsigned long int htonl (unsigned long int hostlong); unsigned short int htons (unsigned short int hostshort); unsigned long int ntohl (unsigned long int netlong); unsigned short int ntohs (unsigned short int netshort);
The current client2.c and server2.c only work on the same host --- that's not too exciting. We can make some changes in client2.c to create client3.c that allow us to run client3.c anywhere on the internet. Here are the changes to create client3.c
#include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <netdb.h> int main(int argc, char * argv[]) { int sockfd; int len; struct sockaddr_in address; struct hostent *hp; char * hostname = "gettysburg";// default host for server is gettysburg int result; char ch = 'A'; /* Create a socket for the client. */ sockfd = socket(AF_INET, SOCK_STREAM, 0); /* Name the socket, as agreed with the server. */ if (argc > 1) hostname = argv[1]; hp = gethostbyname(hostname); if (hp == NULL) { fprintf(stderr, "\n%s: unknown host.\n", hostname); return -1; } address.sin_family = AF_INET; // Old line: address.sin_addr.s_addr = inet_addr("127.0.0.1"); memcpy(&address.sin_addr, hp->h_addr, hp->h_length); address.sin_port = htons(17xx0); len = sizeof(address); /* Now connect our socket to the server's socket. */ result = connect(sockfd, (struct sockaddr *)&address, len); if(result == -1) { perror("oops: client2"); exit(1); } /* We can now read/write via sockfd. */ write(sockfd, &ch, 1); read(sockfd, &ch, 1); printf("char from server = %c\n", ch); close(sockfd); exit(0); }
change the line that looks like:
TO:
Most TCP/IP based programs tend to allow multiple sessions at the same time. Note that while server1.c and server2.c was busy doing read and write calls, that it couldn't be processing an accept call. This means that any new connects will sit in a queue waiting for the server to get back to its accept call.
To solve this dilemma, we need to spawn (fork) multiple tasks. The approach we are going to show you shows up in many applications: telnet, ftp, smtp, pop, imap, and pretty much any popular application.
We will build an Echo server that just echos what ever is sent to it. Just for fun, our echo server will upper case everything it receives before it sends it back so it's easier to distinguish between the sent data and the received data:
#include <sys/types.h> #include <sys/wait.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/time.h> #include <ctype.h> void server(int aPort); void myChild(int fd_client); // Signal handler to process terminated children void mysig(int nsig) { int nStatus, nPid; nPid = waitpid(-1, &nStatus, WNOHANG); if (nPid > 0) printf("Child %d died\n", nPid); printf("\nSignal(%d) received\n****************************\n\n\n\n\n\n\n", nsig); signal(SIGCHLD, mysig); } main (int argc, char **argv) { int i, aPort; if (argc < 2) { fprintf(stderr, "usage:%s porta \n" , argv[0]); exit(1); } aPort = atoi(argv[1]); printf("\nI will be an echo server for port %d\n", aPort); signal(SIGCHLD, mysig); // To clean up terminated children server(aPort); exit(0); } // Server Loop for processing Echo Server requests. // Any new connect to Echo Server results in spawning a child // process to baby sit the connection void server(int aPort) { struct sockaddr_in sin, fsin; int nChildCnt = 0; int fd_connect, fd_accept; int fromlen, nOpt =1; long pid; if ((fd_accept = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("server: socket()"); exit(1); } // The following makes it quicker to reclaim a port when you // restart your application -- normally ports can't be reused for a time setsockopt(fd_accept, SOL_SOCKET, SO_REUSEADDR, &nOpt, sizeof(int)); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(aPort); sin.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(fd_accept, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("server: bind()"); exit(1); } if (listen(fd_accept, 5) < 0) { perror("server: listen()"); exit(1); } fromlen = sizeof(fsin); for (;;) { if ((fd_connect = accept(fd_accept, (struct sockaddr *)&fsin, &fromlen)) < 0) { perror("server:accept()"); continue; /* hope this is normally due to a child death*/ } nChildCnt += 1; printf("\n#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_\nCHILD(%d)\n", nChildCnt); fflush(stdout); if ((pid = fork()) > 0) { /* Parent */ close(fd_connect); printf("Child created: %d\n", pid); // Go wait on another Connect in the accept call continue; } if (pid == 0) { /* Must be the child */ myChild(fd_connect); exit(0); // Don't want to continue } if (pid < 0) printf("**** Can't fork CHILD\n"); } close(fd_accept); } // Child process to manager one connect requests reads/writes // When the connection seems to go away, the child terminates #define BUFF_SIZE 10000 void myChild(int fd_client ) { int i, nBytes; unsigned char buffer[BUFF_SIZE]; for (;;) { nBytes = read(fd_client, buffer, BUFF_SIZE); if (nBytes <= 0) { // We end up here when client closes its connection printf("\nRead Error on on %d\n", fd_client); break; } if (nBytes > 0) { printf("received on file descriptor: %d\n", fd_client); for (i= 0; i < nBytes; i++) buffer[i] = toupper(buffer[i]); if (write(fd_client, buffer, nBytes) <= 0) { printf("\nWrite Error on %d ==> ", fd_client); break; } } } }
The easiest way to come up with a client to your echo_server is to run a program called telnet. Try this:
Start your echo_server with the command:
./echo_server 17xx0
In another telnet session, execute the following command:
telnet gettysburg 17xx0
Key in some input and hit the enter key. Getting out of telnet is a bit of a problem. I would recommend just killing your window.
Access your echo_server from shrek with the same command on shrek(assuming your echo_server is running on gettysburg).
You now have enough socket programming that you can create a lot of interesting programs. I would like to experiment with creating requests similar to what a browser would make to a web server or an email client makes to send email.
There are no new concepts on the following program, except that this program makes it easier to type in socket requests. This program also allows you to create scripts that can generate a stream of socket commands:
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
void process_cmds(FILE *fp);
int connect_host(char * hostname, int port);
void disconnect_host(int socket);
void sendBuffer (int socket, char * sendBuff, int numBytes);
void receive(int socket, char * receiveBuff, int size_of_buff);
void receiveUntilDone(int socket, char * receiveBuff, int size_of_buff);
main (int argc, char * argv[])
{
FILE *fp;
if (argc > 1)
{
fp = fopen(argv[1], "r");
if (fp == NULL)
{
fprintf(stderr,"Unable to open (%s) : %s\n",
argv[1], strerror(errno));
return;
}
process_cmds(fp);
}
else
process_cmds(stdin);
}
void process_cmds(FILE *fp)
{
char *cmd, *hostname;
char *str, *str2;
int port;
int socket_num = -1;
char cmdBuff[500];
char sendBuff[10000];
char receiveBuff[30000];
int total = 0;
for (;;)
{
printf("Enter Cmd (-buff: -buffend: -send: -connect:host:port: "
" etc. \n");
cmdBuff[0] = 0;
cmd = fgets(cmdBuff, sizeof(cmdBuff), fp);
if (cmd == NULL) break;
if (strlen(cmd) <= 1) break;
str = strchr(cmd, ':');
if (str == NULL)
{
printf("All commands terminate with :\n");
continue;
}
*str = 0;
if (strcmp(cmd, "-buff")== 0)
{
//buff: logic
total = 0;
for (;;)
{
str = fgets(&sendBuff[total], sizeof(sendBuff)-total -1, fp);
if (str == NULL) break;
if (strncmp("-buffend:", str, strlen("-buffend:")) == 0)
break;
total += strlen(str);
}
sendBuff[total] = 0;
}
else if(strcmp(cmd, "-buffnn") == 0)
{
//-buffnn: logic
total = 0;
str = fgets(sendBuff, sizeof(sendBuff), fp);
if (str != NULL )
{
str2 = strtok(str, "\r\n");
*str2 = 0;
total = strlen(str);
}
}
else if (strcmp(cmd,"-send") == 0)
sendBuffer(socket_num, sendBuff, total);
else if (strcmp(cmd,"-receive") == 0)
receive(socket_num, receiveBuff, sizeof(receiveBuff));
else if (strcmp(cmd, "-receiveUntilDone") == 0)
receiveUntilDone(socket_num, receiveBuff, sizeof(receiveBuff));
else if (strcmp(cmd, "-connect") == 0)
{
hostname = strtok(&str[1], ":\n");
str2 = strtok(NULL, ":\n");
if (str2 != NULL)
port = atoi(str2);
socket_num = connect_host( hostname, port);
}
else if (strcmp(cmd,"-disconnect") == 0)
disconnect_host(socket_num);
else
printf("illegal command: %s\n"
"LEGAL cmds:\n"
"-send:\n"
"-receive:\n"
"-receiveUntilDone:\n"
"-buff: This command is followed by data followed by -buffend:\n"
"-buffend:\n"
"-buffnn: buffer with no newline\n"
"-connect:hostname:port:\n"
"-disconnect:\n",
cmd);
}
}
int connect_host(char * hostname, int port)
{
int socket_num = -1;
struct sockaddr_in sin;
struct hostent *hp;
hp = gethostbyname(hostname);
if (hp == NULL)
{
fprintf(stderr, "\n%s: unknown host.\n", hostname);
return -1;
}
if ((socket_num = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
fprintf(stderr, "socket err: %s\n", strerror(errno));
return socket_num;
}
else
{
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
if (connect(socket_num , (struct sockaddr *)&sin, sizeof(sin)) < 0)
{
fprintf(stderr, "connect err:%s\n", strerror(errno));
return -1;
}
printf("CONNECT Successful to host %s, port %d\n",
hostname, port);
}
return socket_num;
}
void disconnect_host(int socket_num)
{
if (socket_num > 0)
close(socket_num);
printf("DISCONNECTED\n");
}
void sendBuffer (int socket_num, char * buff, int numBytes)
{
int num;
if (socket_num < 0)
{
fprintf(stderr, "Trying to send to a bad socket(%d)\n", socket_num);
return;
}
printf("SENDING:\n%s\n", buff);
num = write(socket_num, buff, numBytes);
if (num < numBytes)
{
printf("Intended to send %d bytes, but only sent %d bytes\n",
numBytes, num);
if (num < 0)
fprintf(stderr, "send err: %s\n", strerror(errno));
}
}
void receive(int socket_num, char * receiveBuff, int size_of_buff)
{
int numBytes;
if (socket_num < 0)
{
fprintf(stderr, "Trying to receive from a bad socket(%d)\n", socket_num);
return;
}
numBytes = read(socket_num, receiveBuff, size_of_buff-1);
if (numBytes > 0)
{
receiveBuff[numBytes] = 0;
printf("RECEIVED(%d):\n%s\n",numBytes, receiveBuff);
}
else
{
fprintf(stderr, "read Err: %s\n", strerror(errno));
}
}
void receiveUntilDone(int socket_num, char * receiveBuff, int size_of_buff)
{
int numBytes;
int total = 0;
if (socket_num < 0)
{
fprintf(stderr, "Trying to receive from a bad socket(%d)\n",
socket_num);
return;
}
do
{
if (total >= size_of_buff -1)
break;
numBytes = read(socket_num, &receiveBuff[total], size_of_buff-total-1);
if (numBytes > 0) total += numBytes;
else
{
fprintf(stderr, "read Err: %s\n", strerror(errno));
break;
}
} while (numBytes > 0);
if (total >= 0)
{
receiveBuff[total] = 0;
printf("RECEIVED-UntilDone(%d):\n%s\n", total, receiveBuff);
}
}
In one window, assume you have started your echo_server with the command:
./echo_server 17120
In another window issue commands like the following:
./cmdProcess
-connect:gettysburg:17120:
-buff:
How are you doing?
Is anyone really here?
I'm afraid of echos
-buffend:
-send:
-receive:
-send:
-buff:
It's time to go
Au revoir (not sure of my French?)
-buffend:
-send:
-receive:
-disconnect:
Create the file echo_server.cmd that contains the following:
-connect:gettysburg:17120:
-buff:
Clem sends his greetings
to the echo server ----
Howdy Partners
-buffend:
-send:
-receive:
-buff:
What did you have for
lunch today?
Hope you are
having a good day.
-buffend:
-send:
-receive:
Now issue the command:
./cmdProcess echo_server.cmd
From a socket programming point of view, there are a lot of programs that are not much more complicated than the above. A browser's request from a web server is easy to fake using socket programming techniques. Check it out:
Execute:
./cmdProcess http.cmd
where http.cmd contains:
-connect:gettysburg.wccnet.org:80: -buff: GET /~c27500/hi/hi.html HTTP/1.1 Host: gettysburg.wccnet.org -buffend: -send: -receive: -disconnect:
The beauty of this approach is that you can try out things which your browser may not be cooperating with (sound familiar).
Before you use the following, please change the FROM address and the TO address to your own email account so you can check out that the request worked properly:
Issue:
./cmdProcess email.cmd
where email.cmd contains:
-connect:stu.wccnet.org:25: -receive: -buff: EHLO wccnet.org -buffend: -send: -receive: -buff: MAIL FROM:<fritz@wccnet.org> -buffend: -send: -receive: -buff: RCPT TO:<bclinton@stu.wccnet.org> -buffend: -send: -receive: -buff: DATA -buffend: -send: -receive: -buff: Message-ID: <3CF3D317.4040203@wccnet.org> Date: Tue, 28 May 2002 11:57:27 -0700 From: Frizgerald<fritz@wccnet.org> User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; m18) Gecko/20010131 Netscape6/6.01 X-Accept-Language: en MIME-Version: 1.0 To: bclinton@stu.wccnet.org Subject: What's UP from cmdProcess Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit This email was sent with cmdProcess. The To address and the From address in this could be weird. This is the third line in the email. This is the fourth line in the email. This is the fifth line in the email. This is the sixth and last line in the email. -buffend: -send: -buff: . -buffend: -send: -receive: -buff: quit -buffend: -send: -receive: -disconnect:
I have a tool that you could write yourself by the time we finish this section. It's called snoop. We need a few more concepts before you can write snoop yourself, but you can at least see how to use snoop to analyze TCP/IP socket conversations.
Suppose you want to see the conversation your Web Browser has with your web server. You can stick the snoop program in the middle. Just point your Browser to snoop as though snoop were the Web Server. Tell Snoop where the real web server is. Packets sent from the Web Browser will go to snoop and then to the web server. Snoop can print out what it saw as the packet passed through. The Web Server things that Snoop is the Web Browser, so Web responses are sent back to Snoop. Snoop prints out the response and then sends it on to the web browser.
Consider accessing the following URL in your browser:
http://gettysburg.wccnet.org/~c27500/hi/hi.html
Now run snoop with a command on gettysburg like:
snoop 17xx0 80
Change the URL in your browser to:
http://gettysburg.wccnet.org:17xx0/~c27500/hi/hi.html
Take a look at the conversation captured by snoop.
With some work you can watch mail sending. If your mail server is on stu.wccnet.org,
then in this case, you will want a snoop call like:
snoop 17xx0 25 -h stu.wccnet.org
We can demo this with Netscape as your email client.
It's a lot tougher to look at the incoming mail stream (using pop or imap), because we would need to do something about an encrypted password. It used to be easier before email tightened up because of hackers (like me? :-)
gethostbyname returns an ip address from a domain name. We have been using it in a simpler form, but this example comes from the book to demonstrate some of the gethostbyname features. There is also a routine gethostbyaddr routine which takes an ip address and returns a domain name. I haven't managed to get an example using gethostbyaddr to work properly however. I have seen code that used this command before, but I can't seem to put it back together.
There is a simpler way to use gethostbyname which can be seen in the above examples (for example cmdProcess.c above).
/* As usual, make the appropriate includes and declare the variables. */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
char *host, **names, **addrs;
struct hostent *hostinfo;
/* Set the host in question to the argument supplied with the getname call,
or default to the user's machine. */
if(argc == 1) {
char myname[256];
gethostname(myname, 255);
host = myname;
}
else
host = argv[1];
/* Make the call to gethostbyname and report an error if no information is found. */
hostinfo = gethostbyname(host);
if(!hostinfo) {
fprintf(stderr, "cannot get info for host: %s\n", host);
exit(1);
}
/* Display the hostname and any aliases it may have. */
printf("results for host %s:\n", host);
printf("Name: %s\n", hostinfo -> h_name);
printf("Aliases:");
names = hostinfo -> h_aliases;
printf("**** alias name list\n");
while(*names) {
printf(" %s", *names);
names++;
}
printf("\n");
/* Warn and exit if the host in question isn't an IP host. */
if(hostinfo -> h_addrtype != AF_INET) {
fprintf(stderr, "not an IP host!\n");
exit(1);
}
/* Otherwise, display the IP address(es). */
printf("***** IP address list\n");
addrs = hostinfo -> h_addr_list;
while(*addrs) {
printf(" %s", inet_ntoa(*(struct in_addr *)*addrs));
addrs++;
}
printf("\n");
exit(0);
}
Create a Primitive File Transport Server called pfts. Modify the Echo Server to become pfts by adding the following pfts protocol:
First line of input is terminated by a New Line and contains either:
get filename
or
put filename
If the command is get filename, then pfts will open the requested file byte for byte and then close the connection.
If the command is put filename, then pfts will read the input stream and store it into the requested filename.
You will also need a Client pfts program to work with your server pfts.