今天来讲讲TCP 套接字编程,明天讲讲UDP编程。这方面在企业应用还是很重要的,很多聊天现在都是用套接字,不用XMPP,但是XMPP,还是聊天的主流,有事没事看看RFC3920,功能还是很强大的。
一、Socket 简介
在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
什么是Socket?
上面我们已经知道网络中的进程是通过socket来通信的,那什么是socket呢?socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
socket连接和http连接的区别
网页都是http协议传输到浏览器, 而http是基于socket之上的。socket是一套完成tcp,udp协议的接口。
HTTP协议:简单对象访问协议,对应于应用层 ,HTTP协议是基于TCP连接的
tcp协议: 对应于传输层
ip协议: 对应于网络层 TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。
Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
http连接:http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断掉;
socket连接:socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉
TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。一个TCP连接必须要经过三次“对话”才能建立起来,我们来看看这三次对话的简单过程:1.主机A向主机B发出连接请求数据包;2.主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包;3.主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据。
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去! UDP适用于一次只传送大量数据、对可靠性要求不高的应用环境。
tcp协议和udp协议的差别
是否连接 面向连接 面向非连接
传输可靠性 可靠 不可靠
应用场合 传输少量数据 大量数据
速度 慢 快
socket中TCP的三次握手建立连接详解
我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:
• 客户端向服务器发送一个SYN J
• 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
• 客户端再想服务器发一个确认ACK K+1
只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:
图1、socket中发送的TCP三次握手
从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
socket中TCP的四次握手释放连接详解
上面介绍了socket中TCP的三次握手建立过程,及其涉及的socket函数。现在我们介绍socket中的四次握手释放连接的过程,请看下图:
图2、socket中发送的TCP四次握手
图示过程如下:
• 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
• 另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
• 一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
• 接收到这个FIN的源发送端TCP对它进行确认。
这样每个方向上都有一个FIN和ACK。
TCPSocket的使用
1.TCP客户端的写法
1).创建客户端套接字对象
_clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
建立与服务器的链接
[_clientSocket connectToHost:@"192.168.1.1" onPort:5555 error:nil];
客户端监听
[_clientSocket readDataWithTimeout:-1 tag:100];
2)客户端代理
建立与服务器的连接
- (BOOL)onSocketWillConnect:(AsyncSocket *)sock
连接服务器成功
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
[sock writeData:[@"登录请求" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:100];
向服务器发送数据成功
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
收到来自服务器的消息
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
断开连接
- (void)onSocketDidDisconnect:(AsyncSocket *)sock
2.TCP服务端的写法
1).创建服务端套接字对象
_serverSocket = [[AsyncSocket alloc]initWithDelegate:self];
if ([_serverSocket acceptOnPort:5555 error:nil])
{
NSLog(@"服务器启动了");
}else
{
NSLog(@"服务器启动失败");
}
2).服务端代理
收到了来自客户端的TCP连接请求
- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket
[newSocket writeData:[@"蓝翔技校欢迎你..." dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:100];
//保存客户端
_clientSocket = newSocket;
收到了来自客户端的数据
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
服务端发送消息成功
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
//继续监听客户端数据
[_clientSocket readDataWithTimeout:-1 tag:100];
TCPSocket的使用
1.TCP客户端的写法
1).创建客户端套接字对象
_clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
建立与服务器的链接
[_clientSocket connectToHost:@"192.168.1.1" onPort:5555 error:nil];
客户端监听
[_clientSocket readDataWithTimeout:-1 tag:100];
2)客户端代理
建立与服务器的连接
- (BOOL)onSocketWillConnect:(AsyncSocket *)sock
连接服务器成功
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
[sock writeData:[@"登录请求" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:100];
向服务器发送数据成功
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
收到来自服务器的消息
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
断开连接
- (void)onSocketDidDisconnect:(AsyncSocket *)sock
2.TCP服务端的写法
1).创建服务端套接字对象
_serverSocket = [[AsyncSocket alloc]initWithDelegate:self];
if ([_serverSocket acceptOnPort:5555 error:nil])
{
NSLog(@"服务器启动了");
}else
{
NSLog(@"服务器启动失败");
}
2).服务端代理
收到了来自客户端的TCP连接请求
- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket
[newSocket writeData:[@"蓝翔技校欢迎你..." dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:100];
//保存客户端
_clientSocket = newSocket;
收到了来自客户端的数据
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
服务端发送消息成功
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
//继续监听客户端数据
[_clientSocket readDataWithTimeout:-1 tag:100];
TCPSocket的使用
1.TCP客户端的写法
1).创建客户端套接字对象
_clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
建立与服务器的链接
[_clientSocket connectToHost:@"192.168.1.1" onPort:5555 error:nil];
客户端监听
[_clientSocket readDataWithTimeout:-1 tag:100];
2)客户端代理
建立与服务器的连接
- (BOOL)onSocketWillConnect:(AsyncSocket *)sock
连接服务器成功
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
[sock writeData:[@"登录请求" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:100];
向服务器发送数据成功
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
收到来自服务器的消息
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
断开连接
- (void)onSocketDidDisconnect:(AsyncSocket *)sock
2.TCP服务端的写法
1).创建服务端套接字对象
_serverSocket = [[AsyncSocket alloc]initWithDelegate:self];
if ([_serverSocket acceptOnPort:5555 error:nil])
{
NSLog(@"服务器启动了");
}else
{
NSLog(@"服务器启动失败");
}
2).服务端代理
收到了来自客户端的TCP连接请求
- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket
[newSocket writeData:[@"蓝翔技校欢迎你..." dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:100];
//保存客户端
_clientSocket = newSocket;
收到了来自客户端的数据
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
服务端发送消息成功
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
//继续监听客户端数据
[_clientSocket readDataWithTimeout:-1 tag:100];
AsyncSocket是iOS下专门用于Socket套接字开发的一套开源库,它封装了CFNetwork、BSD Socket,提供了一步的开发模型和简便的开发接口。它支持TCP也支持UDP,支持UDP广播,组播。
AsyncSocket支持GCD/Blocks和Runloop模式
AsyncSocket支持ARC哦。
官方网站:http://code.google.com/p/cocoaasyncsocket
写了一个小的Demo,希望能帮到大家学习套接字编程。
上传到我的资源了,但是链接还没出来,我还是拷贝过来吧,链接出来了,就放上来。
链接出来了:http://download.csdn.net/detail/u010576399/9189401
对了 我用的开源网络库:GCDAsyncSocket
在storyboard加一个navigationController,
源码1.ClientViewController.m(.h啥也没写^_^)
//
// ClientViewController.m
// TCPDemo
//
// Created by JackYang on 15/10/17.
// Copyright © 2015年 JackYang. All rights reserved.
//
#import "ClientViewController.h"
#import "GCDAsyncSocket.h"
@interface ClientViewController ()<GCDAsyncSocketDelegate>
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (weak, nonatomic) IBOutlet UIButton *sendMessage;
@property (weak, nonatomic) IBOutlet UIButton *quitBtn;
@property (nonatomic) GCDAsyncSocket * clickSocket;
@end
@implementation ClientViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self createClickSocket];
}
- (IBAction)quitBtnClick:(id)sender {
NSString * sendText = self.textField.text;
if(sendText.length == 0){
return ;
}
if([self.clickSocket isConnected]){
NSData * data = [sendText dataUsingEncoding:NSUTF8StringEncoding];
[self.clickSocket writeData:data withTimeout:-1 tag:0];
}
}
- (IBAction)sendBtnClick:(id)sender {
[self.clickSocket disconnect];
}
- (void)createClickSocket {
self.clickSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
//连接到服务器
[self.clickSocket connectToHost:@"127.0.0.1" onPort:6789 withTimeout:-1 error:nil];
}
#pragma mark - GCDAsyncSocketDelegate
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
NSLog(@"【客户端】:握手完成,完成连接");
//发送心跳包
}
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
NSLog(@"[客户端]:发送数据完毕");
//等待回执,需要调用readDataWithTimeout
[self.clickSocket readDataWithTimeout:-1 tag:0];
}
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
NSLog(@"【客户端】:已经与服务端断开连接 --%@",err);
//监控网络
}
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
NSString * receiveMessage = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"【客户端】:收到回执---%@",receiveMessage);
}
@end
源码2.ViewController.m(viewcontroller模拟的服务端)
//
// ViewController.m
// TCPDemo
//
// Created by JackYang on 15/10/17.
// Copyright © 2015年 JackYang. All rights reserved.
//
#import "ViewController.h"
#import "ClientViewController.h"
#import "GCDAsyncSocket.h"
@interface ViewController ()<UITableViewDataSource,GCDAsyncSocketDelegate>
@property (nonatomic) UITableView * tableView;
@property (nonatomic) NSMutableArray * dataSource;
@property (nonatomic) GCDAsyncSocket * serverSocket;
//保存接收链接时生成的新的socket
@property(nonatomic)GCDAsyncSocket *acceptNewSocket;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self createTableView];
[self createSocket];
}
- (void)createTableView{
self.dataSource = [NSMutableArray array];
self.tableView = [[UITableView alloc]initWithFrame:self.view.bounds];
self.tableView.dataSource = self;
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellId"];
[self.view addSubview:self.tableView];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"客户端" style:UIBarButtonItemStylePlain target:self action:@selector(client)];
}
- (void)client {
ClientViewController * client = [[ClientViewController alloc]init];
[self.navigationController pushViewController:client animated:YES];
}
- (void)createSocket{
self.serverSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
//监听端口
[self.serverSocket acceptOnPort:6789 error:nil];
}
#pragma mark - TableViewDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.dataSource.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellId" forIndexPath:indexPath];
cell.textLabel.text = self.dataSource[indexPath.row];
return cell;
}
#pragma mark - GCDAsyncSocketDelegate
//当接受到客户端的链接之后调用的代理方法
//newSocket :新生成的socket 用于与客户端进行数据收发
//self.serverSocket 用于监听数据
-(void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{
NSLog(@"[服务端]:接受到客户端连接");
self.acceptNewSocket = newSocket;
//等待数据到来 该方法只等待一次
[self.acceptNewSocket readDataWithTimeout:-1 tag:0];
}
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
NSString * receiveMessage = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
if(receiveMessage){
[self.dataSource addObject:receiveMessage];
[self.tableView reloadData];
//发送确认包,服务器回执
NSString * ackMessage = @"服务端回执";
NSData * writeData = [ackMessage dataUsingEncoding:NSUTF8StringEncoding];
[self.acceptNewSocket writeData:writeData withTimeout:-1 tag:0];
//需要等待数据到来
[self.acceptNewSocket readDataWithTimeout:-1 tag:0];
}
}
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
NSLog(@"【服务端】: 回执发送完毕");
}
@end