問題描述
使用 C 的 select() 系統調用為 UDP 應用程序創建多個客戶端和一個服務器 (Creating Multiple Client and One Server for UDP application using select() system call with C)
幫助我更正這個程序是這個代碼在不同的會話中為多個客戶端運行,這意味著在為一個客戶端運行後它會停止,當我再次啟動服務器時,它也可以為另一個客戶端提供服務。我希望我的代碼只在一個會話中無限期地工作,並為盡可能多的客戶提供服務。
Server Code:‑
'''
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define port1 8080
#define MAXN 1024
#define TRUE 1
int main(){
int sockfd,sockfd1;
struct sockaddr_in servaddr,cliaddr;
char buffer[MAXN];
char buff[MAXN];
int max_clients=2,valread,new_socket;
char *hello = "Hello Client";
char *message = "hiiii Server";
struct timeval timeout;
timeout.tv_sec = 20;
timeout.tv_usec = 0;
//create socket 2
sockfd = socket(AF_INET, SOCK_DGRAM,0);
if(sockfd<0){
perror("Error Creating Socket0");
exit(1);
}
//memset(&servaddr, 0, sizeof(servaddr));
//memset(&cliaddr, 0, sizeof(cliaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port1);
servaddr.sin_addr.s_addr = INADDR_ANY;
if(bind(sockfd, (const *)&servaddr, sizeof(servaddr))<0){
perror("Error in binding0 ");
exit(1);
}
//Use Select......
//I have created 2 socket having file deescriptor sockfd and sockfd1
int s;
int client_socket[2]={0,0};
fd_set readfds;
while(){
FD_ZERO(&readfds);
FD_SET(sockfd,&readfds);
//Let's say sockfd is max_fd
int max_fd = sockfd,sd,activity;
for(int i=0;i<2;i++){
sd = client_socket[i];
if(sd>0){
FD_SET(sd,&readfds);
}
if(sd>max_fd)
max_fd = sd;
}
activity = select( max_fd + 1 , &readfds , NULL , NULL , &timeout);
if ((activity < 0))
{
printf("select error");
}
int addrlen = sizeof(servaddr);
//If something happened on the master socket ,
//then its an incoming connection
if (FD_ISSET(sockfd, &readfds))
{
//inform user of socket number ‑ used in send and receive commands
printf("New connection , socket fd is %d , ip is : %s , port : %d\n " , new_socket , inet_ntoa(servaddr.sin_addr) , ntohs
(servaddr.sin_port));
//send new connection greeting message
if( send(new_socket, message, strlen(message), 0) != strlen(message) )
{
perror("send");
}
puts("Welcome message sent successfully");
//add new socket to array of sockets
for (int i = 0; i < max_clients; i++)
{
//if position is empty
if( client_socket[i] == 0 )
{
client_socket[i] = new_socket;
printf("Adding to list of sockets as %d\n" , i);
break;
}
}
}
//else its some IO operation on some other socket
for (int i = 0; i < max_clients; i++)
{
sd = client_socket[i];
if (FD_ISSET( sd , &readfds))
{
//Check if it was for closing , and also read the
//incoming message
if ((valread = read( sd , buffer, 1024)) == 0)
{
//Somebody disconnected , get his details and print
getpeername(sd , (struct sockaddr*)&servaddr , \
(socklen_t*)&addrlen);
printf("Host disconnected , ip %s , port %d \n" ,
inet_ntoa(servaddr.sin_addr) , ntohs(servaddr.sin_port));
//Close the socket and mark as 0 in list for reuse
//close( sd );
client_socket[i] = 0;
}
//Echo back the message that came in
else
{
//set the string terminating NULL byte on the end
//of the data read
buffer[valread] = '\0';
send(sd , buffer , strlen(buffer) , 0 );
}
}
}
}
return 0;
}
'''
This is one of the Client Code and this code is running :‑
'''
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT 8080
#define MAXN 1024
// Driver code
int main() {
int sockfd;
char buffer[MAXN];
char *hello = "Hello from Multipleclient";
struct sockaddr_in servaddr;
// Creating socket file descriptor
if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) {
perror("Error in socket creation");
exit(1);
}
memset(&servaddr, 0, sizeof(servaddr));
// Filling server information
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = INADDR_ANY;
int n, len;
sendto(sockfd, (const char *)hello, strlen(hello),
MSG_CONFIRM, (const struct sockaddr *) &servaddr,
sizeof(servaddr));
printf("Hello message sent.\n");
n = recvfrom(sockfd, (char *)buffer, MAXN,
MSG_WAITALL, (struct sockaddr *) &servaddr,
&len);
buffer[n] = '\0';
printf("Server : %s\n", buffer);
close(sockfd);
return 0;
}
參考解法
方法 1:
Some minor things to start with:
In the client code, you do not initialize len
before passing it to recvfrom
. Also, it is of the wrong type (int
instead of socklen_t
). This can be corrected by defining len
as socklen_t len = sizeof(servaddr);
However, since you do not use the length or address anywhere, you can just pass in NULL
for both. Also, UDP does not support MSG_WAITALL
, so pass 0
instead for the flags. recvfrom(sockfd, (char *)buffer, MAXN, 0, NULL, NULL)
.
In the client code, you fill in the server IP address as IPADDR_ANY
, i.e. 0.0.0.0
, which is only valid when calling bind
. You are calling sendto
. When specifying a destination address, you probably want IPADDR_LOOPBACK
for the local computer.
In the server code, the main while()
loop is missing a condition inside the parenthesis, so the code does not even compile. You probably want while(1)
for an infinite loop.
In the server code, in the call to bind
, you are casting to the wrong type. (const *)
means (const int *)
, but you want (const struct sockaddr *)
.
The biggest problem:
Based on the comments and code in the server code, you seem to think that UDP communication has persistent connections, and try to save client_socket
values for these. However, UDP does not have persistent connection. There is no such thing as a client socket in this context. select
does not tell you that a new "connection" is ready. Instead, it tells you that data has been received on your one and only UDP socket (sockfd
). This is entirely unlike TCP, where you accept
a connection that lasts until one side terminates it.
From the comments in the server code, it seems that you intend for clients to have persistent state in the server, at least enough to be recognized when they send more datagrams (otherwise they would always get the welcome message).
A possible way to make this work is the following:
// create and bind a UDP socket as you already do;
// initialize and empty list of clients;
while(1) {
struct sockaddr_in cliaddr;
socklen_t cliaddr_len = sizeof(cliaddr);
recvfrom(sockfd, buffer, bufferlen, /*flags*/ 0, (struct sockaddr *)&cliaddr, &cliaddr_len);
// check whether cliaddr is in your list;
if (!is_in_list) {
// this is a new client
// add to list of clients;
// send welcome message;
}
// do something else, maybe echo back the buffer contents;
}
You will notice that the code does not actually use select
anymore. select
is unnecessary if you have nothing to do while waiting, and you only listen to one socket. If you are doing other stuff not shown in the question, you can add the select
back at the top of the infinite loop, and only handle the UDP socket if sockfd
is set in the readfds
from select
.
Also note that the client cannot actually receive answers from the server, since there is no persistent connection, and the client has not passed any socket to bind
. And also note that UDP messages may get lost without warning, and you should make sure your code can deal with that. It seems that this client‑server‑strategy would be easier to implement using TCP.
As a side note, it is strongly recommended to enable warnings in your compiler. That would have caught the wrong type for the address length variables, as well as told you that new_socket
is used but never set. It is polite to fix all warnings before posting a question, unless of course the question is about the warning.
(by Rahul Kumar Dubey、rtoijala)