第二个 recv 调用不接收数据,在 C 中停止执行

Posted

技术标签:

【中文标题】第二个 recv 调用不接收数据,在 C 中停止执行【英文标题】:Second recv call doesn't receive data, halts the execution in C 【发布时间】:2016-12-30 19:26:24 【问题描述】:

这是我从服务器请求文件的客户端程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define SERVER_PORT 5959
#define MAX_LINE 512

void setstring(char *str)
    str[MAX_LINE-1]='\0';


int main(int argc, char * argv[])
    FILE *fp;
    struct hostent *hp;
    struct sockaddr_in sin;
    char *host;
    char filename[MAX_LINE],buf[MAX_LINE],reply[MAX_LINE],rec_line[MAX_LINE];
    int s;
    char msg[MAX_LINE];
    int len,new_len,rec_file;
    if (argc==2) 
        host = argv[1];
    
    else 
        fprintf(stderr, "usage: simplex-talk host\n");
        exit(1);
    

    /* translate host name into peer's IP address */
    hp = gethostbyname(host);
    if (!hp) 
        fprintf(stderr, "simplex-talk: unknown host: %s\n", host);
        exit(1);
    
    else
        printf("Client's remote host: %s\n", argv[1]);

    /* build address data structure */  
    bzero((char *)&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    bcopy(hp->h_addr, (char *)&sin.sin_addr, hp->h_length);
    sin.sin_port = htons(SERVER_PORT);

    /* active open */
    if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) 
        perror("simplex-talk: socket");
        exit(1);
    
    else
        printf("Client created socket.\n");

    int send_file_name,rec_msg;
    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    
        perror("simplex-talk: connect");
        close(s);
        exit(1);
    
    else
        printf("Client connected.\n");
        /* main loop: get and send lines of text */
    printf("Hello from server\n");
    while(!(strcmp(reply,"bye")==0))
            printf("Enter the file name:\n");
            scanf("%s",filename);
            setstring(filename);
            send_file_name=send(s,filename,strlen(filename)+1,0);
            if(send_file_name<0)
                fputs("Error sending filename",stdout);
            rec_msg=recv(s,msg,sizeof(msg),0);
            if(strcmp(msg,"File not found")==0)
                printf("File not found\n");
            else
                printf("%s\n",msg);
                fp=fopen(filename,"w");
                printf("CP1\n");
                if(rec_file=recv(s,rec_line,sizeof(rec_line),0)>0)
                    printf("CP2");
                    printf("String recieved:%s\n",rec_line);
                    if(len=fwrite(rec_line,1,rec_file+1,fp)>0)
                        printf("Recieved file\n");
                    else
                        printf("Error writing to file\n");
                
                else
                    printf("Not recieved\n");
            
            printf("Enter 'bye' to terminate requesting files\n");
            scanf("%s",reply);
        
    
    return 0;

这是我的服务器程序,它接受来自客户端的文件请求:

 #include <stdio.h>
 #include <stdlib.h> 
 #include <string.h>
 #include <strings.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <netdb.h>

 #define SERVER_PORT 5959
 #define MAX_PENDING 5
 #define MAX_LINE 256

 void setstring(char* str)
    str[MAX_LINE-1]='\0';
 

 int main()
    FILE *fp;
    struct sockaddr_in sin;
    char buf[MAX_LINE],msg[MAX_LINE],*rec_line;
    int len;
    int s, new_s,count;
    char str[INET_ADDRSTRLEN];
    int error_file,send_msg,read_line,send_file;

    bzero((char *)&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = inet_addr("0.0.0.0");
    sin.sin_port = htons(SERVER_PORT);

    /* setup passive open */
    if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) 
        perror("simplex-talk: socket");
        exit(1);
    

    inet_ntop(AF_INET, &(sin.sin_addr), str, INET_ADDRSTRLEN);
    printf("Server is using address %s and port %d.\n", str, SERVER_PORT);

    if ((bind(s, (struct sockaddr *)&sin, sizeof(sin))) < 0) 
        perror("simplex-talk: bind");
        exit(1);
    
    else
        printf("Server bind done.\n");

    listen(s, MAX_PENDING);
    /* wait for connection, then receive and print text */
    while(1) 
        if ((new_s = accept(s, (struct sockaddr *)&sin, &len)) < 0) 
            perror("simplex-talk: accept");
            exit(1);
        

        printf("Server Listening.\n");
        printf("Greetings\n");
        int rec_file_name=recv(new_s,buf,sizeof(buf),0);
        if(rec_file_name>0)
            printf("File requested:%s\n",buf);
            fp=fopen(buf,"r");
            if(fp==NULL)
            
                fputs("File not found\n",stdout);
                strcpy(buf,"File not found");
                if(error_file=send(new_s,buf,strlen(buf)+1,0)>0)
                    fputs("Successfully send error message to client\n",stdout);
            
            else
                bzero(buf,MAX_LINE);
                printf("File found :) \n");
                strcpy(buf,"OK");
                if(send_msg=send(new_s,buf,strlen(buf)+1,0)>0)
                    fputs("File found message sent to client\n",stdout);

                fseek(fp,0,SEEK_END);
                int file_size=ftell(fp);
                fseek(fp,0,SEEK_SET);
                printf("File size:%d\n",file_size);
                rec_line=(char *)malloc(sizeof(char)*(file_size));
                read_line=fread(rec_line,1,file_size+1,fp);
                printf("File read: %s\n",rec_line);

                if(send_file=send(new_s,rec_line,strlen(rec_line)+1,0)>0)
                    printf("File string sent to client\n");
            
        

        close(new_s);   
     

问题是在客户端,我的第二个recv() 调用,它应该接收文件的内容,但什么也没显示。程序此时停止,但服务器程序显示它已发送文件内容。客户端没有收到。

【问题讨论】:

检查了 Wireshark 以查看数据是否正在传输到网络中? 这是不可读的!首先格式化&缩进这个烂摊子。 请正确格式化这个难以辨认的混乱。 TCP 不是消息协议。如果要发送和接收消息,则必须在 TCP 之上实现消息协议。 【参考方案1】:

您的错误检查是随意的,因此您肯定错过了在您观察到的行为之前发生的问题。我建议你遵循 RW Steven 的成语:

int n;
if( (n = recv(new_s, buf, sizeof(buf), 0)) < 0 ) 
    err(EXIT_FAILURE, "recv %d", __LINE__);

测试每个函数调用,并处理每个错误。对于简单的程序,只需在出错时调用 err(3)。始终如一地这样做,程序的行为将不那么神秘(如果仍然偶尔令人惊讶)。不要害怕空格键!它很容易被击中,也更容易阅读。

如果可以的话,我的另一点建议是担心

int send_file_name,rec_msg;

这样的名字令人困惑。名称几乎从来都不是整数。对于 I/O 大小,只需使用一个简单的名称,例如 nlensize。即使您不关心自己,在公开论坛上发布您的问题之前,您也要关心一下。否则,当人们看到

send_file_name=send(s,filename,strlen(filename)+1,0);

他们可能认为send发送(2) 以外的其他功能,或者提出问题的人粗心。

【讨论】:

他大部分时间都在打电话给perror(),这已经足够了。您还没有发现这里的实际问题。 那么实际问题是什么? @Olaf 问题中说明了实际问题。就是他的第二个recv()被屏蔽了,这个答案根本没有解决。 @EJP:抱歉,我的意思是“实际问题的解决方案是什么?”,当然。 (我总是想知道谁支持这样的 cmets)。【参考方案2】:

我看到的主要问题是客户端和服务器通常都没有正确处理套接字 I/O。他们没有处理读取和写入传输的字节数少于请求的情况,您需要循环 I/O。无论如何,客户端正在从服务器读取太多字节,这就是为什么您的第二个 recv() 阻塞的原因。而且您依靠断开连接来指示已到达文件末尾,但这不允许客户端进行足够的错误检查以了解是否实际收到了完整文件。

此外,当发送文件内容时,服务器试图将整个文件读入内存(糟糕!),没有对文件 I/O 进行充分的错误检查,并且将文件内容视为文本而不是二进制(不要在二进制数据上使用strlen()!)。

试试类似的方法:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define SERVER_PORT 5959
#define MAX_LINE 512

int sendstring(int sock, const char *str) 
    if (!str) str = "";
    int len = strlen(str) + 1;

    do 
        int ret = send(sock, str, len, 0);
        if (ret <= 0) return -1;
        str += ret;
        len -= ret;
    
    while (len > 0);

    return 0;


int readbuf(int sock, void *buf, int buflen) 
    char *pbuf = (char*) buf;

    while (buflen > 0) 
        int len = recv(sock, pbuf, buflen, 0);
        if (len <= 0) return -1;
        pbuf += len;
        buflen -= len;
    

    return 0;


int readstring(int sock, char *str, int maxlen) 
    while (maxlen > 0) 
        if (recv(sock, str, 1, 0) <= 0) return -1;
        if (*str == '\0') return 0;
        ++str;
        --maxlen;
    
    return -2;


int readfile(int sock, int fd) 
    int filesize;
    char buf[MAX_LINE];

    if (readbuf(sock, &filesize, sizeof(filesize)) < 0) return -1;
    filesize = ntohl(filesize);

    while (filesize > 0) 
        int len = readbuf(sock, buf, min(sizeof(buf), filesize));
        if (len < 0) return -1;
        if (fwrite(buf, len, 1, fp) != 1) return -2;
        filesize -= len;
    

    return 0;


int main(int argc, char * argv[]) 
    char filename[MAX_LINE], reply[MAX_LINE];

    if (argc != 2) 
        fprintf(stderr, "usage: simplex-talk host\n");
        exit(1);
    

    char *host = argv[1];

    /* translate host name into peer's IP address */
    struct hostent *hp = gethostbyname(host);
    if (!hp) 
        fprintf(stderr, "simplex-talk: unknown host: %s\n", host);
        exit(1);
    

    if (hp->h_addrtype != AF_INET) 
        fprintf(stderr, "simplex-talk: unsupported address type %d for host: %s\n", hp->h_addrtype, host);
        exit(1);
    

    /* build address data structure */  
    struct sockaddr_in sin;
    bzero((char *)&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    bcopy(hp->h_addr, &sin.sin_addr, hp->h_length);
    sin.sin_port = htons(SERVER_PORT);

    printf("Host's remote IP: %s\n", inet_ntoa(&sin.sin_addr));

    /* active open */
    int s = socket(PF_INET, SOCK_STREAM, 0);
    if (s < 0) 
        perror("simplex-talk: socket");
        exit(1);
    

    printf("Client created socket.\n");

    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    
        perror("simplex-talk: connect");
        close(s);
        exit(1);
    

    printf("Client connected.\n");

    /* main loop: get and send lines of text */
    do 
        printf("Enter the file name ('bye' to quit):\n");

        if (scanf("%512s", filename) != 1) 
            printf("Error reading filename\n");
            break;
        

        if (strcmp(filename, "bye") == 0) 
            sendstring(s, "bye");
            break;
        

        if (sendstring(s, filename) < 0) 
            printf("Error sending filename\n");
            break;
        

        if (readstring(s, reply, sizeof(reply)) < 0) 
            printf("Error reading reply\n");
            break;
        

        if (strcmp(reply, "OK") != 0) 
            printf("%s\n", reply);
            if (strcmp(reply, "bye") == 0) break;
            continue;
        

        FILE *fp = fopen(filename, "wb");
        if (!fp) 
            printf("Error opening file\n");
            break;
        

        printf("Receiving file\n");

        int ret = readfile(s, fd);
        fclose(fp);

        if (ret < 0) 
            if (ret == -2)
                printf("Error writing file\n");
            else
                printf("Error reading file\n");

            break;
        

        printf("Received file\n");
    
    while (1);

    close(s);
    return 0;

#include <stdio.h>
#include <stdlib.h> 
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define SERVER_PORT 5959
#define MAX_PENDING 5
#define MAX_LINE 512

int sendbuf(int sock, void *buf, int buflen) 
    char *pbuf = (char*) buf;

    while (len > 0) 
        int len = send(sock, pbuf, buflen, 0);
        if (len <= 0) return -1;
        pbuf += len;
        buflen -= len;
    

    return 0;


int sendstring(int sock, const char *str) 
    if (!str) str = "";
    return sendbuf(sock, str, strlen(str) + 1);


int sendfile(int sock, int fd) 
    char buf[MAX_LINE];

    struct stat s;
    if (fstat(fd, &s) < 0) return -2;

    int pos = ftell(fp);
    if (pos == -1) return -2;

    int file_size = s.st_size - pos;
    int tmp_file_size = htonl(file_size);
    if (sendbuf(sock, &tmp_file_size, sizeof(tmp_file_size)) < 0) return -1;

    while (file_size > 0) 
        int len = fread(buf, 1, min(sizeof(buf), file_size), fp);
        if (len < 1) return -2;
        if (sendbuf(sock, buf, len) < 0) return -1;
        file_size -= len;
    

    return 0;


int readstring(int sock, char *str, int maxlen) 
    while (maxlen > 0) 
        if (recv(sock, str, 1, 0) <= 0) return -1;
        if (*str == '\0') return 0;
        ++str;
        --maxlen;
    
    return -2;


int main() 
    char msg[MAX_LINE];

    struct sockaddr_in sin;
    bzero((char *)&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_port = htons(SERVER_PORT);

    /* setup passive open */
    int s = socket(PF_INET, SOCK_STREAM, 0);
    if (s < 0) 
        perror("simplex-talk: socket");
        exit(1);
    

    printf("Server is using address %s and port %d.\n", inet_ntoa(&(sin.sin_addr)), SERVER_PORT);

    if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) 
        perror("simplex-talk: bind");
        close(s);
        exit(1);
    

    printf("Server bind done.\n");

    if (listen(s, MAX_PENDING) < 0) 
        perror("simplex-talk: listen");
        close(s);
        exit(1);
    

    printf("Server Listening.\n");

    /* wait for connection, then receive and print text */
    do 
        int len = sizeof(sin);
        int cli_s = accept(s, (struct sockaddr *)&sin, &len);
        if (cli_s < 0) 
            perror("simplex-talk: accept");
            close(s);
            exit(1);
        

        printf("Client connected\n");

        do 
            if (readstring(cli_s, msg, sizeof(msg)) < 0) 
                printf("Error reading request\n");
                break;
            

            if (strcmp(msg, "bye") == 0) break;

            printf("File requested: %s\n", msg);

            FILE *fp = fopen(msg, "rb");
            if (!fp)
            
                printf("Cannot open file\n");
                if (sendstring(cli_s, "Cannot open file") < 0) 
                    printf("Error sending reply\n");
                    break;
                
                continue;
            

            printf("File found :) \n");

            if (sendstring(cli_s, "OK") < 0) 
                printf("Error sending reply\n");
                fclose(fp);
                break;
            

            ret = sendfile(cli_s, fp);
            fclose(fp);

            if (ret < 0) 
                printf("Error sending file\n");
                break;
            

            printf("File sent to client\n");
        
        while (1);

        close(cli_s);   
     
    while (1);

    close(s);
    return 0;

【讨论】:

【参考方案3】:

基本问题是您没有检查返回值来查看实际发送和接收的数据量。所以当客户端调用时:

rec_msg=recv(s,msg,sizeof(msg),0);

它将接收多达sizeof(msg) (512) 个字节,这可能是服务器正在发送的OK 消息和文件内容(在NUL 之后)。这意味着当它执行第二次recv 调用以获取内容时,它会阻塞,因为它已经在第一次调用中读取了内容,并且接收缓冲区中没有更多数据等待。

【讨论】:

以上是关于第二个 recv 调用不接收数据,在 C 中停止执行的主要内容,如果未能解决你的问题,请参考以下文章

c语言如何检测网络是不是连接

I2C 从机发送完数据后,接收不到主机(接收到倒数第二个数据后)发送的停止条件

在socket编程中怎么判断recv是不是接收完成

linux C语言 TCP 多线程 简易聊天室

recv() 在读取响应数据时停止 [C]

framing