COMS 3157 Advanced Programming

Index of 2024-1/code/17

Parent directory
tcp-echo-client.c
tcp-echo-server.c
tcp-recver.c
tcp-sender.c
toupper.c

tcp-echo-client.c

/*
 * tcp-echo-client.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

static void die(const char *s) { perror(s); exit(1); }

int main(int argc, char **argv)
{
    if (argc != 3) {
        fprintf(stderr, "usage: %s <server-ip> <server-port>\n",
                argv[0]);
        exit(1);
    }

    const char *ip = argv[1];
    unsigned short port = atoi(argv[2]);

    // Create a socket for TCP connection

    int sock; // socket descriptor
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        die("socket failed");

    // Construct a server address structure

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr)); // must zero out the structure
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(ip);
    servaddr.sin_port        = htons(port); // must be in network byte order

    // Establish a TCP connection to the server

    if (connect(sock, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
        die("connect failed");

    // Read a line from stdin and send it to the server

    char buf[100];
    if (fgets(buf, sizeof(buf), stdin) == NULL) {
        die("fgets returned NULL");
    }
    size_t len = strlen(buf);

    /*
     * send(int socket, const void *buffer, size_t length, int flags)
     *
     *   - normally, send() blocks until it sends all bytes requested
     *   - returns num bytes sent or -1 for error
     *   - send(sock,buf,len,0) is equivalent to write(sock,buf,len)
     */
    if (send(sock, buf, len, 0) != len)
        die("send failed");

    // Receive the responses from the server and print them

    /*
     * recv(int socket, void *buffer, size_t length, int flags)
     *
     *   - normally, recv() blocks until it has received at least 1 byte
     *   - returns num bytes received, 0 if connection closed, -1 if error
     *   - recv(sock,buf,len,0) is equivalent to read(sock,buf,len)
     */
    int r;
    while (len > 0 && (r = recv(sock, buf, sizeof(buf), 0)) > 0) {
        printf("RECEIVED: [");
        fwrite(buf, 1, r, stdout);
        printf("]\n");
        len -= r;
    }
    if (r < 0)
        die("recv failed");

    // Clean-up

    close(sock);
    return 0;
}

tcp-echo-server.c

/*
 * tcp-echo-server.c
 */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

static void die(const char *s) { perror(s); exit(1); }

int main(int argc, char **argv)
{
    if (argc != 2) {
        fprintf(stderr, "usage: %s <server-port>\n", argv[0]);
        exit(1);
    }

    unsigned short port = atoi(argv[1]);

    // Create a listening socket (also called server socket) 

    int servsock;
    if ((servsock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        die("socket failed");

    // Construct local address structure

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // any network interface
    servaddr.sin_port = htons(port);

    // Bind to the local address

    if (bind(servsock, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
        die("bind failed");

    // Start listening for incoming connections

    if (listen(servsock, 5 /* queue size for connection requests */ ) < 0)
        die("listen failed");

    int r;
    char buf[10];

    int clntsock;
    socklen_t clntlen;
    struct sockaddr_in clntaddr;

    while (1) {

        // Accept an incoming connection

        fprintf(stderr, "waiting for client ... ");

        clntlen = sizeof(clntaddr); // initialize the in-out parameter

        if ((clntsock = accept(servsock,
                        (struct sockaddr *) &clntaddr, &clntlen)) < 0)
            die("accept failed");

        // accept() returned a connected socket (also called client socket)
        // and filled in the client's address into clntaddr

        fprintf(stderr, "client ip: %s\n", inet_ntoa(clntaddr.sin_addr));

        // Receive msg from client, capitalize the 1st char, send it back

        while ((r = recv(clntsock, buf, sizeof(buf), 0)) > 0) {
            *buf = toupper(*buf);
            if (send(clntsock, buf, r, 0) != r) {
                fprintf(stderr, "ERR: send failed\n");
                break;
            }
        }
        if (r < 0) {
            fprintf(stderr, "ERR: recv failed\n");
        }

        // Client closed the connection (r==0) or there was an error
        // Either way, close the client socket and go back to accept()

        close(clntsock);
    }
}

tcp-recver.c

/*
 * tcp-recver.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

static void die(const char *s) { perror(s); exit(1); }

int main(int argc, char **argv)
{
    if (argc != 3) {
        fprintf(stderr, "usage: %s <server-port> <filebase>\n", argv[0]);
        exit(1);
    }

    unsigned short port = atoi(argv[1]);
    const char *filebase = argv[2];

    // Create a listening socket (also called server socket) 

    int servsock;
    if ((servsock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        die("socket failed");

    // Construct local address structure

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // any network interface
    servaddr.sin_port = htons(port);

    // Bind to the local address

    if (bind(servsock, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
        die("bind failed");

    // Start listening for incoming connections

    if (listen(servsock, 5 /* queue size for connection requests */ ) < 0)
        die("listen failed");

    int clntsock;
    socklen_t clntlen;
    struct sockaddr_in clntaddr;

    FILE *fp;
    unsigned int filesuffix = 0;

    // Variable-length array (VLA) is used here to show that it's legal in C.
    // But VLA should be avoided in most cases. The VLA declaration below
    // could and should be replaced with heap allocation:
    //
    //     char *filename = malloc(strlen(filebase) + 100);
    //
    char filename[strlen(filebase) + 100];

    int r;
    char buf[4096];
    uint32_t size, size_net, remaining, limit;
    struct stat st;

    while (1) {

        // Accept an incoming connection

        clntlen = sizeof(clntaddr); // initialize the in-out parameter

        if ((clntsock = accept(servsock,
                        (struct sockaddr *) &clntaddr, &clntlen)) < 0)
            die("accept failed");

        // accept() returned a connected socket (also called client socket)
        // and filled in the client's address into clntaddr

        fprintf(stderr, "client ip: %s\n", inet_ntoa(clntaddr.sin_addr));
        sprintf(filename, "%s.%u", filebase, filesuffix++);
        fprintf(stderr, "file name: %s\n", filename);

        if ((fp = fopen(filename, "wb")) == NULL)
            die(filename);

        // First, receive file size

        r = recv(clntsock, &size_net, sizeof(size_net), MSG_WAITALL);
        if (r != sizeof(size_net)) {
            if (r < 0)
                die("recv failed");
            else if (r == 0)
                die("connection closed prematurely");
            else
                die("didn't receive uint32");
        }
        size = ntohl(size_net); // convert it to host byte order
        fprintf(stderr, "file size received: %u\n", size);

        // Second, receive the file content

        remaining = size; 
        while (remaining > 0) {
            limit = remaining > sizeof(buf) ? sizeof(buf) : remaining;
            r = recv(clntsock, buf, limit, 0);
            if (r < 0)
                die("recv failed");
            else if (r == 0)
                die("connection closed prematurely");
            else {
                remaining -= r;
                if (fwrite(buf, 1, r, fp) != r) 
                    die("fwrite failed");
            }
        }
        assert(remaining == 0);
        fclose(fp);

        // Third, send the file size back as acknowledgement

        stat(filename, &st);
        size = st.st_size;
        fprintf(stderr, "file size on disk:  %u\n\n", size);
        size_net = htonl(size);

        if (send(clntsock, &size_net, sizeof(size_net), 0) 
                != sizeof(size_net))
            die("send size failed");

        // Finally, close the client connection and go back to accept()

        close(clntsock);
    }
}

tcp-sender.c

/*
 * tcp-sender.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

static void die(const char *s) { perror(s); exit(1); }

int main(int argc, char **argv)
{
    if (argc != 4) {
        fprintf(stderr, "usage: %s <server-ip> <server-port> <filename>\n",
                argv[0]);
        exit(1);
    }

    const char *ip = argv[1];
    unsigned short port = atoi(argv[2]);
    const char *filename = argv[3];

    // Open the file to send

    FILE *fp = fopen(filename, "rb");
    if (fp == NULL)
        die(filename);

    // Create a socket for TCP connection

    int sock;
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        die("socket failed");

    // Construct a server address structure

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr)); // must zero out the structure
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(ip);
    servaddr.sin_port        = htons(port); // must be in network byte order

    // Establish a TCP connection to the server

    if (connect(sock, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
        die("connect failed");

    // First, send file size as 4-byte unsigned int in network byte order

    struct stat st;
    stat(filename, &st);
    uint32_t size = st.st_size;
    fprintf(stderr, "file size:  %u\n", size);
    uint32_t size_net = htonl(size);

    /*
     * send(int socket, const void *buffer, size_t length, int flags)
     *
     *   - normally, send() blocks until it sends all bytes requested
     *   - returns num bytes sent or -1 for error
     *   - send(sock,buf,len,0) is equivalent to write(sock,buf,len)
     */
    if (send(sock, &size_net, sizeof(size_net), 0) != sizeof(size_net))
        die("send size failed");

    // Second, send the file content

    char buf[4096];
    unsigned int n;
    unsigned int total = 0;

    while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
        if (send(sock, buf, n, 0) != n)
            die("send content failed");
        else
            total += n;
    }
    if (ferror(fp)) {
        // fread() returns 0 on EOF or error, so we check if error occurred
	die("fread failed");
    }
    fclose(fp);
    fprintf(stderr, "bytes sent: %u\n", total);

    // Third, receive file size back from the server as acknowledgement

    uint32_t ack, ack_net;

    /*
     * recv(int socket, void *buffer, size_t length, int flags)
     *
     *   - normally, recv() blocks until it has received at least 1 byte
     *   - returns num bytes received, 0 if connection closed, -1 if error
     *   - recv(sock,buf,len,0) is equivalent to read(sock,buf,len)
     *   
     *   - With TCP sockets, we can receive less data than we requested;
     *     MSG_WAITALL flag changes this behavior -- it requests that the 
     *     operation block until the full request is satisfied.
     */
    int r = recv(sock, &ack_net, sizeof(ack_net), MSG_WAITALL);
    if (r != sizeof(ack_net)) {
        if (r < 0)
            die("recv failed");
        else if (r == 0)
            die("connection closed prematurely");
        else
            die("didn't receive uint32");
    }
    ack = ntohl(ack_net); // convert it back to host byte order
    if (ack != size)
        die("ack!=size");

    // recv() will return 0 when the server closes the TCP connection
    char x;
    r = recv(sock, &x, 1, 0);
    assert(r == 0);

    // Clean-up

    close(sock);
    return 0;
}

toupper.c

#include <ctype.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
    char c;

#ifdef STDIO
    FILE *fp_in  = stdin;
    FILE *fp_out = stdout;

    while (fread(&c, 1, 1, fp_in) == 1) {
        c = toupper(c);
        fwrite(&c, 1, 1, fp_out);
    }
#endif

#ifdef UNIX
    int fd_in  = 0;
    int fd_out = 1;

    while (read(fd_in, &c, 1) == 1) {
        c = toupper(c);
        write(fd_out, &c, 1);
    }
#endif
}