zoukankan      html  css  js  c++  java
  • socket编程与线程模型四

     

    3、无连接socket与多线程

    无连接socket很灵活,可以通过同一个socket向很多个地址进行数据写入,从同一个地址进行数据读取。所以这种服务器的组织形式也会很灵活。比如,利用多线程共享同一个服务器端的socket,进行数据读取和写入。

    但是需要注意,socket是特殊的I/O,既然属于I/O,那么线程同步与互斥是非常重要的。因为它们读写socket的顺序将不能被保证,或者无法预料。理论上一个端口号对应于不同的缓冲区,也就是端口号是tcp/ip协议栈上数据缓冲区的句柄。

     

    五、有连接的socket

    1、概述

    有连接的socket,其编程方法与无连接的客户端和服务器端有很大差别。

                                                                                       面向连接的socket
     

    需要说明的是,面向连接的socket变成模型中,服务器端创建一个socket,并把一个地址与这个socket显式绑扎。

                                                                                             
                                                                     面向连接的socket
     

    为了详细的了解面向连接的socket,我们从accept()开始。

    accept()从指定的socket的连接请求等待队列里面取出第一个连接请求,然后它就返回新建的一个socket句柄。这个新建的socket可以完成本次取出的连接请求,并开始为它服务。这个被新建的socket具备和用于监听连接请求的socket一样的属性,包括与之一样的异步选择事件(用 WSAAsyncSelect 或者WSAEventSelect 函数选择的事件)。然后,由新建的socketaccept()本次取出的连接请求服务,而原来的监听请求的socket又可以回到监听状态。

    那么面向连接的socket的通信细节和无连接的有何不一样呢?这个需要研究面向连接的socket使用的数据读取和写入接口和其它接口。

    accept(),接受客户端的连接请求,并生成一个 socket为这个客户服务。accept()的出口参数可以提供客户方的SockAddr,即地址。但是服务方返回一些数据没必要用这个地址,在面向连接的数据写入方法中,只需要一个socket就可以了, send()。而面向连接的数据读取方法recv()也只需要同一个socket就可以完成。所以这个通信过程细节如下:

    服务端创建一个特殊的I/O --- socket,这个socket用来监听客户连接请求。所以,这个socket需要和服务端的本地地址帮扎。从前面知道,bind()地址就是从这个socket上读取数据的地址,不管是显式还是隐式。

    然后服务器调用listensocket, num),num是一个表示连接请求队列的最大值的整数。对于这个socket上并发的连接请求(请求连接绑扎地址),服务器不能马上响应的,就会被缓存字这个队列,等待服务器处理。但是队列满了以后,到来的请求就会不能被响应。listen()是非常关键的一步,只有调用了这一步,服务器才能监听客户端请求。

    listen()以后服务器就调用accept(),提供一个出口参数可以获取请求方的地址。当指定的被acceptsocket上的连接请求队列空,accept()会被阻塞。但是accept之前,服务器一直在listen请求。

    如果这个socket上的连接请求缓存队列有连接请求,那么accept()就会脱离阻塞状态执行。accept()新建一个socket为从队列中取出的当前请求服务,而被acceptsocket,或者也就是被listen()的那个socket()继续返回到listen()状态。
                                                                                             面向连接的通信过程

     

    如上图,服务器端创建socket1套接字,然后必须把该套接字和一个本地地址sockaddr1进行显式绑扎,这样就可以从socket1上读取数据的,或者说别人可以发送数据到这个地址。

    随后,服务器在套接字socket1上调用listen(),进行请求的监听。listen()指定了请求缓冲队列的大小。listen之前的客户端连接请求connect()会失败。

    调用listen()之后,服务器调用accept()从连接请求队列中取出一个连接请求,进行服务。如果队列空,accept被阻塞。accept()从队列中取出一个请求,并创建一个为这个取出的请求服务的套接字newSocket,并从出口参数返回该请求的客户端地址ClientAddr1newSocket具有两个特点,第一个是具备与socket1一样的属性,这就是说newSocket也是绑扎在地址sockaddr1,这也就是从它读取数据的地址。另外一个是newSocketClientAddr1也具备了联系,这个地址是把数据写入newSocket的地方。所以,不需要取出出口参数的ClientAddr1这个客户端地址,仅仅通过新的套接字newSocket服务器端就和某个特定的客户端建立了全相关,就可以读取或者写入数据了。

    再看客户端。客户端必须首先得到服务器的绑扎地址sockaddr1,客户端创建一个套接字socket2以后,就在该套接字上调用connect函数。connet()把一个本地地址ClientAddr1隐式绑扎到套接字socket2,作为数据接收地址。并且,connect把服务器地址sockaddr1socket2联系起来。所以,通过socket2,客户端就可以进行读出和写入数据。
                                                                                                面向连接三
     

    如上图所示,全相关的建立过程。现在我们有个统一的观点,在一个socket上进行bind()一个本地地址(只能是本地地址才能被绑扎),就是本地程序在这个socket上的数据读取地址;但对通信的另一方来说就是数据的写入地址。服务器端为每一个不同的客户端产生一个newSocket进行服务,它们不同的地方就是这些newSocket具有不同的数据写入地址。但是具有一致的绑扎地址,尽管如此,不同的客户端发送的数据不会混淆,看来读取地址与socket句柄有关,所以不同的newSocket虽然具备同一个读取地址,但是会读到各自的数据。

    客户端提前知道服务端地址,这是客户端的写地址。通过connect()请求,connect()隐式给客户端绑扎一个本地地址作为读取地址,并且显示绑扎服务端地址作为发送地址。服务器接受请求,并取得客户端地址,作为写地址。这样一来,双方的socket都具备了读写能力,所以建立了一个数据连接通道。


    刚开始学习使用博客,一不小心在博客园首页上发了好几篇, 第五篇就放到文章里啦:
    socket编程与线程模型五

  • 相关阅读:
    Git 学习小问题记录
    Spring缓存源码剖析:(一)工具选择
    最佳线程数
    Python 装饰器备忘
    使用SCSS扩展Bootstrap4
    Flask 路由相关操作
    Flask开发环境搭建
    Python数据分析开发环境
    Python中的矩阵操作
    Windows 安装 MySQL 8.0.11
  • 原文地址:https://www.cnblogs.com/worldreason/p/1189920.html
Copyright © 2011-2022 走看看