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

时间:2016-04-29 16:03:20

标签: c sockets

我正在创建一个聊天应用程序,其中两个程序(聊天服务和聊天客户端)来回发送消息。 我正在尝试将其设置为一次发送一个字符。我计算了消息的长度,并将该号码从聊天服务发送到聊天客户端。数字传输没有问题,但是当我尝试读取/写入消息时,它成功发送了print语句,指示for循环应该结束但是在发送完最后一个字母后,程序才会挂起。用于永久发送/接收的消息的打印消息永远不会被触发。然而,如果Sig INT聊天客户端他们都挂着聊天服务似乎完成其写入并显示完成发送消息。对于正在发生的事情,我有点失落。从我的print语句来看,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");

程序输出(hostmessage是测试消息大小= 8)

文稿:

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

ChatClient

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 = %d\n", 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 recieved\n");
    }
    //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

2 个答案:

答案 0 :(得分:1)

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 EOF\n");
        break;
    } else {
        total_read += n;
    }
}

答案 1 :(得分:0)

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

如果使用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);