c socket发送大文件发生破坏管道错误

时间:2017-10-12 14:46:10

标签: c sockets tcp

我正在尝试使用C套接字发送文件。

我使用pthread创建了一个服务器。服务器按缓冲区的大小读取文件,并将其发送到客户端,与读取大小一样多。

适用于小尺寸文件,但是当我尝试发送大文件时,如mp3文件(超过5MB),它无法正常工作。客户端再次发送请求,管道坏了。

我的服务器在OSX上运行,而我正在使用浏览器作为客户端。

  

当html文件有mp3资源作为标记时,mp3发送OK。(我调用localhost:9999/index.html)但是当我直接调用mp3文件时(例如localhost:9999/music.mp3),会发生管道错误。 (localhost:9999/image.jpeg可以)

我添加了忽略SIGPIPE,但仍然发生了管道错误。

我无法理解。有什么问题,我该如何解决?

server.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <pthread.h>
#include <stdint.h>
#include <fcntl.h>
#include <signal.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_t pthread;
void error(char *msg)
{
    perror(msg);
    exit(1);
}

void *pthread_read_and_write(void *arg);
int writeToClient(int newsockfd, char* msg);
void sendError(int newsockfd);
void sendResponseHeader(int newsockfd, char *httpMsg, long contentLen, char *contentType);
void requestHandler(int newsockfd, char *reqMsg);
int main(int argc, char *argv[])
{
    signal(SIGPIPE, SIG_IGN);
    int sockfd, newsockfd;
    int portno;
    socklen_t clilen;
    struct sockaddr_in serv_addr, cli_addr;

    int n;
    if (argc < 2) {
        fprintf(stderr,"ERROR, no port provided\n");
        exit(1);
    }

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");

    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = atoi(argv[1]);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR on binding");

    listen(sockfd,10);

    clilen = sizeof(cli_addr);

    while(1){
        newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
        if (newsockfd < 0)
            error("ERROR on accept");
        pthread_create(&pthread, NULL, *pthread_read_and_write, (void *)(intptr_t)newsockfd);
        pthread_detach(pthread);
    }
    close(sockfd);

    return 0;
}

void *pthread_read_and_write(void *arg){
    int newsockfd = (int)arg;
    char reqMsg[500];
    int n;

    bzero(reqMsg,500);
    n = read(newsockfd,reqMsg,499);
    if (n < 0) error("ERROR reading from socket");
    printf("========Request Message======\n%s\n",reqMsg);
    requestHandler(newsockfd, reqMsg);
    printf("=============================\n");

    return NULL;
}
void requestHandler(int newsockfd, char *reqMsg){

    printf("client socket : %d\n", newsockfd);

    char file[100];
    char *method = strtok(reqMsg, " /");
    strcpy(file, strtok(NULL, " /"));
    char tmpFileName[100];
    strcpy(tmpFileName, file);
    strtok(tmpFileName, ".");
    char *extension = strtok(NULL, ".");
    printf("method : %s\n", method);
    printf("file : %s\n", file);
    printf("extension : %s\n", extension);
    if(strcmp(method, "GET") == 0 || strcmp(method, "get") == 0){

    }else{
        sendError(newsockfd);
        return;
    }
    if(strcmp(file, "HTTP") == 0 || strcmp(file, "http") == 0){
        strcpy(file , "index.html");
    }
    printf("compare success\n");
    long fsize;
    char type[20];

    if(extension == NULL || strcmp(extension, "html") == 0){
        strcpy(type, "text/html");
    }else if(strcmp(extension, "jpeg") == 0){
        strcpy(type,"image/jpeg");
    }else if(strcmp(extension, "gif") == 0){
        strcpy(type,"image/gif");
    }else if(strcmp(extension, "mp3") == 0){
        strcpy(type, "audio/mpeg");
    }else if(strcmp(extension, "pdf") == 0){
        strcpy(type, "application/pdf");
    }else{
        strcpy(type, "text/plain");
    }
    printf("compare success\n");
    printf("type : %s\n", type);
    FILE *fp = fopen(file, "rb");
    if(fp == NULL){
        sendError(newsockfd);
        return;
    }
    fseek(fp, 0, SEEK_END);
    fsize = ftell(fp);
    fclose(fp);

    char rcvBuf[BUFSIZ+1];
    int fd;
    printf("reading file...\n");
    if((fd = open(file, O_RDONLY)) <0 ){
        printf("sending error...\n");
        sendError(newsockfd);
        printf("send error OK\n");
        return;
    }
    printf("open fd OK\n");

    char *httpMsgOK = "200 OK";
    printf("sending header...\n");
    pthread_mutex_lock(&mutex);
    sendResponseHeader(newsockfd, httpMsgOK, fsize, type);
    printf("send header OK\n");

    pthread_mutex_unlock(&mutex);
    int n;
    bzero(rcvBuf, BUFSIZ + 1);
    if(fd >= 0) {
        while((n=read(fd, rcvBuf, BUFSIZ)) > 0){

            printf("sending rcvBuf : %d, remain : %ld\n", n, fsize-=n);
            int res = send(newsockfd, rcvBuf, n + 1, 0);
            if(res <0) {
                char errMsg[100];
                sprintf(errMsg, "ERROR writing to socket __sock : %d __", newsockfd);
                error(errMsg);
            }
            bzero(rcvBuf, BUFSIZ + 1);
        }
    }
    close(newsockfd);
    printf("closed client socket\n");

}
void sendError(int newsockfd){
    char *msg = "<html><body><h1>400 Bad Request</h1></body></html>";
    sendResponseHeader(newsockfd, "400 Bad Request", strlen(msg), "text/html");
    writeToClient(newsockfd, msg);
    close(newsockfd);
    printf("closed client socket\n");
}

void sendResponseHeader(int newsockfd, char *httpMsg, long contentLen, char *contentType){
    char resMsg[40];
    char conLen[100];
    char conType[50];
    sprintf(resMsg, "HTTP/1.1 %s\r\n",httpMsg);
    sprintf(conLen, "Content-length: %ld\r\n", contentLen);
    sprintf(conType, "Content-Type: %s\r\n\r\n", contentType);

    printf("response message\n%s\n%s\n%s\n", resMsg, conLen, conType);
    writeToClient(newsockfd, resMsg);
    printf("send resMsg OK\n");
    writeToClient(newsockfd, conLen);
    writeToClient(newsockfd, conType);
    printf("send conType OK\n");
}

int writeToClient(int newsockfd, char* msg){
    int n =  write(newsockfd, msg, strlen(msg));
    if (n < 0) error("ERROR writing to socket");
    return n;
}

,结果在

之下
========Request Message======
GET /run.mp3 HTTP/1.1
Host: localhost:9999
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4


client socket : 4
method : GET
file : run.mp3
extension : mp3
compare success
compare success
type : audio/mpeg
reading file...
open fd OK
sending header...
response message
HTTP/1.1 200 OK

Content-length: 5187428

Content-Type: audio/mpeg


send resMsg OK
send conType OK
send header OK
sending rcvBuf : 1024, remain : 5186404
sending rcvBuf : 1024, remain : 5185380
sending rcvBuf : 1024, remain : 5184356
sending rcvBuf : 1024, remain : 5183332
sending rcvBuf : 1024, remain : 5182308
sending rcvBuf : 1024, remain : 5181284
sending rcvBuf : 1024, remain : 5180260
sending rcvBuf : 1024, remain : 5179236
sending rcvBuf : 1024, remain : 5178212
sending rcvBuf : 1024, remain : 5177188
sending rcvBuf : 1024, remain : 5176164
sending rcvBuf : 1024, remain : 5175140
sending rcvBuf : 1024, remain : 5174116

.....

sending rcvBuf : 1024, remain : 2593636
sending rcvBuf : 1024, remain : 2592612
sending rcvBuf : 1024, remain : 2591588
========Request Message======
GET /run.mp3 HTTP/1.1
Host: localhost:9999
Connection: keep-alive
Accept-Encoding: identity;q=1, *;q=0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
Accept: */*
Referer: http://localhost:9999/run.mp3
Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4
Range: bytes=0-


client socket : 6
method : GET
file : run.mp3
extension : mp3
compare success
compare success
type : audio/mpeg
reading file...
open fd OK
sending header...
response message
HTTP/1.1 200 OK

Content-length: 5187428

Content-Type: audio/mpeg


send resMsg OK
send conType OK
send header OK
send header OK
sending rcvBuf : 1024, remain : 5186404
sending rcvBuf : 1024, remain : 5185380
sending rcvBuf : 1024, remain : 5184356
sending rcvBuf : 1024, remain : 5183332
sending rcvBuf : 1024, remain : 5182308
sending rcvBuf : 1024, remain : 5181284
sending rcvBuf : 1024, remain : 5180260
sending rcvBuf : 1024, remain : 5179236
sending rcvBuf : 1024, remain : 5178212
sending rcvBuf : 1024, remain : 5177188

.........

sending rcvBuf : 1024, remain : 4781924
sending rcvBuf : 1024, remain : 4780900
sending rcvBuf : 1024, remain : 4779876
sERROR writing to socket __sock : 4 __: Broken pipe
ending rcvBuf : 1024, remain : 4778852
sending rcvBuf : 1024, remain : 4777828
sending rcvBuf : 1024, remain : 4776804
sending rcvBuf : 1024, remain : 4775780
sending rcvBuf : 1024, remain : 4774756
sending rcvBuf : 1024, remain : 4773732
sending rcvBuf : 1024, remain : 4772708
sending rcvBuf : 1024, remain : 4771684
(EXIT)

编辑2017.10.13

  • intssize_t
  • 当我使用readsendwrite时,使用ssize_t而不是int
  • 确定请求消息为NUL 我在调用read

    后添加了以下代码
    if(reqMsg[0] == 0){
        printf("req msg is null\n");
        close(newsockfd);
        return NULL;
    }
    requestHandler(newsockfd, reqMsg);
    bzero(reqMsg, 500);
    
  • writeToClient中,写入直到发送所有消息。我在下面添加了代码。

    long toSend = strlen(msg);
    while(toSend > 0){
        n = write(newsockfd, msg, toSend);
        printf("write :  %ld\n", n);
        toSend -= n;
    }
    

编辑2017.10.13 - 第二次

当我使用write时,请检查其返回值是0。如果返回值为0 close客户端套接字,则return用于退出pthread。

但仍然发生同样的错误。

2 个答案:

答案 0 :(得分:0)

  

服务器按缓冲区的大小读取文件,并将其发送到客户端,与读取大小一样多。

不,不是。

while((n=read(fd, rcvBuf, BUFSIZ)) > 0){..

这会将n字节正好加载到缓冲区中,不多也不少。

int res = send(newsockfd, rcvBuf, n + 1, 0);

这会向您的客户端发送n+1个字节。额外字节在当前缓冲区数据中无效。如果n正好是BUFSIZ,那么这就是缓冲区溢出。

答案 1 :(得分:-1)

  

我添加了忽略SIGPIPE,但仍然发生了管道错误。

SIGPIPE可以忽略,但如果管道错误太多,OS可能会杀了你。

您的问题是您不明白SIGPIPE意味着您的计划中有错误:

  

SIGPIPE信号在尝试写入管道而没有连接到另一端的进程时发送到进程。

很明显,您尝试在已关闭的套接字上写入。因此,解决方案是查看程序中忘记正确处理错误的位置。

n = read(newsockfd,reqMsg,499);
if (n < 0) error("ERROR reading from socket");

如果n等于0,则套接字已经关闭。

int res = send(newsockfd, rcvBuf, n + 1, 0);
if(res <0) {

writeToClient(newsockfd, msg);

writeToClient(newsockfd, resMsg);
printf("send resMsg OK\n");
writeToClient(newsockfd, conLen);
writeToClient(newsockfd, conType);

int n =  write(newsockfd, msg, strlen(msg));
if (n < 0) error("ERROR writing to socket");

请参阅,你永远不会验证你至少写了1个字节(如果msglen也为0,ofc write可以返回0)。

在真正的服务器中,您总是在写入之前阅读,通过阅读,您可以处理文件末尾,但在您的代码中,您无法正确处理客户端。