私は単純なアプリケーションを C で作成しました。このアプリケーションは、1 つの子をネットワーク サーバーとして機能させ、多くの子をネットワーク クライアントとして機能させます。クライアントはサーバーに接続し、データを要求します。コードは次のとおりです。
#include <stdio.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/queue.h>
#include <strings.h>
#include <sys/wait.h>
#define LISTENSOCKET 1519
#define MAXCLIENT 200
#define MAXLINE 80
void client_do_something(int c)
{
printf("Process %d, server gave %d\n", getpid(), c);
}
void client_body()
{
struct sockaddr_in clientaddr;
struct sockaddr_in localaddr;
int sockfd;
int nread;
int s;
char serveraddr[20] = "127.0.0.1";
char laddr[20];
char command[4] = "GET";
int c;
command[3] = '\0';
socklen_t len;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
clientaddr.sin_family = AF_INET;
clientaddr.sin_port = htons(LISTENSOCKET);
inet_pton(AF_INET, serveraddr, &clientaddr.sin_addr);
printf("Client %d started\n", getpid());
if ((s=connect(sockfd, (struct sockaddr *) &clientaddr, sizeof(clientaddr))) != 0) {
perror("conn err:");
printf("Connect error: pid %d %d\n", getpid(), errno);
close(sockfd);
sleep(1);
exit(1);
}
getsockname(sockfd, (struct sockaddr *) &localaddr, &len);
inet_ntop(AF_INET, &localaddr.sin_addr, laddr, sizeof(laddr));
printf("Client %d passed connect (%d), %s:%d \n", getpid(), s, laddr, ntohs(localaddr.sin_port));
while(1) {
send(sockfd, command, 4, 0);
if ( (nread = recv(sockfd, &c, 4, 0)) < 0 ) {
if (errno == ENOTCONN) {
sleep(1);
continue;
}
perror("client recv err:");
printf("Client %d received error %d ", getpid(), errno);
exit(1);
} else if (nread == 0) {
printf("Pid %d received FIN\n", getpid());
close(sockfd);
exit(0);
}
client_do_something(c);
}
}
int start_server() {
int i, nread, maxi, val, listenfd, connfd, sockfd, maxfd, nready, client[MAXCLIENT];
socklen_t len;
char *c;
struct sockaddr_in servaddr, clientaddr, localaddr;
fd_set rset, allset;
char addr[MAXLINE];
char laddr[INET_ADDRSTRLEN];
struct timeval timeout;
printf("Started\n");
printf("Server PID=%d", getpid());
val = 0;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(LISTENSOCKET);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
perror("socket failed\n");
if(bind(listenfd, (struct sockaddr *) &servaddr, (socklen_t) sizeof(servaddr)) == -1)
perror("listen failed\n");
getsockname(listenfd, (struct sockaddr *) &localaddr, &len);
inet_ntop(AF_INET, &localaddr.sin_addr, laddr, INET_ADDRSTRLEN);
printf("Server %d passed connect , %s:%d \n", getpid(), laddr, ntohs(localaddr.sin_port));
if(listen(listenfd, 10) == -1)
perror("listen failed\n");
maxi = -1;
for(i=0; i < MAXCLIENT; i++)
client[i] = -1;
maxfd = listenfd;
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
bzero(&timeout, sizeof(struct timeval));
timeout.tv_sec = 15;
printf("Server process, after listen, sleep 5s before accept\n");
/* Here I purpousely sleep because I want client to initiate connection before accept */
sleep(5);
printf("Server porcess, slept for 5s\n");
while(1) {
rset = allset;
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
if (FD_ISSET(listenfd, &rset)) {
len = sizeof(clientaddr);
bzero(&clientaddr, sizeof(clientaddr));
if((connfd = accept(listenfd, (struct sockaddr *) &clientaddr, &len)) < 0) {
perror("accept failed");
exit(-1);
}
for(i=0; i < MAXCLIENT; i++) {
if(client[i] < 0) {
client[i] = connfd;
break;
}
}
FD_SET(connfd, &allset);
if(connfd >= maxfd)
maxfd = connfd;
if(i > maxi)
maxi = i;
if(--nready <= 0)
continue;
}
for(i=0; i <= maxi; i++) {
if ( (sockfd = client[i]) < 0)
continue;
if(FD_ISSET(sockfd, &rset)) {
if( (nread = read(sockfd, addr, MAXLINE)) < 0) {
if(errno == EINTR)
nread = 0;
else {
printf("Izlazim");
return -1;
}
} else if (nread == 0) {
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
continue;
}
if (strncmp(addr, "GET", 3) == 0) {
if(val < 100) {
i = send(sockfd, (int *) &val, (size_t) sizeof(val), 0);
val++;
if (i == -1)
printf("nread=%d, errno=%d\n",nread, errno);
} else {
FD_CLR(sockfd, &allset);
client[i] = -1;
close(sockfd);
}
} else {
c = addr;
printf("Poslao je addr=%s\n", addr);
}
if(--nready <= 0)
break;
}
}
}
}
int main(int argc, char *argv[]) {
printf("Pid=%d",getpid());
int i, child_num, status;
pid_t p, pp;
child_num = 100;
printf("%d childs, pid=%d \n", child_num, getpid());
p = fork();
if(p == 0) {
start_server();
exit(0);
} else if (p < 0) {
exit(1);
}
pp = p;
for(i = 0; i < child_num; i++) {
p = fork();
if (p < 0) {
exit(1);
} else if (p == 0){
client_body();
exit(0);
}
}
for(i = 0; i < child_num; i++) {
wait(&status);
}
kill(pp, SIGTERM);
return 0;
}
問題は connect() システム コール (client_body 関数内) にあります。man ページによると、connect は、接続が成功した場合は 0 を返し、エラーの場合は -1 を返す必要があります。私のプログラムでは、サーバーとの接続を確立していないにもかかわらず、connect() が 0 を返したことに気付きました (サーバーから SYN ACK を受信しました)。その後、プログラム内で、同じ子プロセスが recv システム コールを発行し、errno 値 104 (ECONNRESET) のエラーが発生しました。Wireshark のキャプチャをずっと見ていましたが、サーバーから送信された TCP パケットに RESET フラグがあることに気づきませんでした。
誰が何が悪いのか分かりますか?Linux、2.6.38-8-generic カーネルでこのコードをテストしました。