iOS從零開始學習socket編程——HTTP1.0用戶端

來源:互聯網
上載者:User

標籤:socket編程   ios   http   用戶端   asyncsocke   

在開始socket編程之前,首先需要明確幾個概念:
1.網路上的兩個程式通過一個雙向的通訊串連實現資料的交換,這個串連的一端稱為一個socket。
2.socket中文名為“通訊端”,是基於TCP/IP協議通訊機制。
3.用戶端的socket串連需要指定主機的ip地址和連接埠,ip地址類似於家庭地址,用於唯一確認一台主機,而連接埠類似於門牌號,用於唯一確認主機上的某一個程式。

我們類比一次HTTP的請求。首先在終端中輸入

telnet 202.118.1.7 80

我們會得到這樣的提示

Trying 202.118.1.7...Connected to www.neu.edu.cn.Escape character is ‘^]‘.

這時候表示已經和伺服器建立了socket串連,接下來就是傳遞參數。
輸入:

GET / HTTP/1.0HOST: www.neu.edu.cn

兩次斷行符號後得到這樣的資料

HTTP/1.1 200 OKDate: Thu, 16 Apr 2015 13:58:33 GMTContent-Type: text/htmlContent-Length: 9710Last-Modified: Thu, 16 Apr 2015 08:51:04 GMTConnection: closeVary: Accept-EncodingETag: "552f77f8-25ee"Server: Apache/2.4.12 (FreeBSD)Accept-Ranges: bytes<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml">…………

這表示已經請求成功,其中空白的那一行上面的是請求返回的頭部,瀏覽器需要解析這段資料,對我們有用的是Content-Type欄位和空白行下面的內容。Content-Type欄位告訴我們接下來的資料應該以何種方式被儲存。

HTTP協議是基於TCP/IP協議的,所以顯然可以利用socket類比一次HTTP請求。iOS提供了基於C的socket編程介面CFSocket以及輸入輸出資料流CFReadStreamRef、CFWriteStreamRef,但是其實現方法比較複雜,這裡我們使用著名的AsyncSocket開源架構進行socket編程。使用AsyncSocket並不影響我們對socket的理解,同時也被QQ等知名軟體用來實現即時通訊、檔案傳輸等功能。
AsyncSocket:https://github.com/robbiehanson/CocoaAsyncSocket
下載後把RunLoop檔案夾下的AsyncSocket.h和AsyncSocket.m拷貝到工程檔案中即可使用。
需要說明的是,AsyncSocket基於Runloop,可以非同步呼叫方法,並不需要多開一個線程。

我們建立一個SocketDemoViewController類,因為是簡單的HTTP用戶端,所以所有的操作、視圖將會在這個類中實現。在這個簡單的用戶端中,我們將會類比一個HTTP的訪問,將返回頭部顯示在螢幕上,並且儲存擷取到的圖片和html檔案等。

//SocketDemoViewController.h#import <UIKit/UIKit.h>#import "AsyncSocket.h"#define HOST_IP @"202.118.1.7"#define HOST_PORT 80@interface SocketDemoViewController : UIViewController <UITextViewDelegate>@property (nonatomic, strong) AsyncSocket *client;@property (nonatomic, strong) UITextView *inputMsg;@property (nonatomic, strong) UITextView *outputMsg;@property (nonatomic, strong) NSString *fileName;@property (nonatomic, strong) NSMutableData *receiveData;- (int) connectServer: (NSString *) hostIP port:(int) hostPort;- (void) sendMsg;- (void) reConnect;

HOST_IP和HOST_PORT我選擇了東北大學首頁www.neu.edu.cn。
我們建立了一個AsyncSocket類的對象client,兩個textview用於顯示輸入和輸出內容。connectServer方法與伺服器進行串連,reConnect重新串連,sendMsg方法向伺服器發送資料。

接下來是SocketDemoViewController.m的代碼,有點長,慢慢分析。

#import "SocketDemoViewController.h"@implementation SocketDemoViewController@synthesize inputMsg, outputMsg,fileName,receiveData;@synthesize client;- (void)viewDidLoad {    [super viewDidLoad];    //UI部分的實現    inputMsg = [[UITextView alloc] initWithFrame: CGRectMake( 10.0f,  40.0f,  355.0f,  100.0f)];    inputMsg.delegate = self;    inputMsg.backgroundColor = [UIColor lightGrayColor];    [self.view addSubview: inputMsg];    outputMsg = [[UITextView alloc] initWithFrame: CGRectMake( 10.0f,  180.0f,  355.0f,  150.0f)];    outputMsg.textColor = [UIColor redColor];    outputMsg.backgroundColor = [UIColor lightGrayColor];    [self.view addSubview: outputMsg];    UIButton *btnSend = [UIButton buttonWithType: UIButtonTypeRoundedRect];    btnSend.frame = CGRectMake(150.0f, 350.0f, 75.0f, 30.0f);    [btnSend setTitle: @"發送" forState: UIControlStateNormal];    [btnSend addTarget: self action: @selector(sendMsg) forControlEvents: UIControlEventTouchUpInside];    [self.view addSubview: btnSend];    UIButton *reConnect = [UIButton buttonWithType: UIButtonTypeRoundedRect];    reConnect.frame = CGRectMake(150.0f, 400.0f, 75.0f, 30.0f);    [reConnect setTitle: @"reConnect" forState: UIControlStateNormal];    [reConnect addTarget: self action: @selector(reConnect) forControlEvents: UIControlEventTouchUpInside];    [self.view addSubview: reConnect];    //與伺服器進行串連    [self connectServer:HOST_IP port:HOST_PORT];   }//連結server- (int) connectServer: (NSString *) hostIP port:(int) hostPort{    if (client == nil)    {        client = [[AsyncSocket alloc] initWithDelegate:self];//初始化client,記得設定代理        NSError *err = nil;        if (![client connectToHost:hostIP onPort:hostPort error:&err])        {            //串連失敗            return 2;        }        else        {            NSLog(@"串連成功");            return 1;        }    }    else    {        [client readDataWithTimeout:-1 tag:0];        return 0;    }}- (void) reConnect{    int stat = [self connectServer:HOST_IP port:HOST_PORT];}- (void)sendMsg{    //把inputMsg的內容發送給伺服器    NSString *inputMsgStr = self.inputMsg.text;    NSData *data = [inputMsgStr dataUsingEncoding:NSUTF8StringEncoding];    [client writeData:data withTimeout:-1 tag:0];}#pragma mark -#pragma mark close Keyboard- (void)textFieldDidEndEditing:(UITextField *)textField{    [inputMsg resignFirstResponder];}- (BOOL)textFieldShouldReturn:(UITextField *)textField{    [inputMsg resignFirstResponder];    return  YES;}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{    [inputMsg resignFirstResponder];}#pragma mark socket delegate- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port{    //成功串連到伺服器執行的回呼函數    [client readDataWithTimeout:-1 tag:0];}- (void)onSocketDidDisconnect:(AsyncSocket *)sock{    client = nil;//置空client}- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{    //收到資料的處理    if (receiveData == nil) {        receiveData = [[NSMutableData alloc] init];    }    [receiveData appendData:data];    NSString* aStr = [[NSString alloc] initWithData:receiveData encoding:NSUTF8StringEncoding];    NSRange endRange = [aStr rangeOfString:@"\r"];    if (endRange.location != NSNotFound) {        NSRange range = [aStr rangeOfString:@"\r\n\r\n"];        if (range.location != NSNotFound) {            NSString *requestHeader = [aStr substringToIndex:(unsigned long)range.location];            self.outputMsg.text = requestHeader;        }        else{            self.outputMsg.text = aStr;        }        //擷取真正的請求到的資料        NSData *contentData = [self getContentDataWithData:data];        NSRange htmlRange = [aStr rangeOfString:@"text/html"];        if (htmlRange.location != NSNotFound) {            NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);            NSString *documentsDirectory =[paths objectAtIndex:0];            NSString *ducumentPlistPath = [documentsDirectory stringByAppendingPathComponent:@"get.html"];//plist檔案位置            [[NSFileManager defaultManager] createFileAtPath:ducumentPlistPath contents:contentData attributes:nil];        }        UIImageView *imgv = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 50, 50)];        imgv.image = [UIImage imageWithData:contentData];        if (imgv.image != nil) {            [self.view addSubview:imgv];            UIImageWriteToSavedPhotosAlbum(imgv.image, self, @selector(imageSavedToPhotosAlbum:didFinishSavingWithError:contextInfo:), nil);        }        receiveData = nil;    }    [client readDataWithTimeout:-1 tag:0];}- (NSData *)getContentDataWithData:(NSData *)data{    //將資料區塊和返回頭部區分開來,返回NSData類型的請求到的資料    NSString *toSearch = @"\r\n\r\n";    NSData *target = [toSearch dataUsingEncoding:NSUTF8StringEncoding];    NSRange doubleChangeLine = [data rangeOfData:target options:0 range:NSMakeRange(0, [data length])];    if (doubleChangeLine.location != NSNotFound) {        NSData *content = [data subdataWithRange:NSMakeRange(doubleChangeLine.location + 4, [data length] - doubleChangeLine.location - 4)];        return content;    }    else{        return nil;    }}- (void)imageSavedToPhotosAlbum:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{    //把image儲存到本地相簿中    if (!error) {        NSLog(@"成功儲存到相簿");    }}@end

運行程式後,只要在輸入框中,輸入之前命令列中的那一段代碼(記得要換行,參見中游標的位置),即可類比HTTP請求。

在onSocket:(AsyncSocket )sock didReadData:(NSData )data withTag:(long)tag方法中,我們根據Content-Type欄位來選擇處理資料的方式,並且將HTML檔案儲存在沙箱documents目錄下的get.html方法中。講可能存在的圖片儲存在本地相簿中。
對於詳細的didReadData解析,參見上一篇文章:《AsyncSocket didReadData函數詳解》
http://blog.csdn.net/abc649395594/article/details/45046871

iOS從零開始學習socket編程——HTTP1.0用戶端

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.