【发布时间】:2019-12-04 09:44:42
【问题描述】:
代码说明
我有一个使用 select() 的并发服务器,它为每 2 个连接的客户端生成一个子节点并获取它们的名称。父级从客户端的套接字读取名称并通过管道将其发送给子级(每个子级都有自己的管道与父级通信,存储在 struct child_fd[] 中,以及它的 2 个客户端的文件描述符),后者回写给客户端.
错误
在 297 行(我在这样的代码中突出显示了它: ----- !!! LINE 297 !!! ----- )(145 在最小复制中)当我写回客户端时,在服务器代码中出现错误的文件描述符错误。我从不关闭程序中的任何套接字,而且,我在编写之前打印了 player2_fd (player_fd = 7),这是正确的:我还在 141 行(25 in Minimal replication) with accept() 也是 7。
此外,即使在我通过管道将这个描述符从父母发送给孩子的那一刻,它也是 7 .
观察
第一个客户端和第二个客户端之间的唯一区别是第一个的描述符在分叉的那一刻被赋予 fork child(因为它复制了所有变量),而第二个是通过管道发送的。
最小生殖示例服务器代码:
int main ()
{
struct {
int player1_fd, player2_fd, pipe[2];
}child_fd[20];
// CODE FOR CONNECTION: listen(), bind()...
/* servim in mod concurent clientii... */
while (1)
{
if (select (nfds+1, &readfds, NULL, NULL, NULL) < 0)
{
perror ("[server] Eroare la select().\n");
return errno;
}
if (FD_ISSET (sd, &readfds)) {
// ----- !!! LINE 25 !!! ------ //
client = accept(sd, (struct sockaddr *) &from, &len);
/* eroare la acceptarea conexiunii de la un client */
if (client < 0) {
perror("[server] Eroare la accept().\n");
continue;
}
if (nfds < client) /* ajusteaza valoarea maximului */
nfds = client;
/* includem in lista de descriptori activi si acest socket */
FD_SET (client, &actfds);
printf("[server] S-a conectat clientul cu descriptorul %d, de la adresa %s.\n", client, conv_addr(from));
fflush(stdout);
clientCounter++;
if (clientCounter % 2 == 1) // if it's a player waiting for opponent
{
child_fd[nrOfChilds].player2_fd = -1; // player2_fd is not known yet
child_fd[nrOfChilds].player1_fd = client; // remember it's fd (need it in match handler child)
if (-1 == pipe(child_fd[nrOfChilds].pipe)) {
perror("Error at creating pipe\n");
return errno;
}
if (-1 == (pid = fork())) {
perror("Error at fork.\n");
return errno;
}
if (pid == 0) { // [child that handles a chess match]
/* child only needs it's own information from the struct so I will create local variables*/
int p[] = {child_fd[nrOfChilds].pipe[0], child_fd[nrOfChilds].pipe[1]},
player1_fd = child_fd[nrOfChilds].player1_fd,
player2_fd = child_fd[nrOfChilds].player2_fd;
int infoFromPipe;
char msgrasp[20]=" ";
printf("pipe: %d %d", p[0],p[1]);
fflush(stdout);
while(1)
{
if (-1 == read(p[0], &infoFromPipe, sizeof(int)))
{
perror("Error at reading from pipe\n");
return errno;
}
printf("--%d--", infoFromPipe);
fflush(stdout);
if (infoFromPipe == 1) { // means there is a fd for player2 coming
if (-1 == read(p[0], &player2_fd, sizeof(int)))
{
perror("Error at reading from pipe\n");
return errno;
}
printf("\np2 fd:%d\n", player2_fd);
fflush(stdout);
}
else
{ // both players connected
int player;
if (-1 == read(p[0], &player, sizeof(int))) // reading the player int
{
perror("Error at reading from pipe\n");
return errno;
}
printf("player:%d\n",player);
fflush(stdout);
if ( player == 1){
// EXACTLY THE SAME AS FOR PLAYER 2
}
}
else{ //player 2
int nameOrMove;
if (-1 == read(p[0], &nameOrMove, sizeof(int))) // reading the name or move int
{
perror("Error at reading from pipe\n");
return errno;
}
printf("nameormove:%d\n",nameOrMove);
fflush(stdout);
if (nameOrMove == 0) { // name
int bytesRead, playerNameLen = 0;
char playerName[20],c;
bzero(playerName,20);
if( -1 == read(p[0], &playerNameLen, sizeof(int)))
{
perror("Error at reading from pipe.\n");
return errno;
}
if( -1 == read(p[0], playerName, playerNameLen))
{
perror("Error at reading from pipe.\n");
return errno;
}
printf("Playername: %s\n", playerName);
fflush(stdout);
//mesaj de raspuns pentru client
bzero(msgrasp,100);
strcat(msgrasp,"Hello ");
strcat(msgrasp,playerName);
printf("[server]Trimitem mesajul inapoi...%s\n",msgrasp);
fflush(stdout);
printf("p2fd: %d\n", player2_fd);
fflush(stdout);
// ----- !!! LINE 145 !!! ----- //
if(-1 == write(player2_fd, msgrasp, sizeof(msgrasp)))
{
perror("Error at writing to player1");
return errno;
}
printf("Player name sent\n");
fflush(stdout);
}
}
}
}
}
nrOfChilds++;
} else { // if it's the second player to be assigned to a waiting player 1
if (pid > 0) {
int x = 1;
if (-1 == write(child_fd[nrOfChilds-1].pipe[1], &x, sizeof(int)))
{
perror("Error at writing in pipe\n");
return errno;
}
if (-1 == write(child_fd[nrOfChilds-1].pipe[1], &client, sizeof(int))) //sending player2 socket descriptor to it's child process handler
{
perror("Error at writing in pipe\n");
return errno;
}
child_fd[nrOfChilds-1].player2_fd = client; //adding it to struct so the parent knows both players associated to each child process
}
}
}
/* vedem daca e pregatit vreun socket client pentru a trimite raspunsul */
if (pid > 0) {
for (fd = 0; fd <= nfds; fd++) /* parcurgem multimea de descriptori */
{
/* este un socket de citire pregatit? */
if (fd != sd && FD_ISSET (fd, &readfds)) {
for(int i = 0; i< nrOfChilds; ++i){ // looking for the child process to whom the client is associated
if ( fd == child_fd[i].player1_fd || fd == child_fd[i].player2_fd){
// READING FROM CLIENT SOCKET, SENDING PLAYER NAME TO CHILD
}
FD_CLR(fd, &actfds);
}
}
}
}
}
/* for */
}
} /* while */
/* main */
服务器代码:
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdbool.h>
/* portul folosit */
#define PORT 2726
extern int errno; /* eroarea returnata de unele apeluri */
/* functie de convertire a adresei IP a clientului in sir de caractere */
char * conv_addr (struct sockaddr_in address)
{
static char str[25];
char port[7];
/* adresa IP a clientului */
strcpy (str, inet_ntoa (address.sin_addr));
/* portul utilizat de client */
bzero (port, 7);
sprintf (port, ":%d", ntohs (address.sin_port));
strcat (str, port);
return (str);
}
/* programul */
void sayHello(int);
int main ()
{
struct sockaddr_in server; /* structurile pentru server si clienti */
struct sockaddr_in from;
fd_set readfds; /* multimea descriptorilor de citire */
fd_set actfds; /* multimea descriptorilor activi */
struct timeval tv; /* structura de timp pentru select() */
int sd, client; /* descriptori de socket */
int optval=1; /* optiune folosita pentru setsockopt()*/
int fd, clientCounter = 0, nrOfChilds = 0, txt_fd; // descriptor folosit pentru
pid_t pid = 1; //parcurgerea listelor de descriptori
int nfds; /* numarul maxim de descriptori */
int len; /* lungimea structurii sockaddr_in */
bool waitingForOpponent;
struct {
int player1_fd, player2_fd, pipe[2];
}child_fd[20];
bzero(child_fd, sizeof(child_fd));
if(-1 == mkfifo("my_fifo", 0600) ) {
if (errno == EEXIST) {
printf("Using already existent fifo: \"my_fifo\" ...\n");
} else {
perror("Error at creating \"my_fifo\" file.\n");
exit(1);
}
}
/* creare socket */
if ((sd = socket (AF_INET, SOCK_STREAM, 0)) == -1)
{
perror ("[server] Eroare la socket().\n");
return errno;
}
/*setam pentru socket optiunea SO_REUSEADDR */
setsockopt(sd, SOL_SOCKET, SO_REUSEADDR,&optval,sizeof(optval));
/* pregatim structurile de date */
bzero (&server, sizeof (server));
/* umplem structura folosita de server */
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl (INADDR_ANY);
server.sin_port = htons (PORT);
/* atasam socketul */
if (bind (sd, (struct sockaddr *) &server, sizeof (struct sockaddr)) == -1)
{
perror ("[server] Eroare la bind().\n");
return errno;
}
/* punem serverul sa asculte daca vin clienti sa se conecteze */
if (listen (sd, 5) == -1)
{
perror ("[server] Eroare la listen().\n");
return errno;
}
/* completam multimea de descriptori de citire */
FD_ZERO (&actfds); /* initial, multimea este vida */
FD_SET (sd, &actfds); /* includem in multime socketul creat */
tv.tv_sec = 1; /* se va astepta un timp de 1 sec. */
tv.tv_usec = 0;
/* valoarea maxima a descriptorilor folositi */
nfds = sd;
printf ("[server] Asteptam la portul %d...\n", PORT);
fflush (stdout);
/* servim in mod concurent clientii... */
while (1)
{
/* ajustam multimea descriptorilor activi (efectiv utilizati) */
bcopy ((char *) &actfds, (char *) &readfds, sizeof (readfds));
/* apelul select() */
if (select (nfds+1, &readfds, NULL, NULL, NULL) < 0)
{
perror ("[server] Eroare la select().\n");
return errno;
}
/* vedem daca e pregatit socketul pentru a-i accepta pe clienti */
if (FD_ISSET (sd, &readfds)) {
/* pregatirea structurii client */
len = sizeof(from);
bzero(&from, sizeof(from));
/* a venit un client, acceptam conexiunea */
// ----- !!! LINE 141 !!! -----
client = accept(sd, (struct sockaddr *) &from, &len);
/* eroare la acceptarea conexiunii de la un client */
if (client < 0) {
perror("[server] Eroare la accept().\n");
continue;
}
if (nfds < client) /* ajusteaza valoarea maximului */
nfds = client;
/* includem in lista de descriptori activi si acest socket */
FD_SET (client, &actfds);
printf("[server] S-a conectat clientul cu descriptorul %d, de la adresa %s.\n", client, conv_addr(from));
fflush(stdout);
clientCounter++;
if (clientCounter % 2 == 1) // if it's a player waiting for opponent
{
child_fd[nrOfChilds].player2_fd = -1; // player2_fd is not known yet
child_fd[nrOfChilds].player1_fd = client; // remember it's fd (need it in match handler child)
if (-1 == pipe(child_fd[nrOfChilds].pipe)) {
perror("Error at creating pipe\n");
return errno;
}
if (-1 == (pid = fork())) {
perror("Error at fork.\n");
return errno;
}
if (pid == 0) { // [child that handles a chess match]
/* child only needs it's own information from the struct so I will create local variables*/
int p[] = {child_fd[nrOfChilds].pipe[0], child_fd[nrOfChilds].pipe[1]},
player1_fd = child_fd[nrOfChilds].player1_fd,
player2_fd = child_fd[nrOfChilds].player2_fd;
int infoFromPipe;
char msgrasp[20]=" ";
printf("pipe: %d %d", p[0],p[1]);
fflush(stdout);
while(1)
{
if (-1 == read(p[0], &infoFromPipe, sizeof(int)))
{
perror("Error at reading from pipe\n");
return errno;
}
printf("--%d--", infoFromPipe);
fflush(stdout);
if (infoFromPipe == 1) { // means there is a fd for player2 coming
if (-1 == read(p[0], &player2_fd, sizeof(int)))
{
perror("Error at reading from pipe\n");
return errno;
}
printf("\np2 fd:%d\n", player2_fd);
fflush(stdout);
}
else
{ // both players connected
int player;
if (-1 == read(p[0], &player, sizeof(int))) // reading the player int
{
perror("Error at reading from pipe\n");
return errno;
}
printf("player:%d\n",player);
fflush(stdout);
if ( player == 1){
int nameOrMove;
if (-1 == read(p[0], &nameOrMove, sizeof(int))) // reading the name or move int
{
perror("Error at reading from pipe\n");
return errno;
}
printf("nameormove:%d\n",nameOrMove);
fflush(stdout);
if (nameOrMove == 0) { // name
int bytesRead, playerNameLen = 0;
char playerName[20],c;
bzero(playerName,20);
if( -1 == read(p[0], &playerNameLen, sizeof(int)))
{
perror("Error at reading from pipe.\n");
return errno;
}
if( -1 == read(p[0], playerName, playerNameLen))
{
perror("Error at reading from pipe.\n");
return errno;
}
printf("Playername: %s\n", playerName);
fflush(stdout);
//mesaj de raspuns pentru client
bzero(msgrasp,100);
strcat(msgrasp,"Hello ");
strcat(msgrasp,playerName);
printf("[server]Trimitem mesajul inapoi...%s\n",msgrasp);
fflush(stdout);
if(-1 == write(player1_fd, msgrasp, sizeof(msgrasp)))
{
perror("Error at writing to player1");
return errno;
}
printf("Player name sent\n");
fflush(stdout);
}
}
else{ //player 2
int nameOrMove;
if (-1 == read(p[0], &nameOrMove, sizeof(int))) // reading the name or move int
{
perror("Error at reading from pipe\n");
return errno;
}
printf("nameormove:%d\n",nameOrMove);
fflush(stdout);
if (nameOrMove == 0) { // name
int bytesRead, playerNameLen = 0;
char playerName[20],c;
bzero(playerName,20);
if( -1 == read(p[0], &playerNameLen, sizeof(int)))
{
perror("Error at reading from pipe.\n");
return errno;
}
if( -1 == read(p[0], playerName, playerNameLen))
{
perror("Error at reading from pipe.\n");
return errno;
}
printf("Playername: %s\n", playerName);
fflush(stdout);
//mesaj de raspuns pentru client
bzero(msgrasp,100);
strcat(msgrasp,"Hello ");
strcat(msgrasp,playerName);
printf("[server]Trimitem mesajul inapoi...%s\n",msgrasp);
fflush(stdout);
printf("p2fd: %d\n", player2_fd);
fflush(stdout);
// ----- !!! LINE 297 !!! -----
if(-1 == write(player2_fd, msgrasp, sizeof(msgrasp)))
{
perror("Error at writing to player1");
return errno;
}
printf("Player name sent\n");
fflush(stdout);
}
}
}
}
}
nrOfChilds++;
} else { // if it's the second player to be assigned to a waiting player 1
if (pid > 0) {
/* All messages from parent to it's children processes will start with a char
* that shows if it's information coming from an already connected player or the fd of a new connection:
* '0' - old client
* '1' - new client
* If the first char = '0', it will be followed by a char specifying from which player is the info coming from:
* '1' - player1
* '2' - player2
* The third char tells me if I'm getting the name of the player or a move:
* '0' - name
* '1' - move
*/
int x = 1;
if (-1 == write(child_fd[nrOfChilds-1].pipe[1], &x, sizeof(int)))
{
perror("Error at writing in pipe\n");
return errno;
}
if (-1 == write(child_fd[nrOfChilds-1].pipe[1], &client, sizeof(int))) //sending player2 socket descriptor to it's child process handler
{
perror("Error at writing in pipe\n");
return errno;
}
child_fd[nrOfChilds-1].player2_fd = client; //adding it to struct so the parent knows both players associated to each child process
}
}
}
/* vedem daca e pregatit vreun socket client pentru a trimite raspunsul */
if (pid > 0) {
for (fd = 0; fd <= nfds; fd++) /* parcurgem multimea de descriptori */
{
/* este un socket de citire pregatit? */
if (fd != sd && FD_ISSET (fd, &readfds)) {
for(int i = 0; i< nrOfChilds; ++i){ // looking for the child process to whom the client is associated
if ( fd == child_fd[i].player1_fd || fd == child_fd[i].player2_fd){
char nameOrMove;
if (-1 == read(fd, &nameOrMove, sizeof(char))) // reading the name or move char
{
perror("Error at reading from pipe\n");
return errno;
}
if (nameOrMove == '0') { // name
int bytesRead;
char playerName[20];
bytesRead = read (fd, playerName, sizeof (playerName));
if (bytesRead < 0)
{
perror ("Eroare la read() de la client.\n");
return errno;
}
int x = 0;
if ( -1 == write(child_fd[i].pipe[1], &x, sizeof(int))){
perror("Error at writing in pipe\n");
return errno;
}
x = (fd == child_fd[i].player1_fd) ? 1 : 2;
if ( -1 == write(child_fd[i].pipe[1], &x, sizeof(int))){
perror("Error at writing in pipe\n");
return errno;
}
x = 0;
if ( -1 == write(child_fd[i].pipe[1], &x, sizeof(int))){
perror("Error at writing in pipe\n");
return errno;
}
int playerNameLen = strlen(playerName);
if ( -1 == write(child_fd[i].pipe[1], &playerNameLen, sizeof(int))){
perror("Error at writing in pipe\n");
return errno;
}
if ( -1 == write(child_fd[i].pipe[1], playerName, playerNameLen)){
perror("Error at writing in pipe\n");
return errno;
}
}
FD_CLR(fd, &actfds);
}
}
}
}
}
/* for */
}
} /* while */
/* main */
【问题讨论】:
-
这是很多代码,但无论如何这听起来像是本地数组的超出范围的索引,或者缓冲区溢出。尝试将所有数组/缓冲区大小增加 10 倍(例如
char msg[100]->char msg[1000])等。如果问题消失,您知道该怎么做。 -
同时避免使用
char foo[100]; ...bzero (foo, 100);之类的结构,而是使用bzero (foo, sizeof foo);,这样可以在更改foo数组的大小时避免大小不匹配。 -
如果它发生在
read()或write()和朋友身上,一个坏的套接字FD 不会'导致这种情况。但是一个错误的缓冲区地址或长度会。 -
您似乎假设文件描述符是全局的,您可以简单地在另一个进程中使用一个进程打开的fd。有一些方法可以将 fd 从一个进程传递到另一个进程——但它们比仅仅通过管道传递它的 fd 编号要复杂一些;-)等)
标签: c file sockets file-descriptor