【代码备份】通过TCP进行文件传输_客户端


这里写目录标题

代码

tcp_server.c

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>

struct fifo_data
{
char fifo_name[32];
char fifo_type[32];
char fifo_buff[1024];
unsigned long long data_len;
int ret;
};

struct msg_data
{
int data_size;
char name[16];
struct fifo_data myfifo;
char buffer[1024];
};

struct client_fd_sql{
int client_fd[50];//文件描述符数组
int last;//记录数组引用到哪一个元素
};

typedef struct client_info{
int client_fd;
char name[256];
char ip[16];
short port;
pthread_t tid;

struct client_info *next;
}client_info_node;

#define list_for_each(head, pos)\
for(pos=head->next; pos!=NULL; pos=pos->next)

//申请客户端信息链表的头节点
static client_info_node *request_client_info_node(const client_info_node *info)
{
client_info_node *new_node;

new_node = malloc(sizeof(client_info_node));
if(new_node == NULL)
{
perror("申请客户端节点异常");
return NULL;
}

if(info != NULL)
*new_node = *info;

new_node->next = NULL;

return new_node;
}

static inline void insert_client_info_node_to_link_list(client_info_node *head, client_info_node *insert_node)
{
client_info_node *pos;

for(pos=head; pos->next != NULL; pos=pos->next);

pos->next = insert_node;
}


void *client_thread(void *arg)
{
int client_fd;
struct msg_data recv_msg;

ssize_t recv_size, send_size;
client_info_node *pos, *list_head = arg;

list_for_each(list_head, pos)
{
if(pthread_self() == pos->tid)
{
client_fd = pos->client_fd;
break;
}
}

while(1)
{
bzero(&recv_msg, sizeof(recv_msg));

/*
接受来自与客户端的数据,这个接受具备阻塞特性
跟read函数差不多,都是指定从client_fd里面读取sizeof(buffer)长的数据放到buffer当中,0则代表按照默认操作读取数据
*/
recv_size = recv(client_fd, &recv_msg, sizeof(recv_msg), 0);
if(recv_size == -1)
{
perror("接受数据异常");
break;
}
else if(recv_size == 0)//代表客户端断开连接
{
//删除链表节点
break;
}

list_for_each(list_head, pos)
{
if(pos->client_fd == client_fd)
continue;

printf("转发数据给端口号为%hu\n", pos->port);

send_size = send( pos->client_fd, &recv_msg, sizeof(recv_msg), 0);
if(send_size == -1)
break;
}
printf("接收到来自与客户端%ld个字节的数据:%s\n", recv_size, recv_msg.buffer);
}


return NULL;
}

int main(void)
{
int skt_fd;
int retval;
client_info_node *list_head, *new_node;
client_info_node cache_client_info;
ssize_t recv_size, send_size;

list_head = request_client_info_node(NULL);

/*
获取程序通信的套接字(接口)
AF_INET:IPV4的协议
SOCK_STREAM:指定TCP协议
0:代表不变化协议内部(ip手册中指定的参数)
*/
skt_fd = socket( AF_INET, SOCK_STREAM, 0);
if(skt_fd == -1)
{
perror("申请套接字失败");
return -1;
}
int sw = 1;

//端口绑定复用
retval = setsockopt(skt_fd, SOL_SOCKET, SO_REUSEADDR, &sw, sizeof(sw));

struct sockaddr_in native_addr;

native_addr.sin_family = AF_INET;//指定引用IPV4的协议

native_addr.sin_port = htons(6666);//指定端口号,转化为网络字节序(大端序)

//inet_aton(INADDR_ANY, &(native_addr.sin_addr));

native_addr.sin_addr.s_addr = htonl(INADDR_ANY);//将所有的IP地址转化为二进制的网络字节序的数据进行绑定


retval = bind( skt_fd, (struct sockaddr *)&native_addr, sizeof(native_addr));
if(retval == -1)
{
perror("绑定套接字地址失败");
goto bind_socket_err;
}

retval = listen(skt_fd, 50);
if(retval == -1)
{
perror("设置最大连接数失败");
goto set_listen_err;
}

int client_fd;
struct sockaddr_in client_addr;
socklen_t sklen = sizeof(client_addr);

//int client_fd;
struct client_fd_sql client_fds = {.last=-1};
int i;

int max_fd = skt_fd;
fd_set readfds;

FD_ZERO(&readfds);

struct msg_data recv_msg;

while(1)
{
FD_SET(0, &readfds);//将标准输入的文件描述符放到集合里面
FD_SET(skt_fd, &readfds);//网络链接的文件描述符放到集合里面

for(i=0; i<=client_fds.last; i++)
{
FD_SET(client_fds.client_fd[i], &readfds);
}

retval = select(max_fd+1, &readfds, NULL, NULL, NULL);

if(retval == -1)
{
perror("复用等待异常");
goto select_err;
}
else if(retval == 0)
{
printf("服用等待超时\n");
}
else if(retval)//如果真的有文件描述符相应,则进入这个判断
{
for(i=0; i<=client_fds.last; i++)
{
if(FD_ISSET(client_fds.client_fd[i], &readfds))
{
bzero(&recv_msg, sizeof(recv_msg));

recv_size = recv(client_fds.client_fd[i], &recv_msg, sizeof(recv_msg), 0);
if(recv_size == -1)
{
perror("读取数据异常");
continue;
}else if(recv_size == 0)
{
printf("客户端断开链接\n");
memcpy(client_fds.client_fd+i, client_fds.client_fd+i+1, client_fds.last-i);
client_fds.last--;
break;
}

client_info_node *pos;

list_for_each(list_head, pos)
{
if(pos->client_fd == client_fds.client_fd[i])
continue;

printf("转发数据给端口号为%hu\n", pos->port);

send_size = send( pos->client_fd, &recv_msg, sizeof(recv_msg), 0);
if(send_size == -1)
break;
}
printf("接收到来自与客户端%ld个字节的数据:%s\n", recv_size, recv_msg.buffer);

// printf("recv data=%s\n", buffer);
}

}

if(FD_ISSET(0, &readfds))//如果0号文件描述符还在集合中,便证明我们当前的相应便是标准输入发起的
{
bzero(&recv_msg, sizeof(recv_msg));
scanf("%s", recv_msg.buffer);
printf("从键盘当中获取到:%s\n", recv_msg.buffer);
}

if(FD_ISSET(skt_fd, &readfds))//如果skt_fd在集合中,则证明是有客户端链接服务器,我们才相应了
{
cache_client_info.client_fd = accept(skt_fd, (struct sockaddr *)&client_addr, &sklen);

if(cache_client_info.client_fd == -1)
{
perror("客户端链接失败");
goto client_connect_err;
}

strcpy(cache_client_info.ip, inet_ntoa(client_addr.sin_addr));//存放IP地址
cache_client_info.port = ntohs(client_addr.sin_port);

//新建节点
new_node = request_client_info_node(&cache_client_info);
//将节点插入链表
insert_client_info_node_to_link_list(list_head, new_node);

printf("服务器:客户端连接成功\n");
printf("客户端信息:\n客户端IP为%s,端口号为%hu\n", cache_client_info.ip, cache_client_info.port);

client_fds.last++;
client_fds.client_fd[client_fds.last] = client_fd;

max_fd = max_fd>client_fd ? max_fd : client_fd;

pthread_create(&(new_node->tid), NULL, client_thread, list_head);

}
}

}

close(client_fd);//关闭客户端通信
close(skt_fd);//关闭服务器的socket资源

return 0;

socket_recv_err:
select_err:
close(client_fd);
client_connect_err:
set_listen_err:
bind_socket_err:
close(skt_fd);
return -1;
}

tcp_client.c

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

#define DEST_FILE "./06.rar" //对路径进行宏定义

int fd1;
int fd2;
struct fifo_data
{
char fifo_name[32];
char fifo_type[32];
char fifo_buff[1024];
unsigned long long data_len;
int ret;
};

struct msg_data
{
int data_size;
char name[16];
struct fifo_data myfifo;
char buffer[1024];
};

void *recv_data(void *arg)
{
int skt_fd = *(int *)arg;
struct msg_data send_msg, recv_msg;

ssize_t recv_size;
ssize_t send_size;
printf("recv_data\n");
while(1)
{
bzero(&(send_msg.myfifo.fifo_buff), sizeof(send_msg.myfifo.fifo_buff));
bzero(&recv_msg, sizeof(recv_msg));

/*
接受来自与客户端的数据,这个接受具备阻塞特性
跟read函数差不多,都是指定从client_fd里面读取sizeof(buffer)长的数据放到buffer当中,0则代表按照默认操作读取数据
*/
recv_size = recv(skt_fd, &recv_msg, sizeof(recv_msg), 0);
if(recv_size == -1)
{
perror("接受数据异常");
break;
}
printf("data is %llu Byte\n",recv_msg.myfifo.data_len);

send_msg.myfifo.ret = read(fd1, send_msg.myfifo.fifo_buff, 1024);
if(send_msg.myfifo.ret == 0)
{
printf("copy %llu Byte , read has finshed!\n", send_msg.myfifo.data_len);
send_msg.data_size = sizeof(send_msg);

send_size = send( skt_fd, &send_msg, send_msg.data_size, 0);
if(send_size == -1)
printf("发送出错\n");
break;
}
send_msg.myfifo.data_len = send_msg.myfifo.ret + send_msg.myfifo.data_len;

send_msg.data_size = sizeof(send_msg);

send_size = send( skt_fd, &send_msg, send_msg.data_size, 0);

//printf("");
if(send_size == -1)
printf("发送出错\n");

}

return NULL;
}

void *recv_data22(void *arg)
{
int skt_fd = *(int *)arg;
struct msg_data send_msg, recv_msg;

ssize_t recv_size;
ssize_t send_size;
printf("recv_data22\n");
while(1)
{
bzero(&recv_msg, sizeof(recv_msg));

/*
接受来自与客户端的数据,这个接受具备阻塞特性
跟read函数差不多,都是指定从client_fd里面读取sizeof(buffer)长的数据放到buffer当中,0则代表按照默认操作读取数据
*/
recv_size = recv(skt_fd, &recv_msg, sizeof(recv_msg), 0);
if(recv_size == -1)
{
perror("接受数据异常");
break;
}
if(recv_msg.myfifo.ret == 0)
{
printf("文件传输完毕,长度为%llu\n", recv_msg.myfifo.data_len);
close(fd2);
break;
}
write(fd2, recv_msg.myfifo.fifo_buff, recv_msg.myfifo.ret);

printf("data is %llu Byte\n",recv_msg.myfifo.data_len);

send_msg.myfifo.data_len = send_msg.myfifo.ret + send_msg.myfifo.data_len;

send_msg.data_size = sizeof(send_msg);

send_size = send( skt_fd, &send_msg, send_msg.data_size, 0);

if(send_size == -1)
printf("发送出错\n");

}
return NULL;

}

//./client 服务器IP 名字
int main(int argc, const char *argv[])
{
int skt_fd;
int retval;

skt_fd = socket( AF_INET, SOCK_STREAM, 0);
if(skt_fd == -1)
{
perror("申请套接字失败");
return -1;
}

struct sockaddr_in srv_addr;

srv_addr.sin_family = AF_INET;//指定引用IPV4的协议

srv_addr.sin_port = htons(6666);//指定端口号,转化为网络字节序(大端序)

srv_addr.sin_addr.s_addr = inet_addr(argv[1]);//将所有的IP地址转化为二进制的网络字节序的数据进行绑定

retval = connect(skt_fd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));

if(retval == -1)
{
perror("客户端连接到服务器失败\n");
goto connect_server_err;
}

printf("客户端:连接服务器成功\n");

struct msg_data send_msg;

stpcpy(send_msg.name, argv[2]);

ssize_t send_size;

printf("接收:1\n还是发送:2\n");
scanf("%d", &retval);
if(retval == 1)
{
fd2 = open("./07.rar", O_WRONLY | O_CREAT | O_TRUNC, 0777);
pthread_t tid;
pthread_create(&tid, NULL, recv_data22, &skt_fd);
}
else
{
fd1 = open(DEST_FILE, O_RDONLY);
if(fd1 == -1)
{
printf("open %s failed! : \n", DEST_FILE); //sterror把错误码转化成错误提示
return -1;
}
send_msg.myfifo.data_len = 0;
send_msg.myfifo.ret = read(fd1, send_msg.myfifo.fifo_buff, 1024);
printf("%d \n", send_msg.myfifo.ret);
send_msg.myfifo.data_len = send_msg.myfifo.ret + send_msg.myfifo.data_len;
send_msg.data_size = sizeof(send_msg);
send_size = send( skt_fd, &send_msg, send_msg.data_size, 0);
if(send_size == -1)
printf("发送出错\n");

pthread_t tid;
pthread_create(&tid, NULL, recv_data, &skt_fd);
}
while(1)
{
bzero(&(send_msg.buffer), sizeof(send_msg.buffer));

scanf("%s", send_msg.buffer);
send_msg.data_size = sizeof(send_msg);

send_size = send( skt_fd, &send_msg, send_msg.data_size, 0);
if(send_size == -1)
break;
}

close(skt_fd);

return 0;

connect_server_err:
close(skt_fd);
return -1;
}

代码说明:

1、服务器只是负责转发消息

2、客户端已经进行端口复用设置

3、需要发送的文件名路径已经设置好了,测试的需要自己更改

4、该客户端代码匆忙上线,只有简陋的文件发送功能。

公众号:图控大叔