一、预备知识
1.计算机网络的7层模型:
网络的7层模型如上图(左边部分)所示,其中应用层,表示层,会话层现在统称为应用层,同时我们的开发就是建立在应用层之上的,而传输层及其以下的层都是比较底层的。
数据从应用层到达物理层转换为电信号的过程叫做封包,从物理层到应用层的过程叫做解包。
在传输层有两种协议:1.TCP协议 2.UDP协议
1.1 TCP协议:TCP协议是面向连接的协议,意思就是说接收数据和传输数据的两方在通信的过程中始终保持连接。TCP在建立连接的时候会有三次握手: 而在断开连接的时候则有4次握手:
通俗点理解就是 1.客户端:"服务器在吗?" 通俗的理解就是:1.客户端:"服务器我好了",
2.服务器:"在呀" 2.服务器:"不需要数据了吗?"
3.客户端:"给我传点数据吧" 3.服务器:"不需要数据我要断开连接了"
然后就建立了TCP连接。 4.客户端:"好的,可以断开了"
PS:以上仅供理解,官方的概念解释请移步百度百科。
由建立连接的三次握手和断开连接的四次握手,可以看出TCP是正儿八经的面向连接的,必须建立连接的双方都需要连接的或者都不需要连接的情况下才会建立或者断开连接。
1.2 UDP协议,UDP协议是面向无连接的协议,就是说一方向另一方发送数据时,不会确认对方是否收到,只是专注于发送数据,有点类似于古时候的飞鸽传书。而这种连接在网络流媒体和游戏中使用得较多。
二、socket
1.什么是socket
socket的官方阐述为套接字,相信大家都和我一样,看到这三个字都是懵逼的。而socket的中文翻译则是插座,socket是网络7层模型中的应用层和传输层之间的桥梁。我们可以就把它理解为插座,那么插座有两头的也有三头的,那socket是几头的呢?可以比较负责任地说 socket是两头的插座,这两头分别代表 IP地址和端口号。IP地址用来确定是哪一台计算机,端口号确定这台计算上提供的什么服务,这样就可以将这个插座插入到对应的插孔从而获得相应的服务(数据)。
2.使用socket进行客户端和服务器端的通信
由于scoket使用C语言来实现的(没有谈及第三方?),所以具有跨平台的特性,各个语言平台上scoket的实现通信的方式大同小异,主要过程如下如下图所示:
2.1 服务端scoket
在OC中使用scoket需要导入一下下面的头文件们:
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <sys/types.h>
然后在viewDidAppear中操作
1、创建scoket: socket()
viewDidAppear中
int serverScket = socket(PF_INET, SOCK_STREAM, 0);
if(serverScket == -1){
NSLog(@"创建socket失败");
}else{
NSLog(@"创建socket成功");
}
创建scoket我们使用 socket(int domain,int type,int protocal); domain表示域,一般我们传递两种:PF_INET , PF_INET6 前者表示IPv4,后者表示IPv6(PS:IPv6协议可以为地球上的每一颗砂砾绑定一个IP地址);第二个参数表示连接的类型是TCP还是UDP。SOCK_STREAM表示TCP,SOCK_DGRAM表示UDP。第三个参数表示协议,我们传0表示根据第二个参数来选择协议。socket()函数的返回值为一个状态码,表示socket是否创建成功。
2、绑定端口和IP地址: bind()
struct sockaddr_in addr;
addr.sin_family = AF_INET;//ipv4
addr.sin_addr.s_addr = htonl(INADDR_ANY);//绑定ip地址为任意
addr.sin_port = htons(1234);//绑定端口号
int result = bind(serverScket,(const struct sockaddr *)&addr, sizeof(addr));
if(result == -1){
NSLog(@"绑定出错");
}else{
NSLog(@"绑定成功");
}
我们使用 bind(int ,const sturct sockeaddr *,int),来将scoket绑定到对应的IP地址和端口号上面,方便提供对应的服务。第一个参数表示我们上面的scoket的返回的int值,第二个参数是一个 结构体指针,并且类型是 sockaddr ,而IP地址和端口号就是封装在其中,所以我们需要先创建一个此类型的结构体,我们这里创建的是 sockaddr_in 类型的 addr,我们首先设置它的 sin_family属性,这个属性表示是IPv4,还是IPv6,第二个设置的属性是 sin_addr下的s_addr,这个属性表示的是IP地址,类型是in_addr_t 类型的,所以我们使用一个htonl()函数来转化,由于是服务器,所以绑定IP地址为任意,接下来绑定的端口号,使用 htons()来绑定端口号。
这个函数也会返回一个int值表示绑定是否成功。
3、监听 : linsten()
result = listen(serverScket, 5);
if(result == -1){
NSLog(@"监听失败");
}else{
NSLog(@"开始监听");
}
使用listen()函数来进行监听,它接受两个参数,第一个参数表示我们创建的socket,第二个参数表示这个服务器的最大承载量。
它的返回值同样也描述状态。
4、接收数据 : accept()
struct sockaddr_in c_addr;//用来保存客户端的 addr(ip地址,端口号等)
socklen_t len;
char buff[1024];
while (1) {
//表示另一个客户端的socket
int newScoket = accept(serverScket, (struct sockaddr *)&c_addr, &len);
if(newScoket == -1){
NSLog(@"接受数据失败");
}else{
//打印出客户端的IP地址
NSLog(@"开始进行会话:%d",c_addr.sin_addr.s_addr);
//接收数据
size_t length = recv(newScoket, buff, 1024, 0);
NSLog(@"接收了%zu个字节:%s",length,buff);
//发送数据
char * msg = "你好";
ssize_t leng = send(newScoket,msg,strlen(msg),0);
NSLog(@"成功发送给客户端%zu字节:",leng);
}
}
我们使用accept()来接收监听到客户端的请求后发来的数据,这个函数有三个参数,第一个参数表示我们服务端的socket,第二个参数又是一个 struct sockaddr * 类型的变量,我们在外面创建一个 struct sockaddr_in 类型的变量 c_addr ,将这个变量的地址传递给accept函数,accept()就会给这个参数注入客户端的 addr(ip地址,端口号),第三个参数表示注入的客户端的 addr的长度,这个参数的意义就是便于内部实现寻址。这个函数返回状态,我们根据这个状态来判断接收数据是否成功,如果成功,我们就可以接收数据和发送数据了
通过 recv()函数来接收数据,接收4个参数,客户端的scoket,接收的数据放在哪里(buff),buff的长度,最后一个参数默认放0。这个函数返回接收数据的长度。
通过send()函数来发送数据,接受4个参数,客户端的scoket,发送的数据,发送数据的长度,最后一个参数默认放0.这个函数返回发送数据的长度。
下一片文章将介绍客户端的scoket....