代码下载
相关概念
socket是一个针对TCP和UDP编程的接口,你可以借助它建立TCP连接等。socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口。
socket连接:socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;但是由于各种环境因素可能会是连接断开,比如说:服务器端或客户端主机down了,网络故障,或者两者之间长时间没有数据传输,网络防火墙可能会断开该连接以释放网络资源。所以当一个socket连接中没有数据的传输,那么为了维持连接需要发送心跳消息~~具体心跳消息格式是开发者自己定义的。
TCP连接的三次握手:
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”。
socket建立网络连接的步骤
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
1。服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
2。客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3。连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
BSD socket
BSD socket:完全由c语言实现,并且可以在Objective-C代码中使用。
优点:不同平台中易于移植
缺点:
无法访问操作系统内建的网络特性(比如系统范围的VPN)。
更糟糕的是初始化socket连接并不会自动打开设备的Wi-Fi或是蜂窝网络,无线网络会智能的关闭以节省电池电量,任何通信连接都会失败,除非其他网络进程激活了无线网。
CFNetwork对BSD Socket的分装可以激活设备的无线网,所以几乎所有场景都建议使用CFNetwork而不是BSD Socket.
最常用的API:
1.int socket(int, int, int):创建并初始化新的socket,如果成功放回一个文件描述符,如果失败返回0。
2.int bind(int, const struct sockaddr *, socklen_t):为指定的地址与端口号分配socket。
3.int listen(int, int) __DARWIN_ALIAS(listen):用于服务端监听客户端
4.int accept(int, struct sockaddr * __restrict, socklen_t * __restrict):接受连接请求,将客户端地址存储到clientAddress中。
5.int connect(int, const struct sockaddr *, socklen_t):连接到指定的服务器。
6.ssize_t send(int, const void *, size_t, int):在socket上最多发送XX数据。
7.ssize_t recv(int, void *, size_t, int):在socket上最多读取XX数据。
……
服务端逻辑:如果接收到客户端发送过来”1”,那么回复”你猜!”;客户端发送过来”2”,那么回复”谢谢!”;客户端发送过来”3”,那么回复”对不起!”;客户端发送过来”4”,那么回复”好的!”;客户端发送过来其他的内容,那么一半概率原样回复,一半概率回复”不知道你在说什么!”。
创建服务端:
1.创建socket
1
2
3
4
5
6
7
8
9
10
11
12
|
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET; //AF_INET互联网地址簇
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(address);
bzero(&server_addr.sin_zero, 8 );
//创建socket
int server_socket = socket(AF_INET, SOCK_STREAM, 0 ); //SOCK_STREAM 有链接
if (server_socket == - 1 ) {
perror( "socket error!" );
return 1 ;
}
|
2.绑定socket:将创建的socket绑定到本地的IP地址和端口,此socket是半相关的,只是负责侦听客户端的连接请求,并不能用于和客户端通信
1
2
3
4
5
6
|
int bind_result = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (bind_result == - 1 ) {
perror( "bind error!" );
return 1 ;
}
|
3.listen侦听
1
2
3
4
|
if (listen(server_socket, 5 )) {
perror( "listen error!" );
return 1 ;
}
|
4.接受客户端连接
1
2
3
4
5
6
7
8
9
10
11
12
|
int client_socket;
socklen_t address_len;
struct sockaddr_in client_address;
for (; ; ) {
address_len = sizeof(client_address);
client_socket = accept(server_socket, (struct sockaddr*)&client_address, &address_len);
//1.使用多线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
//这个函数进行数据操作
str_echo(client_socket);
});
}
|
5.接收和发送数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
char buf[ 1024 ];
while ( 1 ) {
bzero(buf, 1024 );
long byte_num = recv(socket, buf, 1024 , 0 );
if (byte_num < 0 ) {
return ;
}
buf[byte_num] = '\0' ;
printf( "client said:%s\n" , buf);
char *result;
NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
if ([str isEqualToString:@ "1" ]) {
result = "你猜!" ;
}
else if ([str isEqualToString:@ "2" ])
{
result = "谢谢!" ;
}
else if ([str isEqualToString:@ "3" ])
{
result = "对不起!" ;
}
else if ([str isEqualToString:@ "4" ])
{
result = "好的!" ;
}
else
{
if (arc4random()% 2 == 0 ) {
result = "不知道你在说什么!" ;
}
else
{
result = buf;
}
}
send(socket, result, 1024 , 0 );
}
|
创建客户端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons( 11332 );
server_addr.sin_addr.s_addr = inet_addr( "127.0.0.1" );
bzero(&(server_addr.sin_zero), 8 );
int server_socket = socket(AF_INET, SOCK_STREAM, 0 );
if (server_socket == - 1 ) {
perror( "socket error" );
return 1 ;
}
char receive_msg[ 1024 ];
char reply_msg[ 1024 ];
if (connect(server_socket, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) == 0 ) {
//connect 成功之后,其实系统将你创建的socket绑定到一个系统分配的端口上,且其为全相关,包含服务器端的信息,可以用来和服务器端进行通信。
while ( 1 ) {
bzero(reply_msg, 1024 );
printf( "replay:" );
scanf( "%s" , reply_msg);
if (send(server_socket, reply_msg, 1024 , 0 ) == - 1 ) {
perror( "send error!" );
return 1 ;
}
bzero(receive_msg, 1024 );
long byte_num = recv(server_socket, receive_msg, 1024 , 0 );
receive_msg[byte_num] = '\0' ;
printf( "server said:%s\n" , receive_msg);
}
}
return 0 ;
|
NSStream 读写文件
Cocoa Streams包含三个相关的类: NSStream、NSInputStream 和NSOutputStream。
NSStream:是个抽象类,定义了一些基本属性和方法;
NSInputStream:是NSStream的子类,可通过它从NSData、File和socket中读取数据流;
NSOutputStream:也是NSStream的子类,可通过它将数据流写入NSData、File和socket。
还可以给stream对象设置Delegate(NSStreamDelegate),如果没有精确了给stream指定Delegate,那么默认将Delegate设置为其自己。
NSStreamDelegate只有一个方法
(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
NSStreamEventNone :改默认值不代表任何事件
NSStreamEventOpenCompleted :socket被成功打开
NSStreamEventHasBytesAvailable :有可以读取的字节
NSStreamEventHasSpaceAvailable :缓存中可以写入字节
NSStreamEventErrorOccurred :操作出现错误CFReadStreamCopyError()和CFWriteStreamCopyError()会提供更多错误细节
NSStreamEventEndEncountered :socket达到字节流的末尾
通过NSStream对象读数据,可以分为如下步骤完成:
a) 从数据源创建和初始化一个NSStream对象;
b) 配置run loop,并打开stream对象;
c) 响应NSInputStream事件(NSStreamDelegate);
d) 关闭NSInputStream对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
- ( void )viewDidLoad {
[ super viewDidLoad];
NSString *documentStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *dataPath = [documentStr stringByAppendingPathComponent:@ "data.txt" ];
NSFileManager *manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:dataPath]) {
self.read = YES;
NSLog(@ "string:%@" , [NSString stringWithContentsOfFile:dataPath encoding:NSUTF8StringEncoding error:nil]);
NSInputStream *inStream = [[NSInputStream alloc] initWithFileAtPath:dataPath];
[inStream setDelegate:self];
[inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inStream open];
}
else
{
if ([manager createFileAtPath:dataPath contents:nil attributes:nil]) {
self.read = NO;
NSOutputStream *outStream = [[NSOutputStream alloc] initToFileAtPath:dataPath append:YES];
[outStream setDelegate:self];
[outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outStream open];
}
else
{
NSLog(@ "文件创建失败!" );
}
}
}
#pragma mark - <NSStreamDelegate>代理方法
- ( void )stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
if (self.read) {
NSInputStream *inStream = (NSInputStream *)aStream;
switch (eventCode) {
case NSStreamEventHasBytesAvailable:
{
uint8_t data[ 1024 ];
[inStream read:data maxLength: 1024 ];
printf( "%s" , data);
[inStream close];
[inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
inStream = nil;
}
break ;
case NSStreamEventEndEncountered:
{
[inStream close];
[inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
inStream = nil;
}
break ;
default :
break ;
}
}
else {
NSOutputStream *outStream = (NSOutputStream *)aStream;
switch (eventCode) {
case NSStreamEventHasSpaceAvailable:
{
uint8_t data[] = "{name:'张三', age:10}" ;
[outStream write:data maxLength:strlen(( char *)data)];
[outStream close];
[outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
outStream = nil;
}
break ;
case NSStreamEventEndEncountered:
{
[outStream close];
[outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
outStream = nil;
}
break ;
default :
break ;
}
}
}
|
NSStream Socket
NSStream类不支持在IOS平台上创建socket连接,而CFStream支持在IOS平台的socket行为。所以若知道远程主机的DNS或者是IP地址,可以使用CFStreamCreatePairWithSocketToHost函数来创建socket连接,通过该函数创建了CFStream类型为全双工的socket连接,接着可以利用toll-free bridge,将CFStream对象转换为NSStream对象。
通过NSStream对象进行Socket通信,与通过NSStream进行IO操作的步骤基本一样:
a) 创建NSStream对象,通过CFStreamCreatePairWithSocketToHost函数创建CFReadStreamRef 和CFWriteStreamRef 对象;继而将两者转换为NSInputStream 和NSOutputStream 对象;
b) 配置run loop,并打开NSInputStream 和NSOutputStream对象;
c) 响应事件,在Delegate中响应不同的信号;
d) 关闭NSStream对象。
服务端:服务端依然使用上面例子的服务端
客户端的实现:点击1~5这五个按钮,分别从服务端返回不同的数据,进而展示出来。
由于实现相对简单,就不加阐述说明了,实现代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
- ( void )viewDidLoad {
[ super viewDidLoad];
[self startSocket:@ "127.0.0.1" andPort: 11332 ];
}
- ( void )startSocket:(NSString *)address andPort:( int )port
{
CFReadStreamRef readRef;
CFWriteStreamRef writeRef;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)address, port, &readRef, &writeRef);
NSInputStream *inputStream = (__bridge NSInputStream *)readRef;
NSOutputStream *outputStream = (__bridge NSOutputStream *)writeRef;
inputStream.delegate = self;
outputStream.delegate = self;
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
self.outputStream = outputStream;
}
- (IBAction)sendMsg:(UIButton *)sender {
if (sender.currentTitle > 0 ) {
const char *output = sender.currentTitle.UTF8String;
[self.outputStream write:( const uint8_t *)output maxLength:strlen(output)];
}
}
- ( void )showMessage:(NSString *)msg
{
if (!self.msgLabel) {
UIFont *font = [UIFont systemFontOfSize: 14 ];
UIColor *color = [UIColor blackColor];
UIColor *backColor = [UIColor colorWithRed: 0 green: 0 blue: 0 alpha: 0.2 ];
UILabel *label = [[UILabel alloc] init];
label.textColor = color;
label.font = font;
label.textAlignment = NSTextAlignmentCenter;
label.backgroundColor = backColor;
self.msgLabel = label;
}
CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
CGFloat screenH = [UIScreen mainScreen].bounds.size.height;
CGFloat W = screenW - 16 ;
CGSize size = [msg boundingRectWithSize:CGSizeMake(W, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.msgLabel.font} context:nil].size;
W = size.width;
CGFloat H = size.height;
CGFloat X = (screenW - W)/ 2 ;
CGFloat Y = (screenH - H)/ 2 ;
self.msgLabel.frame = CGRectMake(X, Y, W, H);
self.msgLabel.text = msg;
[self.view addSubview:self.msgLabel];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( 2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.msgLabel removeFromSuperview];
});
}
#pragma mark - <NSStreamDelegate>代理方法
- ( void )stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventNone:
break ;
case NSStreamEventOpenCompleted:
break ;
case NSStreamEventHasBytesAvailable:
{
uint8_t buf[ 1024 ];
NSInteger len = 0 ;
NSInputStream *inputStream = (NSInputStream *)aStream;
len = [inputStream read:buf maxLength: 1024 ];
if (len) {
[self showMessage:[NSString stringWithCString:( const char *)buf encoding:NSUTF8StringEncoding]];
}
break ;
}
case NSStreamEventHasSpaceAvailable:
break ;
case NSStreamEventErrorOccurred:
{
[aStream close];
break ;
}
case NSStreamEventEndEncountered:
{
[aStream close];
[aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
aStream = nil;
break ;
}
default :
break ;
}
|
以上所述是小编给大家介绍的iOS socket网络编程,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大的!
原文链接:http://blog.csdn.net/jiuchabaikaishui/article/details/62425730