TCP 和 UDP 共用同一個 Port?
這幾天在做網路實驗,利用 iperf3 來觀測流量的時候,發現一個神奇的地方,那就是 iperf3 在設定 server 端的時候,不用指定是 tcp 還是 udp,這跟 iperf 第2版以前是不一樣的。
iperf2:
SYNOPSIS
iperf -s [ options ]
iperf -u -s [ options ]
GENERAL OPTIONS
-u, --udp
use UDP rather than TCP
iperf3:
SYNOPSIS
iperf3 -s [ options ]
iperf3 -c server [ options ]
CLIENT SPECIFIC OPTIONS
-u, --udp
use UDP rather than TCP
這代表 TCP 和 UDP 的 Socket 是可以同時使用同一個 Port 的。其實我一直以為是不行的(這就叫作孤陋寡聞 ...)。為了實驗起見,我自己寫了一個小小的 Echo Server,在指定的 Port 上同時監聽 TCP 和 UDP,來看看是否能夠運作(Client的程式就不附在上面了)。
結論是 TCP 和 UDP 可以同時在一個 Port 上開 Socket。 我沒有去看到底 Kernel 是怎麼實作的,不過猜測 IP_Proto 也被列在 Socket 的 Tuple 判斷之內。有同事提醒我可以看看 /etc/services,也可以觀察到這個現象。好了,所以又學到一件事情了。
iperf2:
SYNOPSIS
iperf -s [ options ]
iperf -u -s [ options ]
GENERAL OPTIONS
-u, --udp
use UDP rather than TCP
iperf3:
SYNOPSIS
iperf3 -s [ options ]
iperf3 -c server [ options ]
CLIENT SPECIFIC OPTIONS
-u, --udp
use UDP rather than TCP
這代表 TCP 和 UDP 的 Socket 是可以同時使用同一個 Port 的。其實我一直以為是不行的(這就叫作孤陋寡聞 ...)。為了實驗起見,我自己寫了一個小小的 Echo Server,在指定的 Port 上同時監聽 TCP 和 UDP,來看看是否能夠運作(Client的程式就不附在上面了)。
#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() #include <sys/epoll.h> #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 CreateUDPServerSocket( unsigned short ); int AcceptTCPConnection( int ); int HandleTCPClient( int ); int HandleUDPClient( int ); int main( int argc, char *argv[] ) { int tcpServSock; // Socket descriptors for TCP server int udpServSock; // Socket descriptors for UDP server long timeout; // Timeout value given on command-line int cliSock[MAXCLIENT]; // Client Socket Set int running = 1; // 1 if server should be running; 0 otherwise unsigned short portNo; // Actual port number int i, j; // For loop use int epfd; // EPOLL File Descriptor. struct epoll_event ev; // Used for EPOLL. struct epoll_event events[120]; // Used for EPOLL. int noEvents; // EPOLL event number. // Test for correct number of arguments if ( argc != 3 ) { fprintf( stderr, "Usage: %s <Timeout (secs.)> <Port> \n", argv[0] ); fprintf( stderr, "Description: \n\t" ); fprintf( stderr, "This is an echo server program which runs both TCP and UDP\n\t" ); fprintf( stderr, "on the same port.\n" ); exit(1); } timeout = atol( argv[1] ); // First arg: Timeout portNo = atoi( argv[2] ); // Create epoll file descriptor. // MAXCLIENT + 3 = MAXCLIENT + TCP + UDP +STD_IN. epfd = epoll_create( MAXCLIENT + 3 ); // Create port socket tcpServSock = CreateTCPServerSocket( portNo ); udpServSock = CreateUDPServerSocket( portNo ); // Add to the epoll ev.data.fd = tcpServSock; ev.events = EPOLLIN | EPOLLOUT | EPOLLET; epoll_ctl( epfd, EPOLL_CTL_ADD, tcpServSock, &ev ); ev.data.fd = udpServSock; ev.events = EPOLLIN | EPOLLOUT | EPOLLET; epoll_ctl( epfd, EPOLL_CTL_ADD, udpServSock, &ev ); // Add STDIN into the EPOLL set. ev.data.fd = STDIN_FILENO; ev.events = EPOLLIN | EPOLLET; epoll_ctl( epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev ); // Initialize the client socket pool for( i = 0 ; i < MAXCLIENT ; i++ ) { cliSock[i] = -1; } printf( "Starting server: Hit return to shutdown\n" ); while ( running ) { // Wait for events. // int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); // Specifying a timeout of -1 makes epoll_wait() wait indefinitely. noEvents = epoll_wait( epfd, events, FD_SETSIZE , ( timeout * 1000 ) ); if ( noEvents <= 0 ) { printf("No echo requests for %ld secs...Server still alive\n", timeout); continue; } for( i = 0 ; i < noEvents; i++ ) { if( events[i].events & EPOLLIN && STDIN_FILENO == events[i].data.fd ) { printf("Shutting down server\n"); getchar(); running = 0; continue; } else if( ( events[i].events & EPOLLIN ) && ( events[i].data.fd == tcpServSock ) ) { for( i = 0 ; i < MAXCLIENT ; i++ ) { if( cliSock[i] < 0 ) { cliSock[i] = AcceptTCPConnection( tcpServSock ); // Add the client socket to the epoll fdset. ev.data.fd = cliSock[i]; ev.events = EPOLLIN | EPOLLET; epoll_ctl( epfd, EPOLL_CTL_ADD, cliSock[i], &ev ); i = MAXCLIENT; } } } else if( ( events[i].events & EPOLLIN ) && ( events[i].data.fd == udpServSock ) ) { HandleUDPClient( events[i].data.fd ); } else if ( events[i].events & EPOLLIN ) { if( HandleTCPClient( events[i].data.fd ) == 0 ) { printf( "Connection %d Shudown.\n", events[i].data.fd ); // We do not need to del fd from the epfd set. // Close the fd will remove it from epfd automatically. close( events[i].data.fd ); for( j = 0 ; j < MAXCLIENT; j++ ) { if( cliSock[j] == events[i].data.fd ) { cliSock[j] = -1; j = MAXCLIENT; } } } } } } // Close sockets close( tcpServSock ); close( udpServSock ); for ( i = 0; i < MAXCLIENT; i++ ) { if( cliSock[i] > 0 ) { close( cliSock[i] ); } } // Free list of sockets close( epfd ); 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 CreateUDPServerSocket(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_DGRAM, IPPROTO_UDP ) ) < 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); } 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 (%d)\n", inet_ntoa( echoClntAddr.sin_addr ), ntohs( echoClntAddr.sin_port ), 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 { printf( "Recv(%d): %s\n", recvMsgSize, echoBuffer ); // Echo message back to client if ( send( clntSocket, echoBuffer, recvMsgSize, 0) != recvMsgSize ) { perror( "send() failed" ); exit(1); } } return recvMsgSize; } int HandleUDPClient( int clntSocket ) { char echoBuffer[RCVBUFSIZE]; // Buffer for echo string int recvMsgSize; // Size of received message int len = sizeof( struct sockaddr_in ); struct sockaddr_in echoClntAddr; bzero( echoBuffer, RCVBUFSIZE ); // Receive message from client if ( ( recvMsgSize = recvfrom( clntSocket, echoBuffer, RCVBUFSIZE, 0, ( struct sockaddr * )&echoClntAddr, &len ) ) < 0 ) { perror("recv() failed"); exit(1); } // print out the address of the sender printf( "Got a UDP packet from %s port %d\n", inet_ntoa( echoClntAddr.sin_addr ), ntohs( echoClntAddr.sin_port ) ); // Send received string and receive again until end of transmission if ( recvMsgSize > 0 ) // zero indicates end of transmission { printf( "Recv(%d): %s\n", recvMsgSize, echoBuffer ); // Echo message back to client if ( sendto( clntSocket, echoBuffer, recvMsgSize, 0, ( struct sockaddr * )&echoClntAddr, sizeof( echoClntAddr ) ) != recvMsgSize ) { perror( "send() failed" ); exit(1); } } return recvMsgSize; }
結論是 TCP 和 UDP 可以同時在一個 Port 上開 Socket。 我沒有去看到底 Kernel 是怎麼實作的,不過猜測 IP_Proto 也被列在 Socket 的 Tuple 判斷之內。有同事提醒我可以看看 /etc/services,也可以觀察到這個現象。好了,所以又學到一件事情了。
留言
張貼留言