【问题标题】:unix tcp client/server weird behaviorunix tcp 客户端/服务器奇怪的行为
【发布时间】:2012-08-21 12:01:56
【问题描述】:

我一直在尝试学习 unix 网络编程,所以我尝试编写一个客户端/服务器程序,其中客户端发送消息,服务器返回转换为大写字母的消息。

当我运行服务器并将客户端连接到我自己的机器并发送一些测试字符串时,它工作正常,直到我之前输入的内容被写入屏幕。

我怀疑它与缓冲区有关。这是我启动服务器后运行的示例:

can@ubuntu:~$ cd 桌面

can@ubuntu:~/Desktop$ ./tcpuppcli 127.0.0.1

输入要回显的字符串:test

回声响应:测试

输入要回显的字符串:string2

回显响应:STRING2

输入要回显的字符串:string3

回显响应:STRING3

输入要回显的字符串:aaaaaaaaafsfagd

回声响应:AAAAAAAAAFSFAGD

输入要回显的字符串:gdsgsg

回声响应:GDSGSG

AAFSFAGD ----------->!这是带有来自先前输入的字符的奇怪行!

输入要回显的字符串:^C

可以@ubuntu:~/Desktop$

服务端和客户端的代码如下:

// methods with prefix w_ are just error checking wrappers from UNP by Stevens    
// server
#include "socketwrap.h" // headers necessary and constants like MAXLINE
#include <ctype.h>

void sigchld_handler( int sig);
void str_upper( int connfd);
void toUpperCase( char buffer[], int length);

int main( int argc, char** argv)
{
  int listenfd;
  int connfd;
  pid_t childpid;
  struct sockaddr_in serverAddress;
  struct sockaddr_in clientAddress;
  socklen_t length;
  struct sigaction sa;

  // Create the socket
  listenfd = w_socket( AF_INET, SOCK_STREAM, 0);

  // Clear the serverAddress structure
  bzero( &serverAddress, sizeof( serverAddress));
  // Set up the serverAddress structure
  serverAddress.sin_family = AF_INET;
  serverAddress.sin_addr.s_addr = htonl( INADDR_ANY);
  serverAddress.sin_port = htons( 11979);

  // Bind the socket to a well-defined port
  w_bind( listenfd, (struct sockaddr*) &serverAddress, sizeof( serverAddress));

  // Start listening for connections
  w_listen( listenfd, BACKLOG);

  // Handle any zombie children by using a signal handler
  sa.sa_handler = sigchld_handler;
  sigemptyset( &sa.sa_mask);
  sa.sa_flags = SA_RESTART;
  if( sigaction( SIGCHLD, &sa, NULL) == -1)
  {
    perror( "signal error");
    exit( 1);
  }

  printf( "Waiting for connections...\n");

  while( 1)
  {
      length = sizeof( clientAddress);
      connfd = w_accept( listenfd, ( struct sockaddr*) &clientAddress, &length);
      if( connfd < 0)
      {
          if( errno == EINTR)
          {
            continue; // back to while
          }
          else
          {
            perror( "accept error");
            exit( 1);
          }
      }

      printf( "Obtained connection...\n");
      childpid = fork();
      if ( childpid == 0) /* child process */
      {
        w_close( listenfd); /* close listening socket */
        str_upper( connfd); // process the request
        exit( 0);
      }

    w_close( connfd); /* parent closes connected socket */
  }

}

void sigchld_handler( int sig)
{
  while( waitpid( -1, NULL, WNOHANG) > 0);
}

void str_upper( int connfd)
{
  char buffer[MAXLINE];
  while( read( connfd, buffer, MAXLINE - 1) > 0)
  {
    toUpperCase( buffer, strlen( buffer));
    write( connfd, buffer, strlen( buffer));
  }
}

void toUpperCase( char buffer[], int length)
{
  int i;
  for( i = 0; i < length - 1; i++)
  {
      buffer[i] = toupper( buffer[i]);
  }
}

// client
#include "socketwrap.h"

void str_cli( int connfd);

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

  if( argc != 2)
  {
      printf( "Invalid argument count\n");
      printf( "Correct usage: tcpcli4 <IPaddress>\n");
      exit( 1);
  }


  sockfd = w_socket( AF_INET, SOCK_STREAM, 0);
  bzero( &serverAddress, sizeof( serverAddress));
  serverAddress.sin_family = AF_INET;
  serverAddress.sin_port = htons( 11979);
  if( inet_pton( AF_INET, argv[1], &serverAddress.sin_addr) <= 0)
  {
      perror( "inet_pton error");
      exit( 1);
  }

  w_connect( sockfd, ( struct sockaddr*) &serverAddress, sizeof( serverAddress));

  str_cli( sockfd);
  exit( 0);
}

void str_cli( int connfd)
{
  char buffer[MAXLINE];

  printf( "Enter the string to echo: ");
  while( fgets( buffer, MAXLINE, stdin) > 0)
  {

    // Send string to echo server, and retrieve response
    write( connfd, buffer, strlen( buffer));
    read( connfd, buffer, MAXLINE - 1);

    // Output echoed string
    printf( "Echo response: %s\n", buffer);
    printf( "Enter the string to echo: ");
  }
}

如果您需要更多信息或有任何不清楚的地方,请告诉我

感谢所有回复

【问题讨论】:

  • 嗯。每次输入较长的输入后输入较短的输入时,是否会发生这种情况?
  • 试过了,是的,如果输入变得越来越长,但很长接着更短没有问题。

标签: c unix tcp


【解决方案1】:

read() 不会在字符串中添加'\0' 终止符,因此您的strlen() 调用的结果定义不明确。您需要使用read() 的返回值(将其存储在一个变量中,不要只是测试它为&gt; 0 然后将其丢弃)才能知道字符串有多长。

它似乎在一段时间内正常工作,但对于未初始化时位于缓冲区中的'\0's,这只是运气不佳。在读取循环之前使用memset(buffer, 'X', sizeof buffer) 尝试它,您会得到更早的失败。

【讨论】:

  • 将 read() 的结果存储在一个 int 变量中并将其传递给服务器部分的 write() 解决了这个问题。我仍然想知道为什么它只发生在输入比以前短的情况下。 '\0' 和 strlen() 有什么关系?
  • 因为之前的输入还在缓冲区中。
【解决方案2】:

与 Alan Curry 指出的 \0 问题一样,不要忘记 TCP 不是面向消息的传输,它是面向的。

绝对保证一端的单个write 将导致另一端的单个read。事实上,在某些情况下,可以保证不会发生这种情况。

您应该在数据包中加入某种形式的结构或分界线,以指示下一个数据块的长度。在这种情况下,一个好的方法是包含字符串长度的 32 位整数,打包为四个字节。网络协议通常以“大端”格式(又名“网络顺序”)发送此类内容,因此您将使用htonl() 来转换发送时的值,并使用ntohl() 来转换接收时的值。

在发送端,您可以使用writev() 将长度值和数据本身结合到一个系统调用中。在接收端,只需使用read() 获取长度,然后使用第二个read() 读取那么多数据。

另外,不要忘记 任何 read()write() 调用可以发送比实际指定的更少的数据。每个此类操作都应包装在一个循环中,该循环重复调用该函数,直到处理了适当数量的字节。

【讨论】:

  • 那么我可以用一个字符串和一个长度为 int 的结构,填写结构并传递它吗?抱歉,如果这听起来很傻,我有点困惑,是的,我打算将 unix 网络编程书中的包装函数用于 read() 和 write()。
猜你喜欢
  • 2020-09-19
  • 2015-08-30
  • 1970-01-01
  • 2015-03-02
  • 2013-08-20
  • 2012-01-04
  • 2013-02-17
  • 2012-11-03
  • 1970-01-01
相关资源
最近更新 更多