功能:
实际研发过程中,发现要从服务端发送大的数据文件给客户端,但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 RET_ERROR;
}
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
有错误或者逻辑不清的地方,欢迎指出!