循环读/写的C套接字问题



我正在创建一个聊天应用程序,其中两个程序(聊天服务和聊天客户端)来回发送消息。我正在尝试将其设置为一次发送一个字符。我计算了消息的长度,并将该号码从聊天服务发送到聊天客户端。数字传输没有问题,但当我尝试读/写消息时,它被成功发送,并带有指示for循环的打印语句,但在发送最后一个字母后,程序就挂起了。成功发送/接收消息的打印消息从未被触发。然而,如果Sig INT聊天客户端在他们都挂起聊天服务时似乎完成了它的写入,并且它显示完成了发送消息。我有点不知所措。从我的打印语句来看,for循环似乎应该满足它们的条件,但程序似乎都陷入了困境。

聊天服务:在显示的代码之上,我将"Tom:"硬编码为hosthandle,并接受hostmessage的用户输入,在本例中,我将其作为测试。mone是

char mone[2]="";
//send message to chatclient
charcount= strlen(hostmessage);
meslen=charcount+strlen(hosthandle);//total message length
int number_to_send = meslen; 
int converted_number = htonl(number_to_send);
write(newsockfd, &converted_number, sizeof(converted_number)); //sends number to chat client
for (j=0;j<strlen(hosthandle);j++)
{
    mone[0]=hosthandle[j];
    n = write(newsockfd, mone, 2); //writes handle to chatclient
    if (n <= 0)
    {
        perror("Message not n");
    }
    printf("%s  %d n",mone, j);
}
for (j=0;j<strlen(hostmessage);j++)
{
    mone[0]=hostmessage[j];
    n = write(newsockfd, mone, 2); //writes message to chatclient  
    printf("%s  %d n",mone, j);
}
printf("Finished Sending Message");

程序输出(主机消息为测试消息大小=8)

成绩单:

Tom: test
T  0
o  1
m  2
:  3
t  0
e  1
s  2
t  3

聊天客户端

int received_int = 0; //this section of code receives message length from chatserve
int return_status = read(sockfd, &received_int, sizeof(received_int));
if (return_status > 0) {
    fprintf(stdout, "Received int = %dn", ntohl(received_int));
    }
else 
{
    printf("did not receive message length");
}
for(j=0;j<received_int;j++)
{
    n=read(sockfd,kone,2); //reads in letter from chat serve
    if (n <= 0)
    {
        perror("Message not recievedn");
    }
    //printf("%d n", n);
    printf("%s   %d n",kone, j);
}
printf("Received message n");

当我觉得两个for循环都应该达到它们的计数器时,程序的输出就会挂起。

Received int = 8
T   0
o   1
m   2
:   3
t   4
e   5
s   6
t   7

mone只包含一个元素,但您正在使用编写两个字符

n = write(newsockfd, mone, 2);

您需要将mone声明为:

char mone[] = " ";

从而它包含一个字符,后面跟着尾随的空字节。您的声明只有后面的空字节。还有

char[] mone = "";

甚至不是有效的语法,我不明白程序是如何编译的。

在客户端中,您还应该检查read()是否实际返回2个字节。没有什么能保证对read()的每次调用都会得到发送方中相应write()中发送的所有内容。read()可以返回任何数字,最多可以返回您请求的数量,因此它一次可以返回1个字节,您需要再次调用才能获得第二个字节。

int total_needed = 2;
int total_read = 0;
while (total_read < total_needed) {
    n = read(sockfd, mone + total_read, total_needed - total_read);
    if (n < 0) {
        perror("Error while reading");
        break;
    } else if (n == 0) {
        printf("Unexpected EOFn");
        break;
    } else {
        total_read += n;
    }
}

老实说,我不明白你为什么要把每个角色都写出来。这只会使你的生活变得复杂,没有任何好处(至少对我来说是可见的)。

如果使用UDP套接字,您应该尝试将整个消息组合成一个UDP数据包(例如,将转换后的int和完整的数据放入一个缓冲区,并一次性发送(写入)所有内容)。如果使用TCP,您的数据无论如何都会组合成一个流,对客户端来说没有任何可见的边界(将数据打包到ip数据包中是在套接字实现中完成的)。

不确定您是否注意到,您已经隐含地定义了一个协议:首先,您以网络字节顺序用四个字节发送要遵循的字节数,然后是–猜猜看–正是这个数量的数据。。。

如果使用UDP,则必须尝试在一次读取中获取整个消息(如果提供的缓冲区太小,则会丢弃消息的其余部分!)。如果使用TCP,读取不一定会返回完整的消息。因此,客户端将执行以下操作来实现您定义的协议:

  • 从流中读取四个字节–可能,你需要多个电话才能阅读
  • 将这四个字节计算为预期的字节数
  • 从流中读取此字节数–再次可能有多个调用要读取

请注意,读取可能会阻塞;如果您还有其他任务要做,您可以使用select或poll进行测试,如果数据可用并且当时只读取(或者您有多个线程)。

编辑:正如您所要求的(希望不要迟到)–我会这样做:

int readFromSocket(int fd, char* buffer, unsigned long length)
{
    unsigned int count = 0;
    char* b = buffer;
    while(count < length)
    {
        int n = read(fd, buffer, sizeof(length) - count);
        if(n < 0 && errno != EINTR) // EINTR: interrupted due to signal -> can continue
        {
            // handle error, possibly:
            // * closing socket (next messages might be corrupt now!)
            // * calling exit(-1), abort(), ...
            // * returning - as here - an error
            return -1;
        }
        count -= n;
        buffer += n;
    }
    return 0; // OK
}
int handleNextMessage(int fd)
{
    int result = 0;
    unsigned int length;
    char* message;
    result = readFromSocket(fd, (char*)&length, sizeof(length));
    if(result == 0)
    {
        length = htonl(length);
        // possibly check first length for valid range
        message = malloc(length);
        result = readFromSocket(fd, message, length);
        if(result == 0)
        {
            // do what ever needs to be done with message
        }
        // important, else you get a memory leak:
        free(message);
    }
    if(!result != 0)
    {
        // appropriate error handling
        // all handling proposed within readFromSocket could
        // alternatively be done here, too...
        // or outside handleNextMessage
    }
    return result;
}

您可以考虑在堆栈上使用缓冲区:,而不是使用malloc和free

char message[MAX_MESSAGE_LENGTH];

确保服务器发送的消息不会超过客户端可以读取的长度。

一个重要的节点:如果您未能阅读完整的消息,那么您可能只阅读了部分消息。如果您只是继续,您可能会在下一条消息开始时阅读上一条消息的其余部分,并获取损坏的数据。因此,您应该关闭插座,并–如果需要–重新打开。

服务器端还有一个小问题:如何在客户端再次将句柄与消息分离(除非句柄有固定长度)?我建议包括终止0字符(并且,使用TCP时,只需一次写入所有数据):

int isSuccess, result, lenHandle, lenMessage;
// ...
lenHandle = strlen(hosthandle) + 1; // +1: for terminating 0 character
lenMessage = strlen(hostmessage) + 1;
meslen = lenHandle + lenMessage;
meslen = htonl(meslen);
result = write(newsockfd, &meslen, sizeof(meslen);
isSuccess = 0;
if(result == sizeof(meslen))
{
    result = write(newsockfd, hosthandle, lenHandle);
    if(result == lenHandle)
    {
        result = write(newsockfd, hostmessage, lenMessage);
        isSuccess = result == lenHandle;
    }
}
if(!isSuccess)
{
    // handle errno appropriately
}

请注意,写入也可能导致EINTR出错,在这种情况下,您可以继续,因此您可以将每次写入都封装在一个循环中:

do
{
    result = write(/*...*/);
}
while(result < 0 && errno == EINTR);

最新更新