【问题标题】:Some basic HTTP protocol questions (programming and theory)一些基本的 HTTP 协议问题(编程和理论)
【发布时间】:2012-11-22 10:20:50
【问题描述】:

在您决定它是一篇 tl:dr(太长,未阅读)帖子之前,请尝试至少阅读一些内容,因为它是一个被分解成很多小块的问题。其中一些你可能会回答并帮助我。

请尽量帮助我。这些类型的问题在互联网上很常见,我想你会帮助我和我之后的更多人。

我目前正在研究 HTTP 服务和协议本身,以便发现它是否对我有用。
我有一些基本问题以及一些代码需要讨论。

首先我想知道通信是如何开始的?我发现客户端发送了一条请求资源的消息(这是正确的吗?)。然后会发生什么?我(作为服务器)必须回复什么?
我是否需要在每次响应后附加回车和换行符?在某处它说甚至需要两个 (\r\n\r\n)。
如何建立异步写入? (我希望这个问题可以理解)我的主要目标是实现客户端和服务器之间的连接,然后实现从服务器到客户端的连续数据流。客户端是否需要回复收到的每条消息?
我希望我能把我的问题说清楚,因为我不是这些方面的专家(但我对此很感兴趣)。

对于我的问题的编程部分。
我已经设法用 C++(服务器端)在 Qt 中编写了一个简单的程序,并在 Objective C(iOS)中编写了一个简单的客户端。客户端连接,我可以读取请求标头。是这样的:

Data available, incoming:  "GET / HTTP/1.1
Host: localhost:9990
Connection: close
User-Agent: CFStream%20test/1.0 CFNetwork/609 Darwin/12.2.0

我应该手动回复此标题吗?如果有,是什么?

客户端代码如下所示(我知道它不是伪代码,但我认为它非常不言自明):

- (void)setupStream
{
    NSURL *url = [NSURL URLWithString:@"http://localhost:9990"];

    CFHTTPMessageRef message = CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (CFURLRef)url, kCFHTTPVersion1_1);

    stream = CFReadStreamCreateForHTTPRequest(NULL, message);
    CFRelease(message);

    if (!CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue))
    {
        NSLog(@"Some error.");
    }

    CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings();
    CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPProxy, proxySettings);
    CFRelease(proxySettings);

    if (!CFReadStreamOpen(stream))
    {

        CFRelease(stream);
        NSLog(@"Error opening stream.");
    }

    CFStreamClientContext context = {0, self, NULL, NULL, NULL};
    CFReadStreamSetClient(stream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred, readStreamCallback, &context);
    CFReadStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    NSLog(@"Done");
}

这是设置流方法。 stream 变量是CFReadStreamRef 类型的类变量。

回调如下所示:

static void readStreamCallback(CFReadStreamRef aStream, CFStreamEventType event, void *client)
{
    ViewController *controller = (ViewController*)client;
    [controller handleEvent:event forStream:aStream];
}

而句柄事件是这样的:

- (void)handleEvent:(CFStreamEventType)event forStream:(CFReadStreamRef)aStream
{
    if (aStream != stream)
    {
        return;
    }

    NSLog(@"Handle event callback");

    switch (event)
    {
        case kCFStreamEventHasBytesAvailable:
            NSLog(@"Work log");
            UInt8 bytes[11];
            CFIndex length;
            length = CFReadStreamRead(stream, bytes, 11); //I know 11 bytes is hard coded, its in testing stage now. Feel free to suggest me how to do it better.

            if (length == -1)
            {
                NSLog(@"Error, data length = -1");
                return;
            }

            NSLog(@"Len: %li, data: %s", length, bytes);
        break;
        default:
            NSLog(@"Other event");
        break;
    }
}

这就是几乎所有值得一提的客户端代码。 Qt Server 部分(我将只发布重要部分)是这样完成的:(这是一个子类 QTcpServer 类)。首先调用startServer();

bool Server::startServer()
{
    if (!this->listen(QHostAddress::Any, 9990))
        return false;

    return true;
}

当有连接传入时,incomingConnection 会以套接字描述符作为参数触发:

void Server::incomingConnection(int handle)
{
    qDebug("New client connected");
    ServerClient *client = new ServerClient(handle, this); //The constructor takes in the socket descriptor needed to set up the socket and the parent (this)
    client->setVectorLocation(clients.count()); //This is a int from a Qvector in which i append the clients, its not important for understanding right now.
    connect(client, SIGNAL(clientDisconnected(int)), this, SLOT(clientDisconnected(int)), Qt::QueuedConnection); //When the client socket emits a disconnected signal the ServerClient class emits a client disconnected signal which the server uses to delete that client from the vector (thats why I use "setVectorLocation(int)") - not important right now
    clients.push_back(client); //And then I append the client to the QVector - not important right now
}

ClientServer 类构造函数只是创建一个新的套接字并连接所需的方法:

ServerClient::ServerClient(int handle, QObject *parent) :
QObject(parent)
{
    socket = new QTcpSocket(this); //Socket is a class variable

    connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
    connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));

    socket->setSocketDescriptor(handle);
}

Ready read 只是将传入的数据写入我(我认为以后不会有太多用户):

void ServerClient::readyRead()
{
    qDebug() << "Data available, incoming: " << socket->readAll();
}

最后是写入数据:

void ServerClient::writeData(QByteArray *data)
{
    data->append("\r\n\r\n"); //I have read this must be appended to all outgoing data from a HTTP server
    socket->write(*data);
    socket->flush();
    qDebug() << "Written data to client: " << *data;
}

但是,此代码并不总是有效。有时,当我写“消息”之类的消息时,客户端会收到所有数据和一些不应该存在的东西(新行和奇怪的符号 - NSLog 会导致这种情况吗?)。有时当我发送"Hellow" 时,客户端只会收到"Hel" 和其他一些时髦的东西。

有什么问题?我应该更注意什么?任何对我有帮助的东西都将不胜感激。并且请不要粘贴一些包含几百页书的链接,我相信只要给我解释一下就可以解决这个问题。

非常感谢!
一月。

【问题讨论】:

  • 太长了,没看过 :) 很抱歉没有把它更容易。我认为这是一个很常见的短语。我会修复它。
  • 使用 qint64 write( const char*, qint64 ) 重载并检查其返回值(写入字节数) - 当缓冲区已满且写入的字节数少于您的请求时,您可能会丢失数据。此外,除非出于教育目的,否则我强烈建议不要实现自己的 HTTP 堆栈,而是使用现有的。
  • 感谢您的建议。会试一试。 “我强烈建议不要实现自己的 HTTP 堆栈,而是使用现有的堆栈”是什么意思?什么是 HTTP 堆栈?我应该使用哪一个?这是出于教育目的,但如果我设法将它放在某个地方,它也可能在外面有用。
  • @Majster - 弗兰克·奥斯特菲尔德(Frank Osterfeld)正试图说与我相同的事情。使用更高级的库而不是直接手动编写低级 HTTP 协议。但是,我会更进一步:我认为您应该使用 C# 或 Java;或 Perl、Python 或 Lua(它们都有很好的 HTTP 库)而不是 Qt。恕我直言...
  • 好吧,我绝对不能说你们是不对的。最终目标是实现将数据流式传输到客户端的 HTTP 服务器。我找不到任何东西可以让我的工作更轻松,所以我决定制作自己的服务器。但是现在这已经成为一个有趣的项目,并且在低级别进行它可以帮助我了解很多关于这些东西的知识......而且我对这些东西非常感兴趣 :) 所以如果你能帮助我完成我的“任务”,那将是一个双赢的局面:)我之后的人可以在一个地方学习同样的东西。

标签: objective-c qt http sockets stream


【解决方案1】:

你问了很多问题......这是完全合法的做法:)

我承认 - 它太长了,我没有阅读:(

但是...

1) 是的,HTTP 协议确实需要 na "CRLF" ("\r\n")。许多服务器和许多客户端都是“宽容的”,但严格来说 - 是的,你需要它们。

参考:RFC 2616

2) 想要了解 HTTP“内部”也是完全合法的 - 我为你鼓掌。

一种好方法是阅读 RFC。

另一个是使用“telnet”客户端:http://blog.tonycode.com/tech-stuff/http-notes/making-http-requests-via-telnet

还有一个是研究FF中的请求和响应Firebug

3) 套接字编程是另一个问题 - 这解释了为什么有时您可能会读到“hello world”,而有时您可能只是读到“hel”。

强烈推荐:Beej's Guide to Network Programming

4) 最后,我实际上不可能用 C++ 在 Qt 中编写服务器(除了可能作为玩具“科学实验”,或者出于一些真正的离奇要求)

我肯定会用 C#(适用于 Windows 服务器)、Java(适用于其他一切)或我觉得很舒服的脚本语言(Perl、Ruby/RoR、Python 和 Lua 都会想到)编写服务器代码。

恕我直言..希望对您有所帮助!

【讨论】:

  • 感谢您的回答。 1. 好吧,现在很清楚了,每次我写东西时都是“CLRF”。 2. 我正在浏览 RFC 页面,但我不了解其中的所有内容,这就是我通过反复试验学习的原因。 (我是一名 19 岁的开发人员)。 3. 套接字编程 - 对于每个连接请求,我都必须创建一个套接字,对吗? 4.好吧,它需要是跨平台的(C#在这里),我没有Java经验(我真的不喜欢它的语法:)),最终目标是为用户实现一个工作程序,所以我猜测脚本落在这里?链接非常感谢,会检查它们。
【解决方案2】:

您的问题几乎相当于“HTTP 是如何工作的”,完整的答案在the specification

【讨论】:

  • 是的,我知道它有很多问题和代码。但我不理解那些 RFC 中的所有内容。所以它们并不是完全有帮助,而且我也并不总是能找到我想要的东西(这个问题也有一些套接字的东西等等)。
  • 你不需要什么都懂。但如果你不知道沟通是如何开始的,那么你明白了吗?再读一遍吧。
猜你喜欢
  • 2015-06-22
  • 1970-01-01
  • 2020-05-20
  • 2016-03-23
  • 2017-06-17
  • 1970-01-01
  • 2018-11-13
  • 2018-02-14
  • 1970-01-01
相关资源
最近更新 更多