65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Sockets Streams

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (3投票s)

2019 年 12 月 2 日

MIT

4分钟阅读

viewsIcon

10505

downloadIcon

495

Windows 中用于套接字通信的 C++ 流

引言

本文介绍的代码实现了通过套接字进行通信的传统 C++ 流。它基于 socket++,这是一个最初由 Gnanasekaran Swaminathan 开发的库,并带有以下版权声明:

Copyright (C) 1992,1993,1994 Gnanasekaran Swaminathan

在保留上述版权声明和此段落的所有副本的前提下,允许您自行承担风险使用和分发此软件的源代码和二进制形式。本软件按“原样”提供,不提供任何明示或暗示的保证。

然而,经过多年的大规模的修改和升级。

原始的 socket++ 代码仅处理 Unix 套接字,与 Windows 的对应物相比,它们是相当不同的东西。从这个意义上说,我的代码不是可移植的,只能用于 Windows 套接字。

许多函数作为结果返回一个错误代码对象。如果您不知道这些异常和错误代码之间的混合体是什么,请参阅我关于此主题的之前的文章

本文中展示的示例代码使用标准的 TCP 服务 daytime 和 echo。要使它们工作,您必须在“Windows 功能”对话框中启用这些服务。

低级对象

在最低级别,存在套接字和地址的对象封装。

Socket 对象

sock 对象是 Winsock SOCKET 句柄的封装。它可用于流(TCP)或数据报(UDP)套接字。其成员函数封装了大多数 Winsock 函数。列表很长,最好查看代码或 Doxygen 文档。在这里,我想指出一些细节。

为了成为一个表现良好的 C++ 对象,sock 对象需要一个拷贝构造函数和一个赋值运算符。同时,套接字句柄不能使用 DuplicateHandle 函数进行复制。解决方案是使用一个“生命计数器”,该计数器跟踪套接字句柄有多少“生命”。只有当生命计数达到 0 时,句柄才会被关闭(使用 CloseHandle 函数)。此解决方案类似于 shared_ptr 对象使用的解决方案,但在编写此代码的时代,C++ 中还没有 shared_ptr 对象。

如果您发现 sock 对象缺少某个您应用程序中迫切需要的函数的包装器,请不用担心:它有一个 SOCKET 转换运算符,允许您在任何地方使用 SOCKET 的地方使用 sock

Inaddr 对象

inaddr 对象封装了一个 sockaddr_in 结构。请注意,我们的 inaddr 对象仅关注 Internet IPV4 地址。与 sock 对象一样,inaddr 对象具有一个 sockaddr 转换运算符,允许您在任何需要使用 sockaddr 的地方使用它。

inaddr 的成员函数负责主机到网络的转换以及最终的 DNS 查找。

示例

以下是一个 TCP 连接的示例

sock a (SOCK_STREAM);
inaddr him ("google.com", 80);
a.connect (him);

使用 daytime 协议从套接字接收数据

    char buf[256];
    sock a (SOCK_STREAM);
    a.connect (inaddr ("localhost", 13));
    int len = a.recv (buf, sizeof (buf));
    if (len > 0)
    {
      buf[len] = 0;
      printf ("Date is: %s", buf);
    }

使用 echo 协议通过套接字发送(和接收)数据

    const char *fox = "The quick brown fox jumps over the lazy dog\n";
    char buf[256];
    sock a (SOCK_STREAM);
    a.connect (inaddr ("localhost", 7));
    a.send (fox, strlen(fox));
    int len = a.recv (buf, sizeof (buf));
    if (len > 0)
    {
      buf[len] = 0;
      printf ("Echo: %s", buf);
    }

Socket Streams

现在我们已经看到了低级实体,让我们继续讨论套接字流。在这里,主要部分是 sockbuf 对象。它派生自 std::streambufsock 类。因此,它继承了所有 sock 成员函数(connectbindrecvsend 等)。它还实现了 std::streambuf 接口所需的 virtual protected 函数(underflowoverflowshowmanyc 等)。

如果您只打算使用套接字流,则不必过多关注此类实现的细节。只要知道它维护两个独立的缓冲区——一个用于读取,一个用于写入——缓冲区大小可以更改,甚至可以在需要时由用户指定自己的缓冲区就足够了。

实际的套接字流是 isockstreamosockstreamsockstream。它们是模板类 generic_sockstream<strm> 的特化,分别以 std:istreamstd::ostreamstd:iostream 作为模板参数。

示例

让我们使用我们的套接字流重写前面的示例。

daytime 示例可以这样写:

    isockstream is (inaddr ("localhost", 13));
    string s;
    getline (is, s);
    cout << "Date is: " << s << endl;

这里是另一种编写 echo 示例的方法:

    char *fox = "The quick brown fox jumps over the lazy dog";
    sockstream ss (inaddr ("localhost", 7));
    ss << fox << endl;

    char c;
    cout << "Received: ";
    do
    {
      ss >> noskipws >> c;
      cout << c;
    } while (c != '\n');

使用 Socket Streams 进行编程

套接字流就像标准文件流一样工作,您应该很容易使用它们。它们有一个“指针到”运算符

sockbuf* operator ->()

它返回关联的 sockbuf 对象。如果您还记得,sockbuf 派生自 socket,因此这是一种访问所有底层套接字函数的好方法。例如,如果您想关闭与流关联的套接字的一端,您可以这样写:

	sockstream ws;
	....
    ws->shutdown (sock::shut_write);

请记住,输出数据是缓冲的;使用 flush()endl 来真正发送数据。

结束,暂时……

如果您喜欢我的套接字流,请继续关注下一篇文章,我将在其中向您展示如何使用它们构建一个多线程 TCP 服务器和一个小型 HTTP 服务器,这些服务器可以轻松地嵌入到您的程序中。

历史

  • 2019 年 12 月 1 日:初始版本
© . All rights reserved.