7.2 网络编程_day2
tcp实现ftp功能
模拟FTP核心原理:客户端连接服务器后,向服务器发送一个文件。文件名可以通过参数指定,服务器端接收客户端传来的文件(文件名随意),如果文件不存在自动创建文件,如果文件存在,那么清空文件然后写入。
要求:
基于TCP写出服务器和客户端代码,
在同一路径下,将客户端可执行代码复制到其他的路径下,接下来再不同的路径下运行服务器和客户端。
相当于另外一台电脑在访问服务器。
项目功能介绍:
客户端和服务器链接成功后出现以下提示:四个功能
*list //列出服务器所在目录下的文件名(除目录不显示)
*put filename //上传一个文件
get filename //从服务器所在路径下载文件
quit //退出(可只退出客户端,服务器等待下一个客户端链接)
TCP粘包问题:
在本项目中: 如果发送方频率过快, 且发送的数据较小, 则会产生粘包
粘包: TCP协议栈会把多个数据包当做一个数据报发送出去
例如: send() 发送内容为5个字符, “hello”
send()发送的内容为6个字符 “return”
另一端的接收recv() 接受内容为 256个字符
则"hello" 和 “return” 两个数据包, 会被当作"helloreturn"一个数据包发送出去
stat函数:
头文件: #include <sys/types.h> #include <sys/stat.h> #include <unistd.h>
原型: int stat(const char *filename, struct stat *buf);
//用的时候定义一个结构体变量将变量地址传给 struct stat * buf
功能: 通过文件名filename获取文件信息,并保存在buf所指的结构体stat中
参数: pathname:文件名
buf: 存放文件属性信息(地址)
返回值:成功0 失败:-1,更新errno
struct stat 结构体的属性如下:
struct stat {
dev_t st_dev; / device 文件的设备编号
ino_t st_ino; / inode 文件的i-node
mode_t st_mode; / protection 文件的类型和存取的权限(见下面内容)
nlink_t st_nlink; / number of hard links 连到该文件的硬连接数目, 刚建立的文件值为1.
uid_t st_uid; / user ID of owner 文件所有者的用户识别码(用户ID)
gid_t st_gid; / group ID of owner 文件所有者的组识别码 (组ID)
dev_t st_rdev; / device type 若此文件为装置设备文件, 则为其设备编号
off_t st_size; / total size, in bytes 文件大小, 以字节计算
unsigned long st_blksize;
/ blocksize for filesystem I/O 文件系统的I/O 缓冲区大小.
unsigned long st_blocks;
/number of blocks allocated 占用文件区块的个数,每一区块大小为512 个字节.
time_t st_atime;
/ time of lastaccess 文件最近一次被存取或被执行的时间
time_t st_mtime; 与localtime配合转换为月日时间
/ time of last modification 文件最后一次被修改的时间, 一般只有在用
time_t st_ctime;
/ time of last change i-node 最近一次被更改的时间, 此参数会在文件所有者、组、 权限被更新
};
使用: struct stat st; //获取文件的inode号
stat(argv[1],&st); printf(“%ld\n”,st.st_ino);
判断文件类型:
st_mode //这些宏需要st_mode参数传入
1. S_IFMT 0170000 文件类型的位遮罩(st_mode先和S_IFMT & 运算,再得到 以下2-11 的内容)
2、S_IFSOCK 0140000 套接字 s
3、S_IFLNK 0120000 符号连接 l
4、S_IFREG 0100000 普通文件 - if((st.st_mode & S_IFMT) ==S_IFREG)
5、S_IFBLK 0060000 区块设备 b
6、S_IFDIR 0040000 目录 d
7、S_IFCHR 0020000 字符设备 c
8、S_IFIFO 0010000 管道 p
文件类型:(另一种表示方法)
其实就是 #define S_ISREG(mt_mode) mt_mode & S_IFMT == S_IFREG
21、S_ISLNK (st_mode) 是否是一个连接 l if(S_ISLNK(st_mode))
22、S_ISREG (st_mode) 是否是一个普通文件 - if(S_ISREG(st_mode))
23、S_ISDIR (st_mode) 是否是一个目录 d if(S_IFDIR(st_mode))
24、S_ISCHR (st_mode) 是否是一个字符设备 c if(S_ISCHR(st_mode))
25、S_ISBLK (st_mode) 是否是一个块设备 b if(S_ISBLK(st_mode))
26、S_ISFIFO (st_mode) 是否是个管道文件 p if(S_ISIFO(st_mode))
27、S_ISSOCK (st_mode) 是否是个套接字文件 s if(S_ISSOCK(st_mode))
文件权限:
用户:
1. S_IRUSR (S_IREAD) 00400 文件所有者(用户)具可读取权限 r
使用: if(st_mode & S_IRUSR)
13、S_IWUSR (S_IWRITE)00200 文件所有者(用户)具可写入权限 w
使用: if(st_mode & S_IWUSR)
14、S_IXUSR (S_IEXEC) 00100 文件所有者(用户)具可执行权限 x
if(st_mode & S_IXUSR)
组:
15、S_IRGRP 00040 用户组具可读取权限 if(st_mode & S_IRGRP) r
16、S_IWGRP 00020 用户组具可写入权限 if(st_mode & S_IWGRP) w
17、S_IXGRP 00010 用户组具可执行权限 if(st_mode & S_IXGRP) x
其他用户:
18、S_IROTH 00004 其他用户具可读取权限 if(st_mode & S_IROTH) r
19、S_IWOTH 00002 其他用户具可写入权限 if(st_mode & S_IWOTH) w
20、S_IXOTH 00001 其他用户具可执行权限上述的文件类型在 POSIX 中定义了检查这些类型的宏定义
if(st_mode & S_IXOTH) x
./server:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
void get_server(int acceptfd,char *buf,int size);
void put_server(int acceptfd,char *buf,int size);
void list_server(int acceptfd);
int main(int argc, char const *argv[])
{
//1.创建套接字 - 返回建立连接的文件描述符
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("socket is err:");
return -1;
}
printf("sockfd: %d\n",sockfd);
//2. 绑定套接字 - 填充结构体
struct sockaddr_in saddr,caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1])); //填充端口
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");//填充ip
socklen_t len = sizeof(caddr);
if(bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)) < 0)
{
perror("bind is err:");
return -1;
}
//3.监听 - 将主动套接字转换为被动套接字
if(listen(sockfd,5) < 0)
{
perror("listen is err:");
return -1;
}
//循环服务器, 当客户端退出, 服务器等待下一个客户端的链接
while(1)
{
//4.阻塞等待客户端的连接, 返回一个建立通信的文件描述符
int acceptfd = accept(sockfd,(struct sockaddr *)&caddr,&len);
if(acceptfd < 0)
{
perror("accept is err:");
return -1;
}
printf("acceptfd is %d\n",acceptfd);
printf("client ip: %s port: %d\n",inet_ntoa(caddr.sin_addr),\
ntohs(caddr.sin_port));
char buf[256];
while(1)
{
int recvbyte = recv(acceptfd,buf,sizeof(buf),0); //put 123.c
if(recvbyte < 0)
{
perror("recv is err:");
return -1;
}
else if(recvbyte == 0)
{
printf("client is exit\n");
close(acceptfd);
break;
}
else
{
if(strncmp(buf,"quit",4) == 0)
break;
if(strncmp(buf,"list",4) == 0)
{
//目录IO获取当前目录下的文件名,然后发给客户端
list_server(acceptfd);
}
else if(strncmp(buf,"put ",4) == 0)
{
//接受客户端上传的文件, 然后写在新建的文件内
put_server(acceptfd,buf,sizeof(buf));
}
else if(strncmp(buf,"get ",4) == 0)
{
//发送指定的文件给客户端
get_server(acceptfd,buf,sizeof(buf));
}
}
}
}
//关闭文件描述符
close(sockfd);
return 0;
}
void get_server(int acceptfd,char *buf,int size)
{
//1.打开文件
int fd = open(buf+4,O_RDONLY);
if(fd < 0)
{
perror("open is err:");
return;
}
int ret;
//2.循环读取文件内容并发送给客户端
while((ret = read(fd,buf,size-1)) != 0)
{
buf[ret] = '\0';
send(acceptfd,buf,size,0);
}
strcpy(buf,"get ok");
send(acceptfd,buf,size,0);
}
//客户端发来文件, 服务器来接受
void put_server(int acceptfd,char *buf,int size)
{
//1.创建一个文件用来保存客户端发送的文件内容
int fd = open(buf+4,O_CREAT|O_TRUNC|O_WRONLY,0666);
if(fd < 0)
{
perror("open is err:");
return;
}
while(1)
{
if(recv(acceptfd,buf,size,0) < 0)
{
perror("recv is err:");
return;
}
if(strncmp(buf,"put ok",6) == 0)
break;
write(fd,buf,strlen(buf));
}
}
//读取服务器当前路径下的文件名并发送
void list_server(int acceptfd)
{
char buf[256];
//1.打开当前目录文件
DIR * dir = opendir("./");
if(dir == NULL)
{
perror("opendir is err:");
return;
}
//2.循环读取当前目录下的文件名
struct dirent *dp = NULL;
struct stat st;
while((dp = readdir(dir))!= NULL)
{
//stat获取文件类型
stat(dp->d_name,&st);
//判断当前读取的文件名是否是普通文件
if(S_ISREG(st.st_mode))
{ //将文件名拷贝到buf内
strcpy(buf,dp->d_name);
send(acceptfd,buf,sizeof(buf),0);
}
}
strcpy(buf,"list ok");
send(acceptfd,buf,sizeof(buf),0);
}
./client:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void show(void);
void list_client(int sockfd);
void put_client(int sockfd, char *buf, int size);
void get_client(int sockfd, char *buf, int size);
int main(int argc, char const *argv[])
{
if (argc != 3)
{
printf("please input %s <ip> <port>\n", argv[0]);
return -1;
}
//1.创建套接子
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err.");
return -1;
}
//填充结构体
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("connect err.");
return -1;
}
//收发消息
char buf[128];
while (1)
{
//1.请求窗口
show();
//2.获取请求
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
send(sockfd, buf, sizeof(buf), 0);
if (strncmp(buf, "list", 4) == 0)
{ //循环接收服务器发送过来的普通文件名输出到终端
list_client(sockfd);
}
else if (strncmp(buf, "put ", 4) == 0)
{ //上传文件(本地:打开文件读内容发送给服务器)
put_client(sockfd,buf,sizeof(buf));
}
else if (strncmp(buf, "get ", 4) == 0)
{ //下载服务器路径下的文件
//(本地:打开新建文件接收服务器发送过来的内容写到文件)
get_client(sockfd,buf,sizeof(buf));
}
else if (strncmp(buf, "quit", 4) == 0)
{
break;
}
}
close(sockfd);
return 0;
}
//1.list:循环接收服务器发送过来的普通文件名输出到终端
void list_client(int sockfd)
{
char buf[128];
while (1)
{
if (recv(sockfd, buf, sizeof(buf), 0) < 0)
{
perror("list recv err.");
return;
}
if (strncmp(buf, "list ok.", 8) == 0)
{
break;
}
printf("%s\n", buf);
}
}
//2.put 上传文件(本地:打开文件读内容发送给服务器)
void put_client(int sockfd, char *buf, int size) //buf->put xxx.c
{
//1>打开文件 读
int fd = open(buf + 4, O_RDONLY);
if (fd < 0)
{
perror("open file err.");
return;
}
int ret; //实际读到的个数
//先不写size-1,先写size, 不补入\0, 运行会发现粘包,把putok也写入文件里了
//再解释粘包, 使用size-1, 手动补入\0,目的是为了另一端的write可以写入实际字节数strlen
while ((ret = read(fd, buf, size - 1)) != 0)
{
buf[ret] = '\0';
send(sockfd, buf, size, 0);
}
strcpy(buf, "put ok.");
send(sockfd, buf, size, 0);
}
//3.get
void get_client(int sockfd, char *buf, int size)
{
int fd = open(buf + 4, O_TRUNC | O_CREAT | O_WRONLY, 0666);
if (fd < 0)
{
perror("open err.");
return;
}
while (1)
{
if (recv(sockfd, buf, size, 0) < 0)
{
perror("recv err.");
return;
}
if (strncmp(buf, "get ok.", 7) == 0)
break;
//可以先把strlen(buf)写为size, 后期发现粘包问题,再修改
write(fd, buf, strlen(buf));
}
}
void show(void)
{
printf("************list***************\n");
printf("************put filename*******\n");
printf("************get filename*******\n");
printf("************quit***************\n");
}