Related
I have two files: tcp-demo-client.c and tcp-demo-server.c
Functionality: If the connection succeeds, the client receives a simple timestamp from the server. I like to modify the code that the server only sends the timestamp if the client hits the space key. How can I do that?
(It's my first socket project)
tcp-demo-client.c:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
int main(int argc, char **argv)
{
int ret; // return value from functions
// Check command line arguments
if (argc != 3) {
fprintf(stderr,
"Missing parameters. Usage: %s <server-name-or-ip> <server-port>\n",
argv[0]);
return 1;
}
// Address information structure
struct addrinfo aii;
// Set whole structure to 0s
memset(&aii, 0, sizeof(aii));
// A stream (TCP) connection
aii.ai_socktype = SOCK_STREAM;
// We do not care whether it is IPv4 or IPv6
aii.ai_family = PF_UNSPEC;
struct addrinfo *aio;
// Get address information.
// First parameter is host string, either hostname or numerical IPv4/IPv6 address
// Second parameter is port/service string, either as port number
// or well-known identifier, e.g. http
// So, e.g. getaddrinfo( "www.compeng.uni-frankfurt.de", "http", ... getaddrinfo( "141.2.248.1", "80", ...
// Third parameter is input address info structure (cf. above)
// Fourth parameter is output address info structure, a linked list of potential addresses
ret = getaddrinfo(argv[1], argv[2], &aii, &aio);
if (ret) {
fprintf(stderr, "Error getting address for %s:%s: %s\n",
argv[1], argv[2], gai_strerror(ret));
return 1;
}
// File descriptor for the socket
int sock = -1;
struct addrinfo *iter;
// Iterate over linked list of specified output addresses,
// use first address to which a connection can be established
for (iter = aio; iter != NULL && sock == -1; iter = iter->ai_next) {
// Create socket given the parameters from the found address info.
sock =
socket(iter->ai_family, iter->ai_socktype,
iter->ai_protocol);
if (sock < 0)
continue; // Appropriate socket could not be created, try next address
// Socket created successfully, now try to connect to remote target address
// taken from address info
ret = connect(sock, iter->ai_addr, iter->ai_addrlen);
if (ret) {
// Socket could not be connected to remote target
close(sock); // Close socket
sock = -1;
continue; // try next address
}
}
freeaddrinfo(aio); // Release address information allocated in getaddrinfo
if (sock == -1) {
// No connection at all could be established to remote target
fprintf(stderr, "Unable to establish any connection to %s:%s\n",
argv[1], argv[2]);
return 1;
}
// Maximum size of incoming message
int msglen = 100;
// Buffer for message
char buf[msglen + 1]; // One more to ensure that there is a trailing NULL char.
memset(buf, 0, msglen + 1);
ret = read(sock, buf, msglen); // Return value is amount of bytes read, -1 in case of error
printf("Data read: '%s'\n", buf);
// Clean up after us and close the socket.
close(sock);
return 0;
}
tcp-demo-server.c:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#define MAXPENDING 5
int main(int argc, char **argv)
{
unsigned short listen_port; // Server port */
int listen_sock; // Socket descriptor for server
int client_sock; // Socket descriptor for client
struct sockaddr_in listen_addr; // Local address */
struct sockaddr_in client_addr; // Client address */
// Check command line arguments
if (argc != 2) {
fprintf(stderr, "Missing parameters. Usage: %s <server-port>\n",
argv[0]);
return 1;
}
// Create socket for incoming connections
if ((listen_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror("socket() failed");
return 1;
}
// Construct local address structure
listen_port = atoi(argv[1]); // First arg: listening port number
memset(&listen_addr, 0, sizeof(listen_addr)); // Zero out structure
listen_addr.sin_family = AF_INET; // Internet address family
listen_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Any incoming interface
listen_addr.sin_port = htons(listen_port); // Local port
// Bind to the local address
if (bind
(listen_sock, (struct sockaddr *)&listen_addr,
sizeof(listen_addr)) < 0) {
perror("bind() failed");
return 1;
}
// Mark the socket so it will listen for incoming connections
if (listen(listen_sock, MAXPENDING) < 0) {
perror("listen() failed");
return 1;
}
for (;;) { /* Run forever */
socklen_t addr_len = sizeof(client_addr);
// Wait for a client to connect */
if ((client_sock =
accept(listen_sock, (struct sockaddr *)&client_addr,
&addr_len)) < 0) {
perror("accept() failed");
return 1;
}
// client_sock is connected to a client
printf("New connection from %s\n",
inet_ntoa(client_addr.sin_addr));
// Create message to send
time_t t = time(NULL);
char *msg = ctime(&t);
int msglen = strlen(msg) + 1;
int ret;
// Write the whole message in one go, fail if this does not work
ret = write(client_sock, msg, msglen);
// Return value is amount of bytes written, -1 in case of error
if (ret != msglen) {
perror("Error during write");
return 1;
}
close(client_sock);
}
/* NOT REACHED */
return 1;
}
I presume you mean you want the space char as unbuffered input. For POSIX, you could use something along the lines of this to capture the keypress:
#include <termios.h>
[...]
struct termios t;
int c, r;
[...]
tcgetattr(0, &t);
t.c_lflag &= ~(ICANON | ECHO);
t.c_cc[VMIN] = 1;
tcsetattr(0, TCSANOW, &t);
c = getchar();
r = send(sock, c, 1, 0);
Have a look at this for additional information:
setvbuf not able to make stdin unbuffered
http://c-faq.com/osdep/cbreak.html
First off, this is homework, so please no outright answers. I am writing a back and forth chat program in C. I'm extremely new to C (just started learning for this class). Currently I have three files:
server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include "chat.h"
#define SERVER_PORT 1725
#define MAX_PENDING 5
#define MAX_LINE 256
int main()
{
struct sockaddr_in sin;
char buf[MAX_LINE];
int len;
int s, new_s;
struct chat_packet packet;
/* build address data structure */
bzero((char *)&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(SERVER_PORT);
/* setup passive open */
if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("simplex-talk: socket");
exit(1);
}
if ((bind(s, (struct sockaddr *)&sin, sizeof(sin))) < 0)
{
perror("simplex-talk: bind");
exit(1);
}
listen(s, MAX_PENDING);
/* wait for connection, then receive and print text */
while(1)
{
if ((new_s = accept(s, (struct sockaddr *)&sin, &len)) < 0)
{
perror("simplex-talk: accept");
exit(1);
}
/* Stay in the following loop until CTRL+C */
while (len = recv(new_s, &packet, sizeof(packet), 0))
{
fputs(packet.sender_name, stdout);
fputs(": ", stdout);
fputs(packet.data, stdout);
fputs("\nYou: ", stdout);
while (fgets(buf, sizeof(buf), stdin))
{
if(strlen(buf) > 144)
{
printf("Your message is too long. Please enter a new message.\n");
continue;
}
else
{
buf[MAX_LINE-1] = '\0';
strncpy(packet.data,buf,144);
char sender[8] = "Mason"; /*should be argv[index of name]*/
strncpy(packet.sender_name, sender, 8);
send(new_s, &packet, sizeof(packet),0);
}
}
}
close(new_s);
}
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include "chat.h"
#define SERVER_PORT 1725
#define MAX_LINE 256
int main(int argc, char * argv[])
{
FILE *fp;
struct hostent *hp;
struct sockaddr_in sin;
char *host;
char buf[MAX_LINE];
int s;
int len;
struct chat_packet packet;
if (argc==2)
{
host = argv[1];
}
else
{
fprintf(stderr, "usage: simplex-talk host\n");
exit(1);
}
/* translate host name into peer's IP address */
hp = gethostbyname(host);
if (!hp) {
fprintf(stderr, "simplex-talk: unknown host: %s\n", host);
exit(1);
}
/* build address data structure */
bzero((char *)&sin, sizeof(sin));
sin.sin_family = AF_INET;
bcopy(hp->h_addr, (char *)&sin.sin_addr, hp->h_length);
sin.sin_port = htons(SERVER_PORT);
/* active open */
if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("simplex-talk: socket");
exit(1);
}
if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
{
perror("simplex-talk: connect");
close(s);
exit(1);
}
/* main loop: get and send lines of text */
while (fgets(buf, sizeof(buf), stdin))
{
if(strlen(buf) > 144)
{
printf("Your message is too long. Please enter a new message.\n");
continue; /*This allows the user to re-enter a message post-error*/
}
else
{
buf[MAX_LINE-1] = '\0';
strncpy(packet.data, buf, 144);
char sender[8] = "Abby"; /*should be argv[index of name]*/
strncpy(packet.sender_name, sender, 8);
send(s, &packet, sizeof(packet), 0);
recv(s, &packet, sizeof(packet),0);
fputs(packet.sender_name, stdout);
fputs(": ", stdout);
fputs(packet.data, stdout);
fputs("\nYou: ", stdout);
}
}
}
chat.h
#include <stdint.h> /* Needed for unsigned types */
#define MAX_DATA_LEN 144 /* So we are on 16-bit boundary */
#define USER_NAME_LEN 8
/* You must send this packet across the socket. Notice there are
* no pointers inside this packet. Why?*/
struct chat_packet {
u_short version; /* 16 bits -- Set to version 2 in code */
char sender_name[8]; /* 64 bits */
char data[MAX_DATA_LEN]; /* Message goes in here */
};
Everything except what is in the client and server while loops were given to me by my instructor. The base part of the assignment is getting back-and-forth chat functionality. I'm running everything in PuTTY using the command line. I duplicate the session and run client in one and server in the other. To run:
./client serverName
./server
I am able to go back and forth one time, and then nothing else sends or receives. I am still able to type, but the two sessions cannot see each other's messages past the first back and forth. I am not sure where my code is wrong. Any advice would be appreciated, as I'm very new to the language. Thanks in advance!
Okay, here's my hint: Think about what happens when you recv() zero characters. Also, check what happens when the server calls accept() vs. when the client calls connect().
You might also want to check the return values of your recv() calls more judiciously. (and send(), for that matter; if a call can fail, check its return value!) Here's a hint from the man recv page:
RETURN VALUES
These calls return the number of bytes received, or -1 if an error occurred.
Also, if you aren't familiar with a debugger (such as gdb), I would recommend learning it. In a pinch, you might consider adding printf() statements to your code, to figure out what is happening.
Also, think about where your "blocking calls" are. If you're not familiar with what it means to be a "blocking call", we call it "blocking" when you call a function, and that function doesn't return ("blocks") until some specified thing happens. For example, your accept() will block until a connection is accepted. Your fgets() will block until a line of text is received. send() would block if you've already sent too much data, and the buffer is full. recv() would block until you've received the specified number of bytes. recv() also has a behavior you might not expect, that you may need to account for:
If no messages are available at the socket, the receive call waits for a
message to arrive, unless the socket is nonblocking (see fcntl(2)) in
which case the value -1 is returned and the external variable errno set
to EAGAIN. The receive calls normally return any data available, up to
the requested amount, rather than waiting for receipt of the full amount
requested; this behavior is affected by the socket-level options
SO_RCVLOWAT and SO_RCVTIMEO described in getsockopt(2).
In your case, your packets might be small enough that you won't run into cases where you have to reassemble them yourself. But it couldn't hurt to check.
I think that gives you some avenues to explore...
I am trying to create my first UDP client server program in C language slightly modified from the man page of linux gai_strerror(3) - Linux man page. Although that I am using several error functions to check the correct process of the code when I reach the transmission part it fails. Based on what I see the connection is correct there is no error so in theory the message should be transmitted, but in practice it fails. I even open wireshark on local mode to see if there are any data transmitted, but nothing is transmitted. Since this is my first client Server program, and I am new into programing I can not think any other way to debug my code. What am I missing?
Addition:
On the terminal two things are printed:
connected: Success
and the error that is printing is Error sending data!
Any advice would be much appreciated.
#include <sys/types.h>
#include<errno.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#include <sys/time.h> /* The header includes gettimeofday() */
#include <time.h> /* The header includes time types which we will use for day time */
#include <math.h> /* The header includes mathematical declarations */
int main(int argc, char *argv[]) {
struct timeval start; /* struct timeval for tv_sec and tv_usec */
struct addrinfo bind_info;
struct addrinfo *servInfo, *rp; /* addrinfo is filled with destination host information */
int socket_fd, retrieve_info; /* socket_fd represents the file descriptor of socket, rtt round time trip */
ssize_t send;
uint32_t tran = 0;
char *ip_serv = argv[1]; /* Set argv[1] as the server IP */
char *port_number = argv[2];
memset(&bind_info, 0, sizeof(struct addrinfo));
bind_info.ai_family = AF_INET;
bind_info.ai_socktype = SOCK_DGRAM;
bind_info.ai_protocol = IPPROTO_UDP;
bind_info.ai_flags = AI_PASSIVE;
if ((retrieve_info = getaddrinfo(ip_serv, port_number,&bind_info, &servInfo)) !=0 ) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(retrieve_info));
exit(EXIT_FAILURE);
}
for (rp = servInfo; rp != NULL; rp = rp->ai_next) {
if ((socket_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) {
perror("socket");
continue;
}
if (connect(socket_fd, rp->ai_addr, rp->ai_addrlen) != -1) {
perror("connected");
break; /* We made it enough stop */
}
close(socket_fd); /* Release socket after binding */
}
if (rp == NULL) {
fprintf(stderr, "failed to connect\n");
exit(2);
}
freeaddrinfo(servInfo);
gettimeofday(&start, NULL); /* timezone should be NULL */
long int start_time = (start.tv_sec * 1000000 + start.tv_usec);
tran = htonl(start_time);
send = sendto(socket_fd,&tran,sizeof(tran),0,(struct sockaddr *) rp->ai_addr, rp->ai_addrlen);
if (send == 0) {
printf("Transmission to server was correct!\n");
}
else {
fprintf(stderr, "Error sending data!\n");
}
exit(0);
} /* end of int main (argv[]) */
You should read the manual page for sendto, as you seem to not understand its return value.
It will return the number of bytes it sent, or -1 on error. If it fails the call will set errno just like any other system call, and you can use perror like you do for the other functions when they fail.
So in your case you will say that the call was successful when the call have sent zero bytes, and say that the call failed in all other cases (which includes actual successful sending).
I am benchmarking local socket performance on Linux and Solaris for a project. For some reason I cannot find out, performance on Solaris is roughly 100x worse than on Linux. In Linux, opening a socket, exchanging one very short (2 char) message each way and closing it takes about 10us elapsed time. On Solaris, the same thing takes about 1000us.
Set-up is Solaris 10 developer vm in Virtual Box and Linux both in the same Virtual Box and directly on the same hardware (makes no difference).
Is this a known issue with Solaris? Any ways to work around it? I cannot use a local network connection instead for reasons I cannot go into here.
Code for client and server below. Compile with "cc -fast -m64 -lrt -lsocket -lnsl -o server server.c" and the equivalent for the client. Gcc 3.4.3 as delivered with Solaris 10 gives comparable results. This code has been cut down, for example timeouts have been removed end error handling is minimal.
server.c:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#define DIRECTORY "sub/"
#define FULL_PATH "sub/c_socket"
#define MAX_COMMAND_LEN 8192
#define PERMISSIONS 0700
void on_error(int err, char * msg) { // simple convenient error handler
if (err == -1) { // Tests whether 'err' is -1 and
perror(msg); // prints error and msg if so.
exit(-1);
}
}
int main() {
struct sockaddr_un addr;
int srv_fd, inst_fd;
int inst_adr_size;
char c;
int ret;
char readbuf[MAX_COMMAND_LEN];
int num_read;
fd_set rfds;
int fail;
int i;
// make address
memset(&addr, 0, sizeof(addr)); // clear out addr
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, FULL_PATH, sizeof(addr.sun_path));
// Remove old pseudo file if present
ret = unlink(FULL_PATH);
if (ret == -1 && errno != ENOENT) {
on_error(ret,"\nRemoving old socket file\n");
}
// Remove old directory if present
ret = rmdir(DIRECTORY);
if (ret == -1 && errno != ENOENT) {
on_error(ret, "\nRemoving old socket directory\n");
}
// Re-create new directory with appropriate permissonsm
ret = mkdir(DIRECTORY, PERMISSIONS);
on_error(ret,"\nCreating directoroy for socket file\n");
// create server listening socket
srv_fd = socket(AF_UNIX, SOCK_STREAM, 0);
on_error(srv_fd, "\nSocket creation:\n");
// bind server listening socket to address
ret = bind(srv_fd, (struct sockaddr *) &addr, sizeof(addr));
on_error(ret, "\nSocket binding:\n");
// set file permissions for socket file (somewhat redundant)
ret = chmod(FULL_PATH, PERMISSIONS);
on_error(ret, "\nSetting socket file permissions\n");
// set socket listening and queue length
ret = listen(srv_fd, 10);
on_error(ret, "\nSet socket to listen:\n");
while(1) {
// accept requests
inst_fd = accept(srv_fd, NULL, NULL);
on_error(inst_fd, "\n accepting connection:\n");
// prepare to use select on inst_fd
FD_ZERO(&rfds);
FD_SET(inst_fd, &rfds);
// now interact with the client on the instance socket.
while(1) {
num_read = 0;
while (1) {
// read a line terminated by '\n'
ret = select(inst_fd + 1, &rfds, NULL, NULL, NULL);
on_error(ret, "\nSelect on socket\n");
if (ret == 1) {
// we can read something
ret = recv(inst_fd, readbuf+num_read, MAX_COMMAND_LEN-num_read, 0
on_error(ret, "\nrecv:\n");
if (ret == 0) {
break; // we have EOF
}
num_read += ret;
if (readbuf[num_read - 1] == '\n') {
break;
}
}
} /* reading one input line done */
if (num_read == 0) break; // EOF propagated
// process command: Just send 2 chars back
ret = send(inst_fd, "n\n", 2, 0);
}
close(inst_fd); // clean up
}
// runs forever...
}
client.c:
#include <stdio.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define RCVBUFSIZE 8192 /* Size of receive buffer */
#define FULL_PATH "sub/c_socket"
#define CYCLES 100000
void on_error(int err, char * msg) { // more convenient error output
if (err == -1) { // Tests whether 'err' is -1 and
perror(msg); // prints error and msg if so.
exit(-1);
}
}
int main(int argc, char *argv[]) {
int client_fd;
struct sockaddr_un addr;
char readbuf[RCVBUFSIZE+1];
int num_read;
int ret;
int count;
fd_set rfds;
char * msg = "N\n";
// make address
memset(&addr, 0, sizeof(addr)); // clear out addr
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, FULL_PATH, sizeof(addr.sun_path));
for(count = 0; count < CYCLES; count++) {
// create socket
client_fd = socket(PF_UNIX, SOCK_STREAM, 0);
on_error(client_fd, "socket() failed");
// prepare to use select on inst_fd
FD_ZERO(&rfds);
FD_SET(client_fd, &rfds);
// connect
ret = connect(client_fd, (struct sockaddr *) &addr, sizeof(addr));
on_error(ret, "connect() failed");
// send msg to server
ret = send(client_fd, msg, 2, 0);
if (ret != 2) {
on_error(-1, "\nnot all bytes sent\n");
}
num_read = 0;
// read until we have a '\n'
while (1) {
ret = select(client_fd + 1, &rfds, NULL, NULL, NULL);
on_error(ret, "\nSelect on socket\n");
if (ret == 1) {
// we can read something
ret = recv(client_fd, readbuf + num_read, RCVBUFSIZE - num_read, 0)
on_error(ret, "\nrecv:\n");
num_read += ret;
if (readbuf[num_read - 1] == '\n') break;
}
}
if (num_read == 0) break;
close(client_fd);
}
return(0);
}
I had a similar issue when I was studying sockets and tried writing an ftp server: because of a bug in the conversion to ascii I ended up writing files one byte at a time, but on linux it was ok, while on windows I ended up with something like 100KB/s on the loop interface... if that is the case, increasing the number of bytes should lessen the difference a lot.
It seems that under linux the act of requesting a system call is simply faster.
PS
I don't know much about the internals of an operating system, so if anyone can share some pointers to understand the issue (like http://yarchive.net/comp/linux/linux_speed.html) I'd be grateful.
I'm trying to write a web server but with the code I have I'm getting 'open failed'. The html document (ht.html) which is supposed to be opened in the browser.
The code I have is:
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define SERVER_PORT 12345
#define BUF_SIZE 4096
#define QUEUE_SIZE 10
int main(int argc, char *argv[])
{
int s, b, l, fd, sa, bytes, on = 1;
char buf[BUF_SIZE]; //buffer for outgoing file
struct hostent *h; //info about server
struct sockaddr_in channel; //holds IP address
//Build address structure to bind to socket
memset(&channel, 0, sizeof(channel)); //zero channel
channel.sin_family = AF_INET;
channel.sin_addr.s_addr = htonl(INADDR_ANY);
channel.sin_port = htons(SERVER_PORT);
//Passive open. Wait for connection
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); /* create socket */
if (s < 0) fatal("socket failed");
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));
b = bind(s, (struct sockaddr *) &channel, sizeof(channel));
if (b < 0) fatal("bind failed");
l = listen(s, QUEUE_SIZE); /* specify queue size */
if (l < 0) fatal("listen failed");
/* Socket is now set up and bound. Wait for connection and process it. */
while(1) {
sa = accept(s, 0, 0); /* block for connection request */
if (sa < 0) fatal("accept failed");
read(sa, buf, BUF_SIZE); /* read file name from socket */
/* Get and return the file. */
fd = open(buf, O_RDONLY); /* open the file to be sent back */
if (fd < 0) fatal("open failed");
while(1){
bytes = read(fd, buf, BUF_SIZE); /* read from file */
if (bytes <= 0) break; /* check for end of file */
write(sa, buf, bytes); /*write bytes to socket*/
}
close(fd); //close file
close(sa); //close connection
}
}
fatal(char*string)
{
printf("%s", string);
exit(1);
}
Where's my error? Or what has to be added?
Maybe you can start with outputting the data received from the socket, or at least run in a debugger, otherwise everything will be run in the dark, without you knowing what is going on. In the code below I have added a printf to print what we get from the web browser.
Also like others have pointed out, it is good to know what errno is trying to tell us. It is a bit awkward/annoying to use perror + exit, so in Linux and BSD you can use err(3) and warn(3). err will print errno message and then exit, while warn will just print errno message and not exit, I replaced your fatal function with these.
The web browser most likely will send GET /ht.html HTTP/1.1\r\n and this is what you are attempting to open. In order to open the file we need to extract the ht.html part. I have updated your code below and now strchr(3) and strstr(3) are used to extract ht.html.
We also need to send a HTTP response code and tell the web browser we want to send HTML, that is why the HTTP/1.1 200 OK is sent. Remember all HTTP headers need to be separated by \r\n (carriage return - newline). You will find more info about the HTTP protocol in RFC 2616.
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#define SERVER_PORT 12345
#define BUF_SIZE 4096
#define QUEUE_SIZE 10
int main(int argc, char *argv[])
{
int s, b, l, fd, sa, bytes, on = 1;
char buf[BUF_SIZE]; /* buffer for outgoing file */
char *p, *endp, *cp;
struct sockaddr_in channel; /* holds IP address */
/* Build address structure to bind to socket */
memset(&channel, 0, sizeof(channel)); /* zero channel */
channel.sin_family = AF_INET; /* ipv4 */
channel.sin_addr.s_addr = htonl(INADDR_ANY); /* 0.0.0.0 */
channel.sin_port = htons(SERVER_PORT);
/* Passive open. Wait for connection */
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); /* create socket */
if (s < 0) err(1, "socket failed");
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
b = bind(s, (struct sockaddr *) &channel, sizeof(channel));
if (b < 0) err(1, "bind failed");
l = listen(s, QUEUE_SIZE); /* specify queue size */
if (l < 0) err(1, "listen failed");
/* Socket is now set up and bound. Wait for connection and process it. */
while(1) {
sa = accept(s, NULL, NULL); /* block for connection request */
if(sa < 0) {
warn("accept failed");
continue;
}
bytes = 0;
endp = buf + sizeof(buf); /* pointer to end of buf */
cp = NULL;
buf[0] = '\0';
/* read first line from socket */
/* should be "GET /[file] HTTP/1.1" */
for(p = buf; (bytes = read(sa, p, endp - p)) > 0; p += bytes) {
p[bytes] = '\0'; /* read(2) doesn't NUL terminate buf */
if((cp = strchr(p, '\r')) != NULL) /* break at first carriage return */
break;
}
printf("incoming request %lu bytes:\n%s\n", strlen(buf), buf);
/* no carrige return or no "GET /" was found */
if(cp == NULL || strstr(buf, "GET /") != buf) {
warnx("incomplete request");
close(sa);
continue;
}
*cp = '\0'; /* replace '\r' with '\0' */
p = buf + sizeof("GET /") - 1; /* point to after "GET /" */
cp = strchr(p, ' '); /* find " HTTP/1.1" */
if(cp == NULL) {
warnx("HTTP version was not found");
close(sa);
continue;
}
*cp = '\0'; /* replace ' ' with '\0' */
/* Get and return the file. */
fd = open(p, O_RDONLY); /* open the file to be sent back */
if(fd < 0) {
warn("open failed: %s", p);
close(fd);
close(sa);
continue;
}
/* Send HTTP header */
/* Should probably also send Content-Length: <sizeof file>, */
/* this can be checked using fstat(2) */
write(sa, "HTTP/1.1 200 OK\r\n" \
"Content-Type: text/html;charset=UTF-8\r\n\r\n", 58);
while(1) {
bytes = read(fd, buf, sizeof(buf)); /* read from file */
if (bytes <= 0) break; /* check for end of file */
write(sa, buf, bytes); /*write bytes to socket*/
}
close(fd); /* close file */
close(sa); /* close connection */
}
return 0;
}
To connect from your web browser to your HTTP server go to: http://127.0.0.1:12345/ht.html.
Try to add some debug messages or run with a debugger.
I think that the problem relies in the buffer passed to open statement. It looks like buf is not initialized with zeroes and also not NULL terminated by "read".
n = read(sa, buf, BUF_SIZE);
buf[n] = '\0';
In general, when working with read, it should be called in a loop until 0 or -1 returned. It might fill only a fraction of the buffer.
Try to look ERRNO.
if (fd < 0) perror("open failed");
Try to look buf.
if (fd < 0){
printf("%s\n", buf);
perror("open failed");
}
Try to look buf this way:
if (fd < 0){
for(i=0;i<strlen(buf);i++)
printf("%d", buf[i]);
perror("open failed");
}
This will be enough to understand the error because your application simply does not open the file.
The stuff you are reading from the browser is a HTTP request.
You will need to decode this - so read the spec for HTTP.
Example of HTTP requests can be found here