我想学习如何用C编写并发TCP客户端服务器,但我无法理解迭代服务器客户端程序和并发服务器客户端程序之间的区别。在互联网上我找不到太多信息。我在www.geeksfoeks.org网站上查看了TCP客户端服务器的C实现,但我认为这是一个迭代的例子。我如何使它并发?
TCP服务器:
#include <netdb.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#define MAX 80
#define PORT 8080
#define SA struct sockaddr
// Function designed for chat between client and server.
void func(int sockfd)
{
char buff[MAX];
int n;
// infinite loop for chat
for (;;) {
bzero(buff, MAX);
// read the message from client and copy it in buffer
read(sockfd, buff, sizeof(buff));
// print buffer which contains the client contents
printf("From client: %st To client : ", buff);
bzero(buff, MAX);
n = 0;
// copy server message in the buffer
while ((buff[n++] = getchar()) != 'n')
;
// and send that buffer to client
write(sockfd, buff, sizeof(buff));
// if msg contains "Exit" then server exit and chat ended.
if (strncmp("exit", buff, 4) == 0) {
printf("Server Exit...n");
break;
}
}
}
// Driver function
int main()
{
int sockfd, connfd, len;
struct sockaddr_in servaddr, cli;
// socket create and verification
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket creation failed...n");
exit(0);
}
else
printf("Socket successfully created..n");
bzero(&servaddr, sizeof(servaddr));
// assign IP, PORT
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);
// Binding newly created socket to given IP and verification
if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0) {
printf("socket bind failed...n");
exit(0);
}
else
printf("Socket successfully binded..n");
// Now server is ready to listen and verification
if ((listen(sockfd, 5)) != 0) {
printf("Listen failed...n");
exit(0);
}
else
printf("Server listening..n");
len = sizeof(cli);
// Accept the data packet from client and verification
connfd = accept(sockfd, (SA*)&cli, &len);
if (connfd < 0) {
printf("server acccept failed...n");
exit(0);
}
else
printf("server acccept the client...n");
// Function for chatting between client and server
func(connfd);
// After chatting close the socket
close(sockfd);
}
TCP客户端:
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#define MAX 80
#define PORT 8080
#define SA struct sockaddr
void func(int sockfd)
{
char buff[MAX];
int n;
for (;;) {
bzero(buff, sizeof(buff));
printf("Enter the string : ");
n = 0;
while ((buff[n++] = getchar()) != 'n')
;
write(sockfd, buff, sizeof(buff));
bzero(buff, sizeof(buff));
read(sockfd, buff, sizeof(buff));
printf("From Server : %s", buff);
if ((strncmp(buff, "exit", 4)) == 0) {
printf("Client Exit...n");
break;
}
}
}
int main()
{
int sockfd, connfd;
struct sockaddr_in servaddr, cli;
// socket create and varification
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket creation failed...n");
exit(0);
}
else
printf("Socket successfully created..n");
bzero(&servaddr, sizeof(servaddr));
// assign IP, PORT
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(PORT);
// connect the client socket to server socket
if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0) {
printf("connection with the server failed...n");
exit(0);
}
else
printf("connected to the server..n");
// function for chat
func(sockfd);
// close the socket
close(sockfd);
}
如注释所述,迭代服务器将按顺序处理每个连接。在您发布的示例中,如果您运行服务器(在忽略一系列警告之后),则只能连接一个客户端。第二个客户端将成功连接服务器,但服务器永远不会对其做出响应。只有第一个客户端会发送消息并获得响应。并发实现将并行处理两个客户端,并且能够从这两个客户端获取消息。我可以想出3种方法来做到这一点,但其中一种是不推荐的:
- 如注释中所建议的,将accept调用放入循环中。它将被阻止,直到有人连接。当有人连接时,您生成一个线程,并为它提供与客户端通信所需的所有信息,文件描述符(
sockfd
)应该足以作为一个简单的示例。之后线程可以执行你的func
,因为这个线程只知道一个客户端,但主循环会再次阻塞accept
,等待另一个连接。我在谷歌上找到了这个例子 - 使用POSIX系统调用select(或者它的替代
poll
和epoll
),可以监视套接字的活动列表。如果任何受监视的套接字都有活动,则它的read
或accept
不会阻塞,然后处理这些套接字。如果服务器文件描述符有活动,则意味着连接了新的客户端,您应该将客户端文件描述符存储在某个位置,并开始将其传递给select。如果客户端文件描述符有活动,则可以像以前一样处理它们的消息。这种方式不需要线程,所有事情都在主例程上处理,根据您的用例可能会有优势我在谷歌上找到了这两个例子:这里和这里 - 不建议使用这种方式:使用
fcntl
使服务器套接字不阻塞。这样,accept就不会阻塞,而是会立即返回一个错误EAGAIN
或EWOULDBLOCK
。然后,您可以在主循环上重复调用accept,大多数时候它会返回一个错误,但不返回的错误会发出新客户端连接的信号。当客户端连接时,您也可以使其文件描述符不阻塞,并将其存储在某个地方。在您的主例程中,对于每个客户端套接字,您将在它们上尝试read
,但如果没有新的信息可用,它也会返回一个错误。如果有可用的东西,你会像以前一样对待它。这具有总是要求100%CPU利用率的缺点。您可以在循环中插入延迟,但这会增加额外的不必要的延迟。你不应该这样做