TCP 客户端/服务器:何时停止读取套接字
TCP client / server : when stop read socket
我在创建小型 ftp 客户端/服务器 (tcp)
时面临多个问题
客户端有提示。
如何停止接收问题。通过我的套接字从客户端向服务器发送数据,反之亦然。
例如,从服务器向客户端发送一些字符串。客户端如何知道何时停止读取套接字并离开 recv()
循环以打印回提示
这就是为什么我创建了 transfert functions
来知道何时通过预发送停止(如果它是最后一次发送)(CONT - DONE
)并且它工作得很好。 (下面的代码)
然后我需要在服务器上执行类似 ls
的命令并将结果发送到客户端,所以我想到了 2 个选项。
- 在
char*
中复制 execv()
并使用我的传输函数。
- dup
execv()
直接在我的套接字中输出。
第二个选项更清晰,但它让我面临第一个关于如何停止 recv()
循环的问题。
奖励问题:如何将 execv()
复制到字符串。由于 fd
参数,我想使用 mmap
,但我仍然需要提前知道大小。
# define BUFFSIZE 512
# define DONE "DONE"
# define CONT "CONT"
int send_to_socket(int sock, char *msg)
{
size_t len;
int ret[2];
char buff[BUFFSIZE+1];
len = strlen(msg);
bzero(buff, BUFFSIZE+1);
strncpy(buff, msg, (len <= BUFFSIZE) ? len : BUFFSIZE);
/*strncpy(buff, msg, BUFFSIZE);*/
ret[0] = send(sock, (len <= BUFFSIZE) ? DONE : CONT, 4, 0);
ret[1] = send(sock, buff, BUFFSIZE, 0);
if (ret[0] <= 0 || ret[1] <= 0)
{
perror("send_to_socket");
return (-1);
}
// recursive call
if (len > BUFFSIZE)
return (send_to_socket(sock, msg + BUFFSIZE));
return (1);
}
char *recv_from_socket(int cs)
{
char state[5];
char buff[BUFFSIZE+1];
char *msg;
int ret[2];
msg = NULL;
while (42)
{
bzero(state, 5);
bzero(buff, BUFFSIZE+1);
ret[0] = recv(cs, state, 4, 0);
ret[1] = recv(cs, buff, BUFFSIZE, 0);
if (ret[0] <= 0 || ret[1] <= 0)
{
perror("recv_from_socket");
return (NULL);
}
// strfljoin(); concat the strings and free the left parameter
msg = (msg) ? strfljoin(msg, buff) : strdup(buff);
if (strnequ(state, DONE, 4))
break ;
}
return (msg);
}
有什么意义
而(42)
这与
而(1)
如果你只是希望它永远循环
如果我理解正确,你想使用 execv 来执行 'ls' 并捕获输出以便你可以发送它。
看看 'popen' 函数,它将创建一个管道文件描述符(和 return 它),然后执行命令,输出连接到管道。
然后您可以使用常规的 read() 系统调用来读取文件描述符的输出。
像这样
#include <stdio.h>
FILE *f
char buf[BUF_MAX]
f = popen("ls", "r")
if (!f)
// send error message or whatever
return
while (fgets(buf, BUF_MAX, f))
// send buf with your other socket function
pclose(f);
如果您不想使用 libc stdio,而只想使用低级系统例程,那么要做的就是检查 fork() 、 pipe() 和 dup() 系统调用,
首先使用 pipe()(给你两个文件描述符,每一端一个)
然后 fork()
然后关闭未使用的文件描述符(),
然后使用 dup() 或 dup2() 仅将新进程中的 fd 1 更改为之前管道调用的输出 fd 编号。
然后最后你 execv() 到 运行 "ls" 命令,它的输出将进入管道
并且在原始过程中,您从第一个 fd 中读取 () return 由早先的 pipe() 调用编辑
既然你想让客户端知道服务器何时结束,你可以这样做:
const int MSG_DATA = 1;
const int MSG_DONE = 2;
strict message {
int messagetype;
int len;
}
char buf[BUF_SIZE]
message msg;
// Put data into buf
// set msg.len
msg.messagetype= MSG_DATA;
send(msg,size of(msg))
send (buf,msg.len)
然后当你完成后。
msg.messagetype=MSG_DONE
msg.len = 0
send(msg,sizeof(msg))
在客户端:
Message msg;
Char buf[]
while()
...
recv(&msg)
...
if(msg.messagetype == MSG_DATA)
recv(buf)
elif msg.message type == MSG_DONE)
DoSomething()
但是,请记住,您的客户端无论如何都应该始终接收数据包。
如果不是为了学校练习而不能随心所欲,最好使用现有的库,例如消息队列库,也许是 zeromq,而不是用困难的方式来做。
还有一个简单的curl C库,你可以用它来发送HTTP,它已经整理好了所有这些东西,也可以做不定长的内容。
此外(我假设您使用的是 TCP 套接字,而不是 UDP),连接的两端都不能发送 N 个字节,并且期望其他站点上的 recv 将获得 N 个字节。 recv 只会获取操作系统网络堆栈当前在其 TCP 缓冲区中接收和缓冲的内容。而且receiver也可以在一次recv中得到几个已经发送的chunk。网络上的数据包大小可能在 1400-1500 字节左右,您的服务器可能会发送一条几 kB 的消息,该消息将被分成几个数据包,并且可能在第一个数据包之后由接收方处理,在其余部分进来之前,或者您的服务器可能会用您的 DONE 或 CONT header 发送几条小消息,接收方可能会在一次 recv() 中获得所有这些信息。即使您很幸运并且 DONE 或 CONT 实际上位于 TCP 缓冲区的开头,您的代码也会读取 OS TCP 缓冲区中的所有内容,并且只会检查前 4 个字节以查看 DONE 或CONT 从第一条消息开始,并将所有其余消息视为数据。所以你真的需要发送一个长度字段。您可以完全废弃 DONE 或 CONT 并改为发送长度,并发送 0 长度来表示 DONE。然后你的客户端在收到时,可以 recv() 它可以得到的所有东西,进入你的缓冲区,然后使用长度字段依次处理该缓冲区中包含的每条消息。
您的判断是正确的,要通过 stream-oriented 套接字进行除无差别流之外的任何通信,您需要在通信双方之间应用某种 application-layer 协议。这就是你的 send_to_socket()
和 recv_from_socket()
函数正在做的事情,尽管它们有缺陷。*
假设您确实需要使用 application-layer 协议,那么让子进程直接写入套接字根本不是一个选项,除非您的特定 ALP 可以容纳将整个程序输出封装为单个块,您正在使用的块无法做到。
话虽如此,您至少还有一个未考虑的其他选择:让父级将子级的输出发送到套接字 当子级产生输出时,而不是收集所有这些,然后才发送。这将涉及在子项和父项之间建立一个管道,并且可能是 send_to_socket()
的一个单独版本,它读取要从 FD 而不是从字符串发送的数据。您可能会一次累积一个 modest-sized 缓冲区。这种方法将是我的建议。
bonus question: how to dup execv() to a string. i thought using mmap thanks to the fd parameter, but i still need to know the size in advance.
mmap()
采用指定要映射的文件的文件描述符参数,但这并不意味着它只适用于任何文件描述符。它只能保证与指定常规文件和 shared-memory 对象的 FD 一起使用,您不能期望它适用于指定瞬态数据管道的 FD。要在内存中捕获子进程的输出,您需要像我为第三个选项描述的那样操作,但将读取的数据存储在 dynamically-allocated(并根据需要重新分配)缓冲区中,而不是将其发送到客户端,因为它被读取。这可能既昂贵又混乱。
* 你的功能有缺陷:
- 他们假设可以依赖
send()
和 recv()
函数来准确传输请求的字节数,否则就会失败。
事实上,send()
和recv()
都可能执行部分传输。为避免丢失数据和/或不同步,您 必须 将这些函数的 return 值与您尝试传输的字节数进行比较以检测部分传输,如果发生部分传输,您必须发出另一个调用以发送数据的余额。由于同样的事情可能会再次发生,因此您通常需要将整个事情放在一个循环中,不断调用 send()
或 recv()
直到发送完所有数据或 善意的 发生故障。
递归对于发送函数来说是一个糟糕的实现选择。如果您有大量数据要发送,那么您可能会耗尽堆栈,而且每个递归函数调用的开销要比循环返回的开销大得多。
发送 fixed-length 块中的数据是不必要的,并且需要在发送之前将数据复制到单独的缓冲区的开销。
考虑发送消息长度而不是 "CONT" 或 "DONE",后跟那么多字节(但见上文)。您还可以将标志位合并到消息长度中以传达附加信息——例如,一个标志当前块是否是最后一个的位。
- 您的
send()
和 recv()
调用可能因与连接及其持续可行性无关的原因而失败。例如,它们可以被信号中断。由于如果发送方和接收方之间的通信被中断,您将无法 re-sync 通信,因此您应该确保在发生错误时终止通信,尽管实际上不必由您的 send 和 recv 处理自己发挥作用。
我在创建小型 ftp 客户端/服务器 (tcp)
时面临多个问题客户端有提示。
如何停止接收问题。通过我的套接字从客户端向服务器发送数据,反之亦然。
例如,从服务器向客户端发送一些字符串。客户端如何知道何时停止读取套接字并离开 recv()
循环以打印回提示
这就是为什么我创建了 transfert functions
来知道何时通过预发送停止(如果它是最后一次发送)(CONT - DONE
)并且它工作得很好。 (下面的代码)
然后我需要在服务器上执行类似 ls
的命令并将结果发送到客户端,所以我想到了 2 个选项。
- 在
char*
中复制execv()
并使用我的传输函数。 - dup
execv()
直接在我的套接字中输出。
第二个选项更清晰,但它让我面临第一个关于如何停止 recv()
循环的问题。
奖励问题:如何将 execv()
复制到字符串。由于 fd
参数,我想使用 mmap
,但我仍然需要提前知道大小。
# define BUFFSIZE 512
# define DONE "DONE"
# define CONT "CONT"
int send_to_socket(int sock, char *msg)
{
size_t len;
int ret[2];
char buff[BUFFSIZE+1];
len = strlen(msg);
bzero(buff, BUFFSIZE+1);
strncpy(buff, msg, (len <= BUFFSIZE) ? len : BUFFSIZE);
/*strncpy(buff, msg, BUFFSIZE);*/
ret[0] = send(sock, (len <= BUFFSIZE) ? DONE : CONT, 4, 0);
ret[1] = send(sock, buff, BUFFSIZE, 0);
if (ret[0] <= 0 || ret[1] <= 0)
{
perror("send_to_socket");
return (-1);
}
// recursive call
if (len > BUFFSIZE)
return (send_to_socket(sock, msg + BUFFSIZE));
return (1);
}
char *recv_from_socket(int cs)
{
char state[5];
char buff[BUFFSIZE+1];
char *msg;
int ret[2];
msg = NULL;
while (42)
{
bzero(state, 5);
bzero(buff, BUFFSIZE+1);
ret[0] = recv(cs, state, 4, 0);
ret[1] = recv(cs, buff, BUFFSIZE, 0);
if (ret[0] <= 0 || ret[1] <= 0)
{
perror("recv_from_socket");
return (NULL);
}
// strfljoin(); concat the strings and free the left parameter
msg = (msg) ? strfljoin(msg, buff) : strdup(buff);
if (strnequ(state, DONE, 4))
break ;
}
return (msg);
}
有什么意义 而(42)
这与 而(1) 如果你只是希望它永远循环
如果我理解正确,你想使用 execv 来执行 'ls' 并捕获输出以便你可以发送它。
看看 'popen' 函数,它将创建一个管道文件描述符(和 return 它),然后执行命令,输出连接到管道。
然后您可以使用常规的 read() 系统调用来读取文件描述符的输出。
像这样
#include <stdio.h>
FILE *f
char buf[BUF_MAX]
f = popen("ls", "r")
if (!f)
// send error message or whatever
return
while (fgets(buf, BUF_MAX, f))
// send buf with your other socket function
pclose(f);
如果您不想使用 libc stdio,而只想使用低级系统例程,那么要做的就是检查 fork() 、 pipe() 和 dup() 系统调用,
首先使用 pipe()(给你两个文件描述符,每一端一个)
然后 fork()
然后关闭未使用的文件描述符(),
然后使用 dup() 或 dup2() 仅将新进程中的 fd 1 更改为之前管道调用的输出 fd 编号。
然后最后你 execv() 到 运行 "ls" 命令,它的输出将进入管道
并且在原始过程中,您从第一个 fd 中读取 () return 由早先的 pipe() 调用编辑
既然你想让客户端知道服务器何时结束,你可以这样做:
const int MSG_DATA = 1;
const int MSG_DONE = 2;
strict message {
int messagetype;
int len;
}
char buf[BUF_SIZE]
message msg;
// Put data into buf
// set msg.len
msg.messagetype= MSG_DATA;
send(msg,size of(msg))
send (buf,msg.len)
然后当你完成后。
msg.messagetype=MSG_DONE
msg.len = 0
send(msg,sizeof(msg))
在客户端:
Message msg;
Char buf[]
while()
...
recv(&msg)
...
if(msg.messagetype == MSG_DATA)
recv(buf)
elif msg.message type == MSG_DONE)
DoSomething()
但是,请记住,您的客户端无论如何都应该始终接收数据包。
如果不是为了学校练习而不能随心所欲,最好使用现有的库,例如消息队列库,也许是 zeromq,而不是用困难的方式来做。
还有一个简单的curl C库,你可以用它来发送HTTP,它已经整理好了所有这些东西,也可以做不定长的内容。
此外(我假设您使用的是 TCP 套接字,而不是 UDP),连接的两端都不能发送 N 个字节,并且期望其他站点上的 recv 将获得 N 个字节。 recv 只会获取操作系统网络堆栈当前在其 TCP 缓冲区中接收和缓冲的内容。而且receiver也可以在一次recv中得到几个已经发送的chunk。网络上的数据包大小可能在 1400-1500 字节左右,您的服务器可能会发送一条几 kB 的消息,该消息将被分成几个数据包,并且可能在第一个数据包之后由接收方处理,在其余部分进来之前,或者您的服务器可能会用您的 DONE 或 CONT header 发送几条小消息,接收方可能会在一次 recv() 中获得所有这些信息。即使您很幸运并且 DONE 或 CONT 实际上位于 TCP 缓冲区的开头,您的代码也会读取 OS TCP 缓冲区中的所有内容,并且只会检查前 4 个字节以查看 DONE 或CONT 从第一条消息开始,并将所有其余消息视为数据。所以你真的需要发送一个长度字段。您可以完全废弃 DONE 或 CONT 并改为发送长度,并发送 0 长度来表示 DONE。然后你的客户端在收到时,可以 recv() 它可以得到的所有东西,进入你的缓冲区,然后使用长度字段依次处理该缓冲区中包含的每条消息。
您的判断是正确的,要通过 stream-oriented 套接字进行除无差别流之外的任何通信,您需要在通信双方之间应用某种 application-layer 协议。这就是你的 send_to_socket()
和 recv_from_socket()
函数正在做的事情,尽管它们有缺陷。*
假设您确实需要使用 application-layer 协议,那么让子进程直接写入套接字根本不是一个选项,除非您的特定 ALP 可以容纳将整个程序输出封装为单个块,您正在使用的块无法做到。
话虽如此,您至少还有一个未考虑的其他选择:让父级将子级的输出发送到套接字 当子级产生输出时,而不是收集所有这些,然后才发送。这将涉及在子项和父项之间建立一个管道,并且可能是 send_to_socket()
的一个单独版本,它读取要从 FD 而不是从字符串发送的数据。您可能会一次累积一个 modest-sized 缓冲区。这种方法将是我的建议。
bonus question: how to dup execv() to a string. i thought using mmap thanks to the fd parameter, but i still need to know the size in advance.
mmap()
采用指定要映射的文件的文件描述符参数,但这并不意味着它只适用于任何文件描述符。它只能保证与指定常规文件和 shared-memory 对象的 FD 一起使用,您不能期望它适用于指定瞬态数据管道的 FD。要在内存中捕获子进程的输出,您需要像我为第三个选项描述的那样操作,但将读取的数据存储在 dynamically-allocated(并根据需要重新分配)缓冲区中,而不是将其发送到客户端,因为它被读取。这可能既昂贵又混乱。
* 你的功能有缺陷:
- 他们假设可以依赖
send()
和recv()
函数来准确传输请求的字节数,否则就会失败。
事实上,send()
和recv()
都可能执行部分传输。为避免丢失数据和/或不同步,您 必须 将这些函数的 return 值与您尝试传输的字节数进行比较以检测部分传输,如果发生部分传输,您必须发出另一个调用以发送数据的余额。由于同样的事情可能会再次发生,因此您通常需要将整个事情放在一个循环中,不断调用 send()
或 recv()
直到发送完所有数据或 善意的 发生故障。
递归对于发送函数来说是一个糟糕的实现选择。如果您有大量数据要发送,那么您可能会耗尽堆栈,而且每个递归函数调用的开销要比循环返回的开销大得多。
发送 fixed-length 块中的数据是不必要的,并且需要在发送之前将数据复制到单独的缓冲区的开销。
考虑发送消息长度而不是 "CONT" 或 "DONE",后跟那么多字节(但见上文)。您还可以将标志位合并到消息长度中以传达附加信息——例如,一个标志当前块是否是最后一个的位。
- 您的
send()
和recv()
调用可能因与连接及其持续可行性无关的原因而失败。例如,它们可以被信号中断。由于如果发送方和接收方之间的通信被中断,您将无法 re-sync 通信,因此您应该确保在发生错误时终止通信,尽管实际上不必由您的 send 和 recv 处理自己发挥作用。