这是关于多客户端的套接字编程的问题.
当我在考虑如何将我的单个客户端和服务器程序转换为多客户端时,我遇到了如何实现它.但即使我在寻找一切,也存在一种混乱.
我想用select()实现,因为它比fork更重.但我有很多全局变量不能共享,所以我没有考虑使用线程.
所以要使用select(),我可以掌握有关FD_functions的一般知识,但这里我有我的问题,因为一般在网站上的例子中,它只显示多客户端服务器程序...
因为我在客户端和服务器程序中使用顺序recv()和send(),它在单个客户端和服务器上工作得很好,但我不知道如何为多个静音更改它.客户端是否也必须解锁?select()的所有要求是什么?
我在服务器程序上做的事情是多客户端
1)我使用SO_REUSEADDR为重用地址设置了socket选项
2)使用fctl()使用O_NONBLOCK将我的服务器设置为非阻塞模式.
3)并将超时参数设置为零.
并在上面正确使用FD_functions.
但是,当我从第二个客户端运行我的客户端程序时,客户端程序阻塞,而不是被服务器接受.
我想原因是因为我将服务器程序的主要功能部分放入'recv was> 0'的情况.
例如,使用我的服务器代码,
(我使用temp并读取为fd_set,并在此情况下读为master)
int main(void) { int conn_sock, listen_sock; struct sockaddr_in s_addr, c_addr; int rq, ack; char path[100]; int pre, change, c; int conn, page_num, x; int c_len = sizeof(c_addr); int fd; int flags; int opt = 1; int nbytes; fd_set read, temp; if ((listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror("socket error!"); return 1; } memset(&s_addr, 0, sizeof(s_addr)); s_addr.sin_family = AF_INET; s_addr.sin_addr.s_addr = htonl(INADDR_ANY); s_addr.sin_port = htons(3500); if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) == -1) { perror("Server-setsockopt() error "); exit(1); } flags = fcntl(listen_sock, F_GETFL, 0); fcntl(listen_sock, F_SETFL, flags | O_NONBLOCK); //fcntl(listen_sock, F_SETOWN, getpid()); bind(listen_sock, (struct sockaddr*) &s_addr, sizeof(s_addr)); listen(listen_sock, 8); FD_ZERO(&read); FD_ZERO(&temp); FD_SET(listen_sock, &read); while (1) { temp = read; if (select(FD_SETSIZE, &temp, (fd_set *) 0, (fd_set *) 0, (struct timeval *) 0) < 1) { perror("select error:"); exit(1); } for (fd = 0; fd < FD_SETSIZE; fd++) { //CHECK all file descriptors if (FD_ISSET(fd, &temp)) { if (fd == listen_sock) { conn_sock = accept(listen_sock, (struct sockaddr *) &c_addr, &c_len); FD_SET(conn_sock, &read); printf("new client got session: %d\n", conn_sock); } else { nbytes = recv(fd, &conn, 4, 0); if (nbytes <= 0) { close(fd); FD_CLR(fd, &read); } else { if (conn == Session_Rq) { ack = Session_Ack; send(fd, &ack, sizeof(ack), 0); root_setting(); c = 0; while (1) { c++; printf("in while loop\n"); recv(fd, &page_num, 4, 0); if (c > 1) { change = compare_with_pre_page(pre, page_num); if (change == 1) { page_stack[stack_count] = page_num; stack_count++; } else { printf("same as before page\n"); } } //end of if else if (c == 1) { page_stack[stack_count] = page_num; stack_count++; } printf("stack count:%d\n", stack_count); printf("in page stack: <"); for (x = 0; x < stack_count; x++) { printf(" %d ", page_stack[x]); } printf(">\n"); rq_handler(fd); if (logged_in == 1) { printf("You are logged in state now, user: %s\n", curr_user.ID); } else { printf("not logged in.\n"); c = 0; } pre = page_num; } //end of while } //end of if } } //end of else } //end of fd_isset } //end of for loop } //end of outermost while }
如果代码解释需要的话:这个代码的工作原理是,制作一些网页来实现服务器的"浏览器".我想让每个客户端获取服务器的会话以获得登录页面左右.
但正如我上面所说,执行结果是.这是为什么?
客户端程序中的套接字必须是非阻塞模式,以便与非阻塞服务器程序一起使用select()?
或者我应该使用fork或thread来制作多客户端并使用select进行管理?我说这个的原因是,在我考虑了很多关于这个问题之后,'select()'似乎只适用于多客户端聊天程序......许多'分叉'或'线程'客户端可以等待,例如聊天房间.你觉得怎么样?...选择正常的多客户端程序也可以使用或正确吗?
如果有什么我错过了让我的多客户端程序正常工作,请给我一些你的知识或正确使用选择的一些要求.我之前不知道多客户端通信是不是很容易:)我也考虑使用epoll但我认为我需要首先了解选择好.
谢谢阅读.
除了你想要从单客户端到多客户端的事实,还不是很清楚什么在这里阻止你.
你确定你完全明白应该怎么做select
?手册(man 2 select
在Linux上)可能会有所帮助,因为它提供了一个简单的示例.您还可以查看维基百科.
回答你的问题:
首先,你确定你的插座需要非阻塞模式吗?除非你有充分的理由这样做,否则阻塞套接字也适用于多客户端网络.
通常,在C:fork
或中,基本上有两种处理多客户端的方法select
.这两个并没有真正使用(或者我不知道如何:-)).使用轻量级线程的模型本质上是异步编程(我提到它还取决于你所说的'异步'是什么意思?)并且可能对你的工作有点过分(C++中的一个很好的例子是Boost.Asio).
正如您可能已经知道的那样,处理多个客户端时的主要问题是I/O操作(如a read
)正在阻塞,不会让我们知道何时有新客户端,或者客户端说了些什么.
该fork
方法是相当简单明了:服务器插槽(其接受连接的一个)是主要过程,并且每次接受新的客户端时,它fork了一个全新的过程只是为了监测这一新的客户:这种新工艺将是献给它.由于每个客户端有一个进程,我们不关心i/o操作是否阻塞.
这种select
方式允许我们在同一个进程中监视多个客户端:它是一个多路复用器告诉我们什么时候我们给它的套接字发生了什么.服务器端的基本思想是首先将服务器套接字放在select的read_fds FD_SET上.每次select
返回时,您需要对其进行特殊检查:如果服务器套接字设置在read_fds集中(使用FD_ISSET(...)),则表示您有一个新客户端连接:您可以随后调用accept
您的服务器socket用于创建连接.然后,您必须将所有客户端套接字放入您提供的fd_sets中select
,以便监视其上的任何更改(例如,传入消息).
我不太确定你不明白的东西select
,所以这就是重要的解释.但长话短说,这select
是一种干净利落的单线程同步网络方式,它可以在不使用任何fork
线程的情况下同时完全管理多个客户端.请注意,如果您绝对想要处理非阻塞套接字select
,则必须处理不会以阻塞方式处理的额外错误条件(维基百科示例显示它很好,因为它们必须检查是否errno
不是EWOULDBLOCK
) .但这是另一个故事.
编辑:好的,通过更多的代码,更容易知道什么是错的.
select
第一个参数应该是nfds + 1,即"三组中任何一组中编号最大的文件描述符加1"(参见手册),而不是FD_SETSIZE,这是FD_SET的最大大小.通常它是最后一个accept
客户端套接字(或开头的服务器套接字)拥有它.
您不应该像这样循环"检查所有文件描述符".FD_SETSIZE,例如在我的机器上,等于1024.这意味着一旦select返回,即使你只有一个客户端,你将在循环中传递1024次!您可以设置fd
为0(就像在Wikipedia示例中一样),但是因为0是stdin
1 stdout
和2 stderr
,除非您正在监视其中一个,否则可以直接将其设置为服务器套接字的fd(因为它可能是第一个监视套接字,给定套接字编号总是增加),并迭代直到它等于"nfds"(当前最高的fd).
不确定它是强制性的,但在每次调用之前select
,您应该清除(例如使用FD_ZERO)并使用您要监视的所有套接字(即您的服务器套接字和所有客户端套接字)重新填充读取的fd_set.再一次,启发自己的维基百科示例.