Linux C : select简介和epoll 实现

Linux C —— select()函数使用 本文记录的是在Linux下select函数的使用。 所需头文件: sys/time.h 作用: select函数是用来监视一个或多个文件句柄的状态变化的,可阻塞也可不阻塞。 定义: int select(int fd_max, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)... 阅读详情

目录

一、基础知识

二、select 模型服务流程

二、select 模式的缺点。

三、poll 概要

四、epoll 服务端实现流程

1.epoll_create:

2.epoll_ctl

3.epoll_wait

五、epoll示例代码实现

1.epoll实现服务端

2.客户端采用tcp进行访问


一、基础知识

      首先要知道,服务器与客户端的网络通信。服务器首先要创建一个socket用于指定需要监听的ip地址和端口。其中主机ip对应了一台主机,而ip+端口对应了一台主机上的某一个应用程序。当服务器将需要监听socket的文件描述符与监听的ip+端口绑定起来时,服务器就可以开启监听。此时客户端便可以连接上服务器这个端口。服务器利用accept查看监听端口中是否有输入事件,如果有就说明有新的客户端连接请求,并会开启一个新的文件描述符用于服务器和客户端的读写操作了。

        selelct 模型,poll模型,epoll模型 为网络通信中的IO多路复用方法。是为了提高网络通信并发量和通信效率而生的。除了IO多路复用方法,还可以利用多线程,多进程等方式,但是效果都不如IO多路复用。

        select模型,poll模型,epoll模型 首先也要经历创建监听socket,监听socket绑定主机和端口,开启监听,accept客户端连接这些阶段。它们和一般网络通信区别在于如何管理这些多个网络通信连接,使通信过程高效。

        在主机上的ip和端口用的是主机字节序,而网络上的通信用的是网络字节序。网络字节序采用大端的方式,即高位字节排放在内存的低地址端(即该值的起始地址),低位字节排放在内存的高地址端。而主机字节序根据CPU种类可能有小端模式或者大端模式。

二、select 模型服务流程

       1.select模型在accpet一堆客户端连接后,便把这些与客户端通信的文件描述符放进fd_set进行管理。fd_set是一个bitmap,哪些文件描述符被加入到fd_set中,该文件描述符在fd_set对应的bit位就会置为1.

       2.select 函数首先会把fd_set从用户态拷贝进内核态。由内核负责遍历哪一个文件描述符中有数据到达。没有数据就进行阻塞。其中第一个参数 maxfd,用于限定遍历范围,指定从0开始到maxfd之前的文件描述符进行遍历。

       3.如果没数据,进程就会进入阻塞状态,并将改进程放入socket的等待队列中。等待客户端把数据发送过来。

        4.网卡接受到了一个或多个客户端发来的数据。此时会产生一个中断,并且调用DMA拷贝,将网卡收到的数据,拷贝到内核环形缓冲区,而后再根据文件描述符信息放入对应的数据接收队列中。并唤醒进程。

        5.如果遍历中发现了多个客户端通信的文件描述符有接收到数据。那么select 函数就会修改fd_set,将有数据的文件描述符对应的bit位置为1,其他bit位置0. 。而后将修改后的fd_set拷贝会用户态,并return有数据的文件描述符的个数。

        6.应用程序可以遍历修改后的fd_set中的bit位,或者遍历文件描述符,判断哪些描述符中有对应的事件发生。而后可以开始与对应的客户端读写数据。

二、select 模式的缺点。

1.由于fd_set是bitmap,通常的最大容量为1024个bit位,意味着最多遍历1024个文件描述符。有最大连接限制。

2.由于fd_set传进select函数中为引用传递,每次循环都要重新赋值fd_set,无法重用。

3。频繁进行用户态和内核态的,当连接多时开销大。

4.  应用程序的轮询就绪的文件描述符的时间复杂度为O(n).

三、poll 概要

        poll流程和select模型流程大多都一样。都要建立监听,accept客户端连接,把客户端连接放在一个集合中,再将集合放到模型中管理,最后遍历有事件的文件描述符,进行通信。

        poll流程和select模型不一样的地方。也就是将集合fd_set 替换成pollfd。也就是poll模型不用bitmap了,而是用一个pollfd的结构体进行操作。再pollfd中设置需要注册的事件events,而,poll函数中不修改events属性,而是修改revents属性来表示发生的事件。 在客户端响应后只需要重置revents就可以达到重用效果。

1.由于pollfd不再是bitmap,而是一个结构体,再poll模型中,内核态采用链表的形式对accept进来的socket进行管理,意味着再内存允许的范围内不再限制最大连接数。

2.pollfd传进poll函数中为引用传递,但是poll不会修改注册的监听事件属性,可以达到重用的效果。

3。依旧频繁进行用户态和内核态的,当连接多时开销大。

4.  时间复杂度依旧为O(n).

四、epoll 服务端实现流程

        参考 深入了解epoll模型(特别详细) - 知乎

        和正常网络服务端程序一样,服务端首先要创建一个socket用于监听,其中socket地址指定需要监听的主机和程序对应的端口。并与socket对应的文件描述符绑定起来。而后利用epoll函数对网络通信的文件描述符管理起来。

        首先要了解epoll的数据结构,对epoll数据结构的管理和具体操作都是在内核态进行,操作系统对epoll管理都已经封装好了,程序员只需要负责调用就行。epoll的数据结构主要包括:

1.存放已就绪事件的就绪队列,它是双向链表构成的。

2.用于管理socket的红黑树。

3.以及双向链表表示的阻塞进程的队列。

        程序员可对epoll的操作主要是3种:

1.epoll_create:

在epoll文件系统建立了个file节点(B+树),并开辟epoll自己的内核高速cache区,建立红黑树,建立双向链表用于存储准备就绪的事件。其中

首先创建一个struct eventpoll对象;然后分配一个未使用的文件描述符;然后创建一个struct file对象,将file中的struct file_operations *f_op设置为全局变量eventpoll_fops,将void *private指向刚创建的eventpoll对象ep;然后设置eventpoll中的file指针;最后将文件描述符添加到当前进程的文件描述符表中,并返回给用户。因为内核初始化时(操作系统启动)注册了一个新的文件系统,叫"eventpollfs"专门由于服务epoll。返回的fd就是该文件系统新建的文件描述符

2.epoll_ctl

        epoll_ctl()首先判断op是不是删除操作,如果不是则将event参数从用户空间拷贝到内核中;获得struct eventpoll对象,接下来会从epoll实例的红黑树里寻找和被监控文件对应的epollitem对象,如果不存在,也就是之前没有添加过该文件;如果是添加操作,那么就会修改已有的或者创建新的epitem加入到红黑树中。

        这个加入过程将epollitem对象添加到被监视文件的等待队列上去。等待队列实际上就是一个回调函数链表,定义在/include/linux/wait.h文件中。通过struct file的poll操作,以回调的方式返回对象的等待队列,这里设置的回调函数是ep_ptable_queue_proc。

        在回调函数ep_ptable_queue_proc中,内核会创建一个struct eppoll_entry对象,然后将等待队列中的回调函数设置为ep_poll_callback()。也就是说,当被监控文件有事件到来时,比如socket收到数据时,ep_poll_callback()会被回调.ep_poll_callback函数主要的功能是将被监视文件的等待事件就绪时,将文件对应的epitem实例添加到就绪队列中

3.epoll_wait

        观察就绪列表里面有没有数据,并进行提取和清空就绪列表.没数据就sleep直到超时。传入epoll_events,用于获取已就绪的事件。而epoll_wait返回已就绪的个数。换而言之epoll_events避免了像select模型中通过遍历所有监听的socket获取已就绪文件的问题。epoll获取已就绪的算法复杂度达到了O(1).

​

五、epoll示例代码实现

1.epoll实现服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include<unistd.h>
#define MAX 1024
#define SERVER_IP "127.0.0.1"
#define SERVER_HOST "localhost"
// #define SERVER_PORT 1234
int server_init(int port){
	printf("=============server init========\n");
	printf("1. create a TCP socket\n");
	//sock的文件描述符	
  int mysock = socket (AF_INET,SOCK_STREAM,0);
	if(mysock < 0){
		printf("socket call failed\n"); return -1;
	}
	printf("2. fill server_addr with host_ip and port number \n");
	int opt =1; unsigned int len = sizeof(opt);
	setsockopt(mysock,SOL_SOCKET,SO_REUSEADDR,&opt,len);
	setsockopt(mysock,SOL_SOCKET,SO_KEEPALIVE,&opt,len);
	struct sockaddr_in server_addr ;
	server_addr .sin_family = AF_INET;
	server_addr .sin_port   =  htons(port);
	server_addr .sin_addr.s_addr = htonl(INADDR_ANY);
	printf("3.  bind socket to server address \n");
	int r =bind (mysock ,(struct sockaddr *) & server_addr  ,sizeof (server_addr ));
	if( r<0){
		printf("bind call failed\n"); exit(3);
	}
	printf("	hostname =%s, port = %d \n", SERVER_HOST ,port);
	listen(mysock , 5 );
	printf("============init done ===========\n");
	return mysock;
}

int main (int argc, char *argv[]){
	if(argc !=2){
	   printf("usage:./tcpepoll port\n");
	   return 1;	
	}
	//char line [MAX];
	int listensocket=server_init(atoi(argv[1]));
	printf("listensocket=%d\n",listensocket);
	if(listensocket < 0){
	   printf("listensocket failed \n");
	}
	char buffer[1024];
	memset(buffer,0,sizeof(buffer));
//create a fd 	
	int epollfd=epoll_create(1); 
//add a listen event
	struct epoll_event ev;
	ev.data.fd=listensocket;
	ev.events=EPOLLIN;
	epoll_ctl(epollfd,EPOLL_CTL_ADD,listensocket,&ev);
	
	while (1){
	   struct epoll_event events[MAX];
	  // waiting event occurt
	   int infds=epoll_wait(epollfd,events,MAX,-1);
	   if(infds <0 ){
	   	printf("epoll wait failed \n");break;
	   }
	   if(infds ==0){
			printf("epoll wait timeout \n");continue;	
	   }
		for(int ii=0; ii<infds;ii++){
			if( (events[ii].data.fd == listensocket) &&
	      (events[ii].events & EPOLLIN)){
	      	
				//如果监听的socket有事件,那么说明有客户端程序连接上来
				struct sockaddr_in client;
				socklen_t len = sizeof(client);
				printf("accepting \n");
				int clientsock = accept(listensocket,(struct  sockaddr*)&client,&len);
				if(clientsock < 0){
					printf("accept() fail\n"); continue;
				}
				//把监听到的socket加入到epoll中去管理
				memset(&ev,0,sizeof(struct epoll_event));
				ev.data.fd=clientsock;
				ev.events=EPOLLIN;
				epoll_ctl(epollfd,EPOLL_CTL_ADD,clientsock,&ev);
				printf("clientsock=%d success \n",clientsock);
				continue;
			}else if(events[ii].events & EPOLLIN){
				//如果是客户端socket有事件
				char buffer[1024];
				memset(buffer,0,sizeof(buffer));
				//读取客户端数据
				ssize_t  isize = read(events[ii].data.fd,buffer,sizeof(buffer));
				if(isize <= 0){
					//已断开的连接从epoll中删除
					memset(&ev,0,sizeof(struct epoll_event));
					ev.events = EPOLLIN;
					ev.data.fd = events[ii].data.fd;
					epoll_ctl(epollfd,EPOLL_CTL_DEL,events[ii].data.fd,&ev);
					close(events[ii].data.fd);
					continue;
				}
				printf("recv(eventfd=%d,size=%ld): %s \n",events[ii].data.fd,isize,buffer);
				//把收到的保温发回客户端
				write(events[ii].data.fd,buffer,strlen(buffer));
			}
    }
  }
  close(epollfd);
  return 0 ;
}

2.客户端采用tcp进行访问

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<unistd.h>
 
#define MAX 256
#define SERVER_HOST "localhost"
#define SERVER_PORT 1234
 
 
struct sockaddr_in server_addr ;
int sock,r;   
 
int client_init(){
	printf("=============client init========\n");
	printf("1. create a TCP socket\n");
	sock = socket (AF_INET,SOCK_STREAM,0);
	if(sock < 0){
		printf("socket call failed\n"); exit(1);
	}
	printf("2. fill server_addr with host_ip and port number \n");
	
	server_addr .sin_family = AF_INET;
	server_addr .sin_port   =  htons(SERVER_PORT);
	server_addr .sin_addr.s_addr = htonl(INADDR_ANY);
	printf("3.  connecting to server \n");
	r = connect (sock ,(struct sockaddr *) & server_addr  ,sizeof (server_addr ));	
	if( r<0){
		printf("bind call failed\n"); exit(3);
	}
	printf("	hostname =%s, port = %d \n", SERVER_HOST ,SERVER_PORT);
	printf("============client init done ===========\n");
	return 0;
}
 
int main (){
	char line [MAX] , ans[MAX];
	int n; 
	client_init();
	printf(" ********processing loop *******************");
	while (1){
		printf("put a line... \n");
		bzero(line ,MAX);
		fgets(line,MAX,stdin);
		line[strlen(line) - 1] = 0;
		if(line[0] ==0) exit(0);
		n=write(sock,line,MAX);
		printf("client : wrote n =%d bytes ; line %s :\n", n , line);
		n =read(sock ,ans,MAX);	
		printf("client : read n =%d bytes ; line %s :\n", n , line);
	}
 
}

linux下Epoll实现简单的C/S通信 <br />epoll的优点:<br />1.支持一个进程打开大数目的socket描述符(FD)<br />    select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据 阅读详情

相关推荐

select、poll、epoll的区别

是三种 I/O 多路复用机制,用于高效管理多个文件描述符的 I/O 事件。)管理描述符集合,每次调用需将整个集合从用户空间拷贝到内核,内核遍历所有描述符检测就绪状态。:基于事件驱动,内核维护红黑树存储监控的描述符,就绪事件通过双向链表返回。:需非阻塞 I/O 并一次性读取数据,减少系统调用次数,适合高性能场景。:仅在状态变化时通知一次,需一次性处理所有数据。:仅处理就绪事件,性能几乎不受连接数影响。:需结合非阻塞 I/O,避免漏处理事件。:可能重复通知就绪事件,适合简单场景。:只要描述符就绪,持续通知。

HL_LOVE_C的博客 1276

select 模型

SELECT(2) Linux Programmer's Manual SELECT(2)NAME select, pselect, FD_CLR, FD_IS...

weixin_30647065的博客 278

【C/C++】select、poll和epoll

在处理并发IO时,我们需要采取适当的IO多路复用技术,以提高程序效率。本文详细探讨了三种主要的IO多路复用技术:select、poll和epoll。首先,我们介绍了select,一种允许进程指示内核等待多个事件中的任何一个发生的函数。然后,我们讨论了poll,这是一种类似于select的函数,但在处理流设备时,它能提供额外的信息。最后,我们探讨了epoll,这是Linux下多路复用I/O接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

Ricky的博客 1942

Linux C select()函数

1.功能   select 机制会监听它所负责的所有 socket,当其中一个 socket 或者多个 socket 可读或者可写的时候,它就会返回,而如果所有的 socket 都是不可读或者不可写的时候,这个进程就会被阻塞,直到超时或者 socket 可读写,当 select 函数返回后,可以通过遍历 fdset,来找到就绪的描述符。   简单点说就是,假如想在一个线程处理两个阻塞的socket时,就得用select()函数。 2.函数原型 int select(int maxfdp1,fd_set *r

qq_36973838的博客 1183

Linux epoll内核源码剖析

IO多路复用接口Linux内核源码剖析,源码之前,了无密码 poll Linux内核源码剖析 select Linux内核源码剖析 epoll Linux内核源码剖析 epoll Linux内核源码剖析 前面介绍了select/poll,此文章将讲解epoll,epoll是select/poll的增强版,epoll更加高效,当然也更加复杂 epoll高效的一个重要的原因是在获取事件的时候,它无须遍...

JT同学的博客 4686

【C语言】epoll_wait / select

值得注意的是,`EPOLLERR` 通常与其他 I/O 事件(`EPOLLIN`、`EPOLLOUT` 等)一起返回,表示即使有错误,文件描述符也可能处于可读或可写状态。此外,对于某些类型的文件描述符(如 epoll 自己的文件描述符),`EPOLLERR` 也可能意味着某些系统层面的问题,你需要具备相应的处理逻辑。总体来说,`epoll_create1` 提供了更多的控制和扩展性,并且是推荐使用的方式,如果你的环境中存在支持它的 Linux 内核版本。数以万计的文件描述符。

数字人生 2210

Linux 网络 IO 多路复用:select、poll、epoll 详解

多路转接方法是一种高效的网络通信手段,它通过允许单个进程或线程同时监听多个IO事件,实现了在单线程/进程场景下的并发处理。在选择具体的实现方法时,需要根据应用场景和需求进行权衡和选择。

小羊的博客 1519

Linux epoll的使用详解:实现高效事件驱动编程

在Linux环境下,epoll是一个高效的事件通知机制,被广泛用于网络编程和并发处理。epoll是Linux内核提供的一种事件通知机制,用于高效处理大量的I/O事件。epoll基于文件描述符(file descriptor)进行操作,通过注册感兴趣的文件描述符和相应的事件类型,当事件发生时,内核会通知应用程序进行处理。当有事件发生时,函数会返回事件的数量,并将事件信息保存在。首先,我们需要创建一个epoll实例,并注册感兴趣的事件。接下来,我们需要注册感兴趣的事件和相应的文件描述符。数组中供应用程序处理。

TechGlide的博客 478

关于epoll和select的区别

问题:关于 epoll 和 select 的区别,哪些说法是正确的?(多选) A. epoll 和 select 都是 I/O 多路复用的技术,都可以实现同时监听多个 I/O 事件的状态。 B. epoll 相比 select 效率更高,主要是基于其操作系统支持的I/O事件通知机制,而 select 是基于轮询机制。 C. epoll 支持水平触发和边沿触发两种模式。 D. select ...

跨链技术践行者 6199

socket网络通信模型之select与epoll

socket网络通信模型之select和epoll的介绍及对比分析,并结合具体问题进行阐述。

dvlinker的技术专栏 1万+

Linux c - select详解

Linux c - select详解 Linux c - select详解 Linux c - select详解Linux c - select详解

select、poll和epoll的区别

本文总结了从阻塞I/O模型到多路复用I/O模型的演变,以及select、poll、epoll三者各自优缺点。

weixin_38597669的博客 2199

告别Colmap!在ROS2 Humble上,用Fast-LIVO2直接为3D Gaussian Splatting准备数据全流程

本文详细介绍了在ROS2 Humble环境下,使用Fast-LIVO2为3D Gaussian Splatting(3DGS)准备数据的全流程。通过优化Fast-LIVO2配置、实时数据同步机制和3DGS训练参数,显著提升了三维重建效率和质量,相比传统Colmap流程节省95%以上的预处理时间。适用于Ubuntu22.04系统,适合机器人及自动驾驶领域的三维重建应用。

weixin_28688385的博客 584

linux中多路复用IO:select、poll和epoll

函数头文件函数原型函数功能监控一组文件描述符, 阻塞当前进程, 由内核检测相应的文件描述符是否就绪,一旦有文件描述符就绪,将就绪的文件描述符拷贝给进程, 唤醒进程处理函数参数nfds:最大文件描述符加1readfds:读文件描述符集合的指针writefds:写文件描述符集合的指针exceptfds:其他文件描述符集合的指针timeout:超时时间结构体变量的指针函数返回值成功:返回已经就绪的文件描述符的个数。

sir2022的博客 1108

基于CANoe的车载以太网测试

以太网测试 CANoe

Yafet 4545

RK3588嵌入式AI部署实战:从环境搭建到模型推理全链路

嵌入式AI部署涉及交叉编译、NPU驱动、模型量化与运行时环境等多个技术环节,其核心挑战在于软硬件版本矩阵的严格匹配。在边缘计算场景中,RK3588凭借6TOPS NPU算力成为热门平台,但环境搭建与模型转换的复杂性常导致部署效率低下。通过容器化工具链管理、量化参数对齐及多线程推理优化,可显著提升部署稳定性与推理性能。典型应用包括智能安防、工业质检与视频结构化分析。本文围绕RK3588的AI部署与环境搭建,系统梳理从系统烧录到板端推理的完整实践路径。

weixin_42520573的博客 282

linux epoll与select

每次调用select需要把线程x放在所有需要监听的socket的等待队列中(一次遍历),每次有数据接受了,需要把所有socket等待队列中线程x去除(二次遍历),select返回后,程序不知道哪一个socket有数据,需要再次遍历(三次遍历)调用epoll_ctl时,socket等待队列会添加该eventpoll, socket有数据时,系统会操作eventpoll对象而即 将有数据的socket天记到eventpoll的就绪列表里rdlist, 并唤醒进程。

qq_44152415的博客 963

智能手表竞品分析与市场调研报告

本报告旨在通过调研当前主流智能手表产品的功能特点、市场表现、用户反馈、定价策略等方面,辅助我司即将立项的Aurora Watch S1产品在功能定义、目标用户、定价区间、差异化竞争等方面做出科学决策。 调研范围包括国内外中端价位主流品牌(¥199~¥999),重点关注小米、华为、荣耀、Amazfit、Redmi、Haylou 等品牌的热销型号。

hoony123的博客 7万+

Linux网络select/poll/epoll:学习笔记1

1.socket函数。

weixin_68925552的博客 2229

Xilinx系ZYNQ学习笔记(一)Vivado使用说明

vivado使用学习

贾saisai的博客 1873

linux c epoll mysql_epoll简介和使用

epoll简介在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:#define...

weixin_39609423的博客 207

手把手教你学veriolg(二十六)-- Verilog CIC 滤波器设计

目录 Verilog CIC 滤波器设计1. CIC 滤波器的基本原理2. Verilog 实现3. 说明4. 测试激励5. 总结 CIC(Cascade-Integrator-Comb)滤波器是一种特殊的数字滤波器,常用于多速率信号处理中,尤其是用于信号的上采样(插值)和下采样(抽取)。CIC 滤波器的特点在于它具有简单的硬件实现、不需要乘法器(仅使用加法器和延迟元件)、且具有线性相位特性。然而,CIC 滤波器的缺点是它会产生较大的量化误差,因此在设计时需要特别注意字长的选择以避免溢出。1. CIC 滤波

MHD0815的博客 888

select和epoll的选择使用

当通信方式既有can,又有485,又有以太网,使用哪种非阻塞模式。

weixin_62130773的博客 254

树莓派 RP2350B 开发环境搭建

本文介绍了树莓派RP2350B开发环境的搭建过程。

张一西的博客 286

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

【C++|Linux系统编程】IO多路复用(select、poll、epoll)

三、demo2.cpp 基于Linux的文件操作 一、demo3.cpp 二、demo4.cpp socket函数详解 一、什么是协议 人与人沟通的方式有很多种:书信、电话、QQ、微信。如果两个人想沟通,必须先选择一种沟通的方式,如果一方使用电话,另一方也应该使用电话,而不是书信。 协议是网络通讯的规则,是约定。 成功,失败,。 全部网络编程的函数,失败时基本上都是返回-1,errno被设置。只要参数没填错,基本上不会失败。。( ) 三、TCP和UPD 1)TCP和UDP的区别 TCP a)TCP面向

千禧皓月的博客 711

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

上一篇: C++ 线程池的实现
下一篇: nginx学习,看这一篇就够了:下载、安装。使用:正向代理、反向代理、负载均衡。常用命令和配置文件,很全
superSmart_Dong
博客等级 码龄10年 678粉丝 167原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值