Linux TCP Echo Server/Client Example Code
本篇主要在紀錄 TCP/IP 的程式範例。為什麼要紀錄呢?因為年紀大了,很多程式都想撿現成的來改就好了。要用的時候還要找就太麻煩了,所以乾脆找一個自己想要的版本紀錄起來,以後要用就有了。這次的紀錄重點,包含了:
- Linux Socket Programming.
- 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; }
留言
張貼留言