#c #multithreading #server #client #write
Вопрос:
У меня есть клиентская программа и серверная программа. Может быть несколько серверов и несколько
клиентов, которые могут подключаться к нескольким серверам по своему выбору
В клиентской программе отображается меню
- подключение 4000 // подключается к серверу через порт 4000
- ставка 1000 4000 // отправьте значение ставки 1000 на сервер через порт 4000
Теперь сервер может получать ставки от нескольких подключенных к нему клиентов и до сих пор отслеживает самую высокую
ставку. Всякий раз, когда делается новая ставка, сервер отправляет широковещательную передачу каждому клиенту, подключенному
к нему, по одному, как — write(users[i].sock_fd, msg, size)
. Как мне прослушать это сообщение на стороне клиента ? Здесь есть две вещи
- Клиенту необходимо прослушать сообщение, отправленное сервером.
- Клиент также считывает текст или пункты меню (подключение и ставка) из командной строки пользователя.
Я закодировал часть 2), но запутался, как закодировать 1) в клиенте и одновременно сделать 2) также работающим
Код клиента :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define BUF_SIZE 128
#define MAX_AUCTIONS 5
#ifndef VERBOSE
#define VERBOSE 0
#endif
#define ADD 0
#define SHOW 1
#define BID 2
#define QUIT 3
/* Auction struct - this is different than the struct in the server program
*/
typedef struct auction_data
{
int sock_fd;
char item[BUF_SIZE];
int current_bid;
} auction_data;
auction_data *auction_data_ptr;
/* Displays the command options available for the user.
* The user will type these commands on stdin.
*/
void print_menu()
{
printf("The following operations are available:n");
printf(" shown");
printf(" add <server address> <port number>n");
printf(" bid <item index> <bid value>n");
printf(" quitn");
}
/* Prompt the user for the next command
*/
void print_prompt()
{
printf("Enter new command: ");
fflush(stdout);
}
/* Unpack buf which contains the input entered by the user.
* Return the command that is found as the first word in the line, or -1
* for an invalid command.
* If the command has arguments (add and bid), then copy these values to
* arg1 and arg2.
*/
int parse_command(char *buf, int size, char *arg1, char *arg2)
{
int result = -1;
char *ptr = NULL;
if (strncmp(buf, "show", strlen("show")) == 0)
{
return SHOW;
}
else if (strncmp(buf, "quit", strlen("quit")) == 0)
{
return QUIT;
}
else if (strncmp(buf, "add", strlen("add")) == 0)
{
result = ADD;
}
else if (strncmp(buf, "bid", strlen("bid")) == 0)
{
result = BID;
}
ptr = strtok(buf, " "); // first word in buf
ptr = strtok(NULL, " "); // second word in buf
if (ptr != NULL)
{
strncpy(arg1, ptr, BUF_SIZE);
}
else
{
return -1;
}
ptr = strtok(NULL, " "); // third word in buf
if (ptr != NULL)
{
strncpy(arg2, ptr, BUF_SIZE);
return resu<
}
else
{
return -1;
}
return -1;
}
/* Connect to a server given a hostname and port number.
* Return the socket for this server
*/
int add_server(char *hostname, int port)
{
// Create the socket FD.
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0)
{
perror("client: socket");
exit(1);
}
// Set the IP and port of the server to connect to.
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
struct addrinfo *ai;
/* this call declares memory and populates ailist */
if (getaddrinfo(hostname, NULL, NULL, amp;ai) != 0)
{
close(sock_fd);
return -1;
}
/* we only make use of the first element in the list */
server.sin_addr = ((struct sockaddr_in *)ai->ai_addr)->sin_addr;
// free the memory that was allocated by getaddrinfo for this list
freeaddrinfo(ai);
// Connect to the server.
if (connect(sock_fd, (struct sockaddr *)amp;server, sizeof(server)) == -1)
{
perror("client: connect");
close(sock_fd);
return -1;
}
if (VERBOSE)
{
fprintf(stderr, "nDebug: New server connected on socket %d. Awaiting itemn", sock_fd);
}
return sock_fd;
}
/* ========================= Add helper functions below ========================
* Please add helper functions below to make it easier for the TAs to find the
* work that you have done. Helper functions that you need to complete are also
* given below.
*/
/* Print to standard output information about the auction
*/
void print_auctions(struct auction_data *a, int size)
{
printf("Current Auctions:n");
for (int i = 0; i < size; i )
{
struct auction_data auction_data = a[i];
printf("(%d) %s bid = %dn", i, auction_data.item, auction_data.current_bid);
}
/* TODO Print the auction data for each currently connected
* server. Use the follosing format string:
* "(%d) %s bid = %dn", index, item, current bid
* The array may have some elements where the auction has closed and
* should not be printed.
*/
}
/* Process the input that was sent from the auction server at a[index].
* If it is the first message from the server, then copy the item name
* to the item field. (Note that an item cannot have a space character in it.)
*/
void update_auction(char *buf, int size, struct auction_data *a, int index)
{
// TODO: Complete this function
// fprintf(stderr, "ERROR malformed bid: %s", buf);
// printf("nNew bid for %s [%d] is %d (%d seconds left)n", );
}
int main(void)
{
char name[BUF_SIZE];
int size = 0;
// Declare and initialize necessary variables
// TODO
// Get the user to provide a name.
printf("Please enter a username: ");
fflush(stdout);
int num_read = read(STDIN_FILENO, name, BUF_SIZE);
printf("%s-namen", name);
if (num_read <= 0)
{
fprintf(stderr, "ERROR: read from stdin failedn");
exit(1);
}
print_menu();
// TODO
char server_reply[2000];
while (1)
{
print_prompt();
char *command;
scanf("%m[^n]s", amp;command);
getchar();
char arg1[100];
char arg2[100];
int commandNumber = parse_command(command, 1000, arg1, arg2);
char dest[100] = "";
strcpy(dest, name);
dest[strlen(dest) - 1] = '';
if (commandNumber == ADD)
{
printf("%s-name4n", dest);
int port = atoi(arg2);
int sock_fd = add_server(arg1, port);
printf("%s-servern", server_reply);
write(sock_fd, dest, strlen(dest));
auction_data_ptr = (auction_data *)realloc(auction_data_ptr, (size 1) * sizeof(auction_data_ptr));
auction_data_ptr[size].sock_fd = sock_fd;
size ;
}
else if (commandNumber == SHOW)
{
print_auctions(auction_data_ptr, size);
}
else if (commandNumber == BID)
{
int itemIndex = atoi(arg1);
int bidValue = atoi(arg2);
printf("%d-testn", auction_data_ptr[itemIndex].sock_fd);
send(auction_data_ptr[itemIndex].sock_fd, arg2, strlen(arg2), 0);
}
else if (commandNumber == QUIT)
{
}
// TODO
}
return 0; // Shoud never get here
}
Код сервера :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifndef PORT
#define PORT 30000
#endif
#define MAX_BACKLOG 5
#define MAX_CONNECTIONS 20
#define BUF_SIZE 128
#define MAX_NAME 56
int verbose = 0;
struct user
{
int sock_fd;
char name[MAX_NAME];
int bid;
};
typedef struct
{
char *item;
int highest_bid; // value of the highest bid so far
int client; // index into the users array of the top bidder
} Auction;
/*
* Accept a connection. Note that a new file descriptor is created for
* communication with the client. The initial socket descriptor is used
* to accept connections, but the new socket is used to communicate.
* Return the new client's file descriptor or -1 on error.
*/
int accept_connection(int fd, struct user *users)
{
int user_index = 0;
while (user_index < MAX_CONNECTIONS amp;amp; users[user_index].sock_fd != -1)
{
user_index ;
}
if (user_index == MAX_CONNECTIONS)
{
fprintf(stderr, "server: max concurrent connectionsn");
return -1;
}
int client_fd = accept(fd, NULL, NULL);
if (client_fd < 0)
{
perror("server: accept");
close(fd);
exit(1);
}
users[user_index].sock_fd = client_fd;
users[user_index].name[0] = '';
return client_fd;
}
/* Remove rn from str if the characters are at the end of the string.
* Defensively assuming that r could be the last or second last character.
*/
void strip_newline(char *str)
{
if (str[strlen(str) - 1] == 'n' || str[strlen(str) - 1] == 'r')
{
if (str[strlen(str) - 2] == 'r')
{
str[strlen(str) - 2] = '';
}
else
{
str[strlen(str) - 1] = '';
}
}
}
/*
* Read a name from a client and store in users.
* Return the fd if it has been closed or 0 otherwise.
*/
int read_name(int client_index, struct user *users)
{
int fd = users[client_index].sock_fd;
/* Note: This is not the best way to do this. We are counting
* on the client not to send more than BUF_SIZE bytes for the
* name.
*/
int num_read = read(fd, users[client_index].name, MAX_NAME);
if (num_read == 0)
{
users[client_index].sock_fd = -1;
return fd;
}
users[client_index].name[num_read] = '';
strip_newline(users[client_index].name);
if (verbose)
{
fprintf(stderr, "[%d] Name: %sn", fd, users[client_index].name);
}
/*
if (num_read == 0 || write(fd, buf, strlen(buf)) != strlen(buf)) {
users[client_index].sock_fd = -1;
return fd;
}
*/
return 0;
}
/* Read a bid from a client and store it in bid.
* If the client does not send a number, bid will be set to -1
* Return fd if the socket is closed, or 0 otherwise.
*/
int read_bid(int client_index, struct user *users, int *bid)
{
printf("inside bidn");
int fd = users[client_index].sock_fd;
char buf[BUF_SIZE];
char *endptr;
int num_read = read(fd, buf, BUF_SIZE);
if (num_read == 0)
{
return fd;
}
buf[num_read] = '';
if (verbose)
{
fprintf(stderr, "[%d] bid: %s", fd, buf);
}
// Check if the client sent a valid number
// (We are not checking for a good bid here.)
errno = 0;
*bid = strtol(buf, amp;endptr, 10);
if (errno != 0 || endptr == buf)
{
*bid = -1;
}
return 0;
}
void broadcast(struct user *users, char *msg, int size)
{
for (int i = 0; i < MAX_CONNECTIONS; i )
{
if (users[i].sock_fd != -1)
{
if (write(users[i].sock_fd, msg, size) == -1)
{
// Design flaw: can't remove this socket from select set
close(users[i].sock_fd);
users[i].sock_fd = -1;
}
}
}
}
int prep_bid(char *buf, Auction *a, struct timeval *t)
{
// send item, current bid, time left in seconds
printf("robin2-%s-%dn", a->item, a->highest_bid);
printf("robin-%ldn", t->tv_sec);
sprintf(buf, "%s %d %ld", a->item, a->highest_bid, t->tv_sec);
printf("robin-bid2n");
return 0;
}
/* Update auction if new_bid is higher than current bid.
* Write to the client who made the bid if it is lower
* Broadcast to all clients if the bid is higher
*/
int update_bids(int client_index, struct user *users,
int new_bid, Auction *auction, struct timeval *t)
{
char buf[BUF_SIZE];
if (new_bid > auction->highest_bid)
{
auction->highest_bid = new_bid;
auction->client = client_index;
prep_bid(buf, auction, t);
if (verbose)
{
fprintf(stderr, "[%d] Sending to %d:n %sn",
getpid(), users[client_index].sock_fd, buf);
}
broadcast(users, buf, strlen(buf) 1);
}
else
{
fprintf(stderr, "Client %d sent bid that was too low. Ignoredn",
client_index);
}
return 0;
}
int main(int argc, char **argv)
{
argc = 7;
argv[1] = "-v";
argv[2] = "-t";
argv[3] = "5";
argv[4] = "-p";
argv[5] = "4000";
argv[6] = "robin";
Auction auction;
int opt;
int port = PORT;
struct timeval timeout;
struct timeval *time_ptr = NULL;
int minutes = 0;
while ((opt = getopt(argc, argv, "vt:p:")) != -1)
{
switch (opt)
{
case 'v':
verbose = 1;
break;
case 't':
minutes = atoi(optarg);
timeout.tv_sec = minutes * 60;
timeout.tv_usec = 0;
time_ptr = amp;timeout;
break;
case 'p':
port = atoi(optarg);
break;
default:
fprintf(stderr, "Usage: auction_server [-v] [-t timeout] [-p port] itemn");
exit(1);
}
}
if (optind >= argc)
{
fprintf(stderr, "Expected argument after optionsn");
exit(1);
}
auction.item = argv[optind];
auction.client = -1;
auction.highest_bid = -1;
struct user users[MAX_CONNECTIONS];
for (int index = 0; index < MAX_CONNECTIONS; index )
{
users[index].sock_fd = -1;
users[index].name[0] = '';
}
// Create the socket FD.
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0)
{
perror("server: socket");
exit(1);
}
// Set information about the port (and IP) we want to be connected to.
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = INADDR_ANY;
// This sets an option on the socket so that its port can be reused right
// away. Since you are likely to run, stop, edit, compile and rerun your
// server fairly quickly, this will mean you can reuse the same port.
int on = 1;
int status = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
(const char *)amp;on, sizeof(on));
if (status == -1)
{
perror("setsockopt -- REUSEADDR");
}
// This should always be zero. On some systems, it won't error if you
// forget, but on others, you'll get mysterious errors. So zero it.
memset(amp;server.sin_zero, 0, 8);
// Bind the selected port to the socket.
if (bind(sock_fd, (struct sockaddr *)amp;server, sizeof(server)) < 0)
{
perror("server: bind");
close(sock_fd);
exit(1);
}
// Announce willingness to accept connections on this socket.
if (listen(sock_fd, MAX_BACKLOG) < 0)
{
perror("server: listen");
close(sock_fd);
exit(1);
}
if (verbose)
{
fprintf(stderr, "[%d] Ready to accept connections on %dn",
getpid(), port);
}
// The client accept - message accept loop. First, we prepare to listen
// to multiple file descriptors by initializing a set of file descriptors.
int max_fd = sock_fd;
fd_set all_fds;
FD_ZERO(amp;all_fds);
FD_SET(sock_fd, amp;all_fds);
while (1)
{
// select updates the fd_set it receives, so we always use a copy
// and retain the original.
fd_set listen_fds = all_fds;
int nready;
if ((nready = select(max_fd 1, amp;listen_fds, NULL, NULL, time_ptr)) == -1)
{
perror("server: select");
exit(1);
}
if (nready == 0)
{
char buf[BUF_SIZE];
sprintf(buf, "Auction closed: %s wins with a bid of %drn",
users[auction.client].name, auction.highest_bid);
printf("%s", buf);
broadcast(users, buf, BUF_SIZE);
exit(0);
}
// Is it the original socket? Create a new connection ...
if (FD_ISSET(sock_fd, amp;listen_fds))
{
int client_fd = accept_connection(sock_fd, users);
if (client_fd != -1)
{
if (client_fd > max_fd)
{
max_fd = client_fd;
}
FD_SET(client_fd, amp;all_fds);
if (verbose)
{
fprintf(stderr, "[%d] Accepted connection on %dn",
getpid(), client_fd);
}
}
}
// Next, check the clients.
for (int index = 0; index < MAX_CONNECTIONS; index )
{
if (users[index].sock_fd > -1 amp;amp; FD_ISSET(users[index].sock_fd, amp;listen_fds))
{
int client_closed = 0;
int new_bid = 0;
if (users[index].name[0] == '')
{
client_closed = read_name(index, users);
if (client_closed == 0)
{
char buf[BUF_SIZE];
prep_bid(buf, amp;auction, time_ptr);
if (verbose)
{
fprintf(stderr, "[%d] Sending to %d:n %sn",
getpid(), users[index].sock_fd, buf);
}
if (write(users[index].sock_fd, buf, strlen(buf) 1) == -1)
{
fprintf(stderr, "Write to %d failedn", sock_fd);
close(sock_fd);
}
}
}
else
{ // read a bid
client_closed = read_bid(index, users, amp;new_bid);
if (client_closed == 0)
{
update_bids(index, users, new_bid, amp;auction, time_ptr);
}
}
if (client_closed > 0)
{
FD_CLR(client_closed, amp;all_fds);
printf("Client %d disconnectedn", client_closed);
}
}
}
}
// Should never get here.
return 1;
}
Комментарии:
1. Пожалуйста, отредактируйте свой вопрос и разместите свой сервер и клиент в виде текста в блоках кода здесь. Мы не можем по-настоящему разумно комментировать, не видя того, что у вас уже есть.
2. Добавлен код клиента и сервера. Это не полный код, так как он был бы слишком большим. Хотя я могу включить, если это не поможет.
void broadcast(struct user *users, char *msg, int size)
в коде сервера записывается каждому подключенному к нему клиенту. Что мне нужно сделать в клиенте, чтобы прочитать это сообщение3. Короче говоря, я должен сделать что-то вроде
recv(sock_fd, server_reply, 2000, 0
, но мне нужно прослушать сервер/ подождать, пока он получит сообщение. Я думаю, что мы можем проигнорировать весь код и описание и просто сделать его одним лайнером, как —How does client wait for the server to recieve a message in a separate thread
Ответ №1:
Предостережение: Поскольку вы опубликовали только частичный код для сервера и клиента, это будет несколько предложений.
Ваш клиент может подключаться/подключаться к нескольким серверам ставок одновременно. Таким образом, он должен иметь возможность отслеживать несколько подключений аналогично серверу.
Ваша основная [заявленная] проблема заключается в том, что вы блокируете клиента по приглашению пользователя (например, с stdin
помощью scanf
и т. Д.). В настоящее время это означает, что клиент «застрял» в приглашении ввода пользователя и не может отправлять сообщения с серверов, к которым он подключен. Подробнее о том, как это исправить, ниже.
Таким образом, у вас будет куча кода с сервера, который должен быть в клиенте с некоторыми незначительными отличиями. Возможно, вы захотите немного обобщить часть серверного кода, чтобы он мог работать как на сервере, так и на клиенте (например, вы можете переместить его common.c
).
У вас уже есть код на сервере для обработки нескольких подключений. Серверу нужна select
маска, которая является ИЛИ прослушиваемого fd и всех активных клиентских fd.
Аналогично, вашему клиенту нужна select
маска, которая является ИЛИ fd для ввода пользователем (например, 0) и всех активных подключений к серверу.
Выполнение select
на fd 0 и использование stdio.h
потоков не будет работать слишком хорошо. Итак, замените доступ на stdin
(например) read(0,line_buffer,sizeof(line_buffer))
. Вы делаете это, если в маске задано значение fd 0 select
. Эта роль очень похожа на то, что ваш сервер выполняет для accept
on sock_fd
.
Вам нужно будет разрешить частичное чтение и добавлять в буфер, пока вы не увидите новую строку. Таким образом, вам придется выполнить работу, которую fgets
обычно выполняют при сборке целой линии. Тогда ты можешь позвонить parse_command
.
Поскольку read
пользователь не понимает разграничения новой строки, он может ввести более одной строки, прежде чем вы сможете выполнить чтение.
Итак, для ввода пользователем:
connect 4000n
bid 100 4000n
connect 5000n
Вы можете получить частичное чтение:
conn
ect
4000nbid 100 4000
nconnect
5000n
Возможно, вам также потребуется использовать FIONREAD
ioctl на fd 0, чтобы предотвратить блокировку. И, возможно, вам потребуется перевести слой TTY ядра в режим raw с помощью termios
вызовов.
Теперь клиент становится очень похожим на ваш серверный код. Он будет обрабатывать [асинхронно] действия любых подключенных серверов и ввод данных пользователем.
Совет: По СУХОМУ принципу [«не повторяйся»] …
У вас уже есть a struct user
на сервере. Клиенту понадобится что-то похожее/идентичное, например struct server
. При обобщении кода вместо того, чтобы иметь две разные структуры, которые по сути делают одно и то же, подумайте о переименовании существующей структуры в (например) struct connection
Комментарии:
1. Я обновил полный рабочий код на данный момент. вероятно, вы могли бы указать, где именно необходимо внести изменения. Этот тип кодирования кажется мне довольно загадочным