我最近得到了多个项目的想法,都涉及从文件中读取IP地址。由于它们都应该能够处理大量主机,因此我尝试实现多线程或创建套接字池并从中选择()以实现某种形式的并发更好的性能。在多个场合,从文件中读取似乎是提高性能的瓶颈。我理解它的方式,从具有fgets或类似的文件读取是一个同步,阻塞操作。因此,即使我成功实现了一个异步连接多个主机的客户端,该操作仍然是同步的,因为我一次只能从一个文件中读取一个地址。
/* partially pseudo code */
/* getaddrinfo() stuff here */
while(fgets(ip, sizeof(ip), file) {
FD_ZERO(&readfds);
/* create n sockets here in a for loop */
for (i = 0; i < socket_num; i++) {
if (newfd > fd[i]) newfd = fd[i];
FD_SET(fd[i], &readfds);
}
/* here's where I think I should connect n sockets to n addresses from file
* but I'm only getting one IP at a time from file, so I'm not sure how to connect to
* n addresses at once with fgets
*/
for (j = 0; j < socket_num; j++) {
if ((connect(socket, ai->ai_addr, ai->ai_addrlen)) == -1)
// error
else {
freeaddrinfo(ai);
FD_SET(socket, &master);
fdmax = socket;
if (select(socket+1, &master, NULL, NULL, &tv) == -1);
// error
if ((recvd = read(socket, banner, RECVD)) <= 0)
// error
if (FD_ISSET(socket, &master))
// print success
}
/* clear sets and close sockets and stuff */
}
我已经用注释指出了我的问题,但只是为了澄清:我不确定如何在从文件读取的多个目标服务器上执行异步I / O操作,因为从文件中读取条目似乎要严格同步。我遇到了类似的多线程问题,成功程度略高一些。
void *function_passed_to_pthread_create(void *opts)
{
while(fgets(ip_addr, sizeof(ip_addr), opts->file) {
/* speak to ip_addr and get response */
}
}
main()
{
/* necessary stuff */
for (i = 0; i < thread_num; i++) {
pthread_create(&tasks, NULL, above_function, opts)
}
for (j = 0; j < thread_num; j++)
/* join threads */
return 0;
}
这似乎有效,但由于多个线程都在处理同一个文件,因此结果并不总是准确的。我想象一下,因为多个线程可能同时处理来自文件的相同地址。
我考虑将文件中的所有条目加载到数组/内存中,但如果文件特别大,我想可能会导致内存问题。最重要的是,我不确定无论如何都要做到这一点。
作为最后的说明;如果我从中读取的文件碰巧是一个包含大量IP的特别大的文件,那么我不相信任何一种解决方案都可以很好地扩展。尽管如此,C还是有可能的,所以我想有一些方法可以达到我希望的目的。
总结这篇文章;我想在从文件中读取条目时,使用异步I / O或多线程来找到改善客户端应用程序性能的方法。
答案 0 :(得分:5)
有些人在他们的评论中暗示了一个很好的解决方案,但可能更值得详细解释。 完整的解决方案有很多细节,代码非常复杂,所以我将使用伪代码来解释我推荐的内容。
你所拥有的实际上是对经典生产者/消费者问题的一种变化:你只有一个产生数据的东西,以及许多试图消费这些数据的东西。在你的情况下,它必须是一个单一的东西&#34;产生这些数据,因为源文件的每一行的长度都是未知的:你不能只是向前跳过&#39; n&#39;字节,不知何故在下一个IP。一次只能有一个actor将读指针移动到\n
的下一个未知位置,因此根据定义你有一个生产者。
有三种方法可以解决这个问题:
解决方案A 涉及让每个线程从共享文件缓冲区中拉出更多,并在每次最后一次启动异步(非阻塞)读取时阅读完成。由于它对文件系统和正在执行的工作之间的时序差异非常敏感,因此有很多令人头痛的问题,因为它对文件系统和正在执行的工作之间的时序差异非常敏感:如果文件读取速度很慢,那么工作人员都将停止等待文件。如果工作人员很慢,文件阅读器将停止或填满内存,等待他们使用数据。这个解决方案很可能绝对最快,但同样令人难以置信的同步代码可以解决大量的问题。除非您是线程专家(或极其巧妙地滥用epoll_wait()
),否则您可能不想走这条路。
解决方案B 有一个&#34; master&#34;线程,负责读取文件,并使用它读取的数据填充某种线程安全队列,每个队列条目有一个IP地址(一个字符串)。每个工作线程只是尽可能快地使用队列条目,查询远程服务器然后请求另一个队列条目。这需要一点点关注,但通常比解决方案A 更安全,特别是如果你使用其他人的队列实现。
解决方案C 非常讨厌,但根据您正在做的事情,您不应该将其解雇。 。这个解决方案只涉及使用Un * x sed
命令(参见Get a range of lines from a file given the start and end line numbers)将源文件切割成一堆&#34; chunky&#34;提前提供源文件 - 比方说,其中有二十个。然后你使用&
并行地运行一个非常简单的单线程程序的20个副本,每个程序在不同的&#34;切片上#34;的文件。与一个小的shell脚本一起自动化它,这可以是一个足够好的&#34;解决方案满足了很多需求。
让我们仔细看看解决方案B - 具有线程安全队列的主线程。我将作弊并假设您可以构建一个工作队列实现(如果没有,有关于使用pthreads实现线程安全队列的StackOverflow文章:pthread synchronized blocking queue)。
在伪代码中,这个解决方案是这样的:
main()
{
/* Create a queue. */
queue = create_queue();
/* Kick off the master thread to read the file, and give it the queue. */
master_thread = pthread_create(master, queue);
/* Kick off a bunch of workers with access to the queue. */
for (i = 0; i < 20; i++) {
worker_thread[i] = pthread_create(worker, queue);
}
/* Wait for everybody to finish. */
pthread_join(master_thread);
for (i = 0; i < 20; i++) {
pthread_join(worker_thread[i]);
}
}
void master(queue q)
{
FILE *fp = fopen("ips.txt", "r");
char buffer[BIGGER_THAN_ANY_IP];
/* Inhale the file as fast as we can, and push each line we
read onto the queue. */
while (fgets(fp, buffer) != NULL) {
char *next_ip = strdup(buffer);
enqueue(q, next_ip);
}
/* Add some final messages in the queue to let the workers
know that we're out of data. There are *much* better ways
of notifying them that we're "done", but in this case,
pushing a bunch of NULLs equal to the number of threads is
simple and probably good enough. */
for (i = 0; i < 20; i++) {
enqueue(q, NULL);
}
}
void worker(queue q)
{
char *ip;
/* Inhale messages off the queue as fast as we can until
we get a "NULL", which means that it's time to stop.
The call to dequeue() *must* block if there's nothing
in the queue; the call should only return NULL if the
queue actually had NULL pushed into it. */
while ((ip = dequeue(q)) != NULL) {
/* Insert code to actually do the work here. */
connect_and_send_and_receive_to(ip);
}
}
在实际实现中有很多警告和细节(例如:我们如何实现队列,环形缓冲区或链接列表?如果文本不是所有IP,该怎么办?如果char缓冲区不是&#该怎么办? 39;足够大?有多少线程就够了?我们如何处理文件或网络错误?malloc性能会成为瓶颈吗?如果队列变得太大会怎么样?我们能不能更好地重叠网络I / O?)
但是,除了警告和细节外,我上面提到的伪代码是一个很好的起点,你可以将它扩展为一个可行的解决方案。
答案 1 :(得分:0)
从文件中读取IP,拥有工作线程,继续向工作线程提供IP。让所有套接字通信都发生在工作线程中。此外,如果IPv4地址存储为十六进制格式而不是ascii,可能只能在一次拍摄中读取它们的倍数,并且会更快。
答案 2 :(得分:0)
如果您只想异步读取,可以使用ncurses中的getch(),将延迟设置为0.它是posix的一部分,因此您不需要任何其他依赖项。你也有unlocked_stdio。
另一方面,我不禁要问为什么fgets()成为瓶颈。只要您在文件中有数据,它就不应该阻止。即使数据量巨大(如1MB或100k ip地址),在启动时将其读入列表也应该不到1秒钟。
为什么要打开列表中每个ip的sockets_num连接?你有sockets_num同时乘以ip地址的数量。由于每个套接字都是Linux上的文件,当您尝试打开超过几千个文件时,您将遇到系统问题(请参阅ulimit -Sn)。在这种情况下,您能确认问题不在connect()中吗?