socket傳輸大文件demo,附各函數原型

功能:

實際研發過程中,發現要從服務端發送大的數據文件給客戶端,但socket一次發送的數據包大小是有限制的,需要循環發送;循環發送需要考慮到文件何時發送完畢,所以,這裏服務端先發送文件大小給客戶端,然後再發送文件,客戶端根據接收文件大小進行判定。

 

/*
*struct sockaddr_in 結構體
*在頭文件#include<netinet/in.h>或#include <arpa/inet.h>中定義
*/
struct sockaddr_in
{
    sa_family_t    sin_family;    //地址族
    uint16_t       sin_port;      //16位TCP/UDP端口號
    struct in_addr sin_addr;      //32位IP地址
    char           sin_zero[8];  //不使用 
};

/*
*struct sockaddr結構體
*/
struct sockaddr
{
    sa_family_t    sin_family;    //地址族
    char           sa_data[14];   //14字節,包含套接字中的目標地址和端口信息
}

sockaddr_in該結構體解決了sockaddr的缺陷,把port和addr分開存儲中兩個變量中。
二者長度一樣,都是16字節,即佔用的內存大小是一致的,二者可以相互轉化。

sockaddr_in用於變量的賦值,sockaddr用於函數參數。

 

/*
*struct in_addr 存放32位IP地址
*/
struct in_addr
{
    In_addr_t s_addr;    //32位IPv4地址  
};
/*
*INADDR_ANY表示爲0.0.0.0的地址
*/
/*
*htonl
*將一個32位數從主機字節順序轉換成網絡字節順序
*例:server_address.sin_addr.s_addr = htonl(INADDR_ANY);
*這邊是服務端用於修改INADDR_ANY
*/
#include <arpa/inet.h>
uint32_t htonl(uinit32_t hostlong);
/*
*htons
*將整型變量從主機字節順序轉變成網絡字節順序
*hostshort:16位無符號整數
*例:server_address.sin_port = htons(PORT);
*/
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
/*
*linux socket函數
*af:地址族,也就是IP地址類型,常用AF_INET, AF_INET6
*type:數據傳輸方式/套接字類型,常用SOCK_STREAM(流格式套接字/面向連接的套接字)
*SOCK_DGRAM(數據報套接字/無連接的套接字)
*protocol:傳輸協議,常用IPPROTO_TCP,IPPTOTO_UDP,分別表示TCP協議、UDP協議,
IPPROTO_IP爲0表示接收任何的IP數據包
例:server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
返回值:成功時返回一個非負整型socket描述符,用於綁定、監聽等;失敗返回-1
*/
#include <sys/socket.h>
int socket(int af, int type, int protocol);
/*
*socket bind函數
*作用:將address指向的sockaddr結構體中的描述的一些屬性與socket套接字進行綁定
*socket:socket創建的套接字
*address:地址信息
*address_len:d地址信息長度
*例:bind(server_sockfd, (struct sockaddr*) & server_address, sizeof(server_address));
*返回值:綁定成功,返回0;失敗返回-1
*/
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
/*
*socket listen函數
*作用:將主動連接套接口變爲被動連接套接口,使得一個進程可以接受其他進程的請求。
*sockfd:表示要設置的服務端套接字文件描述符
*backlog:表示要設置服務端套接字連接隊列的大小(已完成隊列和未完成隊列中的連接)
返回值:成功返回0;失敗返回-1
*/
#include <sys/socket.h>
int listen(int sockfd, int backlog);
/*
*socket accept函數
*作用:提取所監聽套接字的等待連接隊列中的第一個連接請求,創建一個新的套接字,
*並返回指向該套接字的文件描述符
*sockfd:socket()函數創建的套接字
*addr:struct sockaddr結構體指針,一般存客戶端地址信息
*addrlen:這邊填struct sockaddr_in結構體的長度
例:client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, &sin_size)
返回值:成功時,返回接收到的套接字的描述符;出錯時,返回-1
*/
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/*
socket connect函數
作用:完成面向連接的協議連接過程
sockfd:套接字
server_addr:struct sockaddr結構體指針,存放要連接的地址信息
addrlen:指定server_addr結構體長度
例:connect(sockfd, (struct sockaddr*) & server_addr, sizeof(server_addr));
返回值:成功返回0,失敗返回-1
*/
#include <sys/socket.h>
#include <sys/types.h>
int connect(int sockfd, const struct sockaddr *server_addr, socklen_t addrlen);
/*
*socket send函數
作用:發送消息到套接字中
sockfd:客戶端/服務端套接字
buf:存放發送數據的緩衝區
len:實際要發送的數據的字節數
flags:調用執行方式,一般爲0
例:send(fd, data_length, strlen(data_length), 0);
返回值:成功則返回發送數據的大小(發送0字節也成功),失敗則返回-1
*/
#include <sys/type.h>
#include <sys/socket.h>
int send(int sockfd, const void *buf, int len, int flags);
/*
*socked recv函數
*作用:從TCP連接的另一端接收數據
sockfd:客戶端/服務端套接字
buf:存放recv函數接收數據的緩衝區
len:指明接收數據的大小
flags:調用執行方式,一般爲0
例:recv(fd, recv_buf, 1024, 0);
返回值:recv實際返回copy的字節數;成功執行時,返回接收到的字節數(send發送0,則recv接收也爲0 );
另一端關閉返回0;失敗返回-1
*/
#include <sys/types.h>
#include <sys/socket.h>
int recv(int sockfd, void *buf, int len, int flags);

DEMO:

/*server*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <string.h>
#include <time.h>

#define MIDLEN 1024
#define RET_ERROR 0
#define RET_OK 1
#define PORT 8888
#define LINTEN_QUEUE 5

/****************************************************************
 * * function name 		: send_big_data
 * * functional description	: 服務端讀文件發送數據
 * * input parameter		:fd:客戶端套接字
 * * output parameter	: None
 * * return value: None
 * * history	:
 * *****************************************************************/
void send_big_data(int fd)
{
    char recv_buf[MIDLEN] = "";
    recv(fd, recv_buf, MIDLEN, 0); 

    /*接收到的客戶端消息,爲get_data則進行傳輸*/
    if(strcmp(recv_buf, "get_data") != 0)
    {
        return;
    }
    /*8250爲要傳輸的文件的大小,這裏爲方便測試,直接寫的固定值*/
    /*先發送要傳輸文件的大小,然後再發送整個文件*/
    char data_length[MIDLEN] = "8250";
    send(fd, data_length, strlen(data_length), 0); 
    printf("data_length:%s\n", data_length);
    sleep(3);

    /*讀取要發送的文件*/
    FILE *fp = fopen("/tmp/print_hello", "rb");
    if(fp == NULL)
    {
        return;
    }
    
    char send_buff[MIDLEN] = "";
    int read_size;
    while ( (read_size = fread(send_buff, sizeof(char), MIDLEN, fp)) > 0)
    {
        if (send(fd, send_buff, read_size, 0) < 0)
        {
            break;
        }
        memset(send_buff, '\0', MIDLEN);
    }
    fclose(fp);
    return;
}
/****************************************************************
 * * function name 		: socket_bind_listen
 * * functional description	: socket綁定、監聽
 * * input parameter		:None
 * * output parameter	:None
 * * return value: server_sockfd:創建的套接字
 * * history	:
 * *****************************************************************/
int socket_bind_listen()
{
    struct sockaddr_in server_address;
    int server_sockfd;
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(PORT);

    bind(server_sockfd, (struct sockaddr*) & server_address, sizeof(server_address));
    listen(server_sockfd, LINTEN_QUEUE);
    
    return server_sockfd;
}

/****************************************************************
 * * function name 		: socket_select
 * * functional description	: select模型監控套接字數據
 * * input parameter		:套接字
 * * output parameter	:None
 * * return value: None
 * * history	:
 * *****************************************************************/
void socket_select(int server_sockfd)
{
    fd_set readfds, testfds;
    int client_sockfd;
    struct sockaddr_in client_address;
    int client_len;
    FD_ZERO(&readfds);
    FD_SET(server_sockfd, &readfds);

    struct timeval tv;
    while(1)
    {
        tv.tv_sec = 5;
        tv.tv_usec = 0;
    
        int fd;
        int nread;
        testfds = readfds;
     
        int result = select(FD_SETSIZE, &testfds, (fd_set*)0, (fd_set*)0, NULL); 
        if (result < 0)
        {
            perror("server select error");
            return;
        }
        else if (result == 0)
        {
            printf("time out\n");
            //continue;
        }

        for (fd = 0; fd < FD_SETSIZE; fd++)
        {
            if (FD_ISSET(fd, &testfds))
            {
                if (fd == server_sockfd)
                {
                    client_len = sizeof(client_address);
                    client_sockfd = accept(server_sockfd, (struct sockaddr*) & client_address, &client_len);

                    FD_SET(client_sockfd, &readfds);
                }
                else
                {
                    ioctl(fd, FIONREAD, &nread);
                    if (nread == 0)
                    {
                        close(fd);
                        FD_CLR(fd, &readfds);
                        printf("close client_sockfd:%d\n", fd);
                    }
                    else
                    {
                        send_big_data(fd); 
                    }
                }
            }
        }
    }

}

int main()
{
    int server_sockfd;

    server_sockfd = socket_bind_listen();
    socket_select(server_sockfd);

    return 0;
}
/*client.c*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MIDLEN 1024
#define RET_ERROR 0
#define RET_OK 1
#define PORT 8888
#define SERVERIP "127.0.0.1"

/****************************************************************
 * * function name 		: client_socket_init
 * * functional description	: 客戶端進行socket連接
 * * input parameter		: None
 * * output parameter	:None
 * * return value: 客戶端連接的套接字
 * * history	:
 * *****************************************************************/
int client_socket_init()
{
    int sockfd;
    struct sockaddr_in server_addr;

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        return;
    }
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = inet_addr(SERVERIP);

    if (connect(sockfd, (struct sockaddr*) & server_addr, sizeof(server_addr)) == -1)
    {
        printf("connect error\n");
        return RET_ERROR;
    }
    return sockfd;
}
/****************************************************************
 * * function name 		: recv_big_data
 * * functional description	: 客戶端接收大文件數據
 * * input parameter		: None
 * * output parameter	:None
 * * return value: None
 * * history	:
 * *****************************************************************/
void recv_big_data()
{
    int sockfd = client_socket_init();
    if(sockfd == RET_ERROR)
    {
        printf("client connect socket error\n");
        return;
    }

    /*先發送get_data給服務端,進行數據接收*/
    char client_send[MIDLEN] = "get_data";
    int res = send(sockfd, client_send, strlen(client_send), 0);
    if (res == -1)
    {
        printf("send error\n");
        return;
    }
    
    /*先接收服務端發送過的文件大小,再去循環接收文件*/
    char buf_head_length[MIDLEN] = "";
    if(recv(sockfd, buf_head_length, MIDLEN,0) <= 0)
    {
        close(sockfd);
        return;
    }
    printf("buf_head_length:%s\n", buf_head_length);    
 
    /*接收文件寫入到print_hello_client文件中*/
    FILE *fp = fopen("/tmp/print_hello_client", "wb");
    if(fp == NULL)
    {
        printf("file connot be open\n");
    } 
    else
    {
        char recv_buf[MIDLEN] = "";
        int recv_size = 0;
        int total_length = 0;
        while (1)
        {
            recv_size = recv(sockfd, recv_buf, MIDLEN, 0);
            if (recv_size <= 0)
            {
                printf("recv error\n");
                break;
            }
            total_length += recv_size;
            int write_buf = fwrite(recv_buf, sizeof(char), recv_size, fp);
            if (write_buf < recv_size)
            {
                printf("file write error\n");
            }
            /*接收完整個數據包之後,退出*/
            if (total_length >= (atoi)(buf_head_length)) break;
            memset(recv_buf, '\0', MIDLEN);
        }
        fclose(fp);
    }
    close(sockfd);

    return;
}

int main()
{
    recv_big_data();

    return 0;
}

參考:

https://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html

 

有錯誤或者邏輯不清的地方,歡迎指出!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章