Linux TCP Echo Server/Client Example Code

本篇主要在紀錄 TCP/IP 的程式範例。為什麼要紀錄呢?因為年紀大了,很多程式都想撿現成的來改就好了。要用的時候還要找就太麻煩了,所以乾脆找一個自己想要的版本紀錄起來,以後要用就有了。這次的紀錄重點,包含了:
  1. Linux Socket Programming.
  2. Select Usage.
範例程式主要來自下面這本書:

TCP/IP Sockets in C: Practical Guide for Programmers, Second Edition (ISBN: 978-0-12-374540-8) by Kenneth L. Calvert and Michael J. Donahoo

其實我根本沒買書啦,反正網路上有範例程式可以下載,我就直接拿來改囉。

下面就是改寫後的程式碼。改寫的部份主要是風格的問題,以及不想讓 client 傳完一個句子就關閉 socket。

Server:

#include <stdio.h>          // for printf() and fprintf() 
#include <sys/socket.h>     // for socket(), bind(), and connect() 
#include <arpa/inet.h>      // for sockaddr_in and inet_ntoa() 
#include <stdlib.h>         // for atoi() and exit() 
#include <string.h>         // for memset() 
#include <unistd.h>         // for close() 
#include <sys/time.h>       // for struct timeval {} 
#include <fcntl.h>          // for fcntl() 

#define MAXPENDING 5        // Maximum outstanding connection requests
#define MAXCLIENT  100      // Maximum client connections
#define RCVBUFSIZE 1024     // Size of receive buffer 

int     CreateTCPServerSocket( unsigned short );
int     AcceptTCPConnection( int );
int     HandleTCPClient( int );

int main( int argc, char *argv[] )
{
    int             *servSock;        // Socket descriptors for server 
    int             maxDescriptor;    // Maximum socket descriptor value 
    fd_set          sockSet;          // Set of socket descriptors for select() 
    long            timeout;          // Timeout value given on command-line 
    struct timeval  selTimeout;       // Timeout for select() 
    
    int cliSock[MAXCLIENT];           // Client Socket Set
    
    int running = 1;                 // 1 if server should be running; 0 otherwise 
    
    int noPorts;                     // Number of port specified on command-line 
    int port;                        // Looping variable for ports 
    unsigned short portNo;           // Actual port number 
    
    int i;                           // For loop use
    
    // Test for correct number of arguments
    
    if ( argc < 3 )     
    {
        fprintf( stderr, "Usage:  %s <Timeout (secs.)> <Port 1> ...\n", argv[0]);
        exit(1);
    }

    timeout = atol(argv[1]);        // First arg: Timeout 
    noPorts = argc - 2;             // Number of ports is argument count minus 2 

    // Allocate list of sockets for incoming connections 
    servSock = (int *) malloc( noPorts * sizeof(int) );    
  
    // Create list of ports and sockets to handle ports 
    for ( port = 0; port < noPorts; port++ )
    {
        // Add port to port list, skip first two arguments 
        portNo = atoi( argv[port + 2] ); 

        // Create port socket 
        servSock[port] = CreateTCPServerSocket( portNo );        
    }
    
    // Initialize the client socket pool
    for( i = 0 ; i < MAXCLIENT ; i++ )
    {
        cliSock[i] = -1;
    }

    printf( "Starting server:  Hit return to shutdown\n" );
    while ( running )
    {
        // Zero socket descriptor vector and set for server sockets 
        // This must be reset every time select() is called 
        FD_ZERO( &sockSet );
        
        // Add keyboard to descriptor vector 
        FD_SET( STDIN_FILENO, &sockSet );
        
        // Initialize maxDescriptor for use by select() 
        maxDescriptor = -1;
        
        // Add server sockets to descriptor vector 
        for ( port = 0; port < noPorts; port++ )
        {
            FD_SET( servSock[port], &sockSet );
            
            // Determine if new descriptor is the largest 
            if ( servSock[port] > maxDescriptor ) 
            {
                maxDescriptor = servSock[port];
            }
        }
        
        // Add client sockets to descriptor vector 
        for ( i = 0; i < MAXCLIENT; i++ )
        {
            if ( cliSock[i] > 0 ) 
            {
                FD_SET( cliSock[i], &sockSet );
            }
            
            if ( cliSock[i] > maxDescriptor ) 
            {
                maxDescriptor = cliSock[i];
            }
        }
        
        // Timeout specification 
        // This must be reset every time select() is called 
        selTimeout.tv_sec = timeout;       // timeout (secs.) 
        selTimeout.tv_usec = 0;            // 0 microseconds 

        // Suspend program until descriptor is ready or timeout 
        if ( select( maxDescriptor + 1, &sockSet, NULL, NULL, &selTimeout ) == 0 )
        {
            printf("No echo requests for %ld secs...Server still alive\n", timeout);
        }
        else 
        {
            // Check keyboard 
            if (FD_ISSET(0, &sockSet)) 
            {
                printf("Shutting down server\n");
                getchar();
                running = 0;
                continue;
            }
            
            // Check Listening Sockets
            for ( port = 0; port < noPorts; port++ )
            {
                if (FD_ISSET( servSock[port], &sockSet ) )
                {
                    printf("Request on port %d:  ", port);
                    for( i = 0 ; i < MAXCLIENT ; i++ )
                    {
                        if( cliSock[i] < 0 ) 
                        {
                            cliSock[i] = AcceptTCPConnection( servSock[port] );
                            i = MAXCLIENT;
                        }
                    }
                }
            }
            
            // Check Client Sockets
            for ( i = 0 ; i < MAXCLIENT ; i++ )
            {
                if ( FD_ISSET( cliSock[i], &sockSet ) )
                {
                    if( HandleTCPClient( cliSock[i] ) == 0 )
                    {
                        printf( "Connection %d Shudown.\n", cliSock[i] );
                        close( cliSock[i] );
                        cliSock[i] = -1;
                    }
                }
            }
        }
    }

    // Close sockets 
    for ( port = 0; port < noPorts; port++ )
    {
        close( servSock[port] );
    }
    
    for ( i = 0; i < MAXCLIENT; i++ )
    {
        if( cliSock[i] > 0 )
        {
            close( cliSock[i] );
        }
    }
    
    // Free list of sockets 
    free( servSock );

    return 0;
}

int CreateTCPServerSocket(unsigned short port)
{
    int sock;                        // socket to create 
    struct sockaddr_in echoServAddr; // Local address 

    // Create socket for incoming connections 
    if ( ( sock = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP ) ) < 0 )
    {
        perror( "socket() failed" );
        exit(1);
    }
      
    // Construct local address structure 
    memset( &echoServAddr, 0, sizeof( echoServAddr ) );     // Zero out structure 
    echoServAddr.sin_family = AF_INET;                      // Internet address family 
    echoServAddr.sin_addr.s_addr = htonl( INADDR_ANY );     // Any incoming interface 
    echoServAddr.sin_port = htons( port );                  // Local port 

    // Bind to the local address 
    if ( bind(sock, (struct sockaddr *) &echoServAddr, sizeof( echoServAddr ) ) < 0 )
    {
        perror( "bind() failed" );
        exit(1);
    }
    
    // Mark the socket so it will listen for incoming connections 
    if ( listen( sock, MAXPENDING ) < 0 )
    {
        perror( "listen() failed" );
        exit(1);
    }
    
    return sock;
}

int AcceptTCPConnection( int servSock )
{
    int                 clntSock;     // Socket descriptor for client 
    struct sockaddr_in  echoClntAddr; // Client address 
    unsigned int        clntLen;      // Length of client address data structure 

    // Set the size of the in-out parameter 
    clntLen = sizeof( echoClntAddr );
    
    // Wait for a client to connect 
    if ( ( clntSock = accept( servSock, (struct sockaddr *) &echoClntAddr, &clntLen ) ) < 0 )
    {
        perror("accept() failed");
        exit(1);
    }
    
    // clntSock is connected to a client! 
    
    printf("Handling client %s(%d)\n", inet_ntoa( echoClntAddr.sin_addr ), clntSock );

    return clntSock;
}

int HandleTCPClient( int clntSocket )
{
    char    echoBuffer[RCVBUFSIZE];        // Buffer for echo string 
    int     recvMsgSize;                   // Size of received message 
    
    bzero( echoBuffer, RCVBUFSIZE );
    // Receive message from client 
    if ( ( recvMsgSize = recv( clntSocket, echoBuffer, RCVBUFSIZE, 0 ) ) < 0 )
    {
        perror("recv() failed");
        exit(1);
    }

    // Send received string and receive again until end of transmission 
    if ( recvMsgSize > 0 )      // zero indicates end of transmission 
    {
        // Echo message back to client 
        if ( send( clntSocket, echoBuffer, recvMsgSize, 0) != recvMsgSize )
        {
            perror( "send() failed" );
            exit(1);
        }
    }
    
    return recvMsgSize;
}


Client:

#include <stdio.h>      // for printf() and fprintf() 
#include <sys/socket.h> // for socket(), connect(), send(), and recv() 
#include <arpa/inet.h>  // for sockaddr_in and inet_addr() 
#include <stdlib.h>     // for atoi() and exit() 
#include <string.h>     // for memset() 
#include <unistd.h>     // for close() 

#define STRBUFSIZE 1024 // Size of receive buffer 

void DieWithError(char *errorMessage);  // Error handling function 

int main(int argc, char *argv[])
{
    int sock;                        // Socket descriptor 
    struct sockaddr_in echoServAddr; // Echo server address 
    unsigned short echoServPort;     // Echo server port 
    char *servIP;                    // Server IP address (dotted quad)
    char echoString[STRBUFSIZE];     // Buffer for echo string  
    char echoBuffer[STRBUFSIZE];     // Buffer for echo string 
    unsigned int echoStringLen;      // Length of string to echo 
    int bytesRcvd, totalBytesRcvd;   // Bytes read in single recv() and total bytes read 
    int running = 1;                 // Check the loop state

    if ( ( argc < 2 ) || (argc > 3 ) )    // Test for correct number of arguments 
    {
       fprintf( stderr, "Usage: %s <Server IP> [<Echo Port>]\n", argv[0] );
       exit(1);
    }

    servIP = argv[1];             // First arg: server IP address (dotted quad) 

    if ( argc == 3 )
    {
        // Use given port, if any 
        echoServPort = atoi(argv[2]); 
    }
    else
    {
        // 7 is the well-known port for the echo service
        echoServPort = 7;  
    } 

    // Create a reliable, stream socket using TCP 
    if ( ( sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP ) ) < 0 )
    {
        perror("socket() failed");
        exit(1);
    }
    
    // Construct the server address structure 
    memset(&echoServAddr, 0, sizeof(echoServAddr));     // Zero out structure 
    echoServAddr.sin_family      = AF_INET;             // Internet address family 
    echoServAddr.sin_addr.s_addr = inet_addr(servIP);   // Server IP address 
    echoServAddr.sin_port        = htons(echoServPort); // Server port 

    // Establish the connection to the echo server 
    if ( connect( sock, (struct sockaddr *) &echoServAddr, sizeof( echoServAddr ) ) < 0 )
    {
        perror( "connect() failed" );
        exit(1);
    }
    
    while ( running )
    {
        bzero( echoString, STRBUFSIZE );
        bzero( echoBuffer, STRBUFSIZE );
        
        printf("Enter(q for exit): \n"); 
        
        fgets( echoString, STRBUFSIZE, stdin ); 
        
        echoStringLen = strlen( echoString );
        
        if ( echoStringLen == 2 && echoString[0] == 'q' && echoString[1] == '\n' )
        {
            running = 0;
            continue;
        }
        
        if ( send(sock, echoString, echoStringLen, 0) != echoStringLen )
        {
            perror( "send() sent a different number of bytes than expected");
            exit(1);
        }
        
        printf("Received"); 
        
        if ( ( bytesRcvd = recv( sock, echoBuffer, STRBUFSIZE - 1, 0) ) <= 0 )
        {
            perror("recv() failed or connection closed prematurely");
            exit(1);
        }
        printf( "(%d): ", bytesRcvd );
        printf( "%s", echoBuffer);      // Print the echo buffer 
        
    }
    
    printf("\n");    // Print a final linefeed 

    close(sock);
    return 0;
}

留言

這個網誌中的熱門文章

我弟家的新居感恩禮拜分享:善頌善禱

如何將Linux打造成OpenFlow Switch:Openvswitch

Openssl 範例程式:建立SSL連線