我正在创建一个聊天应用程序,其中两个程序(聊天服务和聊天客户端)来回发送消息。我正在尝试将其设置为一次发送一个字符。我计算了消息的长度,并将该号码从聊天服务发送到聊天客户端。数字传输没有问题,但当我尝试读/写消息时,它被成功发送,并带有指示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);