vc++一个进程负责写入共享区,然后多个进程读取,会有什么异常吗?
WinSock(Windows Sockets)是处理网络通信的Windows API。许多函数与在BSD中使用的Berkely套接字函数是相同的。套接字,简单的说就是通信双方的一种约定,用套接字中的相关函数来完成通信过程。
应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket)的接口。
Socket可以看成在两个程序进行通讯连接中的一个端点(endpoint),一个程序将一段信息写入Socket中,该Socket将这段信息发送给另外一个Socket中,使这段信息能传送到其他程序中。一般一个server服务器对应很多客户端client连接,服务器必须维护一张客户连接列表,每增加一个客户端连接服务器端都要新建一个套接字负责与新增客户端进行对话通信。
传输套接字主要有两类:流式套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)。流类型的套接字是为需要可靠连接的应用程序设计的。这些程序通常使用连续的数据流。用于这种类型套接字的协议是TCP,适合FTP这类实现。流套接字是最常用的,一些众所周知的协议如HTTP、TCP、SMTP、POP3等都是基于面向流的协议。
数据报套接字使用UDP做为下层协议,是无连接的,有一个最大缓冲区大小(数据包大小的最大值)。它是为那些需要发送小数据包,并且对可靠性要求不高的应用程序设计的。与流式套接字不同,数据报套接字并不保证数据会到达终端,也不保证它是以正确的顺序到来的。数据报套接字的传输效率相当高,它经常用于音频或视频应用程序。对这些程序来说,速度比可靠性更加重要。
一个连接由(server_ip, server_port)和(client_ip, client_port)唯一确定。你可以调用 getsockname() 函数获取与某个套接字关联的本地地址,调用 getpeername() 函数获取与某个套接字关联的目的地址。
套接字有阻塞(锁定)和非阻塞(非锁定)两种模式。非阻塞和阻塞描述的是 WinSock API 调用行为特性。
在一个阻塞套接字上调用任何 WinSock API 函数(accept()/recv()/send()),都会耗费或长或短的时间“等待”返回,这会阻塞调用线程。直到有点数据可读或可写时,这种调用才返回。
阻塞模式的优点是符合时序,容易入手。缺点也是显而易见的,假设这样一种情景,一个线程中要处理多个客户(套接字A、B),我们在套接字A、B上顺序调用recv企图依次接收数据。若客户A始终不发数据,则recv(A)函数将可能永远无法返回,在这期间我们错失了客户B发送来的数据。
大多数WinSock应用都遵循“生产者-消费者”模型,应用程序需要读取(或写入)指定数量的字节,然后再对读取的数据执行一些计算。在应用程序中,可以为每个套接字都创建一个负责读取网络数据的读线程(ReadThread),以及一个负责对数据执行计算的数据处理线程(ProcessThread)。尽管这会增大一些开销,但的确是一种可行的方案。缺点便是扩展性极差,而且无法应对大规模的通信情况。
将一个套接字置为非阻塞模式之后,WinSock API 调用会立即返回。大多数情况下,这些调用都会“失败”(SOCKET_ERROR),并返回一个WSAEWOULDBLOCK错误。它意味着请求的操作在调用期间没有时间完成。举个例子来说,假如在系统的输入缓冲区中,尚不存在“待决”的数据,那么recv()调用就会返回WSAEWOULDBLOCK错误。通常,我们需要重复调用同一个函数(轮询),直至获得一个成功返回代码。
假如需要编写更多的代码,以便在每个 Winsock调用中,对收到一个WSAEWOULDBLOCK错误的可能性加以应付,那么非阻塞套接字便显得有些难于操作。在这些情况下,可考虑使用“套接字I/O模型”,它管理I/O完成通知,帮助应用程序判断套接字何时可供读写。
套接字模式讨论的是套接字函数的调用特性,而I/O模型讨论的是调用背后的行为特性。
当CPU执行代码(当前活动线程)时遇上一个I/O请求(例如调用ReadFile()/WriteFile()或recv()/send())时,系统产生一个中断,当前活动线程阻塞在此,让CPU去完成这个I/O请求,等到完成后,系统再次产生一个中断让原先的程序继续运行。也就说通过中断保持这两者间的同步,可以将中断理解为硬件化的信号量。
这就是所谓的同步I/O,一个线程中只可能同时处理一个I/O请求。因为一个I/O操作是非常耗时的,所以代码挂起后等待I/O完成的这段时间内,这个线程浪费了很多个指令周期。如果要同时反复读写大文件,则同步I/O的效率是很低的。
当然,可以考虑使用多线程来处理。例如在设计服务器时可以使用多线程来处理客户请求,每有一个客户连接请求,就创建一个新线程,专门处理它的通信请求。对于小型服务器来说,这不是问题。对于同时处理成千上万个请求的大型服务器而言,使用多线程是无效的,因为系统能够支持的线程数量毕竟是有限制的。另外一种解决方案就是使用共享负载的线程池,这涉及到异步I/O。
2024-10-28 广告