Socket编程模式理解与对比
本文主要分析了几种Socket编程的模式。主要包括基本的阻塞Socket、非阻塞Socket、I/O多路复用。其中,阻塞和非阻塞是相对于套接字来说的,而其他的模式本质上来说是基于Socket的并发模式。I/O多路复用又主要分析了分析linux和windows下的常用模型。最后,比较这几种Socket编程模式的优缺点,并讨论多线程与Socket的组合使用和服务器开发的常用模式。
阻塞模式
阻塞模式是最基本的Socket编程模式,在各种关于网络编程的书籍中都是入门的例子。就像其名所说,阻塞模式的Socket会阻塞当前的线程,直到结果返回,否则会一直等待。
非阻塞模式
非阻塞模式是相对阻塞模式来说,Socket并不会阻塞当前线程,非阻塞模式不会等到结果返回,而会立即运行下去。
//设置套接字为非阻塞模式
fcntl( sockfd, F_SETFL, O_NONBLOCK); //O_NONBLOCK标志设置非阻塞模式
这里需要注意,阻塞/非阻塞、同步/异步之前的区别。在本质上它们是不同的。同步和异步是相对操作结果来说,会不会等待结果结果返回。而阻塞和非阻塞是相对线程是否被阻塞来说的。其实,这两者存在本质的区别,它们的修饰对象是不同的。阻塞和非阻塞是指进程访问的数据如果尚未就绪,进程是否需要等待,简单说这相当于函数内部的实现区别,也就是未就绪时是直接返回还是等待就绪。而同步和异步是指访问数据的机制,同步一般指主动请求并等待I/O操作完毕的方式,当数据就绪后在读写的时候必须阻塞,异步则指主动请求数据后便可以继续处理其它任务,随后等待I/O,操作完毕的通知,这可以使进程在数据读写时也不阻塞。因为两者在表现上经常相同,所以经常被混淆。
I/O多路复用
I/O多路复用是一种并发服务器开发技术(处理多个客户端的连接)。通过该技术,系统内核缓冲I/O数据,当某个I/O准备好后,系统通知应用程序该I/O可读或可写,这样应用程序可以马上完成相应的I/O操作,而不需要等待系统完成相应I/O操作,从而应用程序不必因等待I/O操作而阻塞。
在linux下主要有select、poll、epoll三种模型,在freeBSD下则有kqueue,windwos下select、事件选择模型、重叠I/O和完成端口等。
linux上I/O复用模型
select
select本质是通过设置或检查存放fd标志位的数据结构来进行下一步的处理。select是采用轮询fd集合来进行处理的。
//select相关函数
int select(int maxfdp1, fd_set *readset, fd_set *writeset,
fd_set *exceptset,const struct timeval *timeout)
//返回值:就绪描述符的数目,超时返回0,出错返回-1
void FD_ZERO(fd_set *fdset); //清空集合
void FD_SET(int fd, fd_set *fdset); //将一个给定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset); //将一个给定的文件描述符从集合中删除
int FD_ISSET(int fd, fd_set *fdset); // 检查集合中指定的文件描述符是否可以读写
但是,select存在一定的缺陷。单个进程可监视的fd数量被限制,linux下一般为1024。虽然是可以修改的,但是总是有限制的。在每次调用select时,都需要把fd集合从用户态拷贝到内核态,而且需要循环整个fd集合,这个开销很多时候是比较大的。
poll
poll的实现和select非常相似,本质上是相同,只是描述fd集合的方式不同。poll是基于链表来存储的。这虽然没有了最大连接数的限制,但是仍然还有fd集合拷贝和循环带来的开销。而且poll还有一个特点是水平触发,内核通知了fd后,没有被处理,那么内核就会不断的通知,直到被处理。
//poll相关函数
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
epoll
epoll是对select和poll的改进。相较于poll,epoll使用“事件”的就绪通知,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程,这样不在需要轮询,判断fd合计合集是否为空。而且epoll不仅支持水平触发,还支持边缘触发。边缘触发是指内核通知fd之后,不管处不处理都不在通知了。在存储fd的集合上,epoll也采用了更为优秀的mmap,而且会保证fd集合拷贝只会发生一次。
//epoll相关函数
int epoll_create(int size); //句柄的创建
int epoll_ctl(int epfd, int op, int fd,
struct epoll_event *event); //事件注册
int epoll_wait(int epfd, struct epoll_event * events,
int maxevents, int timeout); //等待事件的发生
Windows上的I/O复用模型
事件选择模型
事件选择模型是基于消息的。它允许程序通过Socket,接收以事件为基础的网络事件通知。
//事件选择模型相关函数
WSAEVENT WSACreatEvent(void); //创建事件对象
int WSAEventSelect(SOCKET s, WSAEVENT hEventObject,
long lNetworkEvents); //关联事件
重叠I/O模型
重叠I/O模型是异步I/O模型。重叠模型的核心是一个重叠数据结构。重叠模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个Winsock I/O请求。若想以重叠方式使用文件,必须用FILE_FLAG_OVERLAPPED 标志打开它。当I/O操作完成后,系统通知应用程序。利用重叠I/O模型,应用程序在调用I/O函数之后,只需要等待I/O操作完成的消息即可。
HANDLE hFile = CreateFile(lpFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
完成端口模型(IOCP)
IOCP完成端口是目前Windows下性能最好的I/O模型,当然也是最复杂的。简单的说,IOCP 是一种高性能的I/O模型,是一种应用程序使用线程池处理异步I/O请求的机制。IOCP将所有用户的请求都投递到一个消息队列中去,然后线程池中的线程逐一从消息队列中去取出消息并加以处理,就可以避免针对每一个I/O请求都开线程。不仅减少了线程的资源,也提高了线程的利用率。
//IOCP简单流程
//创建完成端口
Port port = createIoCompletionPort(INVALID_HANDLE_VALUE,
0, 0, fixedThreadCount());
//将Socket关联到IOCP
CreateIoCompletionPort((HANDLE )m_sockClient,m_hIocp,
(ULONG_PTR )m_sockClient, 0);
//投递AcceptEx请求
LPFN_ACCEPTEX m_lpfnAcceptEx; // AcceptEx函数指针
GUID GuidAcceptEx = WSAID_ACCEPTEX; // GUID,这个是识别AcceptEx函数必须的
DWORD dwBytes = 0;
WSAIoctl(
m_pListenContext->m_Socket,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidAcceptEx,
sizeof(GuidAcceptEx),
&m_lpfnAcceptEx,
sizeof(m_lpfnAcceptEx),
&dwBytes,
NULL,
NULL);
//使用GetQueuedCompletionStatus()监控完成端口
void *lpContext = NULL;
OVERLAPPED *pOverlapped = NULL;
DWORD dwBytesTransfered = 0;
BOOL bReturn = GetQueuedCompletionStatus(
pIOCPModel->m_hIOCompletionPort,
&dwBytesTransfered,
(LPDWORD)&lpContext,
&pOverlapped,
INFINITE );
//收到通知
int nBytesRecv = WSARecv(pIoContext->m_Socket, pIoContext ->p_wbuf,
1, &dwBytes, 0, pIoContext->p_ol, NULL);
线程的使用
在以上I/O复用模型的讨论中,其实都含有线程的使用。重叠I/O和I/O完成端口都是利用了线程。这也可以看出在高并发服务器的开发中,采用线程也是十分必要的。在I/O完成端口的使用中,还会使用到线程池,这也是现在应用十分广泛的。通过线程池,可以降低频繁创建线程带来的开销。
在Windows下一般使用windows提供I/O模型就足够应付很多场景。但是,在linux下I/O模型都是和线程不相关的。有时为了更高的性能,也会采取线程池和I/O复用模型结合使用。比如许多Linux服务端程序就采用epoll和线程池结合的形式,当然引入线程也带来了更多的复杂度,需要注意线程的控制和性能开销(线程的主要开销在线程的切换上)。而epoll本来也足够优秀,所以仅用epoll也是可以的,像libevent这种著名的网络库也是采用epoll实现的。当然,在linux下也有只使用多进程或多线程来达到并发的。这样会带来一定缺点,程序需要维护大量的Scoket。在服务端开发中使用线程,也要劲量保证无锁,锁也是很高的开销的。
Socket编程模式理解与对比的更多相关文章
- Socket编程模式
Socket编程模式 本文主要分析了几种Socket编程的模式.主要包括基本的阻塞Socket.非阻塞Socket.I/O多路复用.其中,阻塞和非阻塞是相对于套接字来说的,而其他的模式本质上来说是基于 ...
- Java 中的 IO 与 socket 编程 [ 复习 ]
一.Unix IO 与 IPC Unix IO:Open-Read or Write-Close IPC:open socket - receive and send to socket - clos ...
- Python Socket 编程——聊天室示例程序
上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和客户端的代码了解基本的 Python Socket 编程模型.本文再通过一个例子来加强一下对 Socket 编程的 ...
- Python Socket 编程——聊天室演示样例程序
上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和client的代码了解主要的 Python Socket 编程模型.本文再通过一个样例来加强一下对 Socket ...
- 从TCP到Socket,彻底理解网络编程是怎么回事
进行程序开发的同学,无论Web前端开发.Web后端开发,还是搜索引擎和大数据,几乎所有的开发领域都会涉及到网络编程.比如我们进行Web服务端开发,除了Web协议本身依赖网络外,通常还需要连接数据库,而 ...
- 简单理解php的socket编程
php的socket编程算是比较难以理解的东西吧,不过,我们只要理解socket几个函数之间的关系,以及它们所扮演的角色,那么理解起来应该不是很难了,在笔者看来,socket编程,其实就是建立一个网络 ...
- 简单理解php的socket编程【网摘】
php的socket编程算是比较难以理解的东西吧,不过,我们只要理解socket几个函数之间的关系,以及它们所扮演的角色,那么理解起来应该不是很难了,在笔者看来,socket编程,其实就是建立一个网络 ...
- Day10 Python网络编程 Socket编程
一.客户端/服务器架构 1.C/S架构,包括: 1.硬件C/S架构(打印机) 2.软件C/S架构(web服务)[QQ,SSH,MySQL,FTP] 2.C/S架构与socket的关系: 我们学习soc ...
- linux网络编程之socket编程(八)
学习socket编程继续,今天要学习的内容如下: 先来简单介绍一下这五种模型分别是哪些,偏理论,有个大致的印象就成,做个对比,因为最终只会研究一个I/O模型,也是经常会用到的, 阻塞I/O: 先用一个 ...
随机推荐
- android studio使用的各种问题
1.添加权限,没有图形界面.添加权限的位置在<application>节点外,如果在节点内添加会报错的 2.查看logcat:要查看logcat的内容,要点一下设备 3.显示行号:在set ...
- [LeetCode]题解(python):020-Valid Parentheses
题目来源: https://leetcode.com/problems/valid-parentheses/ 题意分析: 这道题输入一段只包括括号的字符串,判断这个字符串是否已经配对.配对的规则是,每 ...
- 我的Python成长之路---第六天---Python基础(20)---2016年2月20日(晴)
一.面向对象基础 面向对象名词解释: 类(Class): 用来描述具有相同的属性和方法的对象的集合.它定义了该集合中每个对象所共有的属性和方法.对象是类的实例. 类变量:类变量在整个实例化的对象中是公 ...
- clear伪类使用
都知道float会脱离文档流 用什么办法撑开父元素呢? 手动在本区块的所有float元素之后加上一个块元素并对其添加clear:both 可以 但是这样还要再去修改html页面 而且多了一个仅仅是 ...
- JS实现常用的分享到按钮
我们阅读博客的时候经常会用到这样功能,当然有时候也会想把自己的网站上也加入类似的分享功能,各大厂商已经给出了相应的API,点击一个按钮即可弹出窗口进入分享,我们事先可以设置一些参数,一般常用的就是 网 ...
- Vim中如何全选并复制?
全部删除:按esc后,然后dG全部复制:按esc后,然后ggyG 全选高亮显示:按esc后,然后ggvG(这个好像有点问题)或者ggVG正确 vim如何与剪贴板交互(将vim的内容复制出来) 习惯了在 ...
- 搜狐畅游:每月给员工直系长辈2000元爱孝薪_企业新闻_265G产业频道
搜狐畅游:每月给员工直系长辈2000元爱孝薪_企业新闻_265G产业频道 搜狐畅游:每月给员工直系长辈2000元爱孝薪
- Apache 2.2 到 2.4的不同
1.权限设定方式变更 2.2使用Order Deny / Allow的方式,2.4改用Require apache2.2: Order deny,allowDeny from allapache2.4 ...
- [Android学习笔记5]四大应用组件之一:Service 下
绑定方式的Service使用 在实现绑定服务时,最重要的是定义onBind()回调方法返回的接口,有三种方式: 1. 继承Binder类 2. 使用Messenger 3. 使用AIDL 这里对1,2 ...
- ArcGIS Engine 改变线IPolyline的方向
有时候需要改变一下线的方向来组成要要的图形,可以按一下方法来变换一下. 如果看官有好的方法的话,请不吝贴上为谢! private IPolyline ChangeDirection(IPolyline ...