c - 选择服务器多读问题

标签 c select server client

我正在用 C 编写服务器/客户端应用程序

实际上,我试图让服务器接受新客户端并(几乎)同时接收数据。 我发送了两次数据,第一次我发送了一个登录名并且它有效。

第二次我发送了一些字符串数据,就像客户端一次又一次地发送它们,但我已经检查过,它们只发送了一次。

有人可以帮帮我吗?

我使用 gcc -Wall -pedantic 来编译它们。

这是客户端代码:需要一个参数,它可以是任何文本

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
#include <sys/socket.h>

#define PATH "soquette"
#define BACKLOG 2
#define TAILLE_BUFFER 256
#define TIME_SLEEP 10

int main(int argc,char ** argv){
    if(argc == 2){
        struct sockaddr_un addr;
        int serveur_socket;
        ssize_t taille_lue;
        char buffer[TAILLE_BUFFER];
        char * buffer2;
        if((serveur_socket = socket(PF_UNIX,SOCK_STREAM,0))<0){
            perror("socket \n");
            exit(EXIT_FAILURE);
        }
        memset(&addr,0,sizeof(struct sockaddr_un));
        addr.sun_family = AF_UNIX;
        strncpy(addr.sun_path,PATH,sizeof(addr.sun_path)-1);
        if(connect(serveur_socket,(struct sockaddr *)&addr,sizeof(struct sockaddr_un))<0){
            perror("connect \n");
            exit(EXIT_FAILURE);
        }
        printf("pseudo %s \n",argv[1]);
        if(write(serveur_socket,argv[1],strlen(argv[1])*sizeof(char))<0){
            perror("1 st write \n");
            exit(EXIT_FAILURE);
    }
        sleep(5);
        taille_lue = read(STDIN_FILENO,buffer,TAILLE_BUFFER);
        buffer2 = malloc(sizeof(int) + taille_lue * sizeof(char));
        sprintf(buffer2,"%ld",taille_lue);
        strcat(buffer2,buffer);
        if(write(serveur_socket,buffer2,sizeof(buffer2))<0){
            perror("write \n");
            exit(EXIT_FAILURE);
        }
        printf("message envoyé %s \n",buffer2);
        free(buffer2);
        exit(EXIT_SUCCESS);
    }
    else{
        printf("bad arguments number \n");
        exit(EXIT_SUCCESS);
    }
    exit(EXIT_SUCCESS);
}

这是服务器端。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <signal.h>
#include <fcntl.h>

#define PATH "soquette"
#define NB_MAX_CONNECTION 10
#define TAILLE_BUFFER 256
#define NB_BOUCLE 10
#define TIME_WAIT 10


int socket_server;

void signal_handler(){
    printf("signal handler \n");
    if(close(socket_server)==-1){
        perror("close \n");
    }
    if(unlink(PATH)==-1){
        perror("unlink \n");
    }
    exit(EXIT_SUCCESS);
}


int main(){
    int i,retval,j,fd_max,new_fd;
    ssize_t taille_recue;
    struct sockaddr_un addr;
    char buffer[TAILLE_BUFFER];
    struct timeval tv;
    fd_set rfds,active_fd_set;
    if(signal(SIGINT,signal_handler)==SIG_ERR){
        perror("signal \n");
    }
    tv.tv_sec=TIME_WAIT;
    tv.tv_usec=0;
    FD_ZERO(&rfds);
    FD_ZERO(&active_fd_set);

    printf("server launch \n");
    if((socket_server = socket(PF_UNIX,SOCK_STREAM,0))<0){
        perror("socket \n");
        exit(EXIT_FAILURE);
    }
    memset(&addr,0,sizeof(struct sockaddr_un));
    addr.sun_family = PF_UNIX;
    strncpy(addr.sun_path,PATH,sizeof(addr.sun_path)-1);
    if((bind(socket_server,(struct sockaddr *)&addr,sizeof(struct sockaddr_un))==-1)){
        perror("bind \n");
        exit(EXIT_FAILURE);
    }
    if(listen(socket_server,NB_MAX_CONNECTION)==-1){
        perror("listen \n");
        exit(EXIT_FAILURE);
    }
    FD_SET(socket_server,&active_fd_set);
    fd_max = socket_server;
    for(i=0;i<NB_BOUCLE;i++){
        FD_ZERO(&rfds);
        rfds = active_fd_set;
        printf("tour number  %d \n",i);
        if((retval = select(fd_max+1,&rfds,NULL,NULL,&tv))<0){
            perror("select \n");
        }
        for(j=0;j<=fd_max;j++){
            if(FD_ISSET(j,&rfds)){
                if(j == socket_server){
                    if((new_fd = accept(socket_server,NULL,NULL))<0){
                        perror("accept \n");
                        signal_handler();
                        exit(EXIT_FAILURE);
                    }
                    printf("new client \n");
                    FD_SET(new_fd,&active_fd_set);
                    if(read(new_fd,buffer,TAILLE_BUFFER)<0){
                        perror("read 1\n");
                    }
                    else{
                        printf("read from buffer %s \n",buffer);
                        fd_max = new_fd;
                    }
                }
                else{
                    printf("client already in the list \n");
                    if((taille_recue = read(j,buffer,sizeof(int)))<0){
                        if(taille_recue == 0){
                            close(j);
                            FD_CLR(j,&rfds);
                        }
                        else{
                            signal_handler();
                            perror("read server 2 \n");
                            exit(EXIT_FAILURE);
                        }
                    }
                    else{
                        printf("read from buffer %s \n",buffer);
                        FD_CLR(j,&rfds);
                    }
                }
            }
        }
    }
    printf("fermeture du serveur \n");
    close(socket_server);
    unlink(PATH);
    exit(EXIT_SUCCESS);
}

这是客户端输出

/client 1
pseudo 1 
salut
message envoyé 6salut
/0 

这是服务器输出

MacBook-Pro-de-Kevin:tp10 kevin$ ./server 
server launch 
tour number  0 
new client 
read from buffer 1 
tour number  1 
client already in the list 
read from buffer 6sal 
tour number  2 
client already in the list 
read from buffer ut
/ 
tour number  3 
client already in the list 
read from buffer ut
/ 
tour number  4 
client already in the list 
read from buffer ut
/ 
tour number  5 
client already in the list 
read from buffer ut
/ 
tour number  6 
client already in the list 
read from buffer ut
/ 
tour number  7 
client already in the list 
read from buffer ut
/ 
tour number  8 
client already in the list 
read from buffer ut
/ 
tour number  9 
client already in the list 
read from buffer ut
/ 
fermeture du serveur 

最佳答案

服务器没有正确处理连接的套接字

  • 首先,当它接受一个新连接时,服务器会立即尝试从套接字中读取数据。此时可能没有可用数据,因此读取可能会阻塞。虽然这不能解释你问的问题,但它与你的目标相冲突。

  • 服务器假定任何新接受的连接的 fd 必须是集合中的最大 fd。虽然它还没有影响你,但这种假设是不安全的。文件描述符在关闭时被释放并可供重用。

  • 服务器在关闭连接时不会更新 fd_max。然而,尽管这可能导致后续 select() 调用不严格符合该函数的规范,但它可能不会导致任何实际的不当行为。

服务器和客户端没有正确处理 I/O

  • 您似乎假设客户端的 write() 调用总是写入指定给它们的完整字节数,并且服务器的下一个读取将读取最多写入的所有字节读取()。这些假设通常并不安全,尽管对于 unix 域套接字,它们确实很有可能得到满足。一般来说,对于 read()write(),您必须考虑返回值,不仅要发现错误/文件结尾,还要确保所有预期的字节被写入/读取。您必须准备好循环才能传输所有需要的字节。

  • 在基于 select() 的场景中,上一点中描述的循环需要通过 select() 循环,否则您可能会引入阻塞。因此,您可能需要对每个连接进行统计,以计算在任何给定时间您希望读取/写入的字节数。事实上,除非您的服务器除了尽可能快地将字节从源传输到接收器之外什么都不做,否则它很可能需要维护一些每个连接的状态。

  • 奇怪的是,对于已建立的连接,您尝试在任何给定的读取操作中仅读取 int 中的字节数,而实际上可用的字节数可能多于该字节数,并且缓冲区可以容纳更多。就在这里:

if((taille_recue = read(j,buffer,sizeof(int)))<0){
  • 现在仔细考虑上面引用的行:只有当 read() 返回负值时,才会执行 if block 。特别是,当 read() 返回 0 以指示文件结束时,该 block 不会执行,但它在该 block 中,而不是 else block ,您可以在其中测试文件结束条件。 这就是导致您询问的行为的原因。位于 EOF 处的打开文件总是准备好读取,但是您对来自 read() 的 EOF 信号处理不当,将其视为数据已被读取,而不是识别它是什么。

  • 此外,如果您想通过 printf()%s 字段描述符打印缓冲区内容,那么您必须确定要插入一个空值在有效数据之后将字符 ('\0') 放入缓冲区,或者使用最大字段宽度将输出限制为缓冲区中的有效字节数。

关于c - 选择服务器多读问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37413695/

相关文章:

MySql 查询嵌套 select 语句

xml - 如何在xpath中选择属于同一组的节点?

mysql - 条件不起作用的 NULL 值

c - 获取客户端IP地址和端口?

java - 如何在客户端服务器多线程中停止服务器

c - 使用 setfsuid() 创建文件时出现段错误?

c - 我的程序不会按条件停止

c - 如何从内核模块添加系统调用?

c - 检测到不存在的文件

git - Jenkins 选择了错误的 git 二进制文件