功能:
實際研發過程中,發現要從服務端發送大的數據文件給客戶端,但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
有錯誤或者邏輯不清的地方,歡迎指出!