#c #sockets #network-programming #network-protocols
#c #сокеты #сетевое программирование #сетевые протоколы
Вопрос:
Я пытаюсь написать программу сервер-клиент. Идея заключается в том, что сервер
- прослушивает () на данном порту
- Когда пользователь подключается, он принимает () соединение и прекращает прослушивание
- При отключении пользователя он возвращается в состояние прослушивания, и это продолжается вечно.
Теперь я создал сервер, и связь идет нормально, однако я не уверен, как прекратить прослушивание при подключении пользователя и начать прослушивание при отключении. Кто-нибудь может мне с этим помочь?
Кроме того, я следую руководству beejs
Спасибо
Ответ №1:
(Единственный) способ прекратить прослушивание — закрыть прослушивающий сокет. Это не повлияет на уже принятые соединения, поэтому их можно продолжать использовать. Чтобы начать прослушивание снова, вам нужно будет открыть новый прослушивающий сокет и привязать его. Вероятно, вам понадобится опция SO_REUSEADDR в сокете, если вы хотите повторно открыть порт до истечения периода задержки TCP.
Вместо этого вы могли бы оставить прослушивающий сокет и просто не принимать больше подключений, пока не закончите с первым, но это на самом деле не будет не прослушиваться — любой дополнительный клиент, который пытался подключиться, получит рукопожатие от ядра (чтобы оно считало, что оно подключено), а не отклонение.
Третья возможность, которая еще больше не соответствует тому, что вы просите, но, вероятно, лучшим решением было бы оставить сокет открытым и принимать дополнительные подключения во время обработки первого подключения, но затем сразу же закрыть эти новые подключения каким-то сообщением о ЗАНЯТОСТИ. Тогда клиенты могли бы знать хотя бы что-то о том, что происходит.
Все зависит от того, что вы хотите, чтобы клиенты видели, когда они пытаются подключиться к занятому серверу.
Комментарии:
1. Отлично. Итак, я попробовал первый вариант в моем случае. Хотя казалось, что все работает нормально, после 2-3-кратного подключения и отключения клиента привязка сокета выдает некоторую ошибку. Так проблема связана с тем, что порт все еще используется? Как я могу это исправить? Кроме того, я хотел бы узнать о последнем варианте какие-либо ресурсы?
2. Если вы хотите повторно связать сокет вскоре после его закрытия, вам нужно установить параметр
SO_REUSEADDR
сокета.
Ответ №2:
Единственный способ заставить сервер отказаться от подключений от клиентов, когда он принял соединение close(2)
, — это использовать сокет, который вы используете для accept(2)
подключений.
Нет возможности заставить ядро отклонять соединения по одному соединению за раз. Ядро отклоняет соединение только в том случае, если его не прослушивает сокет.
Ниже приведен пример сервера, в котором принимающий сокет закрыт между соединениями, поэтому, если вы запустите второе соединение, второе получит ECONNREFUSED
сообщение об ошибке.
Но для этого есть условие гонки: если произойдет второе соединение, пока серверная программа находится между accept(2)
close(2)
системными вызовами и, второе соединение будет открыто (и закрыто) сервером без ошибок. Клиент во втором соединении получит eof от сервера, если он попытается read(2)
, и ошибку, если он попытается подключиться write(2)
к соединению.
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define F(_fmt) __FILE__":%d:%s: "_fmt, __LINE__, __func__
#define ERR(_ex_cod, _fmt, ...) do {
fprintf(stderr,
F("ERROR: "_fmt),
##__VA_ARGS__);
if (_ex_cod)
exit(_ex_cod);
} while (0)
char *
sockaddr2str(
struct sockaddr_in *addr,
char *buf,
size_t bufsz)
{
char *ret_val = buf;
snprintf(buf, bufsz,
"%s:%d",
inet_ntoa(addr->sin_addr),
ntohs(addr->sin_port));
return ret_val;
}
int main()
{
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(12345);
for(;;) {
int acc_sockfd = socket(PF_INET, SOCK_STREAM, 0);
if (acc_sockfd < 0)
ERR(EXIT_FAILURE,
"socket: %sn", strerror(errno));
/* this is needed in case you need to reopen it in a short time. */
int reuse_addr = 1;
int res = setsockopt(
acc_sockfd,
SOL_SOCKET, SO_REUSEADDR,
amp;reuse_addr, sizeof reuse_addr);
if (res < 0)
ERR(EXIT_FAILURE,
"setsockopt: %sn", strerror(errno));
res = bind(
acc_sockfd,
(struct sockaddr *) amp;server_addr,
sizeof server_addr);
if (res < 0)
ERR(EXIT_FAILURE,
"bind: %sn", strerror(errno));
/* 0 listen(2) will make the queue size 0, so only
* connections that enter while server is accept()ing
* them will enter */
res = listen(acc_sockfd, -1);
if (res < 0)
ERR(EXIT_FAILURE,
"listen: %sn", strerror(errno));
struct sockaddr_in client_addr;
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = INADDR_ANY;
client_addr.sin_port = 0;
socklen_t client_addr_sz = sizeof client_addr;
int conn_sockfd = accept(
acc_sockfd,
(struct sockaddr *)amp;client_addr,
amp;client_addr_sz);
if (res < 0)
ERR(EXIT_FAILURE,
"accept: %sn", strerror(errno));
close(acc_sockfd);
char client_name[256];
sockaddr2str(amp;client_addr,
client_name, sizeof client_name);
char buff[1024];
printf("Connection from %sn", client_name);
FILE *f = fdopen(conn_sockfd, "r");
if (!f)
ERR(EXIT_FAILURE,
"fdopen: %sn", strerror(errno));
int c;
while((c = fgetc(f)) != EOF) {
size_t n = snprintf(
buff, sizeof buff,
"[x]%s",
c,
c == 'n'
? "rn"
: "");
write(conn_sockfd, buff, n);
if (c == '33') break;
}
printf("Connection from %s endedn", client_name);
close(conn_sockfd);
fclose(f);
}
}
Комментарии:
1. Хотя я не использовал этот код, а просто посмотрел на поток и повторил несколько вещей, и теперь мой код работает. Теперь дело в том, что я не знаю, что заставило это сработать. Спасибо вам 🙂