Reputation: 799
I am trying to implement a TCP server and client in C, running on Solaris. I am new to sockets and am using Beej's Guide as an example.
For starters, what I would like is for the client to send a message to the server in the form of word1 word2
. Upon receipt, I want the server to extract word2
from the message and send that back to the client.
The initial client --> server message sending works fine. But the server --> client response is not working. There are several failure symptoms:
send()
anything to the client.accept: Interrupted system call
, then returns to the top of the while(1)
loop and remains there until I Ctrl-C out of it.recv()
returns 0 bytes.I found an old thread here, where the last post says this:
accept is being interrupted by the child process sending a signal back to the parent when it terminates (SIGCHLD, if I remember write). You can either ignore SIGCHLD, or you can code accept() to handle the interrupt better (errno is set to EINTR)
However, I'm not understanding this. Why is the child process terminating before even attempting the send()
portion? What does "handle the interrupt better" mean?
After searching some more, I found this question on Stack Overflow: How to handle EINTR (interrupted System Call).
I tried adding the code in the accepted answer, replacing write()
with send()
, but I still see the same behavior.
Server code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/wait.h>
#include <signal.h>
const char* nodename = "localhost";
const char* FILESERV1_LISTEN_PORT = "41063";
const unsigned int MAXDATASIZE = 100;
void sigchld_handler(int s)
{
// from http://beej.us/guide/bgnet/examples/server.c
while(waitpid(-1, NULL, WNOHANG) > 0);
}
// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
// from http://beej.us/guide/bgnet/examples/server.c
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
int main(void)
{
// from http://beej.us/guide/bgnet/examples/server.c
int rv;
const unsigned int BACKLOG = 3; // how many pending connections queue will hold
int tcp_sockfd, new_tcp_sockfd; // listen on tcp_sockfd, new connection on new_tcp_sockfd
struct addrinfo fs_hints, *fileservinfo, *p_fsinfo;
struct sockaddr_storage client_addr; // connector's address information
socklen_t sin_size;
struct sigaction sa;
char yes='1'; // char for Solaris
char s[INET6_ADDRSTRLEN];
int tcp_numbytes, tcp_numbytes_written, size;
char tcp_recv_buf[MAXDATASIZE];
char *word1, *word2;
memset(&fs_hints, 0, sizeof fs_hints);
fs_hints.ai_family = AF_INET; // force IPv4
fs_hints.ai_socktype = SOCK_STREAM;
//fs_hints.ai_flags = AI_PASSIVE; // use my IP
if ((rv = getaddrinfo(nodename, FILESERV1_LISTEN_PORT, &fs_hints, &fileservinfo)) != 0) {
fprintf(stderr, "file_server1: getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
// loop through all the results and bind to the first we can
for(p_fsinfo = fileservinfo; p_fsinfo != NULL; p_fsinfo = p_fsinfo->ai_next) {
if ((tcp_sockfd = socket(p_fsinfo->ai_family, p_fsinfo->ai_socktype,
p_fsinfo->ai_protocol)) == -1) {
perror("file_server1: socket");
continue;
}
if (setsockopt(tcp_sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
sizeof(int)) == -1) {
perror("file_server1: setsockopt");
exit(1);
}
if (bind(tcp_sockfd, p_fsinfo->ai_addr, p_fsinfo->ai_addrlen) == -1) {
close(tcp_sockfd);
perror("file_server1: bind");
continue;
}
break;
}
if (p_fsinfo == NULL) {
fprintf(stderr, "file_server1: failed to bind\n");
return 2;
}
freeaddrinfo(fileservinfo); // all done with this structure
if (listen(tcp_sockfd, BACKLOG) == -1) {
perror("file_server1: listen");
exit(1);
}
sa.sa_handler = sigchld_handler; // reap all dead processes
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("file_server1: sigaction");
exit(1);
}
printf("DEBUG: file_server1: waiting for connections...\n");
//signal(SIGCHLD, SIG_IGN); /* now I don't have to wait()! */
while(1) { // main accept() loop
printf("DEBUG: Top of while loop\n");
sin_size = sizeof client_addr;
new_tcp_sockfd = accept(tcp_sockfd, (struct sockaddr *)&client_addr, &sin_size);
if (new_tcp_sockfd == -1) {
perror("file_server1: accept");
continue;
}
inet_ntop(client_addr.ss_family, get_in_addr((struct sockaddr *)&client_addr), s, sizeof s);
printf("DEBUG: file_server1: got connection from %s\n", s);
if (!fork()) { // this is the child process
printf("DEBUG: inside if\n");
close(tcp_sockfd); // child doesn't need the listener
if ((tcp_numbytes = recv(new_tcp_sockfd, tcp_recv_buf, MAXDATASIZE-1, 0)) == -1) {
perror("file_server1: recv");
exit(1);
}
tcp_recv_buf[tcp_numbytes] = '\0';
printf("DEBUG: file_server1: received: %s\n", tcp_recv_buf);
sscanf(tcp_recv_buf, "%s %s", word1, word2);
printf("DEBUG: file_server received word1: %s and word2: %s.\n", word1, word2);
size = strlen(word2);
while (size > 0) {
printf("DEBUG: top of inner while size\n");
tcp_numbytes_written = send(new_tcp_sockfd, word2, strlen(word2), 0);
if (tcp_numbytes_written == -1) {
if (errno == EINTR) {
printf("DEBUG: continuing after EINTR\n");
continue;
}
else {
printf("DEBUG: -1 on send(), not EINTR\n");
perror("DEBUG: file_server1: send");
return -1;
}
}
word2 += tcp_numbytes_written;
size -= tcp_numbytes_written;
printf("DEBUG: bottom of inner while size\n");
}
printf("DEBUG: file_server has sent %s to client.\n", word2);
close(new_tcp_sockfd);
//exit(0);
}
printf("DEBUG: outside of if\n");
close(new_tcp_sockfd); // parent doesn't need this
printf("DEBUG: Bottom of while loop\n");
}
return 0;
}
Client code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
const char* nodename = "localhost";
const char* FILESERV1_LISTEN_PORT = "41063";
const unsigned int MAXDATASIZE = 100; // max number of bytes we can get at once
// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
// http://beej.us/guide/bgnet/examples/client.c
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
int main(void)
{
// from http://beej.us/guide/bgnet/examples/client.c
int tcp_sockfd, tcp_numbytes;
char buf[MAXDATASIZE];
struct addrinfo fs_hints, *fileservinfo, *p_fsinfo;
struct sockaddr_storage my_addr; // for storing local dynamic port number
unsigned short int client_tcp_port;
char client_ipaddr_str[INET_ADDRSTRLEN];
socklen_t addrlen;
int rv;
int getsock_check;
char fs_ipaddr_str[INET6_ADDRSTRLEN];
char *msg_to_send = "abcdef hijklm"; // word1 and word2
memset(&fs_hints, 0, sizeof fs_hints);
fs_hints.ai_family = AF_INET; // IPv4
fs_hints.ai_socktype = SOCK_STREAM;
if ((rv = getaddrinfo(nodename, FILESERV1_LISTEN_PORT, &fs_hints, &fileservinfo)) != 0) {
fprintf(stderr, "client1: getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
// loop through all the results and connect to the first we can
for(p_fsinfo = fileservinfo; p_fsinfo != NULL; p_fsinfo = p_fsinfo->ai_next) {
if ((tcp_sockfd = socket(p_fsinfo->ai_family, p_fsinfo->ai_socktype, p_fsinfo->ai_protocol)) == -1) {
perror("client1: socket");
continue;
}
if (connect(tcp_sockfd, p_fsinfo->ai_addr, p_fsinfo->ai_addrlen) == -1) {
close(tcp_sockfd);
perror("client1: connect");
continue;
}
break;
}
printf("DEBUG: client1: socket and connect successful\n");
if (p_fsinfo == NULL) {
fprintf(stderr, "client1: failed to connect\n");
return 2;
}
addrlen = sizeof my_addr;
if ((getsock_check=getsockname(tcp_sockfd, (struct sockaddr *)&my_addr, (socklen_t *)&addrlen)) == -1) {
perror("client1: getsockname");
exit(1);
}
printf("DEBUG: client1: getsockname successful\n");
client_tcp_port = ntohs(((struct sockaddr_in *)&my_addr)->sin_port);
inet_ntop(AF_INET, &(((struct sockaddr_in *)&my_addr)->sin_addr), client_ipaddr_str, INET_ADDRSTRLEN);
printf("DEBUG: client1 has dynamic TCP port number %hu and IP address %s.\n", client_tcp_port, client_ipaddr_str);
inet_ntop(p_fsinfo->ai_family, get_in_addr((struct sockaddr *)p_fsinfo->ai_addr), fs_ipaddr_str, sizeof fs_ipaddr_str);
printf("DEBUG: client1: connecting to %s\n", fs_ipaddr_str);
freeaddrinfo(fileservinfo); // all done with this structure
if (send(tcp_sockfd, msg_to_send, strlen(msg_to_send), 0) == -1) {
perror("client1: send");
exit(1);
}
printf("DEBUG: The request from client1 has been sent to the file_server1\n");
if ((tcp_numbytes = recv(tcp_sockfd, buf, MAXDATASIZE-1, 0)) == -1) {
perror("client1: recv");
exit(1);
}
buf[tcp_numbytes] = '\0';
printf("DEBUG: client1: received %d bytes, content '%s'\n", tcp_numbytes, buf);
close(tcp_sockfd);
return 0;
}
Output from server:
DEBUG: file_server1: waiting for connections...
DEBUG: Top of while loop
DEBUG: file_server1: got connection from 127.0.0.1
DEBUG: outside of if
DEBUG: Bottom of while loop
DEBUG: Top of while loop
DEBUG: inside if
DEBUG: file_server1: received: abcdef hijklm
file_server1: accept: Interrupted system call
DEBUG: Top of while loop
Output from client:
DEBUG: client1: socket and connect successful
DEBUG: client1: getsockname successful
DEBUG: client1 has dynamic TCP port number 51196 and IP address 127.0.0.1.
DEBUG: client1: connecting to 127.0.0.1
DEBUG: The request from client1 has been sent to the file_server1
DEBUG: client1: received 0 bytes, content ''
Lastly, I eventually want this server to handle the same 2-way transaction with a possible second or third client (for now, not conerned with concurrently serving clients). Do I want to keep the sockfd returned by listen()
open in that situation?
Upvotes: 2
Views: 19063
Reputation: 229234
The accept() call "failing" with EINTR is normal. Your child process ended, and the current system call is interrupted. This happens for a few system calls even when you set SA_RESTART
in sigaction flags.
You just continue the loop, trying to accept a new client, so all is ok.
Your child process ends because it crashes.
char *word1, *word2;
...
sscanf(tcp_recv_buf, "%s %s", word1, word2);
Here you're trying to write to word1
and word2
, which are uninitialized pointers.
Upvotes: 6