仅回送第一个数据包的TCP Echo服务器(C语言)

时间:2011-03-02 18:08:45

标签: c tcp network-programming

我在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 个答案:

答案 0 :(得分:4)

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

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 ......它有其目的,但在大多数情况下,编写良好的代码很少使用它。

答案 1 :(得分:1)

最大的问题是你将TCP视为数据报协议,而不是数据报协议。这是一个流协议。

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

编辑有很多错误。

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

其次,由于TCP是一种流协议,因此您需要确保接收方知道每个逻辑消息需要多少字节。您需要坚持使用固定长度的消息,或者为每条消息添加长度前缀。你总是通过发送MAXLINE个字节来做前者,但不够一致(例如,writen(sockfd, buf, n)写回n个字节,而n可能不同来自MAXLINE)。

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