首先 在linux中 我们最常采用的是epoll,这种网络模型是异步的,而且相比于poll和select网络模型,在连接的客户端较多时,epoll的处理效率要更高。
这里先采用epoll来写一个服务器用于与客户端之交互。
既然用到epoll,我们可以先简单了解一下epoll
核心部分就是三个API
Epoll通常有三大部分
Epoll_create创建红黑树
Struct epoll_event定义要监控的文件结构体
Epoll_ctl把要监控的文件描述添加到一颗红黑树,epoll_wait等待发生的消息列表返回,链表中所有的消息都是已经发生的
我们遍历列表处理事件即可
这里先例举一个简单的单线程epoll框架
上图的服务器模型可以实现简单的与客户端之间的对话
我们之后会在这个框架的基础上改进
首先我创建了一个函数recvdata 用来接受数据
这里我介绍一下我的传输逻辑:
(1)先传输一个文件信息包(不含文件内容)
typedef struct fileinfo
{
int filesize;
char filepath[MAXSIZE];
}fileinfo;
来告知服务器:我的文件想存放在哪里,我的文件大小是多少。
(2)然后服务器校验文件信息,如果满足条件就发送回复“OK“。
(3)客户端收到“OK”后发送文件内容
(4)服务器接受文件内容 直到接受完整在再进行写入文件(这样尽量不会影响接受效率)
(5)最后,在客户端退出时,会发送推出标识“OT”,服务器一旦受到推出标识就会与相应客户端断开连接。
改进后的
服务器代码如下:
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<poll.h>
#include<sys/epoll.h>
#include<pthread.h>
#include<string.h>
#define MAXSIZE 1024
//定义文件包
typedef struct fileinfo
{
int filesize;
char filepath[MAXSIZE];
}fileinfo;
void recvdata(int fd,int offest,fileinfo*fileif)
{
//打开文件
char szbuf[10240];
char mark[3];
fileinfo*info=(fileinfo*)fileif;
printf("filesize is %d\n",info->filesize);
printf("filepath is %s\n",info->filepath);
if(info->filesize>0)
{
//FILE*fp=fopen(pfile->filepath,"a");
FILE*fp=fopen(info->filepath,"a");
if(NULL==fp)
{
printf("fopen failed...\n");
return;
}
strcpy(mark,"OK");mark[2]='\0';send(fd,mark,3,0);
int nerl=0;
size_t nwr=0;
bzero(szbuf,10240);
while((nerl=recv(fd,szbuf,sizeof(szbuf),0))>0)
{
printf("recv data is %s\n",szbuf);
printf("nerl is %d\n",nerl);
offest+=nerl;
if(offest>=info->filesize)
{
//收文件
fwrite(szbuf,1,offest,fp);
printf("offest is %d\n",offest);
printf("file size is: %d\n",info->filesize);
fclose(fp);
offest=0;
printf("recvdata success...\n");
break;
}
}
}
}
int main()
{
//创建套接子socket()
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
perror("socket failed...");
exit(0);
}
//绑定bind()
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=0;
addr.sin_port=htons(8899);
if(-1==bind(sockfd,(struct sockaddr*)&addr,sizeof(addr)))
{
perror("bind failed...");
exit(0);
}
//监听listen()
if(-1==listen(sockfd,128))
{
perror("listen failed...");
exit(0);
}
//定义struct
int epfd=epoll_create(1024);
struct epoll_event ep;
struct epoll_event sockary[1024];
ep.data.fd=sockfd;
ep.events=EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ep);
//将socket放入集合
int nerl=0;
char szbuf[10240];
int nreadnum=0;
int clientfd;
//连接accept()或者接受文件recv()
while(1)
{
//监控的红黑数文件描述,已经发生的消息的连表,最大管理数量,-1
nreadnum=epoll_wait(epfd,sockary,1024,-1);
int i=0;
int offest=0;
printf("nreadnum is %d\n",nreadnum);
while(i<nreadnum)
{
if(sockary[i].events&EPOLLIN)
{
if(sockary[i].data.fd==sockfd)
{
clientfd=accept(sockfd,0,0);
printf("accept success");
struct epoll_event ep;
ep.data.fd=clientfd;
ep.events=EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ep);
}
else
{
bzero(szbuf,10240);
if((nerl=recv(sockary[i].data.fd,szbuf,10240,0))>0)
{
if(strcmp(szbuf,"OT")==0){close(sockary[i].data.fd);continue;}
recvdata(sockary[i].data.fd,offest,(fileinfo*)szbuf);
}sleep(1);
sockary[i].events=EPOLLIN;
}
//bzero(szbuf,sizeof(szbuf));
}
i++;
}
sleep(5);
}
sleep(1);
close(sockfd);
for(int i=1;i<1024;i++){if(sockary[i].data.fd!=-1)close(sockary[i].data.fd);}
//close(clientfd);
return 0;
}
到了客户端 我想要实现多个文件传输,这里有一个功能是将一个文件夹下的所有文件都传输出去。
这里借鉴了
(57条消息) Linux环境下C语言Socket编程——客户端向服务端单次发送一个或多个文件的功能_linux网络编程-C代码类资源-CSDN文库
先简单介绍一下客户端传输的步骤:
(1)先确定传一个还是多个文件,按0传一个,按1传多个。
(2)传一个:输入当前想要上传的文件名,程序会校验输入的文件是否存在,不存在重新输入,存在则输入接收端存放的路径,程序也会校验,如果路径下的那个文件不存在,程序会在路径创建文件。
传多个:程序会遍历当前文件夹的所有文件,循环上传,只不过你需要依次输入存放路径。
(3)我们的上传文件函数中会先发送文件信息,待收到“OK”后再发送文件内容。
在退出时,发送“OT”。
客户端代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#define MAXSIZE 1024
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#define SERVER_PORT 6666
#define BUFFER_SIZE 1024
#define CLIENT_PATH "../test/send/"
#define SERVER_PATH "../test/recv/"
typedef struct fileinfo
{
int filesize;
char filepath[MAXSIZE];
}fileinfo;
int pack_send_File(char*path,int clientfd, FILE* fp)
{
//定义发送缓冲字符串和一些变量
size_t nerl;
char sendbuff[1024];
char mark[3];
bzero(sendbuff,1024);
int bigfilesize=0;
//获取文件大小
fseek(fp,0,SEEK_END);
bigfilesize=ftell(fp);
printf("bigsize is %d\n",bigfilesize);
fseek(fp,0,SEEK_SET);
//定义包内容
fileinfo *info=(fileinfo*)malloc(sizeof(fileinfo));
info->filesize=bigfilesize;
strcpy(info->filepath,path);
printf("info->filepath is %s\n",info->filepath);
//发送包内容 等待回复
if(send(clientfd,(char*)info,1028,0)<=0){printf("first send failed...\n");}
recv(clientfd,mark,3,0);
printf("recv mark is %s\n",mark);
//校验回复 看让不让发
if(strcmp(mark,"OK")==0)
{
printf("w and s is begin...\n");
//发送内容
while((nerl=fread(sendbuff,1,1024,fp))>0)
{
printf("sendbuff is %s\n",sendbuff);
if(send(clientfd,sendbuff,nerl,0)<0)
{
printf("file send failed...\n");
break;
}
bzero(sendbuff,1024);
}sleep(1);
}
//不让发
else{printf("the conform recv error...\n");}
free(info);
}
int submit_Files(int clientSocket)
{
FILE* fp;
char filename[100] = {0}, pre_filename[100] = {0};
struct dirent* ptr;
DIR* path = NULL;
char send_buf[BUFFER_SIZE] = {0};
path = opendir((char*)CLIENT_PATH);
while((ptr=readdir(path)) != NULL) {
if(strcmp(ptr->d_name,".")==0||strcmp(ptr->d_name,"..")==0) {
continue;
}
if(ptr->d_type==DT_REG) {
printf("%s\n",ptr->d_name);
strcpy(pre_filename,CLIENT_PATH);
strcat(pre_filename,ptr->d_name);
fp = fopen(pre_filename, "rb");
while(1)
{
bzero(pre_filename,sizeof(pre_filename));
bzero(filename,sizeof(filename));
strcpy(pre_filename,SERVER_PATH);
printf("请输入你要写入的文件名: ");
scanf("%[^\n]%*c",filename);
strcat(pre_filename,filename);
FILE*fp1 = fopen(pre_filename, "w");
if(fp1 == NULL) {
printf("您提供的文件不存在并且 创建文件失败,请重新输入!\n");
fclose(fp1);
continue;
}
fclose(fp1);
break;
}
printf("已找到或创建好了您的文件:路径是%s\n",pre_filename);
pack_send_File(pre_filename,clientSocket, fp);
printf("文件%s上传成功!\n",ptr->d_name);
}
}
}
int submit_File(int clientSocket)
{
FILE *fp;
char send_buf[BUFFER_SIZE] = {0};
char filename[100] = {0};
char* pre_filename = (char*)malloc(sizeof(char)*100);
while(1){
strcpy(pre_filename,CLIENT_PATH);
printf("请输入你要提交的文件名: ");
scanf("%[^\n]%*c",filename);
strcat(pre_filename,filename);
fp = fopen(pre_filename, "rb");
if(fp == NULL) {
printf("您提供的文件不存在,请重新输入!\n");
continue;
}
break;
}
printf("已找到您的文件:路径是%s\n",pre_filename);
while(1){
bzero(pre_filename,sizeof(pre_filename));
bzero(filename,sizeof(filename));
strcpy(pre_filename,SERVER_PATH);
printf("请输入你要写入的文件路径: ");
scanf("%[^\n]%*c",filename);
strcat(pre_filename,filename);
FILE*fp1 = fopen(pre_filename, "w");
if(fp1 == NULL) {
printf("您提供的文件不存在,请重新输入!\n");
fclose(fp1);
continue;
}
fclose(fp1);
break;
}
printf("已找到您的文件:路径是%s\n",pre_filename);
pack_send_File(pre_filename,clientSocket, fp);
printf("文件上传成功!\n");
close(clientSocket);
}
int main()
{
//socket()
int clientfd=socket(AF_INET,SOCK_STREAM,0);
//connect()
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=inet_addr("127.0.0.1");
addr.sin_port=htons(8899);
if(-1==connect(clientfd,(struct sockaddr*)&addr,sizeof(addr)))
{
perror("connect failed");
exit(0);
}
int choice=-1;
printf("请选择上传单个文件还是多个文件,单个文件输入0,多个文件输入1:");
while(1)
{
scanf("%d",&choice);
getchar();
if(choice == 0 || choice == 1)
break;
else
printf("请正确输入0或1");
}
if(choice == 0)
{
submit_File(clientfd);
}
else
{
//int filenum = get_File_Num((char*)CLIENT_PATH);
submit_Files(clientfd);
}
send(clientfd,"OT",3,0);
close(clientfd);
return 0;
}