Windows Sockets Streams






4.33/5 (3投票s)
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::streambuf
和 sock
类。因此,它继承了所有 sock
成员函数(connect
、bind
、recv
、send
等)。它还实现了 std::streambuf
接口所需的 virtual protected
函数(underflow
、overflow
、showmanyc
等)。
如果您只打算使用套接字流,则不必过多关注此类实现的细节。只要知道它维护两个独立的缓冲区——一个用于读取,一个用于写入——缓冲区大小可以更改,甚至可以在需要时由用户指定自己的缓冲区就足够了。
实际的套接字流是 isockstream
、osockstream
和 sockstream
。它们是模板类 generic_sockstream<strm>
的特化,分别以 std:istream
、std::ostream
和 std: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 日:初始版本