我正在尝试C
中的UDP应用程序,其中应用程序必须同时发送和接收数据。类似于我们的聊天应用程序。当一个人输入数据时,他应该能够接收数据。
我正在计划如何实现这一点。如果我的想法是正确的,请帮助我。
或者请建议我实现这一目标的方法。 pthread条件变量是否适用于此场景。其他想法也欢迎。
答案 0 :(得分:5)
使用传输和接收线程的方法应该有效,但是您稍后提到要在线程上使用非阻塞IO。这很好,但我有点疑惑为什么你想同时使用两个非阻塞IO 和线程。
如果您正在使用线程,则无需将套接字标记为非阻止 - 只需使用标准阻止recvfrom()
和read()
调用即可。当您从发送线程中的read()
获取stdin输入时,您将发送一条带有sendto()
的消息,当您在接收线程中收到消息时,您可以正常显示它到stdout。
除非你在线程之间传递信息,否则你不应该需要一个pthreads条件变量 - 如果你想要的不仅仅是双向聊天(即多个用户),那么你可能需要这个。
如果您正在使用非阻塞IO(我认为这是更清洁的解决方案 - 尽可能避免使用线程),您通常不需要使用线程 - 您可以使用select()
等功能和poll()
来管理您的文件描述符。我建议poll()
,因为我认为它有一个更方便的界面,但两者都可以。在Linux上也可能有更高效的等效项,例如epoll()
,但通常您可以忽略这些,除非您的应用程序将处理大量流量。
您通常只有一个带循环的线程。在循环开始时,您调用poll()
或select()
,这将阻塞,直到读取其中一个文件描述符(可以是套接字)进行读取或写入。您可以监听多个文件描述符,但在您的情况下,它也可以正常使用。
函数返回后,如果poll()
或select()
指示为“已准备好”,则可以从套接字读取信息,然后根据需要发送信息。如果这是一个面向连接的套接字,你需要缓冲输出并监视套接字是否为“可写”,并根据需要从输出缓冲区刷新数据。但是,对于UDP而言,“写就绪”的概念并不存在,所以只要有数据就可以发送数据。
事实上,如果您使用poll()
或类似的类似内容,则甚至不需要将套接字标记为非阻塞。请参阅下面的示例代码,它实现了一个非常简单的UDP聊天客户端。您必须在命令行上手动指定目标IP地址和端口,并且一些错误检查非常简单,因为它只是示例代码,但它应该足以满足您自己的目的。
你可以在Unix机器上测试这个,方法是编译成一个名为“chat”的二进制文件,然后在一个运行中打开两个终端窗口:
chat 8888 127.0.0.1 9999
......而另一个正在运行:
chat 9999 127.0.0.1 8888
请注意,第一个端口是侦听端口,其余两个参数指定要连接到远程对等端的IP地址和端口。
代码在这里:
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void start_chat(int sock_fd, struct sockaddr_in *peer)
{
int ret;
ssize_t bytes;
char input_buffer[1024];
char output_buffer[1024];
struct pollfd fds[2];
/* Descriptor zero is stdin */
fds[0].fd = 0;
fds[1].fd = sock_fd;
fds[0].events = POLLIN | POLLPRI;
fds[1].events = POLLIN | POLLPRI;
/* Normally we'd check an exit condition, but for this example
* we loop endlessly.
*/
while (1) {
/* Call poll() */
ret = poll(fds, 2, -1);
if (ret < 0) {
printf("Error - poll returned error: %s\n", strerror(errno));
break;
}
if (ret > 0) {
/* Regardless of requested events, poll() can always return these */
if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("Error - poll indicated stdin error\n");
break;
}
if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("Error - poll indicated socket error\n");
break;
}
/* Check if data to read from stdin */
if (fds[0].revents & (POLLIN | POLLPRI)) {
bytes = read(0, output_buffer, sizeof(output_buffer));
if (bytes < 0) {
printf("Error - stdin error: %s\n", strerror(errno));
break;
}
printf("Sending: %.*s\n", (int)bytes, output_buffer);
bytes = sendto(sock_fd, output_buffer, bytes, 0,
(struct sockaddr *)peer, sizeof(struct sockaddr_in));
if (bytes < 0) {
printf("Error - sendto error: %s\n", strerror(errno));
break;
}
}
/* Check if data to read from socket */
if (fds[1].revents & (POLLIN | POLLPRI)) {
bytes = recvfrom(sock_fd, input_buffer, sizeof(input_buffer),
0, NULL, NULL);
if (bytes < 0) {
printf("Error - recvfrom error: %s\n", strerror(errno));
break;
}
if (bytes > 0) {
printf("Received: %.*s\n", (int)bytes, input_buffer);
}
}
}
}
}
int main(int argc, char *argv[])
{
unsigned long local_port;
unsigned long remote_port;
int sock_fd;
struct sockaddr_in server_addr;
struct sockaddr_in peer_addr;
/* Parse command line arguments for port numbers */
if (argc < 4) {
printf("Usage: %s <local port> <remote host> <remote port>\n", argv[0]);
return 1;
}
local_port = strtoul(argv[1], NULL, 0);
if (local_port < 1 || local_port > 65535) {
printf("Error - invalid local port '%s'\n", argv[1]);
return 1;
}
remote_port = strtoul(argv[3], NULL, 0);
if (remote_port < 1 || remote_port > 65535) {
printf("Error - invalid remote port '%s'\n", argv[3]);
return 1;
}
/* Parse command line argument for remote host address */
peer_addr.sin_family = AF_INET;
peer_addr.sin_port = htons(remote_port);
if (inet_aton(argv[2], &peer_addr.sin_addr) == 0) {
printf("Error - invalid remote address '%s'\n", argv[2]);
return 1;
}
/* Create UDP socket */
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_fd < 0) {
printf("Error - failed to open socket: %s\n", strerror(errno));
return 1;
}
/* Bind socket */
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(local_port);
if (bind(sock_fd, (struct sockaddr *)(&server_addr),
sizeof(server_addr)) < 0) {
printf("Error - failed to bind socket: %s\n", strerror(errno));
return 1;
}
/* Call chat handler to loop */
start_chat(sock_fd, &peer_addr);
close(sock_fd);
return 0;
}