一些基本的HTTP协议问题(编程和理论)

时间:2012-12-04 17:49:02

标签: objective-c qt http sockets stream

在你确定它之前(太长时间,没读过)之前,请尝试阅读至少一些内容,因为它的问题分解成许多小部分。其中一些你可以回答并帮助我。

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

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

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

对于我的问题的编程部分。
我已经设法将一个简单的程序放在Qt的C ++(服务器端)和一个简单的客户端在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);
}

准备好阅读只是写了我传入的数据(我认为以后用户不会太多):

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"和其他一些时髦的东西。

有什么问题?我应该多加注意什么?任何能帮助我的事都将受到青睐。请不要粘贴一些包含几百页书籍的链接,我相信只要向我解释一下就可以解决这个问题。

感谢很多!
一月

2 个答案:

答案 0 :(得分:0)

您的问题非常类似于“HTTP如何工作”,完整的答案在于the specification

答案 1 :(得分:0)

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

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

但是......

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)最后,我真的不能用Q ++在Qt中编写服务器(除了可能作为玩具“科学实验”,或者某些真正的非常规要求)

我肯定会用C#(用于Windows服务器),Java(用于其他所有内容)或者我觉得很熟悉的脚本语言编写服务器代码(Perl,Ruby / RoR,Python和Lua都会浮现在脑海中)。

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