【问题标题】:TCP Echo server that only echoes first packet (C Language)仅回显第一个数据包的 TCP 回显服务器(C 语言)
【发布时间】:2011-03-02 18:08:44
【问题描述】:

我用 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");
}

客户端代码主要:

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);

}
}

【问题讨论】:

  • 您的客户端和服务器对我来说工作得很好。检查以确保您进行的所有系统调用(readwriteconnect 等)都没有返回错误或小于预期值。尝试使用 Wireshark 查看实际通过网络发送的内容,这将告诉您问题出在客户端还是服务器上。尝试使用netcat 作为替代客户端。
  • 谢谢 :) 我会尝试使用它们

标签: c tcp network-programming


【解决方案1】:

好吧,让我们看看你的这部分代码:

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");
}

必须记住,对readwrite 的调用可能会阻塞(因为我没有看到您将套接字设置为非阻塞IO),并且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 ...它有其目的,但在大多数情况下,编写良好的代码很少使用它。

【讨论】:

  • 它成功了 :) 非常感谢您的解决方案和您宝贵的建议:D
  • 另外一个小提示:您还应该检查fork()是否返回-1,这表示错误。
  • @Adam:绝对是。好的做法是检查 -1、0 和其他任何值;没有专注于那个,所以我错过了那个调整。
【解决方案2】:

最大的问题是您将 TCP 视为数据报协议,但事实并非如此。它是一个流协议。

我还没有弄清楚到底发生了什么,但现在我的钱是在客户端打印它已经在其缓冲区中的消息(而不是第二次获取它)。向我们展示客户端代码。

编辑有多个错误。

首先,一次调用write 可能需要多次调用另一端的read

其次,由于 TCP 是一种流协议,因此您需要确保对于每条逻辑消息,接收方都知道需要多少字节。您要么需要坚持使用固定长度的消息,要么在每条消息前面加上它的长度。您总是发送MAXLINE 字节,但不够一致(例如,writen(sockfd, buf, n) 写回n 字节,n 可能与MAXLINE 不同)。

另一个编辑以解决 cmets 中提出的观点。 Telnet 是一个不好的类比:在 telnet 中,有一个 stream 字符流向一个方向,一个 stream 个字符流向另一个方向。在您的协议中,您发送离散的多字节消息(或数据报)。这正是您发现数据报协议 (UDP) 更容易应用于您的问题的原因。

【讨论】:

  • 您可以使用 telnet 作为客户端..但是好的,我将编辑帖子并添加客户端
  • 不管你是否使用 telnet 作为客户端,它仍然是一个流。
猜你喜欢
  • 1970-01-01
  • 2013-08-30
  • 1970-01-01
  • 1970-01-01
  • 2021-07-10
  • 1970-01-01
  • 2014-11-24
  • 2020-08-27
  • 2019-12-20
相关资源
最近更新 更多