TCP Echo-сервер, который отображает только первый пакет (язык C) - PullRequest
3 голосов
/ 02 марта 2011

Я написал некоторый код на C для TCP-сервера, который отражает все, что он получает.Проблема в том, что когда я отправляю данные в первый раз, они отражают их, а в следующий раз сервер отправляет обратно первый отправленный мной пакет.Журнал выглядит так:

Client Send : Packet1
Server reply : Packet1
Client Send : Packet2
server reply : Packet1

Код сервера выглядит следующим образом:

int main(int argc, char** argv) {
int listenfd,connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in servaddr,cliaddr;
listenfd = socket(AF_INET,SOCK_STREAM,0);
printf("Socket listenfd : %d    with %d And %d\n",listenfd,AF_INET,SOCK_STREAM);
bzero(&servaddr, sizeof(servaddr));

servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
printf("Server address: %d\n",servaddr.sin_addr.s_addr);
bind(listenfd, (SA*) &servaddr, sizeof(servaddr));
printf("Listened: %d\n",listenfd);
listen(listenfd,LISTENQ);
printf("After Listening: %d\n",listenfd);
int num=0;
for( ; ; ){
    clilen=sizeof(cliaddr);
    connfd = accept(listenfd, (SA*) &cliaddr,&clilen);
    printf("Client no. %d connected\n",++num);
    if( (childpid=fork())==0){
        close(listenfd);
        echo(connfd);
        exit(0);
    printf("Client no. %d Terminated\n",++num);
    }
   close(connfd);
}
return (EXIT_SUCCESS);
}

И моя функция эха:

void echo(int sockfd) {
 ssize_t n;
 char    buf[MAXLINE];
 again:
 while ( (n = read(sockfd, buf, MAXLINE)) > 0)
     writen(sockfd, buf, n);
if (n < 0 && errno == EINTR)
     goto again;
 else if (n < 0)
     printf("read error");
}

код клиента main:

int main(int argc, char** argv) {
int sockfd;
struct sockaddr_in servaddr;

sockfd= socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));

servaddr.sin_family = AF_INET;
servaddr.sin_port= htons(SERV_PORT);
inet_pton(AF_INET,"0.0.0.0",&servaddr.sin_addr);
printf("%d , %d \n",sockfd,servaddr.sin_addr.s_addr);
connect(sockfd, (SA*) &servaddr,sizeof(servaddr));
printf("%d\n",sockfd);
replyBack(stdin,sockfd);

printf("RETURN\n");
return (EXIT_SUCCESS);
}

функция replyBack:

void replyBack(FILE *fp, int sockfd) {
char sendline[MAXLINE], recvline[MAXLINE];
printf("ENTER  YOUR ECHOED:  \n");
while (fgets(sendline, MAXLINE, stdin) != NULL) {
    write(sockfd, sendline, sizeof(sendline));

    if (read(sockfd, recvline, MAXLINE) == 0)
    {
        printf("str_cli: server terminated prematurely");
        exit(-1);
    }
    fputs(recvline, stdout);

}
}

Ответы [ 2 ]

4 голосов
/ 02 марта 2011

Хорошо, давайте посмотрим на этот раздел вашего кода:

printf("After Listening: %d\n",listenfd);
int num=0;
for( ; ; ){
    clilen=sizeof(cliaddr);
    connfd = accept(listenfd, (SA*) &cliaddr,&clilen);
    printf("Client no. %d connected\n",++num);
    if( (childpid=fork())==0){
        close(listenfd);
        echo(connfd);
        exit(0);
    printf("Client no. %d Terminated\n",++num);
    }
   close(connfd);
}

Вызов exit завершает работу вашего приложения, так что следующий за ним printf никогда не будет выполнен.Незначительно, но стоит отметить.

Кроме того, в процессе «потомок» не следует закрывать сокет прослушивания.Единственный сокет, с которым он должен работать, это клиентское соединение, поэтому у вас должно быть что-то более похожее на:

if ( (childpid = fork ()) == 0 ) {
  echo ( connfd );
  close ( connfd );
  printf ( "Client no %d terminated.\n", num ); /* Don't use the ++ here or your count will be off */
  exit ( 0 );
}

Теперь давайте посмотрим на ваш эхо-код:

void echo(int sockfd) {
  ssize_t n;
  char    buf[MAXLINE];
  again:
  while ( (n = read(sockfd, buf, MAXLINE)) > 0)
      writen(sockfd, buf, n);
 if (n < 0 && errno == EINTR)
      goto again;
 else if (n < 0)
      printf("read error");
}

Естьпомните, что вызовы read и write могут блокироваться (поскольку я не вижу, чтобы вы устанавливали сокет на неблокирующий ввод / вывод), и write может не отправлять весь буфер при вызове, поэтому вам необходимопроверьте здесь еще несколько вещей.

void
echo ( int sockfd )
{
  ssize_t bytes_in, bytes_out, bytes_remaining;
  int write_err;
  char buf[MAXLINE];
  char * send_start_pos;
  while ( 1 ) {
    bytes_in = read ( sockfd, buf, MAXLINE );
    if ( bytes_in < 1 ) {
      if ( errno == EINTR )
        continue;
      break; /* other error occurred, or EOF (0 bytes read) */
    }
    bytes_remaining = bytes_in;
    send_start_pos = buf;
    write_err = 0;
    while ( ( bytes_remaining > 0 ) && !( write_err ) ) {
      bytes_out = write ( sockfd, send_start_pos, bytes_remaining );
      if ( bytes_out < 0 ) {
        if ( errno == EINTR )
          continue;
        write_err = 1;
        break;
      }
      bytes_remaining -= bytes_out;
      send_start_pos += bytes_out;
    }
    if ( write_err )
      break;
  }
}

После выхода из функции echo сокет будет закрыт в вызывающей функции.Как правило, я бы предложил закрыть сокет в функции echo, если это не понадобится впоследствии.Я почти наверняка предложил бы закрыть его при возникновении ошибки, но, опять же, это зависит от вас.

Как и в стороне, держитесь подальше от goto ... это имеет свои цели, но длябольшая часть хорошо написанного кода редко когда-либо его использует.

1 голос
/ 02 марта 2011

Самая большая проблема в том, что вы рассматриваете TCP как протокол дейтаграмм, а это не так. Это потоковый протокол.

Мне еще предстоит разобраться, что именно происходит, но сейчас мои деньги лежат на клиенте, который печатает уже имеющееся в буфере сообщение (а не получает его во второй раз). Покажите нам код клиента.

Редактировать Есть несколько неправильных вещей.

Прежде всего, для одного звонка на write может потребоваться несколько звонков на read на другом конце.

Во-вторых, поскольку TCP является потоковым протоколом, вам необходимо убедиться, что для каждого логического сообщения принимающая сторона знает, сколько байтов ожидать. Вам нужно либо придерживаться сообщений фиксированной длины, либо префиксить каждое сообщение его длиной. Вы как бы делаете первое, всегда отправляя MAXLINE байт, но не достаточно последовательно (например, writen(sockfd, buf, n) записывает n байт назад, а n может отличаться от MAXLINE).

Еще одно редактирование для устранения проблемы, отмеченной в комментариях. Telnet - плохая аналогия: в telnet поток символов течет в одну сторону, а поток символов - в другую. В вашем протоколе вы отправляете дискретные многобайтовые сообщения (или дейтаграммы ). Именно поэтому вы находите протокол дейтаграмм (UDP) более простым для решения вашей проблемы.

...