Socket(套接字)通信笔记

前言:

网络进程间通信在传输层中,基本上都是用的 Socket,Cocoa 中使用 Socket 有三种方式:
Cocoa 层:NSURL
Core Foundation 层:基于 C的 CFNetwork 和 CFNetServices
OS层:基于 C的 BSD socket

解释:
1.Cocoa 层是最上层的基于 Objective-C的 API,比如 URL访问,NSStream,Bonjour,GameKit 等,这是大多数情况下我们常用的 API.Cocoa 层是基于 CoreFoundation 实现的.
2.Core Foundaton 层:因为直接使用 socket 需要更多的编程工作,所以苹果对 OS层的 socket 进行简单的封装以简化编程任务.该层提供了 CFNetwork 和 CFNetServices,其中 CFNetwor 又是基于 CFStream 和 CFSocket.
3.OS层:最底层的 BSD socket 提供了对网路编程最大程度的控制,但是编程工作也是最多的.因此,苹果建议我们使用 Core Foundation 及以上层的 API今次那个编程.
其中 OS 层中提供的 BSD Socket 最为灵活,但是抽象层次比较低,使用最为复杂.

一.什么是 Socket?

Socket 又称”套接字”,是系统提供的用于网络通信的方法.它的是指并不是一种协议,没有规定计算机应当怎么传递信息,只是给程序员提供了一个发送消息的接口,程序员能使用这个接口同的方法,发送与接收消息.Socket 描述了一个 IP,端口对.它简化了程序员的操作,知道对方的 IP以及 port 就可以给对方发送消息,再由服务器处理发送的这些消息,Socket 包含了通信的双发,即客户端与服务端.

我们通过一张图知道 Socket 在哪里
Socket(套接字)通信笔记_第1张图片
二.Socket 的通信过程

每一个应用或者服务,都有一个端口.比如 DNS的53端口(FTP 的21端口),http 的80端口.我们能由 DNS请求到查询信息,是因为DNS服务器时时刻刻都在监听53端口,当收到我们的查询请求以后,就能够返回我们想要的 IP信息.所以,从程序设计上来讲,应该包含以下步骤:
1.服务端利用 Socket 监听端口
2.客户端发起链接
3.服务端返回信息,建立连接,开始通信
4.客户端,服务端断开连接

通过一张图了解下 Socket 的通讯过程

Socket(套接字)通信笔记_第2张图片

三.Socket 的基本操作

Socket 起源于 Unix,而 Unix/Linux 基本哲学之一就是”一切皆文件”,都可以用”open->write/read->close”模式来操作.那么 socket 就提供了这些操作对应的函数接口.

1.Socket()函数

int socket(int domain,int type,int protocol);

socket函数对应于普通文件的代开操作.普通文件的代开操作返回一个文件描述字,而 socket()用于创建一个 socket 描述符(socket descriptor),它唯一标识一个 socket.这个 socket 描述字和文件描述字一样,后续的操作都用到它,

2.bind()函数

int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen);

正如上面所说bind()函数把一个地址族中的特定地址赋给 socket.例如对应 AF_INET,AF_INET6就是把一个 ipv4或 ipv6地址和端口号组合赋给 socket.通常服务器在启动的时候都会绑定一个中所周知的地址(如 ip 地址+端口号),用于提供服务,客户就可以通过它来链接服务器,而客户端就不用制定,由系统自动分配一个端口号和自身的 ip 地址组合,这就是为什么通常服务器端在 listen 之前都会调用 bind(),而客户端就不会调用,而是在 connect()时有系统随机生成一个.

3.listen(),connect()函数

如果作为一个服务器,在调用 socket(),bind()之后就会调用 listen()来监听这个socket,如果客户端这时调用 connect()发出连接请求,服务器端就会接收到这个请求.

int listen(int sockfd,int backlog);
int connect(int sockfd,const struct sockaddr*addr,socklen_t addrlen);

listen 函数的第一个参数即为要监听的 socket 描述字,第二个参数为相应 socket 可以排队的最大连接数.socket()函数创建的 socket 默认是一个主动类型的,listen 函数将 socket 变为被动类型,等待客户的请求连接.
connect 函数的第一个参数亦为客户端的 socket 描述字,第二个参数为服务器的 socket 地址,第三个参数为 socket 地址的长度.客户端通过调用 connect 函数来建立与 TCP服务器的链接.

4.accept()函数

TCP服务器端依次调用 socket(),bind(),listen()函数之后,就会监听制定的 socket 地址了.
TCP客户端依次调用 socket(),conne()之后就向 TCP服务器发送了一个连接请求,TCP服务器监听到这个请求之后,就会调用 accept()函数来接收请求,这样连接就建立好了.之后就可以开始网路 I/O操作了,即类同于普通文件的 I/O操作.

int accept(int sockfd,struct sockaddr*addr,socklen_t*addrlen);

accept函数的第一个参数为服务器的 socket 描述字,第二个参数为指向 struct sockaddr*的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度,如果 accept 成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的 TCP链接.
注意:accept 的第一个参数为服务器的 socket 描述字,是服务器开始调用 socket()函数生成的,称为监听 socket 描述字,而 accept 函数返回的是已连接的(客户端) socket 描述字.一个服务器通常仅仅只创建一个监听 socket 描述字,它在该服务器的生命周期内一直存在.内核为每个服务器进程接收的客户链接创建了一个已连接 socket 描述字,当服务器完成了对某个客户的服务,相应的已连接 socket 描述字就被关闭.

5.read(),write()等函数

通信管道建立完成之后就可以进行想要的操作了.
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()

read函数是负责从 fd 中读取内容,当读取成功时,read 返回实际所读的字节数,如果返回值为零表示已经读到文件的而结束了,小于零表示出现了错误,如果错误为 EINTR 说明读取由中断引起,如果是ECONNREST表示网络连接出现了问题.
wirite 函数将 buf 中的 nbytes 字节内容写入文件描述符 fd.成功时返回吸入的字节数.失败时返回-1,并设置 errno 变量.在网络程序中,当我们向套接字(socket)文件描述符写入是有两种可能.write 的返回值大于零,表示写入了部分或者全部的数据.返回值小于零,出现错误如果错误为 EINTR表示写入中断,如果为 EPIPE表示网络连接出现了问题(对方关闭了连接).

close()函数

在服务器已客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的 socket 描述字,好比操作完打开的文件要调用 fclose 关闭打开的文件.

int close(int fd);

注意:close 操作只是使相应 socket 描述字的引用计数-1,只有当引用计数为0的时候,才会触发 TCP客户端向服务器发送终止链接请求,当 close 调用了之后 close 所关闭的 socket 描述字所对应的 socket 不能使用.

附:练习这里封装了一下 少了一个服务器的 close 方法

#import "Socket.h"
#import <netdb.h>
#import <arpa/inet.h>

@implementation Socket

//服务器端
+(void)serviceWithPort:(int)port block:(Myblock)block;
{
    //获取全局队列
    dispatch_queue_t global = dispatch_get_global_queue(0, 0);

    dispatch_async(global, ^{
        //搭建服务器端
        //1.创建socket
       int serviceid = socket(AF_INET, SOCK_STREAM, 0);

        if (!serviceid) {
            NSLog(@"socket error");
            return ;
        }

        //创建地址结构体
        struct sockaddr_in addr;

        //初始化addr参数
        memset(&addr, 0, sizeof(addr));

        //设置ip地址类型
        addr.sin_family = AF_INET;

        //设置端口
        addr.sin_port = port;

        //设置ip地址
        addr.sin_addr.s_addr = htonl(INADDR_ANY);

       //2.绑定地址
       int bindid =  bind(serviceid, (struct sockaddr *)&addr, sizeof(addr));

        if (bindid == -1) {
            NSLog(@"bind error");
            return;
        }

        //3.监听客户端连接信息
        int listenid = listen(serviceid, 10);

        if (listenid == -1) {
            NSLog(@"listen error");
            return;
        }
        while (1) {

            //接受客户端连接信息
            int acceptid = accept(serviceid, NULL, NULL);

            if (acceptid == -1) {
                continue;
            }

            //创建数组接受客户端数据
            char str[1000];

            //接收客户端信息
            long recvid = recv(acceptid, &str, 1000, 0);

            if (recvid) {
                block(str);
            }

            NSLog(@"str is:%s",str);

        }

    });
}

//客户端
+(void)clientWithPort:(int)port address:(NSString *)address msg:(NSString *)msg
{
    dispatch_queue_t global = dispatch_get_global_queue(0, 0);

    dispatch_async(global, ^{
        //创建客户端
        //1.创建socket
        int clientid = socket(AF_INET, SOCK_STREAM, 0);

        if (!clientid) {
            NSLog(@"socket error");
            return;
        }

        //创建地址结构体
        struct sockaddr_in addr;

        //初始化addr参数
        memset(&addr, 0, sizeof(addr));

        //设置ip地址类型
        addr.sin_family = AF_INET;

        //设置端口
        addr.sin_port = port;

        //将oc字符串地址 转化 为从字符串
        const char *str = [address UTF8String];

        inet_pton(AF_INET, str, &addr.sin_addr);

        //2.连接connect
        int connectid = connect(clientid, (struct sockaddr *)&addr, sizeof(addr));

        if (connectid == -1) {
            NSLog(@"connect error");
            return;
        }


        //将oc字符串信息 转化为 c字符
        const char *str2 = [msg UTF8String];
        //发送信息
        send(clientid, str2, sizeof(str2), 0);

        close(clientid);
    });
}

@end

四.各协议的区别

在上面的代码中我们发现在 socket 中反复的提到了一些协议 TCP/IP HTTP TCP IP下面一一解释

TCP/IP SOCKET HTTP

网络七层由下向上分别为物理层,数据链路层,网络层,传输层,会话层,表示层,和应用层.其中物理层,数据链路层和网络层通常被称为媒体层,是网络工程师所研究的对象.传输层,会话层,表示层和应用层则被称为主机层,是用户所面向和关心的内容.

http 协议,对应用层
tcp 协议,对应传输层
ip 协议,对应网络层

TCP/IP是传输层协议,主要解决数据如何在网络中传输.而 HTTP是应用层协议,主要解决如何包装数据.

我们在传输数据时,可以只使用传输层(TCP/IP),但是那样的话,由于没有应用层,便无法识别数据内容.如果想要使传输的数据有意义,则必须使用应用层协议,应用层协议很多,有 HTTP,FTP,TELNET(常用端口号HTTP:80/8080/3128/8081/9080,FTP:21,TELNET23 )等等.也可以自己定义应用层协议.WEB使用 HTTP做传输层协议,以封装 HTTP文本信息,然后使用 TCP/IP做传输层协议将它发送到网络上.

Socket 是对 TCP/IP协议的封装,Socket 本身并不是协议,而是一个调用接口 (API),通过 Socket,我们才能使用 TCP/IP协议.
别人的博客

未完待续.....

你可能感兴趣的:(socket)