模拟FTP核心原理:客户端连接服务器后,向服务器发送一个文件。文件名可以通过参数指定,服务器端接收客户端传来的文件(文件名随意),如果文件不存在自动创建文件,如果文件存在,那么清空文件然后写入。
知识点
TCP编程流程图
服务器:
socket:创建一个用与链接的套接字(用于链接)
bind:绑定自己的ip地址和端口
listen:监听,将主动套接字转为被动套接字
accept:阻塞等待客户端链接,链接成功返回一个用于通信套接字
recv:接收消息
send:发送消息
close:关闭文件描述符
客户端:
socket:创建一个套接字
填充结构体:填充服务器的ip和端口
connect:阻塞等待链接服务器
recv/send:接收/发送消息
close:关闭
函数介绍
1.socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建套接字文件
参数:
domain:协议族 ,选择通信方式
AF_UNIX, AF_LOCAL 本地通信
AF_INET IPv4 ip和端口
AF_INET6 IPv6
type:通信协议-套接字类型
SOCK_STREAM 流式套接字
SOCK_DGRAM 数据报套接字
SOCK_RAW 原始套接字
protocol:协议 填0,自动匹配底层TCP或UDP等协议。根据type匹配
系统默认自动帮助匹配对应协议
传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
返回值:成功。返回同于链接的文件描述符
失败 -1,更新errno
2、bind
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:绑定套接字 - ip和端口
功能:
sockfd:套接字文件描述符
addr:用于通信结构体 (提供的是通用结构体,需要根据选择通信方式,填充对应结构体-通信结构体由socket第一个参数确定)
addrlen:结构体大小
返回值: 成功0
失败:-1 更新errno
通用结构体:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
ipv4的通信结构体:
struct sockaddr_in {
sa_family_t sin_family; /*AF_INET */
in_port_t sin_port; /* 端口 */
struct in_addr sin_addr; /* ip地址 */
};
struct in_addr {
uint32_t s_addr;
};
本地通信结构体:
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* 套接字文件 */
};
3、listen
int listen(int sockfd, int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
sockfd:套接字
backlog:同时响应客户端请求链接的最大个数,不能写0.
不同平台可同时链接的数不同,一般写6-8个
(队列1:保存正在连接)
(队列2,连接上的客户端)
返回值:成功 0 失败-1,更新errno
4、accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept(sockfd,NULL,NULL);
阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
则accept()函数返回,返回一个用于通信的套接字文件;
参数:
Sockfd :套接字
addr: 链接客户端的ip和端口号
如果不需要关心具体是哪一个客户端,那么可以填NULL;
addrlen:结构体的大小
如果不需要关心具体是哪一个客户端,那么可以填NULL;
返回值:
成功:文件描述符; //用于通信
失败:-1,更新errno
5、recv
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能: 接收数据
参数:
sockfd: acceptfd ;
buf 存放位置
len 大小
flags 一般填0,相当于read()函数
MSG_DONTWAIT 非阻塞
返回值:
< 0 失败出错 更新errno
==0 表示客户端退出
>0 成功接收的字节个数
6、send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
sockfd:socket函数的返回值
buf:发送内容存放的地址
len:发送内存的长度
flags:如果填0,相当于write();
返回值:
< 0 失败出错 更新errno
>0 成功发送的字节个数
7、connet
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:用于连接服务器;
参数:
sockfd:socket函数的返回值
addr:填充的结构体是服务器端的;
addrlen:结构体的大小
返回值
-1 失败,更新errno
正确 0
项目功能介绍
1.均有服务器和客户端代码,基于TCP写的。
2.在同一路径下,将客户端可执行代码复制到其他的路径下,接下来再不同的路径下运行服务器和客户端。
3.相当于另外一台电脑在访问服务器。
客户端和服务器链接成功后出现以下提示:四个功能
***********put filename********** //上传一个文件
***********get filename********** //重服务器所在路径下载文件
**************quit*************** //退出(可只退出客户端,服务器等待下一个客户端链接)
client.c 代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//1创建套接字 用于链接
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket error.");
return -1;
}
printf("sockfd=%d\n", sockfd);
//填充ipv4的通信结构体 服务器端
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(8888); //小端转化成大端
//发送数据的时候必须要将自己的主机字节序转换为网络字节序(即“大端”字节序)
serveraddr.sin_addr.s_addr = inet_addr("192.168.50.77");
//点分十进制数串 -> IPv4地址结构(inet_addr)
//2.请求连接服务器
if (connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("connect err.");
return -1;
}
//5.循环的发送消息
char buf[128] = "";
int sendbyte;
while (1)
{
printf("input:\n");
fgets(buf, sizeof(buf), stdin);
memset(buf, 0, sizeof(buf));
sendbyte = send(sockfd, buf, sizeof(buf), 0);
if (sendbyte < 0)
{
perror("send err.");
return -1;
}
close(sockfd);
}
return 0;
}
server.c 代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[])
{
//1创建套接字 用于链接
int sockfd, acceptfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket error.");
return -1;
}
printf("sockfd=%d\n", sockfd);
//填充ipv4的通信结构体 服务器端
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(8888); //小端转化成大端
//发送数据的时候必须要将自己的主机字节序转换为网络字节序(即“大端”字节序)
serveraddr.sin_addr.s_addr = inet_addr("192.168.50.77");
//点分十进制数串 -> IPv4地址结构(inet_addr)
//2.绑定套接字
if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("bind err.");
return -1;
}
printf("bind ok.\n");
//3.监听套接字,将主动套接字转为被动套接字
if (listen(sockfd, 5) < 0)
{
perror("listen err.");
return -1;
}
//4.阻塞等待客户端链接,链接成功返回一个用于通信的文件描述符
acceptfd = accept(sockfd, NULL, NULL); //不需要关心具体是哪一个客户端
if (acceptfd < 0)
{
perror("accept err.");
return -1;
}
printf("accept=%d\n", acceptfd); //验证:telnet ip 端口
//5.循环的接受消息
char buf[128] = "";
int recvbyte;
while (1)
{
recvbyte = recv(acceptfd, buf, strlen(buf), 0); //相当于read//flag:比read 多个非阻塞的功能,直接返回
if (recvbyte < 0)
{
perror("recv err.");
return -1;
}
else if (recvbyte == 0)
{
printf("client exit.\n"); //客户端退出
break;
}
else
{
buf[recvbyte] = '\0'; //
printf("buf:%s\n", buf);
}
close(acceptfd);
close(sockfd);
}
return 0;
}