iOS 多线程网络

Socket网络编程

2016-05-01  本文已影响154人  6ffd6634d577

TCP/IP

要想理解socket首先得熟悉一下TCP/IP协议族, TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何在它们之间传输的标准.

从字面意思来看TCP/IP是TCP和IP协议的合称,但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于OSI模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中.

应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
传输层:TCP,UDP
网络层:IP,ICMP,OSPF,EIGRP,IGMP
数据链路层:SLIP,CSLIP,PPP,MTU

每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的.

Snip20160501_1.png

socket

我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。


1、IP地址[主机名]

TIP:通过ip138.com可以速查某个域名对应的IP地址。

2、端口号

3、传输协议(通信规则)

主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次对话;主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:“可以,你什么时候发?”,这是第二次对话;主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据。

注意:断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”

TCP和UDP使用场合
tcp一般用于:

文件传输(ftp http对数据准确性要求高,速度可以相对慢)。
发送或接收邮件(pop imap smtp 对数据准确性要求高,非紧急应用)。
远程登录(telnet ss 对数据准确性有一定要求,有连接的概念)。

UDP一般用于

即时通信(qq聊天对数据准确性和丢包要求比较低,但速度必须快)。
在线视频(rtsp速度一定要快,保证视频连续,但是偶尔花了一个图像帧,人们还是能接受的)。
网络语音电话(VoIP语音数据包一般比较小,需要高速发送,偶尔断音或串音也没有问题)。

网络通讯三要素归纳一句话:通过 ip找机器,通过 端口找程序,通过 协议 确定如何传输数据


能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。

Snip20160501_2.png

socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

socket通信流程

socket是"打开—读/写—关闭"模式的实现,以使用TCP协议通讯的socket为例,其交互流程大概是这样子的

Snip20160501_3.png

文字描述:

服务器:
服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
服务器为socket绑定ip地址和端口号
服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开

客户端:
客户端创建socket
客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket

服务器:
服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求

客户端:
客户端连接成功,向服务器发送连接状态信息

服务器:
服务器accept方法返回,连接成功

客户端:
客户端向socket写入信息

服务器:
服务器读取信息

客户端关闭
服务器端关闭

三次握手
在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接

Snip20160501_4.png

第一次握手:客户端尝试连接服务器,向服务器发送syn包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入SYN_SEND状态等待服务器确认

第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手

定睛一看,服务器socket与客户端socket建立连接的部分其实就是大名鼎鼎的三次握手


socket编程API

前面提到socket是"打开—读/写—关闭"模式的实现,简单了解一下socket提供了哪些API供应用程序使用,还是以TCP协议为例,看看Unix下的socket API,Socket 是纯 C语言的,是跨平台的。

访问本地服务器

1、导入三个头文件

    #import <sys/socket.h>
    #import <netinet/in.h>
    #import <arpa/inet.h>

2、创建socket

   /**
     参数
     domain:    协议域/协议族,AF_INET(IPV4的网络开发)
     type:      Socket 类型,SOCK_STREAM(TCP)/SOCK_DGRAM(UDP,报文)
     protocol:  IPPROTO_TCP,协议,如果输入0,可以根据第二个参数自动选择协议

     返回值
     socket,如果>0 就表示成功 
  */

   int clientSocket = socket(AF_INET, SOCK_STREAM,0);

3、连接到服务器

    /**
     参数
      1> 客户端socket
      2> 指向数据结构sockaddr的指针,其中包括目的端口和IP地址。即服务器的“结构体”地址
      3> 结构体数据长度
      返回值
      0 成功/其他 错误代号,非0即真
    */

    struct sockaddr_in serverAddress;
    //协议族   
    serverAddress.sin_family = AF_INET;
    //ip 找机器   
    serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
    //端口号找程序
    serverAddress.sin_port = htons(12345);
    
    // C语言函数如果需要传递结构体的地址,一般都需要将结构体长度传递过去
    //连接服务器
   int result =  connect(clientSocket, (const struct
sockaddr *)&serverAddress, sizeof(serverAddress));
   if (result == 0)
{
      NSLog(@"成功");
   }else {
      NSLog(@"失败");
    }

友情提示
    在C语言开发时,如果要传递结构体的地址,通常会一起传递结构的长度。因为C 语言中取数据是通过指针
寻址的,告诉长度的目的是防止取错数据。
    在终端输入:nc -lk 12345 相当于在本机上启动了一个服务器,ip是本机地址,端口号是12345。

    // 发送数据
    /**
      参数
      1> 客户端socket
      2> 发送内容地址 void * == id
      3> 发送内容长度,是指字节的长度。
      4> 发送方式标志,一般为0
      返回值
          如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
      */
   NSString *msg = @"约吗?";
   ssize_t sendLength = send(clientSocket, msg.UTF8String, strlen(msg.UTF8String), 0);
   NSLog(@"发送了%ld长度的字节,%zd,%zd",sendLength,msg.length,strlen(msg.UTF8String));
    
    // 读取数据
    /**
      参数
      1> 客户端socket
      2> 接受内容地址 void * == id
      3> 接收内容长度,是指字节的长度。告诉服务器一次最多只能接收多少字节的内容。
      4> 接收标志,一般填0,表示阻塞式的,一直等待服务器返回数据
      返回值
          如果成功,则返回接收的字节数,失败则返回SOCKET_ERROR
      */

    // 缓冲区,准备接受来自服务器的数据
    //C语言中,数组的名字,就是指向数组第一个元素的指针。

    unsigned char buffer[1024];
    ssize_t recvLength = recv(clientSocket, buffer, sizeof(buffer), 0);
    // 将buffer转换成二进制数据
    NSData *data = [NSData dataWithBytes:buffer length:recvLength];
    // 将二进制转换字符串
    NSString *resultStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"接收了%ld长度的字节,内容是:%@",recvLength,resultStr);
    // 断开链接
    close(clientSocket);

控制台打印的信息:

Snip20160501_6.png

终端收到发送的内容:

Snip20160501_5.png
Socket发送HTTP请求远程服务器(百度,京东,起点)
#import "ViewController.h"
#import <sys/socket.h>
#import <arpa/inet.h>
#import <netinet/in.h>


@interface ViewController ()
// 客户端socket
@property (nonatomic, assign) int clientSocket;

@property (nonatomic, weak) IBOutlet UIWebView *webView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self connectiontToRemoteServer];
}

- (void)connectiontToRemoteServer{
    NSLog(@"come on");

    // 访问百度网页
    //因为浏览网页服务默认的端口号都是80

    if (![self connectionWithHost:@"106.39.164.186" port:80])return;
    NSLog(@"连接成功");
    
    // 发送请求给服务器
    NSString *request = @"GET / HTTP/1.1\r\n"
    "Connection:Close\r\n"
    "Host:m.jd.com\r\n\r\n";
    // URL:协议头://主机地址/资源路径
    NSString *result = [self sendAndRecv:request];
    NSLog(@"result = %@",result);
    // 查找 \r\n\r\n
    NSRange range = [result rangeOfString:@"\r\n\r\n"]; // 在字符串中查找子串
    NSLog(@"%zd--%zd",range.location,range.length);
    if (range.location != NSNotFound) { // 查找到了 \r\n\r\n
         result = [result substringFromIndex:range.location ];
    }
    [self.webView loadHTMLString:result baseURL:[NSURL URLWithString:@"http://m.jd.com"]];
}

/**
 *  建立Socket连接
 */
- (BOOL)connectionWithHost:(NSString *)host port:(int)port{
    // 创建客户端Socket
    /**
          参数
          domain:    协议域/协议族,AF_INET(IPV4的网络开发)
          type:      Socket 类型,SOCK_STREAM(TCP)/SOCK_DGRAM(UDP,报文)
          protocol:  IPPROTO_TCP,协议,如果输入0,可以根据第二个参数自动选择协议
          
          返回值
          socket,如果>0 就表示成功
       */
    self.clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (self.clientSocket > 0) {
        NSLog(@"创建成功 = %d",self.clientSocket);
    } else {
        NSLog(@"创建失败");
    }
    
    // 建立连接
    /**
     参数
      1> 客户端socket
      2> 指向数据结构sockaddr的指针,其中包括目的端口和IP地址。即服务器的“结构体”地址
      3> 结构体数据长度
      返回值
      0 成功/其他 错误代号,非0即真
    */
    struct sockaddr_in serverAddress;
    // 设置ip--> 通过ip找主机
    // inet_addr内部也会对ip地址进行字节翻转。就是高低互换
    serverAddress.sin_addr.s_addr = inet_addr(host.UTF8String);
    // 设置端口号--> 通过端口找程序
    // htons函数会对传人的整数进行字节翻转。
    serverAddress.sin_port = htons(port);
    // 设置协议 --> 通过协议 确定如何传输数据
    serverAddress.sin_family = AF_INET;
    
    // C语言函数如果需要传递结构体的地址,一般都需要将结构体长度传递过去
    return (connect(self.clientSocket, (const struct sockaddr *)&serverAddress, sizeof(serverAddress)) == 0);
}

/**
 *  发送和接收
 */
- (NSString *)sendAndRecv:(NSString *)msg {
    // 发送数据
    /**
          参数
          1> 客户端socket
          2> 发送内容地址 void * == id
          3> 发送内容长度,是指字节的长度。
          4> 发送方式标志,一般为0
          返回值
              如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
      */
    ssize_t sendLength = send(self.clientSocket, msg.UTF8String, strlen(msg.UTF8String), 0);
    NSLog(@"发送了%ld长度的字节,%zd,%zd",sendLength,msg.length,strlen(msg.UTF8String));
    
    // 读取数据
    /**
          参数
          1> 客户端socket
          2> 接受内容地址 void * == id
          3> 接收内容长度,是指字节的长度。告诉服务器一次最多只能接收多少字节的内容。
          4> 接收方式标志,0 代表阻塞式接收服务响应
          返回值
              如果成功,则返回接收的字节数,失败则返回SOCKET_ERROR
      */
    unsigned char buffer[1024];
    
    NSMutableData *dataM = [NSMutableData data];
    ssize_t recvLength =-1;
    while (recvLength != 0) {
        // 循环接收服务返回的数据
        recvLength = recv(self.clientSocket, buffer, sizeof(buffer), 0);
         NSLog(@"接收了%ld长度的字节",recvLength);
        // 拼接数据
        [dataM appendBytes:buffer length:recvLength];
    }
    // 将二进制转换字符串
    NSString *resultStr = [[NSString alloc] initWithData:dataM encoding:NSUTF8StringEncoding];
   
    return resultStr;
}

/**
 *  断开链接
 */
- (void)disconnection {
    // 断开链接
    close(self.clientSocket);
}
@end

友情提示

上一篇下一篇

猜你喜欢

热点阅读