iPhone网络通讯之一 BSD Socket

在进行iPhone网络通讯程序的开发中,不可避免的要利用Socket套接字。iPhone提供了Socket网络编程的接口CFSocket,不过笔者更喜欢使用BSD Socket。

iPhone BSD Socket进 行编程所需要的头文件基本都位于/Xcode3.1.4/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.1.sdk/usr/include/sys下,既然本篇文章作为基础篇, 那么笔者就从最基本的知识讲解开始。

首先,Socket是 进行程序间通讯(IPC, Internet Process Connection)的BSD方 法,这意味着Socket是用来让一个进程和其他的进程互相通讯的,就 像我们用电话来和其他人交流一样。

既然说Socket像 个电话,那么如果要打电话首先就要安装一部电话,“安装电话”这个动作对BSD Socket来 说就是初始化一个Socket,方法如下:

int     socket(int, int, int);

第一个int参 数为Socket的地址方式,既然要“安装电话”,那么就要首先确认所 要安装的电话是音频的还是脉冲的。而如果要给BSD Socket安装 电话,有两种类型可供读者选择:AF_UNIX和AF_INET, 它们代表Socket的地址格式。如果选择AF_UNIX, 意味着需要为Socket提供一个类似Unix路 径的名称,这个选项主要用于本地程序之间的socket通讯;本文主要 讲解网络通讯,所以需要选择参数AF_INET。

第二个int参 数为Socket的类型,“安装电话”需要首先确定是装有线的还是装无 线的,安装Socket也一样,在Socket中 提供了两种类型:SOCK_STREAM和SOCK_DGRAM。SOCK_STREAM表 明数据像字符流一样通过Socket;而SOCK_DGRAM则 表明数据以数据报(Datagrams)的形式通过Socket, 本文主要讲解SOCK_STREAM,因为它的使用更为广泛。

第三个int参 数为所使用的协议,本文里使用0即可。

“安装电话”的代码如下:

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }

到现在为止,怎么安装电话已经清 楚了。因为本文主要演示如何在iPhone上 使用BSD Socket获取内容,更多的功能是“打电话”而不是“接 电话”,所以下面主要讲解BSD Socket扮演“客户端”角色的操 作。

既然要“打电话”,那么首先要有 打电话的对象,更确切的说需要一个“电话号码”,BSD Socket中的“电话号码”就是IP地 址。更糟糕的情况是,如果只知道联系人的名字而不知道电话号码,那么还需要程序查找相应联系人的电 话号码,根据联系人姓名查找电话号码的过程在BSD Socket中叫做DNS解 析,代码如下:

- (NSString*)getIpAddressForHost:(NSString*) theHost
{
    struct hostent *host = gethostbyname([theHost UTF8String]);

    if(!host)
    {
        herror("resolv");
        return NULL;
    }

    struct in_addr **list = (struct in_addr **)host->h_addr_list;
    NSString *addressString = [NSString stringWithCString:inet_ntoa(*list[0])];
    return addressString;
}

hostent是个结构体,使用它需要#import <netdb.h>,通过这个方法得到theHost域 名的第一个有效的IP地址并返回。

正确的“找到电话号码”后,就需 要“拨打电话”了,代码如下:

        their_addr.sin_family = AF_INET;
        their_addr.sin_addr.s_addr = inet_addr([[self getIpAddressForHost:hostName] UTF8String]);
        NSLog(@"getIpAddressForHost :%@",[self getIpAddressForHost:hostName]);

        their_addr.sin_port = htons(80);
        bzero(&(their_addr.sin_zero), 8);

        int conn = connect(sockfd, (struct sockaddr*)&their_addr, sizeof(struct sockaddr));

        NSLog(@"Connect errno is :%d",conn);

笔者最初试图采用NHost进 行主机域名的解析,奈何iPhone的这个类为private的, 在application的开发中不可使用。

如果“电话”能顺利的接通,那么 就可以进行“讲话”了,反之则会断开“电话连接”,如果友好的话,最好能给个提示,诸如“您所拨打的电话不在服务区之类”:)

        if(conn != -1)
        {
            NSLog(@"Then the conn is not -1!");

            NSMutableString* httpContent = [self makeHttpHeader:hostName];

            NSLog(@"httpCotent is :%@",httpContent);

            if(contentSended != nil)
                [httpContent appendFormat:contentSended];

            NSLog(@"Sended content is :%@",httpContent);

            NSData *data = [httpContent dataUsingEncoding:NSISOLatin1StringEncoding];
            ssize_t dataSended = send(sockfd, [data bytes], [data length], 0);

            if(dataSended == [data length])
            {
                NSLog(@"Datas have been sended over!");
            }

            printf("send %d bytes to %s\n",dataSended,inet_ntoa(their_addr.sin_addr));

            NSMutableString* readString = [[NSMutableString alloc] init];
            char readBuffer[512];

            int br = 0;
            while((br = recv(sockfd, readBuffer, sizeof(readBuffer), 0)) < sizeof(readBuffer))
            {
                NSLog(@"read datas length is :%d",br);

                [readString appendFormat:[NSString stringWithCString:readBuffer length:br]];

                NSLog(@"Hava received datas is :%@",readString);
            }

            close(sockfd);
        }else {
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[@"Connection failed to host " stringByAppendingString:hostName] message:@"Please check the hostname in the preferences." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
            [alert show];
            [alert release];
        }

“讲话”通过send(), “听话”通过recv(),这个两个函数的原型如下:

int send(int sockfd, const void *msg, int len, int flags);
int recv(int sockfd,void *buf,int len,unsigned int flags);

可以看出,这两个函数的参数基本 相同。
第一个参数为套接字的句柄。
第二个参数为数据缓冲区。
第三个参数为数据长度。

最后一个参数有点特殊,这个参数 是为了让BSD Socket能支持“带外数 据”,何谓“带外数据”?顾名思义,就是“带内以外的数据”,而带内数据就是常规的按照Socket字 节流顺序进行传递的数据。通常情况下,数据由连接的一端流到接收的一端,并且认为数据的所有字节都 是精确排序的,晚写入的字节绝不会早于先写入的字节到达。但是如果我们“挂断了电话”,而接收方还有大量已经被接收的缓冲数据,这些数据还没被程序读取, 那么接收方需要在读取这些缓冲的“带内数据”之前先读取一个标识取消的请求,这个请求就可以利用带外请求的方法进行传送。请求带外数据传送需要把标识位置 为MSG_OOB,如下:

char buf[64];
int len;    
int s;       

send(s,buf,len,MSG_OOB);


至此,一个完整的“通话过程”已 经结束,最后别忘记调用close(sockfd)“挂 断电 话”。

=============

下面笔者尝试请求www.baidu.com的首页,并把请求的页面内容打印到控制台,所以需要对请求进行封装,以支持HTTP协议。很简单,只需要在请求的内容前面加上相应的HTTP头信息即可,如下:

#define HTTPMETHOD @"GET"
#define HTTPVERSION @"HTTP/1.1"
#define HTTPHOST @"Host"

#define KENTER @"\r\n"
#define KBLANK @" "

- (NSMutableString*) makeHttpHeader:(NSString*) hostName
{
    NSMutableString *header = [[NSMutableString alloc] init];

    [header appendFormat:HTTPMETHOD];
    [header appendFormat:KBLANK];
    [header appendFormat:@"/index.html"];
    [header appendFormat:KBLANK];
    [header appendFormat:HTTPVERSION];
    [header appendFormat:KENTER];

    [header appendFormat:HTTPHOST];
    [header appendFormat:@":"];
    [header appendFormat:hostName];
    [header appendFormat:KENTER];
    [header appendFormat:KENTER];

    return header;
}

在上面的方法中,笔者封装了HTTP头信息,对HTTP不熟悉的同学可以先熟悉熟悉HTTP的格式
  最后发码:
===========================================
//

//  BSDHttpExampleViewController.h

//  BSDHttpExample

//

//  Created by sun dfsun2009 on 09-11-12.

//  Copyright __MyCompanyName__ 2009. All rights reserved.

//



#import <UIKit/UIKit.h>
#define MYPORT 4880
#import <stdio.h>
#import <stdlib.h>
#import <unistd.h>
#import <arpa/inet.h>
#import <sys/types.h>
#import <sys/socket.h>
#import <netdb.h>

@interface BSDHttpExampleViewController : UIViewController {
int sockfd;
struct sockaddr_in their_addr;
}



@end


实现文件如下:

//

//  BSDHttpExampleViewController.m

//  BSDHttpExample

//

//  Created by sun dfsun2009 on 09-11-12.

//  Copyright __MyCompanyName__ 2009. All rights reserved.

//


#import "BSDHttpExampleViewController.h"

@implementation BSDHttpExampleViewController

#define HTTPMETHOD @"GET"

#define HTTPVERSION @"HTTP/1.1"

#define HTTPHOST @"Host"

#define KENTER @"\r\n"

#define KBLANK @" "


/*

// The designated initializer. Override to perform setup that is required before the view is loaded.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {

if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {

// Custom initialization

}

return self;

}

*/



/*

// Implement loadView to create a view hierarchy programmatically, without using a nib.

- (void)loadView {

}

*/



void error_handle(char *errorMsg)

{

fputs(errorMsg, stderr);

fputc('\n',stderr);

exit(1);

}



- (NSMutableString*) makeHttpHeader:(NSString*) hostName

{

NSMutableString *header = [[NSMutableString alloc] init];

[header appendFormat:HTTPMETHOD];

[header appendFormat:KBLANK];

[header appendFormat:@"/index.html"];

[header appendFormat:KBLANK];

[header appendFormat:HTTPVERSION];

[header appendFormat:KENTER];
[header appendFormat:HTTPHOST];
[header appendFormat:@":"];
[header appendFormat:hostName];
[header appendFormat:KENTER];
[header appendFormat:KENTER];

return header;
}


- (NSString*)getIpAddressForHost:(NSString*) theHost

{
struct hostent *host = gethostbyname([theHost UTF8String]);
if(!host)

{
herror("resolv");
return NULL;

}
struct in_addr **list = (struct in_addr **)host->h_addr_list;
NSString *addressString = [NSString stringWithCString:inet_ntoa(*list[0])];
return addressString;
}


- (void)Connect:(NSString *)hostName content:(NSString *)contentSended

{  
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

{
perror("socket");
exit(1);
}


//NSHost *host = [NSHost hostWithName:hostName];

//if(host)

//{

their_addr.sin_family = AF_INET;
//their_addr.sin_addr.s_addr = inet_addr([[host address] UTF8String]);

their_addr.sin_addr.s_addr = inet_addr([[self getIpAddressForHost:hostName] UTF8String]);

NSLog(@"getIpAddressForHost :%@",[self getIpAddressForHost:hostName]);



their_addr.sin_port = htons(80);

bzero(&(their_addr.sin_zero), 8);

int conn = connect(sockfd, (struct sockaddr*)&their_addr, sizeof(struct sockaddr));
NSLog(@"Connect errno is :%d",conn);
if(conn != -1)

{
NSLog(@"Then the conn is not -1!");
NSMutableString* httpContent = [self makeHttpHeader:hostName];

NSLog(@"httpCotent is :%@",httpContent);

if(contentSended != nil)
[httpContent appendFormat:contentSended];
NSLog(@"Sended content is :%@",httpContent);
NSData *data = [httpContent dataUsingEncoding:NSISOLatin1StringEncoding];
ssize_t dataSended = send(sockfd, [data bytes], [data length], 0);
if(dataSended == [data length])

{
NSLog(@"Datas have been sended over!");
}
printf("send %d bytes to %s\n",dataSended,inet_ntoa(their_addr.sin_addr));
NSMutableString* readString = [[NSMutableString alloc] init];
char readBuffer[512];
int br = 0;
while((br = recv(sockfd, readBuffer, sizeof(readBuffer), 0)) < sizeof(readBuffer))

{
NSLog(@"read datas length is :%d",br);
[readString appendFormat:[NSString stringWithCString:readBuffer length:br]];
NSLog(@"Hava received datas is :%@",readString);

}
close(sockfd);
}else {

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[@"Connection failed to host " stringByAppendingString:hostName] message:@"Please check the hostname in the preferences." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];

[alert show];

[alert release];

}

/*

}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[@"Could not look up host " stringByAppendingString:hostName] message:@"Please check the hostname in the preferences." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
[alert release];

}

**/

}

- (void)Send:(id)sender
{
char message[7] = "aaag";
send(sockfd,message,sizeof(message),0);
NSLog(@"%s",message);
}



// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.

- (void)viewDidLoad {
[self Connect:@"www.baidu.com" content:nil];
[super viewDidLoad];
NSLog(@"view has been loaded!");

}



/*

// Override to allow orientations other than the default portrait orientation.

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {

// Return YES for supported orientations

return (interfaceOrientation == UIInterfaceOrientationPortrait);

}

*/


- (void)didReceiveMemoryWarning {

// Releases the view if it doesn't have a superview.

[super didReceiveMemoryWarning];

// Release any cached data, images, etc that aren't in use.
}


- (void)viewDidUnload {

// Release any retained subviews of the main view.

// e.g. self.myOutlet = nil;

}

- (void)dealloc {

[super dealloc];

}

@end
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值