需求
在Linux上利用流套接字提供客户端和服务端面向连接的交互。进行登陆验证、查看服务器列表、下载文件、远程执行命令的几个操作。
设计
- 每次请求客户端首先发出一个报头,得到服务端的响应后确保连接的正确建立,之后开始传文件。
- 对于下载文件操作:客户端输入一个相对路径或者绝对路径以及文件名,等到服务端响应一个确认报文后开始进行收文件。其中文件的传递利用二进制流的形式发送,这样的优点是能够将文件分成许多块发送且能够方便客户端读取。
- 对于执行命令操作服务端通过客户端传来的命令,将其切割成各个命令以及参数块,之后利用fork以及exec函数族的形式让子进程执行改命令。要注意的是,通过exec执行的命令很难与其他命令嵌套,比如我们要把操作结果返回给客户端需要用到文件重定向,这里需要使用system函数,其内部实现是exec+fork+文件;能够轻易把传来的命令正确执行最后将结果重定向到文件中reply给客户端。
报文设计:
实现
服务端:
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h> // 提供属性操作函数
#include <sys/types.h> // 提供mode_t 类型
#include <malloc.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <dirent.h> // 提供目录流操作函数
#define PORT 56193
#define MAX_UNAME 8
#define MAX_PASSWD 8
#define MAX_BUF_SZ 1024
#define MAX_CMD_SZ 20
#define TYPE_LOGIN 1
#define TYPE_READ 2
#define TYPE_EXIT 3
#define TYPE_EXEC_CMD 4
#define TYPE_GET_FILE 5
#define TYPE_NOT_FILE 6
#define TYPE_YES_FILE 7
struct hdr_st{
unsigned short type;
unsigned short len;
};
void scan_dir(char *dir, char *strbuf, int bufsz) // 定义目录扫描函数
{
DIR *dp; // 定义子目录流指针
struct dirent *entry; // 定义dirent结构指针保存后续目录
struct stat statbuf; // 定义statbuf结构保存文件属性
int sz, remain = bufsz;
char *pos = strbuf;
if((dp = opendir(dir)) == NULL) // 打开目录,获取子目录流指针,判断操作是否成功
{
puts("can't open dir.");
return;
}
chdir (dir); // 切换到当前目录
while((entry = readdir(dp)) != NULL) // 获取下一级目录信息,如果未否则循环
{
lstat(entry->d_name, &statbuf); // 获取下一级成员属性
if(S_IFDIR &statbuf.st_mode) // 判断下一级成员是否是目录
{
if (strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0)
continue;
}
sz = strlen(entry->d_name);
if(sz < remain){
remain -= sz;
sprintf(pos, "%s\n", entry->d_name); // 输出属性不是目录的成员
pos += (sz+1);
}else return ;
}
chdir(".."); // 回到上级目录
closedir(dp); // 关闭子目录流
}
void split(char cmd_list[][MAX_CMD_SZ], char *cmd, int *sz) {
int i, j, len, flag = 0;
len = strlen(cmd);
*sz = 0;
j = 0;
for (i = 0; i < len; i++) {
if (cmd[i] == ' ' && i && cmd[i-1] != ' ') {
cmd_list[*sz][j] = 0;
*sz = *sz + 1;
j = 0;
flag = 0;
} else if (cmd[i] != ' ') {
cmd_list[*sz][j++] = cmd[i];
flag = 1;
}
}
if (flag) {
cmd_list[*sz][j] = 0;
*sz = *sz + 1;
}
}
int do_login(int sAccept)
{
char usrname[MAX_UNAME];
char passwd[MAX_PASSWD];
int sz;
printf("Receive Login request!\n");
/* TODO here do not handle the sending delay */
sz = recv(sAccept, usrname, MAX_UNAME, 0);
if(sz < 0){
printf("Did not receive the enough Message in login.\n");
return 0;
}
sz = recv(sAccept, passwd, MAX_UNAME, 0);
if(sz < 0){
printf("Did not receive the enough Message in login.\n");
return 0;
}
if (strcmp(usrname, "root") != 0 || strcmp(passwd, "123456") != 0) {
return 0;
}
printf("User name :%s\n", usrname);
printf("passwd: %s\n", passwd);
return 1;
}
void do_get_file_list(int sockfd) {
char file_list[MAX_BUF_SZ];
scan_dir(".", file_list, MAX_BUF_SZ - 1);
if ( send(sockfd, &file_list, sizeof(file_list), 0) < 0 ) {
printf("send() failed\n");
} else {
printf("file list send success!\n");
}
}
void exec_cmd(int sockfd) {
char cmd_name[MAX_BUF_SZ];
char cmd_list[MAX_CMD_SZ][MAX_CMD_SZ];
char *p[MAX_CMD_SZ];
int sz, cmd_sz,i,pfd,len;
struct hdr_st hdr;
FILE* fp;
char file[MAX_BUF_SZ];
sz = recv(sockfd, cmd_name, MAX_BUF_SZ, 0);
if ( sz < 0 ) {
printf("recv() failed\n");
return ;
} else if (sz == 0) {
printf("connect close\n");
return ;
}
printf("recv : %s\n", cmd_name);
strcat(cmd_name, " > result");
split(cmd_list, cmd_name, &cmd_sz);
// strcpy(cmd_list[cmd_sz++], ">result");
strcpy(cmd_list[cmd_sz++], "NULL");
for (i = 0; i < cmd_sz; i++) p[i] = cmd_list[i];
//run the command
if ((pfd = fork()) == 0) {
#if 0
if (execvp(cmd_list[0], p) < 0) {
printf("execvp error!\n");
}
#else
system(cmd_name);
exit(0);
#endif
} else {
waitpid(pfd, NULL, 0);
printf("run success!\n");
//reply ack or nak
fp = fopen("./result", "rb");
if (fp == NULL) {
printf("open error!\n");
hdr.type = TYPE_NOT_FILE;
if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){
printf("Send error!\n");
return ;
}
return ;
}
hdr.type = TYPE_YES_FILE;
fseek(fp, 0, SEEK_END);
hdr.len = ftell(fp);
fseek(fp, 0, SEEK_SET);
if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){
printf("Send error!\n");
return ;
}
//send command file
while (1) {
len = fread(file, 1, MAX_BUF_SZ, fp);
if ( send(sockfd, file, len, 0) < 0 ) {
printf("send() failed\n");
fclose(fp);
return ;
}
if (feof(fp)) break;
}
fclose(fp);
}
}
void send_file(int sockfd) {
struct hdr_st hdr;
FILE *fp;
char file_name[MAX_BUF_SZ];
char file[MAX_BUF_SZ];
int sz, len;
sz = recv(sockfd, file_name, MAX_BUF_SZ, 0);
if ( sz < 0 ) {
printf("recv() failed\n");
return ;
} else if (sz == 0) {
printf("connect close\n");
return ;
}
printf("recv : %s\n", file_name);
//Reply getfile cmd
fp = fopen(file_name, "rb");
if (fp == NULL) {
printf("open error!\n");
hdr.type = TYPE_NOT_FILE;
if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){
printf("Send error!\n");
return ;
}
return ;
}
hdr.type = TYPE_YES_FILE;
fseek(fp, 0, SEEK_END);
hdr.len = ftell(fp);
fseek(fp, 0, SEEK_SET);
if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){
printf("Send error!\n");
return ;
}
//sendfile
while (1) {
len = fread(file, 1, MAX_BUF_SZ, fp);
if ( send(sockfd, file, len, 0) < 0 ) {
printf("send() failed\n");
fclose(fp);
return ;
}
if (feof(fp)) break;
}
fclose(fp);
printf("send file size is %d\n", hdr.len);
}
int main()
{
struct hdr_st hdr;
struct sockaddr_in serv ;//服务器的地址
int sListen,sAccept;
int rcv_sz;
int rcv_send; //登陆的回传
int tag;
char buf[MAX_BUF_SZ];
//创建TCP套接字
sListen = socket(AF_INET, SOCK_STREAM, 0);
if (sListen < 0){
//创建套接字失败
perror("socket failed:%d\n");//输出错误
return;
}
//填写服务器地址到serv上
serv.sin_family = AF_INET;
serv.sin_port = htons(PORT);
serv.sin_addr.s_addr = inet_addr("127.0.0.1");
//将serv绑定到socket上
if (bind(sListen, (struct sockaddr*)&serv, sizeof(serv))<0){
perror("bind() failed:\n");
return;
}
//让socket进入监听状态,等待连接的最大个数是5
if (listen(sListen, 5)<0){
perror("listen() failed:\n");
return;
}
printf("Waiting for client to connnect.....\n");
//进入循环等待客户端连接请求
struct sockaddr_in cli;
socklen_t peerlen;
while (1) {
peerlen = sizeof(cli);
sAccept = accept(sListen, (struct sockaddr*)&cli, &peerlen);
if (sAccept < 0){
perror("accept() failed\n");
break;
}
printf("accepted client\n");
tag = 1;
while(tag) {
rcv_sz = recv(sAccept, &hdr, sizeof(hdr), 0);
if ( rcv_sz < 0 ) {
printf("recv() failed\n");
break;
}
switch(hdr.type)
{
case TYPE_LOGIN:
if (do_login(sAccept) == 0) {
printf("login fail!\n");
rcv_send = 0;
if ( send(sAccept, &rcv_send, sizeof(rcv_send), 0) < 0 ) {
printf("send() failed\n");
tag = 0;
}
} else {
printf("login success!\n");
rcv_send = 1;
if ( send(sAccept, &rcv_send, sizeof(rcv_send), 0) < 0 ) {
printf("send() failed\n");
tag = 0;
}
}
break;
case TYPE_READ:
do_get_file_list(sAccept);
break;
case TYPE_EXIT:
printf("free connect!\n");
tag = 0;
break;
case TYPE_EXEC_CMD:
exec_cmd(sAccept);
break;
case TYPE_GET_FILE:
send_file(sAccept);
break;
}
hdr.type = 0;
}
close(sAccept);
}
close(sListen);
return 0;
}
客户端
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <malloc.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 56193
#define MAX_UNAME 8
#define MAX_PASSWD 8
#define MAX_LINE 256
#define MAX_BUF_SZ 1024
#define TYPE_LOGIN 1
#define TYPE_READ 2
#define TYPE_EXIT 3
#define TYPE_EXEC_CMD 4
#define TYPE_GET_FILE 5
#define TYPE_NOT_FILE 6
#define TYPE_YES_FILE 7
struct hdr_st{
unsigned short type;
unsigned short len;
};
char *menu[]={
"1.Login",
"2.Get file list",
"3.Quit",
"4.Exec your command",
"5.Get file",
};
int count = sizeof(menu)/sizeof(char *);
int show_menu()
{
int i;
printf("===================\n");
for(i=0; i< count; i++){
printf("%s\n", menu[i]);
}
}
char chose_menu()
{
int chose;
int item;
while(1){
show_menu();
printf("Chose a item:");
chose = getchar();
getchar();
item = chose - '0';
if(item > count || item <= 0){
//printf("Error input: %d %d\n", item, chose);
continue;
}
printf("Chose %d(%s)\n", chose, menu[chose-'1']);
return item;
}
}
void login(int sockfd)
{
struct hdr_st hdr;
char usrname[MAX_LINE];
char passwd[MAX_LINE];
printf("Send Login message....\n");
hdr.type = TYPE_LOGIN;
hdr.len = MAX_UNAME+MAX_PASSWD;
printf("Username:");
scanf("%s", usrname);
usrname[MAX_UNAME-1] = 0;
getchar();
printf("Password:");
scanf("%s",passwd);
getchar();
passwd[MAX_PASSWD -1] = 0;
if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){
printf("Send msg error!");
return ;
}
if(send(sockfd, usrname, MAX_UNAME, 0) <= 0){
printf("Send msg error!");
return ;
}
if(send(sockfd, passwd, MAX_PASSWD, 0) <= 0){
printf("Send msg error!");
return ;
}
printf("Send login ok\n");
}
void get_file_list(int sockfd) {
struct hdr_st hdr;
char buf[MAX_BUF_SZ];
int rcv_sz;
hdr.type = TYPE_READ;
hdr.len = 0;
if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){
printf("Send cmd error!");
return ;
}
rcv_sz = recv(sockfd, buf, MAX_BUF_SZ, 0);
if ( rcv_sz < 0 ) {
printf("recv() failed\n");
return ;
}
buf[MAX_BUF_SZ - 1] = 0;
fputs(buf, stdout);
}
void program_exit(int sockfd) {
struct hdr_st hdr;
hdr.type = TYPE_EXIT;
hdr.len = 0;
if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){
printf("Send cmd error!");
return ;
}
}
void exec_cmd(int sockfd) {
struct hdr_st hdr;
int rcv_sz, len, filelen;
char command[MAX_BUF_SZ],ch,file[MAX_BUF_SZ];
char dir[MAX_BUF_SZ]="../";
FILE* fp;
printf("please input the command:\n");
fgets(command, MAX_BUF_SZ, stdin);
len = strlen(command) - 1;
command[len] = 0;
hdr.type = TYPE_EXEC_CMD;
hdr.len = len;
if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){
printf("Send cmd error!");
return ;
}
if(send(sockfd, command, MAX_BUF_SZ, 0) <= 0){
printf("Send command error!\n");
return ;
}
printf("Send command success!\n");
//wait ack or nak
rcv_sz = recv(sockfd, &hdr, sizeof(hdr), 0);
if ( rcv_sz < 0 ) {
printf("recv() failed\n");
return ;
} else if (rcv_sz == 0) {
printf("connect close\n");
return ;
}
if (hdr.type == TYPE_NOT_FILE) {
printf("The command run error!\n");
return ;
}
if (hdr.type == TYPE_YES_FILE) {
printf("The command run success!\n");
filelen = hdr.len;
}
//download file
strcat(dir, "result");
fp = fopen(dir, "wb");
while(filelen > 0) {
rcv_sz = recv(sockfd, file, MAX_BUF_SZ, 0);
if ( rcv_sz < 0 ) {
printf("recv() failed\n");
fclose(fp);
return ;
}
fwrite(file, 1, rcv_sz, fp);
filelen -= rcv_sz;
}
fflush(fp);
fclose(fp);
//show result
fp = fopen(dir, "r");
if (fp == NULL) {
printf("result file read fail!\n");
return ;
}
while ((ch = fgetc(fp)) != EOF)
putchar(ch);
fclose(fp);
printf("\n");
}
void get_file(int sockfd) {
FILE* fp;
struct hdr_st hdr;
int rcv_sz, filelen;
char filename[MAX_BUF_SZ];
char dir[MAX_BUF_SZ]="../";
//getfilename cmd
printf("please input the file name:(such as fdir/fname)\n");
fgets(filename, MAX_BUF_SZ, stdin);
hdr.type = TYPE_GET_FILE;
hdr.len = strlen(filename);
filename[hdr.len - 1] = 0;
if(send(sockfd, &hdr, sizeof(hdr), 0) <= 0){
printf("Send head error!");
return ;
}
if(send(sockfd, filename, MAX_BUF_SZ, 0) <= 0){
printf("Send filename error!");
return ;
}
printf("the get command send success!\n");
//wait ack or nak
rcv_sz = recv(sockfd, &hdr, sizeof(hdr), 0);
if ( rcv_sz < 0 ) {
printf("recv() failed\n");
return ;
} else if (rcv_sz == 0) {
printf("connect close\n");
return ;
}
if (hdr.type == TYPE_NOT_FILE) {
printf("No such file\n");
return ;
}
if (hdr.type == TYPE_YES_FILE) {
printf("Ready to download the file!\n");
filelen = hdr.len;
}
//download file
strcat(dir, filename);
fp = fopen(dir, "wb");
while(filelen > 0) {
rcv_sz = recv(sockfd, filename, MAX_BUF_SZ, 0);
if ( rcv_sz < 0 ) {
printf("recv() failed\n");
fclose(fp);
return ;
}
fwrite(filename, 1, rcv_sz, fp);
filelen -= rcv_sz;
}
fclose(fp);
printf("get file success!\n");
}
int main()
{
int sockfd;
int server_port = PORT;
char server_ip[] = "127.0.0.1";
struct sockaddr_in addr;
char chose;
int result;
/* * 创建socket,通知系统建立一个通信端口 * AF_INET表示使用IPv4协议 * SOCK_STREAM表示使用TCP协议 */
if((sockfd = socket(AF_INET, SOCK_STREAM, 0))<0){
printf("Init socket error!");
return -1;
}
memset(&addr,0, sizeof(addr));
/*填写地址addr,填写协议、端口号、服务器地址 */
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr(server_ip);
/* 连接使用填写好的addr地址连接服务器 */
if(connect( sockfd, (struct sockaddr *)&addr, sizeof(addr) )<0){
printf("Connect server fail!");
goto socket_close; //0表示成功,-1表示失败
}
while (1)
{
chose = chose_menu();
switch(chose)
{
case TYPE_LOGIN: {
login(sockfd);
recv(sockfd, &result, sizeof(result), 0);
if (result == 0) {
printf("login fail!\n");
} else {
printf("login success!\n");
}
break;
}
case TYPE_READ:
get_file_list(sockfd);
break;
case TYPE_EXIT:
program_exit(sockfd);
goto socket_close;
break;
case TYPE_EXEC_CMD:
exec_cmd(sockfd);
break;
case TYPE_GET_FILE:
get_file(sockfd);
break;
}
}
socket_close:
//关闭socket
close(sockfd);
return 0;
}