我用 C 语言编写了一个简单的应用程序,它派生一个孩子充当网络服务器,而许多孩子充当网络客户端。客户端连接到服务器,并请求数据。这是代码:
#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 函数中);根据手册页,如果连接成功,连接应该返回 0,错误时返回 -1。我注意到,在我的程序中,connect() 返回 0 尽管它没有与服务器建立连接(从服务器接收到 SYN ACK)。后来,在程序中,同一个子进程发出了recv系统调用,产生了errno值为104(ECONNRESET)的错误。我一直在观看wireshark 捕获,并没有注意到服务器发送的任何TCP 数据包中的RESET 标志。
有人知道出了什么问题吗?我已经在 linux,2.6.38-8-generic 内核上测试了这段代码。