2小时学会php网站建设,外汇跟单网站开发,番号网站怎么做,wordpress 插件 发布文章ftp实现 模拟FTP核心原理#xff1a;客户端连接服务器后#xff0c;向服务器发送一个文件。文件名可以通过参数指定#xff0c;服务器端接收客户端传来的文件#xff08;文件名随意#xff09;#xff0c;如果文件不存在自动创建文件#xff0c;如果文件存在#xff0c… ftp实现 模拟FTP核心原理客户端连接服务器后向服务器发送一个文件。文件名可以通过参数指定服务器端接收客户端传来的文件文件名随意如果文件不存在自动创建文件如果文件存在那么清空文件然后写入。 功能要求 1.项目基于tcp连接进行编写 2. 客户端命令行传参传入ip、port、文件路径实现把指定目录下的文件发送到服务器 3. 服务器接收并放到指定文件路径 linux下IO模及其特点 场景假设 假设妈妈有一个孩子孩子在房间里睡觉妈妈需要及时获知孩子是否醒了如何做 1. 进到房间陪着孩子一起睡觉孩子醒了会吵醒妈妈不累但是不能干别的了 2. 时不时进房间看一下简单空闲时间还能干点别的但是很累 3. 妈妈在客厅干活小孩醒了他会自己走出房门告诉妈妈互不耽误 一、Linux下四种模型的特点 阻塞式IO 非阻塞式IO 信号驱动IO(了解) IO多路复用帮助TCP实现并发 1、阻塞式IOBIO 特点简单、常用、效率低 ● 当程序调用某些接口时如果期望的动作无法触发那么进程会进入阻塞态等待状态让出CPU的调度当期望动作可以被触发了那么会被唤醒然后处理事务。 ● 重点理解相对于进程而言的影响 ● 阻塞I/O模式是最普遍使用的I/O模式大部分程序使用的都是阻塞模式的I/O 。 ● 前面学习的很多读写函数在调用过程中会发生阻塞。 阻塞I/O 模式是最普遍使用的I/O 模式大部分程序使用的都是阻塞模式的I/O 。
缺省情况下及系统默认状态套接字建立后所处于的模式就是阻塞I/O 模式。
学习的读写函数在调用过程中会发生阻塞相关函数如下
•读操作中的read、recv、recvfrom读阻塞--》需要读缓冲区中有数据可读读阻塞解除
•写操作中的write、send写阻塞--》阻塞情况比较少主要发生在写入的缓冲区的大小小于要写入的数据量的情况下写操作不进行任何拷贝工作将发生阻塞一旦缓冲区有足够的空间内核将唤醒进程将数据从用户缓冲区拷贝到相应的发送数据缓冲区。
注意sendto没有写阻塞1无sendto函数的原因
sendto不是阻塞函数本身udp通信不是面向连接的udp无发送缓冲区即sendto没有发送缓冲区send是有发送缓存区的即sendto不是阻塞函数。2UDP不用等待确认没有实际的发送缓冲区所以UDP协议中不存在缓冲区满的情况在UDP套接字上进行写操作永远不会阻塞。
•其他操作accept、connectudp与tcp缓存区 仅作为了解 UDP通信没有发送缓存区 它不保证数据的可靠性。因此UDP通信是将数据尽快发送出去不关心数据是否到达目标主机. 但是UDP有接受缓存区, 因为数据发送过快, 如果接收缓存区内数据已满, 则继续发送数据, 可能会出现丢包。 丢包出现原因 接收缓存区满 网络拥堵 传输错误 相比之下TCP是一种面向连接的传输协议它需要保证数据的可靠性和顺序性。TCP有发送缓存区和接收缓存区 如果发送频率过快 且内容小于发送缓存区的大小 可能会导致多个数据的粘包。如果发送的数据大于发送缓存区 可能会导致拆包。 UDP不会造成粘包和拆包, TCP不会造成丢包 UDP是基于数据报文发送的每次发送的数据包在UDP的头部都会有固定的长度 所以应用层能很好的将UDP的每个数据包分隔开 不会造成粘包。 TCP是基于字节流的 每次发送的数据报在TCP的头部没有固定的长度限制也就是没有边界那么很容易在传输数据时把多个数据包当作一个数据报去发送成为了粘包或者传输数据时 要发送的数据大于发送缓存区的大小或者要发送的数据大于最大报文长度 就会拆包 TCP不会丢包因为TCP一旦丢包将会重新发送数据包。超时/错误重传 TCP: UDP: 2、非阻塞式IO(NIO) 特点可以处理多路IO需要轮询浪费CPU资源 •当我们将一个套接字设置为非阻塞模式我们相当于告诉了系统内核“当我请求的I/O 操作不能够马上完成你想让我的进程进行休眠等待的时候不要这么做请马上返回一个错误给我。”
•引导着让大家说出来当一个应用程序使用了非阻塞模式的套接字它需要使用一个循环来不停地测试是否一个文件描述符有数据可读称做polling。---轮询
•应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。
•这种模式使用中不普遍。如何设置非阻塞 1) 通过函数自带参数设置 2) 通过设置文件描述符属性fcntl (file control) #include unistd.h
#include fcntl.h
int fcntl(int fd, int cmd, ...);
功能获取/改变文件属性(linux中一切皆文件)
文件描述符stdin 0、stdout 1、stderr 2参数fd文件描述符cmd: 操作功能选项 (可以定义个变量,通过vi -t F_GETFL 来找寻功能赋值 )F_GETFL:获取文件描述符的原有的状态信息 //不需要第三个参数返回值为获取到的属性F_SETFL:设置文件描述符的状态信息 - 需要填充第三个参数//需要填充第三个参数 O_RDONLY, O_RDWR ,O_WRONLY ,O_CREATO_NONBLOCK 非阻塞 O_APPEND追加O_ASYNC 异步 O_SYNC 同步 F_SETOWN: 可以用于实现异步通知机制。//当文件描述符上发生特定事件时例如输入数据到达内核会向拥有该 文件描述符的进程发送 SIGIO 信号异步以便进程能够及时处理这些事件。
第三个参数:由第二个参数决定set时候需要设置的值,get时候填0
arg:文件描述符的属性 ----------同上参数一般填0返回值: 特殊选择:根据功能选择返回 (int 类型) 其他: 成功 失败: -1;
设置流程int flag;//文件状态的标志 flag fcntl(fd, F_GETFL); //读 flag | O_NONBLOCK;//改 O_NONBLOCK 0x00004000fcntl(fd, F_SETFL, flag);//写3、信号驱动IO异步IO模型 非重点 特点异步通知模式需要底层驱动的支持 操作系统中的同步与异步
在操作系统中特别是在Linux中同步和异步是描述I/O操作方式的两个概念。它们主要区分在于操作完成的通知方式和程序执行的流程。同步Synchronous同步I/O操作是指在执行I/O操作时程序必须等待操作完成才能继续执行。在同步操作中程序提交一个I/O请求后操作系统会阻塞该程序直到请求操作完成。此时程序才能继续执行后续的代码。因此同步操作会导致程序执行流程暂停直至I/O操作完成。
同步I/O的例子read(), write(), recv(), send() 等。异步Asynchronous
异步I/O操作是指程序在发起I/O请求后无需等待操作完成可以继续执行其他任务。当异步I/O操作完成时程序会通过某种方式如回调函数、事件通知、信号等得到通知。因此异步操作使程序执行流程得以继续而不必等待I/O操作完成。 ● 通过信号方式当内核检测到设备数据后会主动给应用发送信号SIGIO。 SIGIO
文件描述符准备就绪, 可以开始进行输入/输出操作.● 应用程序收到信号后做异步处理即可。 ● 应用程序需要把自己的进程号告诉内核并打开异步通知机制。 ● 标准模板 //将APP进程号告诉驱动程序
fcntl(fd, F_SETOWN, getpid());//使能异步通知
int flag;
flag fcntl(fd, F_GETFL);
flag | O_ASYNC; //也可以用FASYNC标志
fcntl(fd, F_SETFL, flag);signal(SIGIO, handler);signal信号处理相关函数 头文件 #include signal.h typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler) 功能信号处理函数(注册信号) 参数 int signum要处理的信号(要修改的信号) sighandler_t handler: 函数指针 void(*handler)(int) (修改的功能) handler------void handler(int num) 自定义的信号处理函数指针 返回值 成功设置之前的信号处理方式 失败 SIG_ERR 用非阻塞方式监听鼠标的数据 查看自己使用的鼠标/dev/input 检查鼠标设备sudo cat /dev/input/mouse0 注意执行的时候需要加sudo #include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include signal.h
#include unistd.h
int fd;
#define N 64
char buf[N] {0};
void handler(int sig)
{int ret;ret read(fd, buf, N);if (ret 0){perror(READ ERR.);return;}else{printf(len %d\n, ret);}
}
int main(int argc, char const *argv[])
{fd open(/dev/input/mouse0, O_RDONLY);if (fd 0){perror(open err);return -1;}// 将APP进程号告诉驱动程序fcntl(fd, F_SETOWN, getpid());// 使能异步通知int flag;flag fcntl(fd, F_GETFL);flag | O_ASYNC; // 也可以用FASYNC标志fcntl(fd, F_SETFL, flag);signal(SIGIO, handler);while (1){printf(-----------\n);sleep(1);}close(fd);return 0;
}4.IO多路复用 4.1、IO多路复用场景假设 假设妈妈有三个孩子分别不同的房间里睡觉需要及时获知每个孩子是否醒了如何做 1. 挨个房间跑 4.2、IO多路复用机制 I/O多路复用 - 帮助TCP实现并发服务器 1. 进程中若需要同时处理多路输入输出 ,在使用单进程和单线程的情况下, 可使用IO多路复用处理多个请求; 2. IO多路复用不需要创建新的进程和线程, 有效减少了系统的资源开销。 场景就比如服务员给50个顾客点餐分两步 顾客思考要吃什么等待客户端数据发送 顾客想好了开始点餐接收客户端数据 要提高效率有几种方法 1. 安排50个服务员 (类似于多进程/多线程实现服务器连接多个客户端,太占用资源) 2. 哪个顾客想好了吃啥, 那个顾客来柜台点菜 (类似IO多路复用机制实现并发服务器) 实现IO多路复用的方式 select poll epoll 基本流程是 1. 先构造一张有关文件描述符的表; 2. 清空表 3. 将你关心的文件描述符加入到这个表中; 4. 调用select函数。 5. 判断是哪一个或哪些文件描述符产生了事件(IO操作); 6. 做对应的逻辑处理; ● 使用I/O多路复用技术。其基本思想是 ○ 先构造一张有关描述符的表然后调用一个函数。 ○ 当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。 ○ 函数返回时告诉进程哪个描述符已就绪可以进行I/O操作。 基本流程
1. 先构造一张有关文件描述符的表(集合、数组);
2. 将你关心的文件描述符加入到这个表中;
3. 然后循环调用一个函数。 select / poll
4. 当这些文件描述符中的一个或多个已准备好进行I/O操作的时候
该函数才返回(阻塞)。
5. 判断是哪一个或哪些文件描述符产生了事件(IO操作);
6. 做对应的逻辑处理;4.3、select 用于监测是哪个或哪些文件描述符产生事件; #includesys/select.h
#includesys/time.h
#includesys/types.h
#includeunistd.h
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);功能select用于监测是哪个或哪些文件描述符产生事件; 参数nfds监测的最大文件描述个数(文件描述符从0开始,这里是个数,记得1)
这里是个数使用的时候注意与文件中最后一次打开的文件描述符所对应的值的关系是什么readfds 读事件集合; // 键盘鼠标的输入,客户端连接都是读事件writefds 写事件集合; //NULL表示不关心exceptfds异常事件集合; //NULL 表示不关心timeout 设为NULL等待直到某个文件描述符发生变化设为大于0的值有描述符变化或超时时间到才返回。超时时间检测如果规定时间内未完成函数功能返回一个超时的信息我们可以根据该信息设定相应需求如果设置了超时检测时间tvselect返回值0 出错0 表示有事件产生;0 表示超时时间已到;struct timeval {long tv_sec; /* seconds */以秒为单位,指定等待时间long tv_usec; /* microseconds */以毫秒为单位,指定等待时间};void FD_CLR(int fd, fd_set *set);//将fd从表中清除int FD_ISSET(int fd, fd_set *set);//判断fd是否在表中void FD_SET(int fd, fd_set *set);//将fd添加到表中void FD_ZERO(fd_set *set);//清空表1select特点 1. 一个进程最多只能监听1024个文件描述符 32位 [64位为 2048] 2. select被唤醒之后要重新轮询(0-1023)一遍驱动,效率低消耗CPU资源 3. select每次会清空未响应的文件描述符每次都需要拷贝用户空间的表到内核空间效率低,开销较大 (0~3G是用户态3G~4G是内核态两个状态来回切换 拷贝是非常耗时耗资源的) select机制(辅助理解): 1. 头文件检测1024个文件描述符 0-1023 2. 在select中0~2存储标准输入、标准输出、标准出错 3. 监测的最大文件描述个数为fd1(如果fd 3,则最大为 4) : //因为从0开始的 4. select只对置1的文件描述符感兴趣 假如事件产生select检测时 , 产生的文件描述符会保持1,未产生事件的会置0; 5. select每次轮询都会清空表(置零的清空) //需要在select前备份临时表 练习1: 如何通过select实现 响应鼠标事件同时响应键盘事件 #include stdio.h
#include unistd.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include sys/select.h//响应鼠标的时候 打印鼠标事件
//输入键盘的时候 打印键盘内容int main(int argc, char const *argv[])
{//1.打开鼠标文件int fd open(/dev/input/mouse0,O_RDONLY);if(fd 0){perror(open is err:);return -1;}//1.创建文件描述符的表fd_set readfds,tempfds;//2.清空表FD_ZERO(readfds);//3.添加关心的文件描述符FD_SET(0,readfds);FD_SET(fd,readfds);int maxfd fd;char buf[128];while(1){tempfds readfds;//4.select检测 阻塞select(maxfd1,tempfds,NULL,NULL,NULL);if(FD_ISSET(0,tempfds)){//1.键盘fgets(buf,sizeof(buf),stdin);if(buf[strlen(buf)-1] \n)buf[strlen(buf)-1] \0;printf(key: %s\n,buf);}if(FD_ISSET(fd,tempfds)){//2.鼠标int ret read(fd,buf,sizeof(buf));buf[ret] \0;printf(mouse: %s\n,buf);}}close(fd);return 0;
} 练习select实现客户端服务器全双工通信并发服务器的建立 在tcp的服务器端, 有两类文件描述符监听的文件描述符
1.只需要有一个
2.不负责和客户端通信, 负责检测客户端的连接请求, 检测到之后调用accept就可以建立新的连接通信的文件描述符
1.负责和建立连接的客户端通信
2.如果有N个客户端和服务器建立了新的连接, 通信的文件描述符就有N个每个客户端和服务器都对应一个通信的文件描述符总结select实现IO多路复用特点 1. 一个进程最多只能监听1024个文件描述符 千级别
2. select被唤醒之后需要重新轮询一遍驱动的poll函数效率比较低消耗CPU资源;
3. select每次会清空表每次都需要拷贝用户空间的表到内核空间效率低一个进程0~4G0~3G是用户态3G~4G是内核态拷贝是非常耗时的;
4.跨平台(1)客户端 /*客户端创建代码 */
#include stdio.h
#include sys/types.h /* See NOTES */
#include sys/socket.h
#include netinet/in.h
#include netinet/ip.h /* superset of previous */
#include arpa/inet.h
#include unistd.h
#include stdlib.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include string.h
#include pthread.h
// #include head.h
enum type_t
{login, //登录chat, //发送信息quit, //退出
};
typedef struct mag_t
{int type; //功能char name[32]; //ipchar text[128]; //内容
} MSG_t;
int main(int argc, char const *argv[])
{if (argc 3){printf(plase input ipport);return -1;}//1.创建套接字,用于链接int sockfd;sockfd socket(AF_INET, SOCK_DGRAM, 0);if (sockfd 0){perror(socket err);return -1;}printf(sockfd:%d\n, sockfd);//2.填充结构体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]);//IPMSG_t msg; //消息包socklen_t len sizeof(saddr); //结构体大小int num0;//交互次数pid_t pid fork();//创建父子进程if (pid 0){perror(fork err);return -1;}else if (pid 0) //子进程接收消息{while (1){//接受信息if (recvfrom(sockfd, msg, sizeof(msg), 0, (struct sockaddr *)saddr, len) 0){perror(recvfrom err);return -1;}printf(ip:%s 状态:%d 内容:%s\n, msg.name, msg.type, msg.text);}}else //父进程发送消息{while (1){strncpy(msg.name, xiaoyang, 8);//客户端昵称//发送信息memset(msg.text, 0, sizeof(msg.text)); //清空数组内容printf(发送内容:);fgets(msg.text, sizeof(msg.text), stdin); //从终端获取内容存放到数组中if (strncmp(msg.text, quit, 4) 0) //输入quit退出客户端{msg.type quit;//退出状态sendto(sockfd, msg, sizeof(msg), 0, (struct sockaddr *)saddr, len);exit(0);}if (msg.text[strlen(msg.text)] \0){msg.text[strlen(msg.text) - 1] \0;}if (num 0) //第一次登入{msg.type login;//登录状态}else{msg.type chat;//交互状态}sendto(sockfd, msg, sizeof(msg), 0, (struct sockaddr *)saddr, len);//发送信号}}close(sockfd);return 0;
} (2) 服务器 /*服务器创建代码 */
#include stdio.h
#include sys/types.h /* See NOTES */
#include sys/socket.h
#include netinet/in.h
#include netinet/ip.h /* superset of previous */
#include arpa/inet.h
#include unistd.h
#include stdlib.h
#include string.h
enum type_t
{login,//登录chat,//发送信息quit,//退出
};
typedef struct mag_t
{int type;//功能char name[32];//ipchar text[128];//内容
} MSG_t;
MSG_t msg;
//链表节点结构体
typedef struct node_t
{struct sockaddr_in addr;//ip地址struct node_t *next;//链表下一个地址
}list_t;
int main(int argc, char const *argv[])
{if (argc 2){printf(plase input ipport\n);return -1;}//1.创建套接字,用于链接int sockfd;sockfd socket(AF_INET,SOCK_DGRAM, 0);if (sockfd 0){perror(socket err);return -1;}printf(sockfd:%d\n, sockfd);//2.绑定 ipport 填充结构体struct sockaddr_in saddr;saddr.sin_family AF_INET; saddr.sin_port htons(atoi(argv[1])); saddr.sin_addr.s_addr inet_addr(0.0.0.0);socklen_t len sizeof(saddr); //结构体大小//bind绑定ip和端口if (bind(sockfd, (struct sockaddr *)saddr, len) 0){perror(bind err);return -1;}printf(bind success\n);// char buf[128] {0};while (1){//接收信息if (recvfrom(sockfd, msg, sizeof(msg), 0, (struct sockaddr *)saddr, len) 0){perror(recvfrom err);return -1;}switch (msg.type){case login:Loginrecv();break;case chat:Chatrecv();break;case quit:Quitrecv();break;}//发送信息printf(server:);fgets(msg.text, sizeof(msg.text), stdin); //从终端获取内容存放到数组中if (strncmp(msg.text, quit, 4) 0) //输入quit退出客户端{break;}if (msg.text[strlen(msg.text)] \0){msg.text[strlen(msg.text) - 1] \0;}sendto(sockfd, msg, sizeof(msg), 0, (struct sockaddr *)saddr, len);}close(sockfd);return 0;
}
void Chatrecv()//chat 型
{// printf(client ip:%s ,port:%d buf:%s\n, inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port),msg.text);printf(ip:%s 状态:chat 内容:%s\n, msg.name,msg.text);}
void Loginrecv()//login 型 首次链接
{}
void Quitrecv()//quit 退出
{//接收信息if (recvfrom(sockfd, msg, sizeof(msg), 0, (struct sockaddr *)saddr, len) 0){perror(recvfrom err);return -1;}// printf(client ip:%s ,port:%d buf:%s\n, inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port),msg.text);printf(ip:%s 状态:chat 内容:%s\n, msg.name,msg.text);
}