iOS从零开始学习socket编程——HTTP1.0服务器端

在前一篇文章《iOS从零开始学习socket编程——HTTP1.0客户端》中已经简单的介绍过了Socket编程和一些基本原理。并且实现了简单的iOS客户端(原文地址:http://blog.csdn.net/abc649395594/article/details/45081567

这里再简单介绍一下如何使用OC搭建socket的服务器端。虽然这并不是一个好的解决方案,通常我们使用Java或者PHP抑或NodeJS来搭建服务器后台。但是还是有必要了解一下OC的做法,顺便加深对Socket编程的理解。

废话不多说,还是导入AsyncSocket.h和AsyncSocket.m文件。这里我们创建的是一个桌面端的软件(iOS端也可。),创建了一个AppController类。

下面直接上代码,有点长,会慢慢解释。原来的程序是有图形界面的,不过为了更好地解释socket的工作原理,这里就把不必要的

//AppController.h
#import <Cocoa/Cocoa.h>
#import "AsyncSocket.h"
@interface AppController : NSObject<AsyncSocketDelegate>
{
    AsyncSocket *listenSocket;
    NSMutableArray *connectedSockets;

    BOOL isRunning;
    IBOutlet id logView;
    IBOutlet id portField;
    IBOutlet id startStopButton;
}

@property (atomic,strong) NSString *fileName;
@property (atomic,strong) NSMutableData *receiveData;
- (IBAction)startStop:(id)sender;
@end

AppController.h文件非常简单,有一个AsyncSocket类的socket对象,还有一个用来存放已连接的socket的数组。isRunning表示是否在运行,三个IBOutlet是界面需要的。fileName用于读取http请求的文件名,还有一个动作事件,在启动/结束键被按下时触发。

#import "AppController.h"
#import "AsyncSocket.h"
#import <AppKit/AppKit.h>

#define WELCOME_MSG  0
#define ECHO_MSG     1
#define WARNING_MSG  2

#define READ_TIMEOUT 15.0
#define READ_TIMEOUT_EXTENSION 10.0

#define FORMAT(format, ...) [NSString stringWithFormat:(format), ##__VA_ARGS__]

@interface AppController (PrivateAPI)
@end

@implementation AppController
@synthesize fileName;
- (id)init
{
    if((self = [super init]))
    {
        listenSocket = [[AsyncSocket alloc] initWithDelegate:self];
        connectedSockets = [[NSMutableArray alloc] initWithCapacity:1];

        isRunning = NO;
    }
    return self;
}

- (void)awakeFromNib
{
    [logView setString:@""];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    [listenSocket setRunLoopModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}

- (IBAction)startStop:(id)sender
{
    if(!isRunning)
    {
        int port = [portField intValue];
        if(port < 0 || port > 65535)
        {
            port = 0;
        }

        NSError *error = nil;
        if(![listenSocket acceptOnPort:port error:&error])
        {
            return;
        }
        isRunning = YES;

        [portField setEnabled:NO];
        [startStopButton setTitle:@"Stop"];
    }
    else
    {
        // Stop accepting connections
        [listenSocket disconnect];

        // Stop any client connections
        NSUInteger i;
        for(i = 0; i < [connectedSockets count]; i++)
        {
            [[connectedSockets objectAtIndex:i] disconnect];
        }
        isRunning = false;
        [portField setEnabled:YES];
        [startStopButton setTitle:@"Start"];
    }
}

- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket
{
    [connectedSockets addObject:newSocket];
}

- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
    [sock readDataWithTimeout:-1 tag:0];//very important
    NSString *welcomeMsg = @"Welcome to the AsyncSocket Echo Server\r\n";
    NSData *welcomeData = [welcomeMsg dataUsingEncoding:NSUTF8StringEncoding];
    [sock readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0];
}

- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
{
    if(tag == ECHO_MSG)
    {
        [sock readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0];
    }
}

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    NSString *msg = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding];
    NSRange endRange = [msg rangeOfString:@"HTTP"];
    NSRange beginRange = [msg rangeOfString:@"/"];

    if (beginRange.location != NSNotFound && endRange.location != NSNotFound) {
        NSRange fileRange = NSMakeRange(beginRange.location, endRange.location - beginRange.location-1);
        fileName = [msg substringWithRange:fileRange];
        if(fileName && ![fileName isEqualToString:@""]){
            NSData *returnData = [self getResponseHeader:fileName];
            [sock writeData:returnData withTimeout:-1 tag:ECHO_MSG];
            fileName = nil;
            [sock disconnectAfterWriting];
        }
        else{
            [sock writeData:data withTimeout:-1 tag:ECHO_MSG];
            fileName = nil;
            [sock disconnectAfterWriting];
        }
    }
    else{
        NSLog(@"Header not found");
        dispatch_async(dispatch_get_main_queue(), ^{
            [sock writeData:data withTimeout:-1 tag:ECHO_MSG];
            fileName = nil;
            [sock disconnectAfterWriting];
        });
    }
}

/**
 * This method is called if a read has timed out.
 * It allows us to optionally extend the timeout.
 * We use this method to issue a warning to the user prior to disconnecting them.
**/
- (NSTimeInterval)onSocket:(AsyncSocket *)sock
  shouldTimeoutReadWithTag:(long)tag
                   elapsed:(NSTimeInterval)elapsed
                 bytesDone:(NSUInteger)length
{
    if(elapsed <= READ_TIMEOUT)
    {
        NSString *warningMsg = @"Are you still there?\r\n";
        NSData *warningData = [warningMsg dataUsingEncoding:NSUTF8StringEncoding];

        [sock writeData:warningData withTimeout:-1 tag:WARNING_MSG];

        return READ_TIMEOUT_EXTENSION;
    }

    return 0.0;
}

- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err
{
    [self logInfo:FORMAT(@"Client Disconnected: %@:%hu", [sock connectedHost], [sock connectedPort])];
}

- (void)onSocketDidDisconnect:(AsyncSocket *)sock
{
    [connectedSockets removeObject:sock];
}

- (NSData *)getResponseHeader:(NSString *)localFileName{
    //获取Response Header
    NSMutableString *header = [[NSMutableString alloc]init];
    NSMutableData *returnData = [[NSMutableData alloc]init];

    NSData *fileContent = [NSData dataWithContentsOfFile:localFileName];
    int contentLength = (int)[fileContent length];

    [header appendString:@"HTTP/1.0 200 OK\r\n"];
    [header appendString:@"Connection: close\r\n"];
    [header appendString:@"Server: Apache/1.3.0(Unix)\r\n"];
    NSString *contentLengthString = [NSString stringWithFormat:@"Content-Length:%d\r\n",contentLength];
    [header appendString:contentLengthString];

    NSString *mimeString = [NSString stringWithFormat:@"Content-Type: %@\r\n",[self getMimeByFileName:localFileName]];
    [header appendString:mimeString];
    [header appendString:@"\r\n"];
    NSData *headerData = [header dataUsingEncoding:NSUTF8StringEncoding];

    NSLog(@"header = %@",header);
    [returnData appendData:headerData];
    [returnData appendData:fileContent];
    //NSLog(@"return data = %@",returnData);
    return (NSData *)returnData;
}

- (NSString *)getMimeByFileName:(NSString *)localFileName{
    //通过文件名判断Response Header的MIME类型
    NSRange htmlRange = [localFileName rangeOfString:@"html"];
    NSRange htmRange = [localFileName rangeOfString:@"htm"];
    NSRange pngRange = [localFileName rangeOfString:@"png"];
    if (htmlRange.location != NSNotFound || htmRange.location != NSNotFound) {
        NSString *mime = [[NSString alloc]initWithFormat:@"text/html"];
        return mime;
    }
    else if(pngRange.location != NSNotFound){
        NSString *mime = [[NSString alloc]initWithFormat:@"image/png"];
        return mime;
    }
    NSString *mime = [[NSString alloc]initWithFormat:@"unknown"];
    return mime;
}

@end

因为Storyboard文件无法上传,所以以上代码只能显示Socket服务器的工作流程而不能直接复制运行。需要自己写出图形界面或者绑定端口号。

关于didReadData方法使用时遇到的问题和解决方法以及详细分析,参见另一篇文章——《AsyncSocket didReadData函数详解》

地址:http://blog.csdn.net/abc649395594/article/details/45046871

时间: 2024-12-17 10:20:27

iOS从零开始学习socket编程——HTTP1.0服务器端的相关文章

iOS从零开始学习socket编程——HTTP1.0客户端

在开始socket编程之前,首先需要明确几个概念: 1.网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. 2.socket中文名为"套接字",是基于TCP/IP协议通信机制. 3.客户端的socket连接需要指定主机的ip地址和端口,ip地址类似于家庭地址,用于唯一确认一台主机,而端口类似于门牌号,用于唯一确认主机上的某一个程序. 我们模拟一次HTTP的请求.首先在终端中输入 telnet 202.118.1.7 80 我们会得到这样的提示 T

iOS从零开始学习socket编程——高并发多线程服务器

在上一篇文章<iOS从零开始学习socket编程--HTTP1.0服务器端>中我们已经简单的接触了OC搭建的HTTP服务器. (地址http://blog.csdn.net/abc649395594/article/details/45131373) 出于用户体验和鲁棒性考虑,这里把这个HTTP服务器改进成多线程的. 首先,AnsycSocket这个类是基于OC的Runloop实现的,Runloop实现了方法的异步调用但并不支持多线程. 在这里首先简单区分一下多线程和方法异步调用的区别.他们都

[转] 3个学习Socket编程的简单例子:TCP Server/Client, Select

以前都是采用ACE的编写网络应用,最近由于工作需要,需要直接只用socket接口编写CS的代码,重新学习这方面的知识,给出自己所用到的3个简单例子,都是拷贝别人的程序.如果你能完全理解这3个例子,估计socket编程就已经基本入门了. 建议:1) 多多查查所用到的网络接口; 2) 最好有一本书,如UNIX环境高级编程,UNIX网络编程,可查询:3) 可以直接使用书上的例子更好. http://blog.csdn.net/zhenjing/article/details/4770490 TCP C

Python学习 : Socket编程

Socket socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用[打开][读写][关闭]模式来操作.socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO.打开.关闭) socket和file的区别: file模块是针对某个指定

Windows Socket 编程:TCP服务器端

#include <stdio.h> #include <stdlib.h> #include <winsock2.h> int main(int argc,char* argv[]) { int szClntAddr; char message[] = "hello szt!~\n"; WSADATA wsaData; SOCKET hServSock,hClntSock; SOCKADDR_IN servAddr,clntAddr; WSASta

socket编程-客户端向服务器端发送某目录下的所有文件名

服务器端: import socket if __name__ == '__main__': server = socket.socket() server.bind(('127.0.0.1',9999)) server.listen() s1, raddr = server.accept() while True: data = s1.recv(1024) strdata = data.decode() if strdata == 'end': break print(strdata) #my

Android必备:Android Socket编程的了解与学习整理

看这里:Android必备:Android Socket编程的了解与学习整理 最近学习Android的过程中,由于项目.业务等因素影响,服务端通过Socket进行通信,于是开始学习Socket编程,之前的开发中,很少涉及此 方面的知识学习,本篇就来简单的整理一下,通过Android客户端进行Socket登录的demo,来进行Adnroid Socket编程的学习. 在开始学习之前,先来了解一下Socket,以下内容来自百度百科: 通常也称作"套接字",用于描述IP地址和端口,是一个通信

c++中Socket编程(入门)

在学习Socket编程时,在其他地方看到了不错Socket入门文档,通俗易懂: 介绍 Socket编程让你沮丧吗?从man pages中很难得到有用的信息吗?你想跟上时代去编Internet相关的程序,但是为你在调用 connect() 前的bind() 的结构而不知所措?等等- 好在我已经将这些事完成了,我将和所有人共享我的知识了.如果你了解 C 语言并想穿过网络编程的沼泽,那么你来对地方了. 读者对象 这个文档是一个指南,而不是参考书.如果你刚开始 socket 编程并想找一本入门书,那么你

Java网络编程基础(二)-- 基于TCP/IP的Socket编程

本节讲点: 客户端套接字:Socket:Socket的创建和使用方法,以及Socket选项和异常. 服务端套接字:ServerSocket:SeverSocket的创建和使用方法,以及ServerSocket选项 简单的Client/Server对话程序 支持多客户端的Client/Server服务响应程序 在学习JDK的网络编程之前先要了解一下网络基础知识和网络协议. TCP(传输控制协议)一种基于连接的通信协议.可靠传输 UDP(用户数据包协议)不稳定连接的通信协议 TCP和UDP的端口如下