技術點:
chdir:將工作目錄切到當前。
fprintf:拼接字符串。
服務器端:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#pragma pack(1)
struct file_info
{
char name[300];
int size; //-1表示爲文件夾,非負整數表示普通文件
};
#pragma pack()
struct file_info fi;
const char* file_path; //要發送文件的路徑
void* srv_thr(void* arg);
void send_file(int sock_conn, const char* path);
int send_regfile(int sock_conn, const char* path);
int send_dir(int sock_conn, const char* path);
int main(int argc, char** argv)
{
if(argc != 2)
{
fprintf(stderr, "命令語法錯誤!\n");
return -1;
}
if(access(argv[1], R_OK) != 0)
{
fprintf(stderr, "%s不存在或不可讀!\n", argv[1]);
return -1;
}
signal(SIGPIPE, SIG_IGN);
//獲取要發送的文件的路徑
file_path = argv[1];
//第一步:創建監聽套接字
//sock_listen:套接字描述符,用來唯一地標識一個套接字
//socket函數的第一個參數表示地址家族
//AF_INET:使用Internet地址家族,即採用TCP/IP網絡標準
//socket函數的第二個參數表示套接字類型,主要有三種類型:
//SOCK_STREAM 即流套接字,用於TCP協議通信
//SOCK_DGRAM 即數據報套接字,用於UDP協議通信
//SOCK_RAW 即原始套接字,用於網絡底層通信
//socket函數的第三個參數表示使用的通信協議,0表示使用默認的協議
int sock_listen = socket(AF_INET, SOCK_STREAM, 0);
//設置端口複用
int opt_val = 1;
setsockopt(sock_listen, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val));
//第二步:給套接字綁定一個地址
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET; //指定地址家族爲Internet地址家族
myaddr.sin_addr.s_addr = INADDR_ANY; //指定IP地址爲本機任意地址
myaddr.sin_port = htons(9999); //指定端口號爲8888
//htons:將一個short類型數據從主機字節序轉化爲網絡字節序
if(-1 == bind(sock_listen, (struct sockaddr*)&myaddr, sizeof(myaddr)))
{
perror("bind");
exit(-1);
}
//第三步:將套接字設置爲監聽狀態
//listen函數的第二個參數表示監聽等待隊列的長度,通常設置爲5
listen(sock_listen, 5);
//設置接收超時爲300ms
struct timeval rcv_timeout;
rcv_timeout.tv_sec = 0;
rcv_timeout.tv_usec = 300000;
pthread_t tid;
int sock_conn;
struct sockaddr_in client_addr;
socklen_t len;
while(1)
{
//第四步:接受客戶端連接請求
//accept函數的返回值爲一個連接套接字,專門用於對應的連接上和客戶端進行通信
//調用accept函數時如果沒有客戶端連接請求到來,它將阻塞當前線程的執行,直到接收到一個客戶端連接請求或者出錯才返回
len = sizeof(client_addr);
sock_conn = accept(sock_listen, (struct sockaddr*)&client_addr, &len);
if(sock_conn == -1)
{
perror("accept");
continue;
}
printf("%s:%d已經連接!\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
//設置recv超時時間爲300ms
setsockopt(sock_conn, SOL_SOCKET, SO_RCVTIMEO, &rcv_timeout, sizeof(rcv_timeout));
if(0 != pthread_create(&tid, NULL, srv_thr, (void*)sock_conn))
{
perror("pthread_create");
close(sock_conn);
}
}
//第七步:關閉監聽套接字
close(sock_listen);
return 0;
}
void* srv_thr(void* arg)
{
pthread_detach(pthread_self());
int sock_conn = (int)arg;
send_file(sock_conn, file_path);
close(sock_conn);
return NULL;
}
//發送文件
void send_file(int sock_conn, const char* path)
{
//獲取文件名和所處路徑
const char* file_name = NULL;
char file_path[300] = "./";
file_name = strrchr(path, '/');
if(file_name == NULL)
{
file_name = path;
}
else
{
file_name++;
strncpy(file_path, path, file_name - path);
}
//將當前工作目錄設置爲要發送的文件所在的目錄
chdir(file_path);
//判斷文件類型
struct stat st;
stat(path, &st);
if(S_ISDIR(st.st_mode))
{
//文件夾
send_dir(sock_conn, file_name);
}
else
{
//非文件夾
send_regfile(sock_conn, file_name);
}
}
//發送普通文件
int send_regfile(int sock_conn, const char* path)
{
//獲取文件相對路徑
struct file_info fi = {"", 0};
strcpy(fi.name, path);
//獲取文件大小
struct stat st;
stat(path, &st);
fi.size = st.st_size;
send(sock_conn, &fi, sizeof(fi), 0);
char msg[1024];
int ret;
FILE* fp =NULL;
fp = fopen(path, "rb");
if(fp == NULL) return 1;
while(!feof(fp))
{
ret = fread(msg, 1, sizeof(msg), fp);
if(send(sock_conn, msg, ret, 0) < 0)
break;
}
fclose(fp);
ret = recv(sock_conn, msg, sizeof(msg)-1, 0);
if(ret > 0)
{
msg[ret] = '\0';
if(strcmp(msg, "ok") == 0)
{
//printf("發送文件成功!\n");
return 0;
}
else if(strcmp(msg, "err") == 0)
{
//printf("發送文件失敗!\n");
return 1;
}
else
{
//printf("非法客戶端!\n");
return 2;
}
}
else
{
return 3;
}
}
//發送文件夾
int send_dir(int sock_conn, const char* path)
{
struct file_info fi = {"", -1};
int ret;
char reply[10];
int send_flag = 0;
strcpy(fi.name, path);
send(sock_conn, &fi, sizeof(fi), 0);
ret = recv(sock_conn, reply, sizeof(reply) - 1, 0);
if(ret > 0)
{
reply[ret] = '\0';
if(strcmp(reply, "ok") == 0)
{
send_flag = 1;
}
}
if(send_flag != 1) return 1;
DIR* dirp = opendir(path);
struct dirent* de = NULL;
char file_path[300];
struct stat st;
while(de = readdir(dirp))
{
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
continue;
sprintf(file_path, "%s/%s", path, de->d_name);
stat(file_path, &st);
if(S_ISDIR(st.st_mode))
{
//文件夾
if(send_dir(sock_conn, file_path) != 0) return 1;
}
else
{
//非文件夾
if(send_regfile(sock_conn, file_path) != 0) return 1;
}
}
closedir(dirp);
return 0;
}
客戶端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <unistd.h>
#pragma pack(1)//設置爲1字節對齊
struct file_info
{
char name[300];
int size;
};
#pragma pack()
int rcv_file(int socket_conn);
int main(int argc,char** argv)
{
if(argc<3 || argc>4)
{
printf("參數有誤");
exit(-1);
}
if(argc==4)//到指定目錄創建文件夾
{
if(-1==access(argv[3],W_OK))//判斷指定目錄是否存在
{
perror("當前目錄不可寫");
exit(-1);
}
if(-1==chdir(argv[3]))
{
perror("設置當前工作目錄失敗");
return -1;
}
}
int socket_conn=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ser_addr;
ser_addr.sin_family=AF_INET;
ser_addr.sin_addr.s_addr=inet_addr(argv[1]);
ser_addr.sin_port=htons(atoi(argv[2]));
if(-1==connect(socket_conn,(struct sockaddr*)&ser_addr,sizeof(ser_addr)))
{
perror("connect");
exit(-1);
}
//設置接收超時爲1秒
struct timeval tv;
tv.tv_sec=1;
tv.tv_usec=0;
setsockopt(socket_conn,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));
int i;
while(1)
{
i=rcv_file(socket_conn);
if(i==0)
{
printf("接收成功\n");
break;
}
}
close(socket_conn);
return 0;
}
int rcv_file(int socket_conn)
{
struct file_info fi = {"",0};
int ret;
char buf[1024];
char name[100];
struct stat s;
ret=recv(socket_conn,&fi,sizeof(fi),0);
if(ret==0)//對方斷開連接
{
return 0;
}
if(ret!=sizeof(fi))
{
fprintf(stderr," 接收失敗\n");
return -1;
}
if(fi.size==-1)//文件夾
{
mkdir(fi.name,0777);
send(socket_conn,"ok",2,0);
}
else if(fi.size>=0)//普通文件
{
FILE* fp=fopen(fi.name,"wb");
int recv_cnt=0;
while(recv_cnt<fi.size)//根據文件大小,判斷什麼時候接收文件
{
ret=recv(socket_conn,buf,sizeof(buf),0);
if(ret<=0)
{
perror("recv");
break;
}
recv_cnt+=ret;
fwrite(&buf,ret,1,fp);
}
fclose(fp);
if(recv_cnt==fi.size)
{
send(socket_conn,"ok",2,0);
}
else
{
send(socket_conn,"err",3,0);
}
}
else
{
fprintf(stderr,"server err\n");
return -1;
}
}