c++ 网络编程(十) LINUX/windows 异步通知I/O模型与重叠I/O模型 附带示例代码
原文作者:aircraft
原文链接:https://www.cnblogs.com/DOMLX/p/9662931.html
一.异步IO模型(asynchronous IO)
(1)什么是异步I/O
异步I/O(asynchronous I/O)由POSIX规范定义。演变成当前POSIX规范的各种早起标准所定义的实时函数中存在的差异已经取得一致。一般地说,这些函数的工作机制是:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。这种模型与前一节介绍的信号驱动模型的主要区别在于:信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。
示意图如下:
我们调用aio_read函数(POSIX异步I/O函数以aio_或lio_开头),给内核传递描述符、缓冲区指针、缓冲区大小(与read相同的三个参数)和文件偏移(与lseek类似),并告诉内核当整个操作完成时如何通知我们。该系统调用立即返回,并且在等待I/O完成期间,我们的进程不被阻塞。本例子中我们假设要求内核在操作完成时产生某个信号,该信号直到数据已复制到应用进程缓冲区才产生,这一点不同于信号驱动I/O模型。
(2)运用到的函数讲解--WSAEventSelect模型
在WSAEventSelect模型中,基本流程如下:
1. 创建一个事件对象数组,用于存放所有的事件对象;
2. 创建一个事件对象(WSACreateEvent);
3. 将一组你感兴趣的SOCKET事件与事件对象关联(WSAEventSelect),然后加入事件对象数组;
4. 等待事件对象数组上发生一个你感兴趣的网络事件(WSAWaitForMultipleEvents);
5. 对发生事件的事件对象查询具体发生的事件类型(WSAEnumNetworkEvents);
6. 针对不同的事件类型进行不同的处理;
7. 循环进行
函数过程:
- 初始化网络环境,创建一个监听的socket,然后进行connect操作。接下来WSACreateEvent()创建一个网络事件对象,其声明如下:
WSAEVENT WSACreateEvent(void); //返回一个手工重置的事件对象句柄
- 再调用WSAEventSelect,来将监听的socket与该事件进行一个关联,其声明如下:
int WSAEventSelect(
SOCKET s, //套接字
WSAEVENT hEventObject, //网络事件对象
long lNetworkEvents //需要关注的事件
);我们客户端只关心FD_READ和FD_CLOSE操作,所以第三个参数传FD_READ | FD_CLOSE。
- 启动一个线程调用WSAWaitForMultipleEvents等待1中的event事件,其声明如下:
DWORD WSAWaitForMultipleEvents(
DWORD cEvents, //指定了事件对象数组里边的个数,最大值为64
const WSAEVENT FAR *lphEvents, //事件对象数组
BOOL fWaitAll, //等待类型,TRUE表示要数组里全部有信号才返回,FALSE表示至少有一个就返回,这里必须为FALSE
DWORD dwTimeout, //等待的超时时间
BOOL fAlertable //当系统的执行队列有I/O例程要执行时,是否返回,TRUE执行例程返回,FALSE不返回不执行,这里为FALSE
);由于我们是客户端,所以只等待一个事件。
- 当事件发生,我们需要调用WSAEnumNetworkEvents,来检测指定的socket上的网络事件。其声明如下:
int WSAEnumNetworkEvents
(
SOCKET s, //指定的socket
WSAEVENT hEventObject, //事件对象
LPWSANETWORKEVENTS lpNetworkEvents //WSANETWORKEVENTS<span style="font-family:Arial, Helvetica, sans-serif;">结构地址</span>
);当我们调用这个函数成功后,它会将我们指定的socket和事件对象所关联的网络事件的信息保存到WSANETWORKEVENTS这个结构体里边去,我们来看下这个结构体的声明:
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;<span style="white-space:pre"> </span>//指定了哪个已经发生的网络事件
int iErrorCodes[FD_MAX_EVENTS];<span style="white-space:pre"> </span>//错误码
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;根据这个结构体我们就可以判断是否是我们所关注的网络事件已经发生了。如果是我们的读的网络事件发生了,那么我们就调用recv函数进行操作。若是关闭的事件发生了,就调用closesocket将socket关掉,在数组里将其置零等操作。
整个模型的流程图如下:
(3)实现服务端代码:
#include <WinSock2.h>
#include <process.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib") SOCKET g_sClient[WSA_MAXIMUM_WAIT_EVENTS] = {INVALID_SOCKET}; //client socket数组
WSAEVENT g_event[WSA_MAXIMUM_WAIT_EVENTS]; //网络事件对象数组
SOCKET g_sServer = INVALID_SOCKET; //server socket
WSAEVENT g_hServerEvent; //server 网络事件对象
int iTotal = ; //client个数
/*
@function OpenTCPServer 打开TCP服务器
@param _In_ unsigned short Port 服务器端口
@param _Out_ DWORD* dwError 错误代码
@return 成功返回TRUE 失败返回FALSE
*/
BOOL OpenTCPServer( _In_ unsigned short Port, _Out_ DWORD* dwError)
{
BOOL bRet = FALSE;
WSADATA wsaData = { };
SOCKADDR_IN ServerAddr = { };
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(Port);
ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
do
{
if (!WSAStartup(MAKEWORD(, ), &wsaData))
{
if (LOBYTE(wsaData.wVersion) == || HIBYTE(wsaData.wVersion) == )
{
g_sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
g_hServerEvent = WSACreateEvent(); //创建网络事件对象
WSAEventSelect(g_sServer, g_hServerEvent, FD_ACCEPT);//为server socket注册网络事件
if (g_sServer != INVALID_SOCKET)
{
if (SOCKET_ERROR != bind(g_sServer, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)))
{
if (SOCKET_ERROR != listen(g_sServer, SOMAXCONN))
{
bRet = TRUE;
break;
}
*dwError = WSAGetLastError();
closesocket(g_sServer);
}
*dwError = WSAGetLastError();
closesocket(g_sServer);
}
*dwError = WSAGetLastError();
}
*dwError = WSAGetLastError(); }
*dwError = WSAGetLastError();
} while (FALSE);
return bRet;
} //接受client请求线程
unsigned int __stdcall ThreadAccept(void* lparam)
{
WSANETWORKEVENTS networkEvents; //网络事件结构
while (iTotal < WSA_MAXIMUM_WAIT_EVENTS) //这个值是64
{
if ( == WSAEnumNetworkEvents(g_sServer, g_hServerEvent, &networkEvents))
{
if (networkEvents.lNetworkEvents & FD_ACCEPT) //如果等于FD_ACCEPT,相与就为1
{
if ( == networkEvents.iErrorCode[FD_ACCEPT_BIT]) //检查有无网络错误
{
//接受请求
SOCKADDR_IN addrServer = { };
int iaddrLen = sizeof(addrServer);
g_sClient[iTotal] = accept(g_sServer, (SOCKADDR*)&addrServer, &iaddrLen);
if (g_sClient[iTotal] == INVALID_SOCKET)
{
printf("accept failed with error code: %d\n", WSAGetLastError());
return ;
}
//为新的client注册网络事件
g_event[iTotal] = WSACreateEvent();
WSAEventSelect(g_sClient[iTotal], g_event[iTotal], FD_READ | FD_WRITE | FD_CLOSE);
iTotal++;
printf("accept a connection from IP: %s,Port: %d\n", inet_ntoa(addrServer.sin_addr), htons(addrServer.sin_port));
}
else //错误处理
{
int iError = networkEvents.iErrorCode[FD_ACCEPT_BIT];
printf("WSAEnumNetworkEvents failed with error code: %d\n", iError);
return ;
}
}
}
Sleep();
}
return ;
} //接收数据
unsigned int __stdcall ThreadRecv(void* lparam)
{
char* buf = (char*)malloc(sizeof(char) * );
while ()
{
if (iTotal == )
{
Sleep();
continue;
}
//等待网络事件
DWORD dwIndex = WSAWaitForMultipleEvents(iTotal, g_event, FALSE, , FALSE);
//当前的事件对象
WSAEVENT curEvent = g_event[dwIndex];
//当前的套接字
SOCKET sCur = g_sClient[dwIndex];
//网络事件结构
WSANETWORKEVENTS networkEvents;
if ( == WSAEnumNetworkEvents(sCur, curEvent, &networkEvents))
{
if (networkEvents.lNetworkEvents & FD_READ) //有数据可读
{
if ( == networkEvents.iErrorCode[FD_READ_BIT])
{
memset(buf, , sizeof(buf));
int iRet = recv(sCur, buf, sizeof(buf), ); //接收数据
if (iRet != SOCKET_ERROR)
{
if (strlen(buf) != )
printf("Recv: %s\n", buf);
}
}
else //错误处理
{
int iError = networkEvents.iErrorCode[FD_ACCEPT_BIT];
printf("WSAEnumNetworkEvents failed with error code: %d\n", iError);
break;
}
}
else if (networkEvents.lNetworkEvents & FD_CLOSE) //client关闭
printf("%d downline\n", sCur);
}
Sleep();
}
if (buf)
free(buf);
return ;
} int main()
{
DWORD dwError = ;
if (OpenTCPServer(, &dwError))
{
_beginthreadex(NULL, , ThreadAccept, NULL, , NULL);
_beginthreadex(NULL, , ThreadRecv, NULL, , NULL);
}
Sleep();
closesocket(g_sServer);
WSACleanup();
return ;
}
二.重叠IO模型
1-重叠模型的优点
1可以运行在支持Winsock2的所有Windows平台,而不像完成端口只支持NT系统
2比起阻塞,select,WSAAsyncSelect以及WSAEventSelect等模型,重叠I/O(Overlapped I/O)模型使应用程序能达到更加系统性能
因为他和其他4种模型不同的是,使用重叠模型的应用程序通知缓冲区收发系统直接使用数据,也就是说,如果应用程序
投递了一个10kb大小的缓冲区来接收数据,而数据已经到达套接字,则将该数据直接拷贝到投递的缓冲区,
而4种模型中,数据达到并拷贝到单套接字接收缓冲区,此时应用程序会被告知可以读入的容量,当应用程序调用
接收函数之后,数据才从单套接字缓冲区拷贝应用程序到缓冲区,差别就体现了。
2-重叠模型的基本原理
重叠模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个Winsock I/O请求,针对这些提交的
请求,在他们完成之后,应用程序会收到通知,于是就可通过自己的代码来处理这些数据了。
使用事件通知的方法来实现重叠IO模型,基于事件的话,就要求将Win事件与WSAOVERLAPPED结构关联在一起,
使用重叠结构,常用的send,sendto,recv,recvform也被WSASend,WSARecv等替换掉,
OVERLAPPER SOCKET(重叠Socket)上进行重叠发送的操作,(简单的理解就是异步send,recv)
他们的参数中都有一个Overlapped参数,就是说所有的重叠Socket都要绑定到这个重叠结构体上,
提交一个请求,其他的事情就交给重叠结构去操心, 而其中重叠结构要与Windows事件绑定在一起,
在样,我们调用完WSARecv后.等重叠操作完成,就会有对应的事件来同意我们操作完成,
3-重叠模型的函数详解
(1)创建套接字
要使用重叠I/O模型,在创建套接字时,必须使用WSASocket函数,设置重叠标志。
The WSASocket function creates a socket that is bound to a specific transport-service provider.
SOCKET WSASocket(
__in int af,
__in int type,
__in int protocol,//前三个参数与socket函数相同
__in LPWSAPROTOCOL_INFO lpProtocolInfo, //指定下层服务提供者 ,可以是NULL
__in GROUP g, //保留
__in DWORD dwFlags //指定套接字属性。要使用重叠I/O模型,必须指定WSA_FLAG_OVERLAPPED
);
由于要用到重叠模型来提交我们的操作,所以原来的recv、send、sendto、recvfrom等函数都要被替换为WSARecv、WSASend、WSASendto、WSARecvFrom函数来代替。
(2)传输数据
在重叠I/O模型中,传输数据的函数是WSASend\WSARecv(TCP)和WSASendTo、WSARecvFrom等,下面是WSASend的定义:
The WSASend function sends data on a connected socket.
int WSASend(
__in SOCKET s,
__in LPWSABUF lpBuffers,
__in DWORD dwBufferCount,
__out LPDWORD lpNumberOfBytesSent,
__in DWORD dwFlags,
__in LPWSAOVERLAPPED lpOverlapped,
__in LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
参数
返回值
可以异步接收连接请求的函数是AcceptEX。这是一个Mincrosoft扩展函数,它接受一个新的连接,返回本地和远程地址,取得客户程序发送的第一块数据,函数定义如下:
The AcceptEx function accepts a new connection, returns the local and remote address, and receives the first block of data sent by the client application.
Note This function is a Microsoft-specific extension to the Windows Sockets specification.
BOOL AcceptEx(
__in SOCKET sListenSocket,
__in SOCKET sAcceptSocket,
__in PVOID lpOutputBuffer,
__in DWORD dwReceiveDataLength,
__in DWORD dwLocalAddressLength,
__in DWORD dwRemoteAddressLength,
__out LPDWORD lpdwBytesReceived,
__in LPOVERLAPPED lpOverlapped
);
参数
sListenSocket[in]侦听套接字。服务器应用程序在这个套接字上等待连接。sAcceptSocket[in]将用于连接的套接字。此套接字必须不能已经绑定或者已经连接。lpOutputBuffer[in]指向一个缓冲区,该缓冲区用于接收新建连接的所发送数据的第一个块、该服务器的本地地址和客户端的远程地址。接收到的数据将被写入到缓冲区0偏移处,而地址随后写入。 该参数必须指定,如果此参数设置为NULL,将不会得到执行,也无法通过GetAcceptExSockaddrs函数获得本地或远程的地址。dwReceiveDataLength[in]lpOutputBuffer字节数,指定接收数据缓冲区lpOutputBuffer的大小。这一大小应不包括服务器的本地地址的大小或客户端的远程地址,他们被追加到输出缓冲区。如果dwReceiveDataLength是零,AcceptEx将不等待接收任何数据,而是尽快建立连接。dwLocalAddressLength[in]为本地地址信息保留的字节数。此值必须比所用传输协议的最大地址大小长16个字节。dwRemoteAddressLength[in]为远程地址的信息保留的字节数。此值必须比所用传输协议的最大地址大小长16个字节。 该值不能为0。dwBytesReceived[out]指向一个DWORD用于标识接收到的字节数。此参数只有在同步模式下有意义。如果函数返回ERROR_IO_PENDING并在迟些时候完成操作,那么这个DWORD没有意义,这时你必须获得从完成通知机制中读取操作字节数。lpOverlapped[in]一个OVERLAPPED结构,用于处理请求。此参数必须指定,它不能为空。
如果没有错误发生,AcceptEx函数成功完成并返回TRUE。 [1]如果函数失败,AcceptEx返回FALSE。可以调用WSAGetLastError函数获得扩展的错误信息。如果WSAGetLastError返回ERROR_IO_PENDING,那么这次行动成功启动并仍在进行中。
AcceptEX函数将几个套接字函数的功能集合在一起。如果它投递的请求成功完成,则执行了如下3个操作:
(1)接受了新的连接
(2)新连接的本地地址和远程地址都会返回
(3)接收到了远程主机发来的第一块数据
AcceptEX和大家熟悉的accept函数有很大的不同就是AcceptEX函数需要调用者提供两个套接字,一个指定了在哪个套接字上监听,另一个指定了在哪个套接字上接受连接,也就是说,AcceptEX不会像accept函数一样为新的连接创建套接字。
如果提供了新的缓冲区,AcceptEX投递的重叠操作直到接受到连接并且读到数据之后才会返回。以SO_CONNECT_TIME为参数调用getsockopt函数可以检查到是否接受了连接,如果接受了连接,这个调用还可以取得连接已经建立了多长时间。
AcceptEX函数是从Mswsock.lib库中导出的,为了能够直接调用它,而不用链接到Mswsock.lib库,需要使用WSAIoctl函数将AcceptEX函数加载到内存,WSAIoctl函数是ioctlsocket函数的扩展,它可以使用重叠I/O。函数的第3个到第6个参数是输入和输出缓冲区,在这里传递AcceptEX函数的指针 (4)接收传输结果 当重叠I/O请求最终完成以后,以之关联的事件对象受信,等待函数返回,应用程序可以使用WSAGetOverlappedResult函数取得重叠操作的结果,函数用法如下:
The WSAGetOverlappedResult function retrieves the results of an overlapped operation on the specified socket.
BOOL WSAAPI WSAGetOverlappedResult(
__in SOCKET s,
__in LPWSAOVERLAPPED lpOverlapped,
__out LPDWORD lpcbTransfer,
__in BOOL fWait,
__out LPDWORD lpdwFlags
);
参数:
lpOverlapped:指向调用重叠操作时指定的WSAOVERLAPPED结构。lpcbTransfer:指向一个32位变量,该变量用于存放一个发送或接收操作实际传送的字节数,或WSAIoctl()传送的字节数。fWait:指定函数是否等待挂起的重叠操作结束。若为真TRUE则函数在操作完成后才返回。若为假FALSE且函数挂起,则函数返回FALSE,WSAGetLastError()函数返回 WSA_IO_INCOMPLETE。lpdwFlags:指向一个32位变量,该变量存放完成状态的附加标志位。如果重叠操作为 WSARecv()或WSARecvFrom(),则本参数包含lpFlags参数所需的结果。
如果函数成功,则返回值为真TRUE。它意味着重叠操作已经完成,lpcbTransfer所指向的值已经被刷新。应用程序可调用WSAGetLastError()来获取重叠操作的错误信息。如果函数失败,则返回值为假FALSE。它意味着要么重叠操作未完成,要么由于一个或多个参数的错误导致无法决定完成状态。失败时,lpcbTransfer指向的值不会被刷新。应用程序可用WSAGetLastError()来获取失败的原因。
4-重叠模型的实例代码:
//完成例程实现重叠io模型伪代码
SOCKET acceptSock;
WSABUF dataBuf; void main()
{
WSAOVERLAPPED overlapped;
//1.初始化
//... //2.接收连接请求
acceptSock=accept(listenSock,NULL,NULL); //3.初始化重叠结构
UINT flag=;
ZeroMemory(&overlapped,sizeof(WSAOVERLAPPED));
dataBuf.len=DATA_BUFSIZE;
dataBuf.buf=buf; if (WSARecv(acceptSock,&dataBuf,,&recvBytes,&flag,&overlapped,workroutine)==SOCKET_ERROR)//最后一个参数时回调函数地址
{
if(WSAGetLastError()!=WSA_IO_PENDING)
{
printf("WSARecv() failed with error %d\n",WSAGetLastError());
return;
}
} //创建事件
eventArray[]=WSACreateEvent();
while (true)
{
int index=WSAWaitForMultipleEvents(,eventArray,FALSE,WSA_INFINITE,TRUE);//最后一个参数最好为true
if (index==WAIT_IO_COMPLETION)//io请求完成
{
break;
}
else//io请求出错
{
return;
}
}
//调用回调函开始进行处理
} void CALLBACK WorkRoutine(DWORD error,DWORD bytesTransferred,LPWSAOVERLAPPED overlapped,DWORD inflag)
{
DWORD sendBytes,recvBytes;
DWORD flags; if(error!=||bytesTransferred==)
{
closesocket(acceptSock);
return;
} flags=; ZeroMemory(&overlapped,sizeof(WSAOVERLAPPED));
dataBuf.len=DATA_BUFSIZE;
dataBuf.data=buf; if (WSARecv(acceptSock,&dataBuf,,&recvBytes,&flag,&overlapped,workroutine)==SOCKET_ERROR)//最后一个参数时回调函数地址
{
if(WSAGetLastError()!=WSA_IO_PENDING)
{
printf("WSARecv() failed with error %d\n",WSAGetLastError());
return;
}
}
}
最后说一句啦。本网络编程入门系列博客是连载学习的,有兴趣的可以看我博客其他篇。。。。c++ 网络编程课设入门超详细教程 ---目录 参考博客:https://www.cnblogs.com/Dreamcaihao/archive/2012/11/14/2770293.html
参考博客:https://www.cnblogs.com/tanguoying/p/8506821.html
参考博客:https://blog.csdn.net/wxf2012301351/article/details/73332588
参考书籍:《TCP/IP网络编程 ---尹圣雨》
若有兴趣交流分享技术,可关注本人公众号,里面会不定期的分享各种编程教程,和共享源码,诸如研究分享关于c/c++,python,前端,后端,opencv,halcon,opengl,机器学习深度学习之类有关于基础编程,图像处理和机器视觉开发的知识
c++ 网络编程(十) LINUX/windows 异步通知I/O模型与重叠I/O模型 附带示例代码的更多相关文章
- C语言网络编程(Linux && Windows)(1)
和朋友一起做课程设计,同时学习C语言的网络编程,以前写的都是python网络编程,但python很多的库都是封装好的,大部分人在使用的时候不会去了解底层的实现,这样对长远的学习不太好,也改正自己这方面 ...
- Linux下TCP网络编程与基于Windows下C#socket编程间通信
一.linux下TCP网络编程基础,需要了解相关函数 Socket():用于套接字初始化. Bind():将 socket 与本机上的一个端口绑定,就可以在该端口监听服务请求. Listen():使s ...
- (51)LINUX应用编程和网络编程之六Linux高级IO
3.6.1.非阻塞IO 3.6.1.1.阻塞与非阻塞 阻塞:阻塞具有很多优势(是linux系统的默认设置),单路IO的时候使用阻塞式IO没有降低CPU的性能 补充:阻塞/非阻塞, 它们是程序在等待消息 ...
- (46)LINUX应用编程和网络编程之一Linux应用编程框架
3.1.1.应用编程框架介绍 3.1.1.1.什么是应用编程 (1)整个嵌入式linux核心课程包括5个点,按照学习顺序依次是:裸机.C高级.uboot和系统移植.linux应用编程和网络编程.驱动. ...
- Linux之异步通知20160702
异步通知,主要说的是使用信号的方式,同时使用信号也是实现进程之间通信的一种方式. 多的不说,我们直接看代码: 首先应用程序的: #include <sys/types.h> #includ ...
- 网络编程介绍(uninx/windows)
1.网络中进程之间如何通信? 2.Socket是什么? 3.socket的基本操作 3.1.socket()函数 3.2.bind()函数 3.3.listen().connect()函数 3.4.a ...
- windows 异步通知I/O模型与重叠I/O模型
一.异步IO模型(asynchronous IO) (1)什么是异步I/O 异步I/O(asynchronous I/O)由POSIX规范定义.演变成当前POSIX规范的各种早起标准所定义的实时函数中 ...
- (50)LINUX应用编程和网络编程之五 Linux信号(进程间通信)
信号实现进程间的通信 3.5.1.什么是信号 ...
- Python网络编程:Linux epoll
原文地址:http://scotdoyle.com/python-epoll-howto.html 介绍 Python已于2.6版本添加访问Linux epoll库的API.这篇教程使用Python ...
随机推荐
- Hdu3549 Flow Problem 2017-02-11 16:24 58人阅读 评论(0) 收藏
Flow Problem Problem Description Network flow is a well-known difficult problem for ACMers. Given a ...
- Altera FPGA 开启引脚片上上拉电阻功能
本博文以矩阵键盘实验为例,介绍了如何开启FPGA管脚的片上上拉电阻. Cyclone IV E FPGA的通用输入输出管脚都支持内部弱上拉电阻,但是时钟输入脚不支持.所以,当需要上拉电阻的信号(如本例 ...
- Android-自定义控件之事件分发
最大范围 外层蓝色区域是继承ViewGroup 中间红色区域,也是继承ViewGroup 最中间黑色区域,是继承View 布局相关的代码: <!-- 事件分发 --> <view.c ...
- [转载]MVC、MVP以及Model2(上)
对于大部分面向最终用户的应用来说,它们都需要具有一个可视化的UI与用户进行交互,我们将这个UI称为视图(View).在早期,我们倾向于将所有与视图相关的逻辑糅合在一起,这些逻辑包括数据的呈现.用户操作 ...
- ASP.NET MVC学习(一)
这几天在学习asp.net mvc 一上来就被书中的什么依赖注入,什么单元测试搞的晕晕呼呼,根本就不理解,前天开始做书中的运动商店项目,一上来就遇到个大难题,书中的连接字符串的写法,跟以往在winfo ...
- Volo.Abp.EntityFrameworkCore.MySQL 使用
创建新项目 打开 https://cn.abp.io/Templates ,任意选择一个项目类型,然后创建项目,我这里创建了一个Web Api 解压项目,还原Nuget,项目目录如下: 首先我们来查看 ...
- 关于StreamReader.ReadToEnd方法
以前写抓取网页的代码喜欢用ReadToEnd,因为简单省事,后来发现,在爬取网页的时候,如果网速很慢,ReadToEnd超时的几率很大.使用Read改写后,超时几率大大减小,完整代码如下: /// & ...
- T-Sql之集合
1.知识点 先了解一下集合概念,集合运算(UNION(并).EXCEPT(补).INTERSECT(交))是指表之间的垂直操作.区别联接(CROSS,INNER.OUTER)是指表之间的水平操作,基础 ...
- Elasticsearch学习(4) spring boot整合Elasticsearch的聚合操作
之前已将spring boot原生方式介绍了,接下将结介绍的是Elasticsearch聚合操作.聚合操作一般来说是解决一下复杂的业务,比如mysql中的求和和分组,由于博主踩的坑比较多,所以博客可能 ...
- [bzoj3995] [SDOI2015]道路修建 线段树
Description 某国有2N个城市,这2N个城市构成了一个2行N列的方格网.现在该国政府有一个旅游发展计划,这个计划需要选定L.R两列(L<=R),修建若干条专用道路,使得这两列之间(包括 ...