通过TCP套接字分别发送和接收字符串

4

我有一个TCP服务器和客户端,已经建立了套接字。假设我有以下情况:

服务器:

char *fn = "John";
char *ln = "Doe";

char buffer[512];

strcpy(buffer, fn);
send(*socket, buffer, strlen(buffer), 0);
strcpy(buffer, ln);
send(*socket, buffer, strlen(buffer), 0);

客户:

char *fn;
char *ln;

char buffer[512];

bytes = recv(*socket, buffer, sizeof(buffer), 0);
buffer[bytes] = '\0';
strcpy(fn, buffer);

bytes = recv(*socket, buffer, sizeof(buffer), 0);
buffer[bytes] = '\0';
strcpy(ln, buffer);

printf("%s", fn); 
printf("%s", ln);

预期结果: 我希望每个字符串都能单独接收(因为我将它们保存在不同的变量中),但实际上第一个recv()得到了fn "John"和ln "Doe"连接在一起的"JohnDoe",而程序在第二个recv()上卡住了...因为我已经发送完毕。
我尝试了以下方法: - 像这样为fn和ln字符串添加\0:
char *fn = "John\0";
char *ln = "Doe\0";

但问题仍然存在。

我是否可以施加某种条件,使得每个字符串都单独接收?

编辑以添加更多问题:

1)为什么在某些情况下它实际上会将它们分开发送,而在其他情况下会同时发送它们?

2)我应该交替使用send()和recv()吗?在我的代码的其他部分中,它似乎是自我调节的,只有在正确的时间接收正确的字符串,而无需检查。这样做可以解决问题吗?

3)关于发送带有要发送的字符串长度的标头,如何解析传入的字符串以知道在哪里拆分?有任何示例将非常好。

4)我一定会检查是否正在获取比我的缓冲区更大的内容,感谢您指出这一点。

编辑2:

5)由于我总是将事物放在大小为512的缓冲字符数组中。即使我发送一个1字母字符串,每个send操作也应该占用整个缓冲区的大小,对吗?这就是困惑的地方。我有“John”,但我将其放入大小为512的数组中并发送它,那不应该填满发送缓冲区,因此recv()函数通过还取整个大小为512的数组来清空网络缓冲区吗?


你跳过了编写客户端/服务器应用程序的第一步——设计客户端和服务器将用于交换信息的协议。如果您从未设计过协议,则无法实现协议,那么您的代码只能通过运气工作(如果它真的能工作)。显然,您想发送消息,但TCP不是消息协议。那么您使用的是什么消息协议? - David Schwartz
4个回答

10

首先,当客户端调用recv(*socket, buffer, sizeof(buffer), 0);时,它将从网络接收最多sizeof(buffer)个字符(这里是512)。这是来自网络缓冲区的数据,与服务器上调用send的次数无关。recv不会寻找\0或任何其他字符——它是从网络缓冲区中按二进制方式读取的。它将读取整个网络缓冲区或sizeof(buffer)个字符。

您需要在第一个recv中检查是否有姓名和姓氏,并适当地处理它们。

您可以通过让服务器将字符串长度作为流的前几个字节发送(一个“标头”),或者扫描接收到的数据来执行此操作以查看是否有两个字符串。

每次调用recv时,您确实需要进行检查——如果字符串长度超过512个字节怎么办?那么你需要调用多次recv来获取整个字符串。而如果你只收到了第一个字符串和第二个字符串的一半怎么办呢?通常这两种情况只会在处理大量数据时发生,但我曾经在自己的代码中看到过,所以即使处理像这样的小字符串时,最好也要对recv进行良好的检查。如果您从一开始就遵循良好的编码策略,则以后就不会出现太多问题。


2
此外,如果字符串足够长,甚至可能一次读取无法返回整个字符串。接收逻辑也必须考虑到这一点。 - mac

2
TCP是面向流的,而不是面向数据报的。如果它是后者,你的东西会很好用。你有以下选项:
  • 使用SCTP
  • 如果你能承受丢失可靠性,则使用UDP
  • 以一种你可以处理的方式分隔字符串,例如在每个字符串前添加长度标记或添加NUL字节来终止每个字符串。

对于后者,你只需要使用send(*socket, buffer, strlen(buffer)+1, 0);即可包含这里仍然存在的NUL,发送方将重复使用recv()并查找字符串终止符,直到找到2个字符串为止。

请注意,在使用strcpy()时应小心:只有当你绝对确定要写入的字符串是安全的时才使用它。

你的接收器执行

bytes = recv(*socket, buffer, sizeof(buffer), 0);
buffer[bytes] = '\0';

这是不好的,因为如果缓冲区已满,则bytes == sizeof(buffer)(在更复杂的情况下可能会发生),buffer[bytes] = '\0'会写入缓冲区之外。因此,请使用:
bytes = recv(*socket, buffer, sizeof(buffer) - 1, 0);

"你可以愉快地做"
buffer[bytes] = '\0';

.


2
正确的做法是用一个头部来包含发送的数据。因此,您将传递消息的大小(包括消息的大小),然后在另一端,您可以确定消息应该有多长。
例如,“John”有4个字符,所以您可以得到长度为“0004John”,这样您就知道何时停止阅读。您可能还想在标题中添加一个命令,这是非常常见的。例如,名字可能是1,姓氏可能是2;
例如,“01004JOHN”会被设置在缓冲区并发送。
要实现这一点,您需要向缓冲区添加项目,然后通过添加的值的sizeof来增加缓冲区中的插入点。您可以使用此方法向缓冲区添加许多部分。 *只要确保不超过缓冲区的大小。
请记住,我给出的这些示例是不正确的,因为您不传递数字的字符串表示形式,而只是数字,因此它会是一个long int 1,long int 4,然后是字符或Unicode,无论哪种方式,都是4个字符。

如果您使用ASCII协议,字符串表示非常好。 - glglgl
在发送字符串之前,如何发送长整型?send()只接受(void *)作为第三个参数。另外,我不理解的是sizeof()应该已经处理了大小问题,不是吗?我正在发送一个长度为8个字节的字符串,recv()不应该读取8个字节并停止吗? - user1202888
你之前没有发送过它,你发送的是缓冲区,你只需填充缓冲区,因此可以使用指针算术运算来移动缓冲区插入点。这里有一个带有该想法的示例问题:http://stackoverflow.com/questions/11680309/c-buffer-combining-adding-extra-empty-values,你只需添加值,然后将刚刚添加到缓冲区的项目的大小提前。 - nycynik

0

使用strlen(fn)代替sizeof(buffer),这样它只会传输确切的字节数。如果您需要空终止符,请使用strlen(fn)+1,但不要忘记使用strcat()连接空终止符。

另外,如果您在没有使用memset清除缓冲区大小的情况下发送所有内容,那么您也会得到垃圾数据,所以请注意。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接