socket的差不多利用

 

  • ① 、网络中经过之间怎样通信?
  • 2、Socket是什么?
  • 叁 、socket的基本操作
  • 3.1、socket()函数
  • 3.2、bind()函数
  • 3.3、listen()、connect()函数
  • 3.4、accept()函数
  • 3.5、read()、write()函数等
  • 3.6、close()函数
  • 四 、socket中TCP的3次握手建立连接详解
  • 伍 、socket中TCP的九回握手释放连接详解

 

① 、网络中经过之间怎么通讯?

地面包车型大巴长河间通讯(IPC)有很各类办法,但足以总计为上边4类:

音信传递(管道、FIFO、音信队列)
共同(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
共享内部存款和储蓄器(匿名的和签署的)
长距离进程调用(Solaris门和Sun 汉兰达PC)
但那几个都不是本文的宗旨!大家要研究的是互连网中经过之间如何通讯?主要化解的题材是如何唯一标识贰个历程,不然通讯无从谈起!在该地能够通过进度PID来唯一标识1个进度,不过在互连网中那是行不通的。其实TCP/IP协议族已经帮我们缓解了那么些标题,网络层的“ip地址”能够唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进度)。那样利用长富组(ip地址,协议,端口)就能够标识互连网的历程了,网络中的进程通讯就足以运用那一个标志与任何进度展开互相。

运用TCP/IP协议的应用程序经常选用采用编制程序接口:UNIX
BSD的套接字(socket)和UNIX System
V的TLI(已经被淘汰),来促成网络进度之间的通信。就现阶段而言,大约全体的应用程序都以利用socket,而最近又是互联网时期,互连网中经过通讯是无处不在,那就是自家干吗说“一切皆socket”。

 

2、什么是Socket?

上边大家早已知晓互联网中的进度是由此socket来通讯的,那什么样是socket呢?socket起点于Unix,而Unix/Linux基本历史学之一就是“一切皆文件”,都得以用“打开open
–> 读写write/read –>
关闭close”格局来操作。我的知道正是Socket正是该格局的多个完结,socket正是一种极度的文件,一些socket函数正是对其进展的操作(读/写IO、打开、关闭),那个函数大家在前边实行介绍。

socket一词的起点

在组网领域的首回采纳是在一九六七年4月1二十七日公告的文献IETF
RFC33
中发觉的,撰写者为StephenCarr、Steve Crocker和Vint
Cerf。依据United States电脑历史博物馆的记载,Croker写道:“命名空间的成分都可称为套接字接口。2个套接字接口构成二个老是的另一方面,而二个老是可完全由一对套接字接口规定。”总结机历史博物馆补充道:“这比BSD的套接字接口定义早了大致12年。”

TCP/IP

三 、socket的基本操作

既然socket是“open—write/read—close”情势的一种完结,那么socket就提供了那么些操作对应的函数接口。下边以TCP为例,介绍多少个着力的socket接口函数。

  要想通晓socket首先得熟谙一下TCP/IP协议族, TCP/IP(Transmission Control
Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数码怎么着再它们之间传输的行业内部。

3.1、socket()函数

int socket(int domain, int type, int protocol);
socket函数对应于普通文书的开辟操作。普通文书的开拓操作重临1个文书讲述字,而socket()用于创制二个socket描述符(socket
descriptor),它唯一标识3个socket。那么些socket描述字跟文件讲述字一样,后续的操作都有选拔它,把它当做参数,通过它来展开部分读写操作。

正如能够给fopen的扩散分裂参数值,以开拓不一样的公文。创设socket的时候,也足以内定区别的参数创造差别的socket描述符,socket函数的多个参数分别为:

domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地点类型,在通讯中必须利用对应的地址,如AF_INET决定了要用ipv4地址(31个人的)与端口号(1多少人的)的三结合、AF_UNIX决定了要用1个纯属路径名作为地点。
type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的品种有哪些?)。
protocol:故名思意,正是钦命协议。常用的协商有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们各自对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(那一个体协会议自身将会独自开篇研商!)。
小心:并不是下面的type和protocol能够随意组合的,如SOCK_STREAM不得以跟IPPROTO_UDP组合。当protocol为0时,会自行采用type类型对应的暗中认可协议。

当大家调用socket创设二个socket时,再次来到的socket描述字它存在于协议族(address
family,AF_XXX)空间中,但尚无叁个切实的地方。假设想要给它赋值三个地点,就非得调用bind()函数,不然就当调用connect()、listen()时系统会自动随机分配二个端口。

  从字面意思来看TCP/IP是TCP和IP协议的合称,但实质上TCP/IP协议是指因特网整个TCP/IP协议族。不一致于ISO模型的多个支行,TCP/IP协议参考模型把拥有的TCP/IP类别协议归类到八个抽象层中。

3.2、bind()函数

正如下面所说bind()函数把2个地址族中的特定地点赋给socket。例如对应AF_INET、AF_INET6便是把3个ipv4或ipv6地址和端口号组合赋给socket。

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

函数的多少个参数分别为:

sockfd:即socket描述字,它是透过socket()函数创造了,唯一标识多个socket。bind()函数便是将给那一个描述字绑定贰个名字。
addr:一个const struct sockaddr
*指南针,指向要绑定给sockfd的协议地址。这几个地方结构根据地点创造socket时的地址协议族的例外而分歧,如ipv4对应的是:

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};
ipv6对应的是: 
struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};

struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};
Unix域对应的是: 
#define UNIX_PATH_MAX    108

struct sockaddr_un { 
    sa_family_t sun_family;               /* AF_UNIX */ 
    char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
};
addrlen:对应的是地址的长度。

一般说来服务器在起步的时候都会绑定贰个引人侧指标地方(如ip地址+端口号),用于提供劳动,客户就能够经过它来几次三番服务器;而客户端就无须内定,有系统自动分配1个端口号和笔者的ip地址组合。那正是为什么通平常服装务器端在listen在此以前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成四个。

网络字节序与主机字节序
主机字节序正是我们日常说的四头和小端方式:分化的CPU有分裂的字节序类型,那一个字节序是指整数在内存中保存的一一,那几个称呼主机序。引用标准的Big-Endian和Little-Endian的定义如下:
  a)
Little-Endian正是低位字节排泄在内部存款和储蓄器的洼地址端,高位字节排泄在内存的高地址端。
  b)
Big-Endian就是高位字节排放在内部存款和储蓄器的盆地址端,低位字节排放在内部存款和储蓄器的高地址端。
互联网字节序:五个字节的32
bit值以下边包车型客车程序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。那种传输次序称作大端字节序。由于TCP/IP首部中持有的二进制整数在互联网中传输时都需要以那种次序,由此它又称作互联网字节序。字节序,顾名思义字节的一一,正是高于二个字节类型的数额在内部存款和储蓄器中的寄放顺序,3个字节的多少尚未种种的标题了。
于是:在将2个地方绑定到socket的时候,请先将主机字节序转换到为互联网字节序,而毫不假定主机字节序跟网络字节序一样接纳的是Big-Endian。由于这么些题材曾掀起过血案!集团项目代码中出于存在这一个标题,导致了好多非驴非马的难题,所以请谨记对主机字节序不要做别的假定,务必将其转化为网络字节序再赋给socket。

  

3.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服务器的连天。

3.4、accept()函数
TCP服务器端依次调用socket()、bind()、listen()之后,就会监听钦点的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了1个老是请求。TCP服务器监听到这些请求之后,就会调用accept()函数取接收请求,那样总是就建立好了。之后就足以起来互联网I/O操作了,即一般于平时文书的读写I/O操作。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);```
accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

3.5、read()、write()等函数
万事具备只欠东风,至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:

read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
我推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数,实际上可以把上面的其它函数都替换成这两个函数。它们的声明如下:

       #include <unistd.h>

       ssize_t read(int fd, void *buf, size_t count);
       ssize_t write(int fd, const void *buf, size_t count);

       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
       ssize_t recv(int sockfd, void *buf, size_t len, int flags);

       ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

       ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。

write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。

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

  应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet
等等

include <unistd.h>

int close(int fd);“`
close二个TCP
socket的缺省级银行为时把该socket标记为以关闭,然后马上回去到调用进度。该描述字不能够再由调用进度使用,也正是说无法再作为read或write的第二个参数。

小心:close操作只是使相应socket描述字的引用计数-1,唯有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

  传输层:TCP,UDP

④ 、socket中TCP的1回握手建立连接详解

大家知道tcp建立连接要开始展览“3回握手”,即沟通四个分组。大约流程如下:

客户端向服务器发送1个SYN J
服务器向客户端响应三个SYN K,并对SYN J实行确认ACK J+1
客户端再想服务器发叁个确认ACK K+1
唯有就完了一遍握手,可是那些2遍握手产生在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在3次握手的第三个次回到,而服务器端的accept在一遍握手的第一次回到。

  网络层:IP,ICMP,OSPF,EIGRP,IGMP

伍 、socket中TCP的七次握手释放连接详解

地点介绍了socket中TCP的1回握手建立进度,及其关联的socket函数。以后大家介绍socket中的四回握手释放连接的进度,请看下图:

图片 2

socket中发送的TCP4回握手

图示进程如下:

某些应用进程首先调用close主动关闭连接,那时TCP发送3个FIN M;
另一端接收到FIN
M之后,执行被动关闭,对这几个FIN举行确认。它的吸收接纳也当作文件截至符传递给应用进度,因为FIN的吸收意味着应用进程在相应的一连上再也接受不到额外数据;
一段时间之后,接收到文件结束符的运用进度调用close关闭它的socket。这造成它的TCP也发送1个FIN
N;
接过到那么些FIN的源发送端TCP对它进行确认。
如此各样方向上都有二个FIN和ACK。

//  服务端

#import "ViewController.h"
#import <sys/socket.h>
#import <arpa/inet.h>
#import <netinet/in.h>
@interface ViewController ()

@end

@implementation ViewController
- (void)service {
    int error;//错误代码 ==>链接不成功
    int fuwu = socket(AF_INET, SOCK_STREAM, 0);
    BOOL success = (fuwu == -1);//代表成功
    if (success) {
        //用bind()方法进行绑定
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(3456);
        addr.sin_len = sizeof(addr);
        addr.sin_addr.s_addr = INADDR_ANY;//接收到的地址,换成客户端地址的话,是可以进行操作的 代表任意常量都可以进行链接
        //返回值是int 判断是否绑定成功
        error = bind(fuwu, (const struct sockaddr *)&addr, sizeof(addr));
        success = (error == 0);

    }
    if (success) {
            NSLog(@"绑定成功");
            //开始进行监听
            error = listen(fuwu, 5);
            success = (error == 0);
    }
    if (success) {
        NSLog(@"监听");
        while (true) {//开始接收客户端数据 ,用while保持长连接(TCP)
            struct sockaddr_in peeAdd;
            int peer;//判断成功与否
            socklen_t addrLen = sizeof(peeAdd);//返回一个长度
            peer = accept(fuwu, (struct sockaddr *restrict)&peeAdd, &addrLen);
            success = (peer != -1);
            if (success) {
                NSLog(@"接收成功");
                //缓存处理
                char buffer[1024];
                ssize_t count;
                size_t len = sizeof(buffer);
                //我们一直在监听,处理数据只处理了一次
                do {
                    count = recv(peer, buffer, len, 0);
                    NSString *recStr = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
                    NSLog(@"%@",recStr);
                } while (strcmp(buffer, "exit") != 0);
            }

        }

    }
    //服务器的监听结束,需在客户的进行操作
    //关闭socket 链接断开
    close(fuwu);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self service];
    // Do any additional setup after loading the view, typically from a nib.
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

//  客户端

#import "ViewController.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    /*
     1. int 类型
     2. int 类型 SOCK_STREAM TCP ,SOCK_DGRAM UDP
     3.
     **/

    int client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    struct sockaddr_in service;
    service.sin_family = AF_INET;
    service.sin_port = htons(3456);
    service.sin_addr.s_addr = inet_addr("127.0.0.1");
    //返回的int 0代表成功,其他正数代表失败状态码
    int result = connect(client, (const struct sockaddr *)&service, sizeof(service));
    if (result == 0) {
        //链接成功
        NSLog(@"链接成功");
        NSString *message = @"jes中国dff";
        /*
         1.客户的 SC
         2.指针 发送内容的一个指针
         3.发送内容的长度
         4.int 指的是发送指示,一般赋值为0
         **/
        size_t lengt = send(client, message.UTF8String, strlen(message.UTF8String), 0);
        NSLog(@"%ld",lengt);

        //服务器
        /*
         1.sc 2.返回内容 3.返回内容长度 4.标志
         接受到的数据 ---->缓存
         接收到的数据和发送的数据最后转化为一致

         **/
        uint8_t buffer[1024];
        //只进行一次处理
        size_t recvLength = recv(client, buffer, sizeof(buffer), 0);
        NSLog(@"接收到了%ld",recvLength);
        //处理服务器返回的数据 ,解析数据的过程
        NSData *data = [NSData dataWithBytes:buffer length:recvLength];
        NSString *receivestr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@",receivestr);
        //关闭socket

    }else{
        //链接失败
    }
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

当然下边包车型客车代码很简短,也有成都百货上千通病,那就只是简短的言传身教socket的宗旨函数使用。其实不管有多复杂的网络程序,都应用的这几个基本函数。上面的服务器使用的是迭代情势的,即唯有处理完3个客户端请求才会去处理下1个客户端的伸手,那样的服务器处理能力是很弱的,现实中的服务器都需求有出现处理能力!为了索要出现处理,服务器必要fork()1个新的历程恐怕线程去处理请求等。

  多少链路层:SLIP,CSLIP,PPP,MTU

  每一虚幻层建立在低一层提供的服务上,并且为高级中学一年级层提供劳务,看起来粗粗是那样子的。

图片 3

图1.TCP/IP密密麻麻协议的多个抽象层

 

图片 4

图2.TCP/IP的ISO模型的多个支行

 

Socket

  网络中经过之间怎么通讯?重要消除的标题是何许唯一标识贰个历程,不然通讯无从谈起!在地点能够通过进度PID来唯一标识3个进度,不过在互连网中那是没用的。其实TCP/IP协议族已经帮大家消除了那个难题,网络层的“ip地址”能够唯一标识互联网中的主机,而传输层的“协议+端口”能够唯一标识主机中的应用程序(进程)。那样利用三元组(ip地址,协议,端口)就足以标识互联网的历程了,互联网中的进度通讯就能够利用这几个标志与任何进度展开相互。

  那什么样是socket呢?大家平常把socket翻译为套接字,socket是在应用层和传输层之间的二个抽象层,它把TCP/IP层复杂的操作抽象为多少个简易的接口供应用层调用,以促成进度网络中通讯。  

    

图片 5

 

图3.socket在TCP/IP抽象层中的地点

 

  

   socket起点于UNIX,在Unix一切皆文件艺术学的合计下,socket是一种“打开—读/写/—关闭”形式的落到实处,服务器和客户端各自维护贰个“文件”,在建立连接打开后,能够向友好文件写入内容供对方读取大概读取对方内容,通信结束时关闭文件。

 

Socket通信流程

  socket是”打开—读/写—关闭”方式的兑现,以应用TCP协议通信的socket为例,其交互流程差不多是那样子的。

 图片 6

图4.TCP研讨的socket通信流程图

 

  

  

  服务器依据地址类型(ipv4,ipv6)、socket类型、协议创制socket

  服务器为socket绑定ip地址和端口号

  服务器socket监听端口号请求,随时准备接受客户端发来的连天,那时候服务器的socket并不曾被打开

  客户端创立socket

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

  服务器socket接收到客户端socket请求,被动打开,起初接到客户端请求,直到客户端重回连接音讯。那时候socket进入阻塞意况,所谓阻塞即accept()方法一贯到客户端重返连接信息后才回来,开始收受下1个客户端连接请求

  客户端连接成功,向服务器发送连接情形音讯

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

  客户端向socket写入消息

  服务器读取音信

  客户端关闭

  服务器端关闭

 

Socket的基本操作

  上面以TCP为例,介绍多少个为主的socket接口函数。

socket()函数

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

  socket函数对应于普通文书的打开操作。普通文书的开辟操作再次回到三个文书讲述字,而socket()用以成立八个socket描述符(socket
descriptor),它唯一标识一个socket。那个socket描述字跟文件讲述字一样,后续的操作都有接纳它,把它看做参数,通过它来开始展览部分读写操作。

  正如能够给fopen的传入分裂参数值,以打开差异的文书。创设socket的时候,也能够钦定不一样的参数创造差异的socket描述符,socket函数的四个参数分别为:

  •   domain:即协议域,又称之为协议族(family)。常用的协议族有,AF_INETAF_INET6AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地方类型,在通信中务必运用对应的地址,如AF_INET决定了要用ipv4地址(31个人的)与端口号(13位的)的三结合、AF_UNIX决定了要用贰个纯属路径名作为地方。
  •   type:指定socket类型。常用的socket类型有:SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等。
  •       protocol:顾名思义,便是点名协议。常用的协议有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等,它们各自对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

 

  注意:并不是地方的type和protocol可以随便组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选取type类型对应的暗中同意协议。

  

  当大家调用socket()创办二个socket时,重回的socket描述字存在于协议族(address
family,AF_XXX)空间中,但未曾三个实际的地址。假如想要给它赋值多个地址,就不可能不调用bind()函数,不然当调用connect()listen()时系统会轻易分配2个端口。

 

bind()函数

  正如上边所说bind()函数把二个地址族中的特定地点赋给socket。例如对应AF_INETAF_INET6不畏把3个ipv4或ipv6地址和端口号组合赋给socket。

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

  函数的多少个参数分别为:

  •   sockfd:即socket描述字,它是由此socket()函数创造了,唯一标识一个socket。bind()函数便是将给这些描述字绑定三个名字。
  •   addr:一个const struct sockaddr *指南针,指向要绑定给sockfd的协商地址。这么些地方结构依据地方创设socket时的地址协议族的例外而各异,
      如ipv4对应的是:

    struct sockaddr_in {
        sa_family_t    sin_family; /* address family: AF_INET */
        in_port_t      sin_port;   /* port in network byte order */
        struct in_addr sin_addr;   /* internet address */
    };
    
    /* Internet address. */
    struct in_addr {
        uint32_t       s_addr;     /* address in network byte order */
    };
    

      ipv6对应的是:

    struct sockaddr_in6 { 
        sa_family_t     sin6_family;   /* AF_INET6 */ 
        in_port_t       sin6_port;     /* port number */ 
        uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
        struct in6_addr sin6_addr;     /* IPv6 address */ 
        uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
    };
    
    struct in6_addr { 
        unsigned char   s6_addr[16];   /* IPv6 address */ 
    };
    

      Unix域对应的是:

    #define UNIX_PATH_MAX    108
    
    struct sockaddr_un { 
        sa_family_t sun_family;               /* AF_UNIX */ 
        char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
    };
    

     

  • addrlen:对应的是地方的尺寸。

  通平常衣服务器在开发银行的时候都会绑定叁个令人侧指标地址(如ip地址+端口号),用于提供劳务,客户就能够通过它来一而再服务器;而客户端就无须钦命,有系统自动分配三个端口号和本身的ip地址组合。那便是为什么经常服务器端在listen从前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成多个。

 

 


 

网络字节序与主机字节序

  主机字节序:便是大家平常说的多方和小端形式:区别CPU有区别的字节序类型,那些字节序是指整数在内部存款和储蓄器中保存的依次,那一个叫做主机序。引用标准的Big-Endian和Little-Endian的概念如下:

  a)Little-Endian就是低位字节排泄在内部存款和储蓄器的洼地址端,高位字节排泄在内部存款和储蓄器的高地址端。

  b)Big-Endian正是高位字节排泄在内部存款和储蓄器的洼地址端,低位字节排放在内部存款和储蓄器的高地址端。

 

  网络字节序:五个字节的32
bit值以下边包车型客车程序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。那种传输次序称作大端字节序。是因为TCP/IP首部中享有的二进制整数在网络中传输时都务求以那种次序,由此它又称作网络字节序。字节序,顾名思义字节的种种,便是大于一个字节类型的多少在内部存款和储蓄器中的存放顺序,3个字节的多寡没有种种的题材了。

 

  所以:在将2个地址绑定到socket的时候,请先将主机字节序转换成为互连网字节序,而并非假定主机字节序跟网络字节序一样选择的是Big-Endian。由于这一个难点曾引发过血案!公司项目代码中由于存在那么些题材,导致了很多不可捉摸的标题,所以请谨记对主机字节序不要做任何假定,务必将其转会为互连网字节序再赋给socket。


  

 

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服务器的连接。

 

 

accept()函数

  TCP服务器端依次调用socket()bind()listen()随后,就会监听内定的socket地址了。TCP客户端依次调用socket()connect()然后就向TCP服务器发送了3个连接请求。TCP服务器监听到这一个请求之后,就会调用accept()函数取接收请求,这样总是就建立好了。之后就足以起来网络I/O操作了,即一般于常常文书的读写I/O操作。

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

  accept()函数的第1个参数为服务器的socket描述字,第②个参数为指向struct sockaddr
*
的指针,用于重回客户端的情商地址,第四个参数为协商地址的长度。借使accpet成功,那么其再次回到值是由基础自动生成的一个崭新的描述字,代表与重临客户的TCP连接。

 

注意:accept的首先个参数为服务器的socket描述字,是服务器开首调用socket()函数生成的,称为监听socket描述字;而accept函数再次回到的是已一连的socket描述字。贰个服务器常常一般唯有只开创一个监听socket描述字,它在该服务器的生命周期内从来留存。内核为各类由服务器进程接受的客户连接创设了1个已一连socket描述字,当服务器达成了对有个别客户的服务,相应的已接连socket描述字就被关门。

 

read()、write()等函数

  万事具备只欠南风,至此服务器与客户已经确立好连接了。能够调用互联网I/O进行读写操作了,即达成了网咯中差别进度之间的通讯!网络I/O操作有下边几组: 

1 read()/write()
2 recv()/send()
3 readv()/writev()
4 recvmsg()/sendmsg()
5 recvfrom()/sendto()

  他们的证明如下:

/*-------------------------------------------------------------------------------*/ 
      #include <unistd.h>

       ssize_t read(int fd, void *buf, size_t count);
       ssize_t write(int fd, const void *buf, size_t count);
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
       ssize_t recv(int sockfd, void *buf, size_t len, int flags);

       ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

       ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
/*-------------------------------------------------------------------------------*/

 

  read函数是负责从fd中读取内容.当读成功时,read再次来到实际所读的字节数,假设回去的值是0意味着早已读到文件的截至了,小于0代表出现了不当。若是不当为EINTR证实读是由刹车引起的,假诺是ECONNREST代表网络连接出了难题。

 

  write函数将buf中的nbytes字节内容写入文件讲述符fd.成功时回来写的字节数。战败时再次回到-1,并设置errno变量。
在互连网程序中,当我们向套接字文件讲述符写时有俩种大概。

  1)write的重回值大于0,表示写了一部分要么是整整的多寡。

  2)再次来到的值小于0,此时出现了错误。大家要依据错误类型来处理。若是不当为EINT普拉多表示在写的时候出现了暂停错误。即使为EPIPE表示网络连接出现了难题(对方早已关闭了一连)。

  其余的自己就不一一介绍这几对I/O函数了,具体参见man文书档案可能baidu、谷歌,上边的例子司令员使用到send/recv。 

 

close()函数

  在服务器与客户端建立连接之后,会进展局地读写操作,实现了读写操作就要关闭相应的socket描述字,好比操作完打开的公文要调用fclose关闭打开的公文。

#include <unistd.h>
int close(int fd);

  close1个TCP
socket的缺省级银行为时把该socket标记为以关闭,然后立即赶回到调用进度。该描述字无法再由调用进程使用,也正是说不能够再作为read或write的首先个参数。

  注意:close操作只是使相应socket描述字的引用计数-1,唯有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

   

socket中TCP的一回握手建立连接详解

  大家精晓tcp建立连接要拓展“1遍握手”,即交流多少个分组。大约流程如下:

  •   客户端向服务器发送一个SYN
    J
  •       服务器向客户端响应三个SYN
    K,并对SYN J实行确认ACK J+1
  •       客户端再向服务器发四个认同ACK
    K+1

  之后就落成了贰遍握手,不过那个一遍握手发生在socket的哪多少个函数中吗?请看下图:

图片 7

 

图5.socket中发送的TCP2回握手

 

  

  从图中能够看来,当客户端调用connect时,触发了连接请求,向服务器发送了SYN
J
包,这时connect进入阻塞状态;服务器监听到几次三番请求,即接到SYN
J
包,调用accept函数接收请求向客户端发送SYN KACK
J+1
,这时accept跻身阻塞状态;客户端收到服务器的SYN KACK
J+1
之后,这时connect返回,并对SYN K进展确认;服务器收到ACK
K+1
时,accept回到,至此二回握手完毕,连接建立。

  


  总括:客户端的connect在三回握手的第③遍回到,而服务器段的accept在三遍握手中的第一次回到。


 

 

socket中TCP的捌遍握手释放连接详解

  上边介绍了socket中TCP的1次握手建立进程,及其关联的socket函数。现在大家介绍socket中的四次握手释放连接的长河,请看下图:  

图片 8

 

图6.socket中发送的TCP八遍握手

 

  图示进度如下:

  • 有些应用进度首先调用close当仁不让关闭连接,那时TCP发送贰个FIN M

  • 另一端接收到FIN
    M
    从此,执行被动关闭,对那一个FIN进展确认。它的收受也视作文件甘休符传递给应用进度,因为FIN的选取意味着应用进度在对应的连日上再也接到不到额外数据;

  • 一段时间之后,接收到文件停止符的选取进度调用close关闭它的socket。那致使它的TCP也发送3个FIN N

  • 收起到这一个FIN的源发送端TCP对它实行确认。

  那样种种方向上都有三个FIN和ACK。

 

多少个例子

劳务器段代码:

 1 /*
 2 2016-9-28 12:09:45
 3 @author:CodingMengmeng
 4 language:C;
 5 */
 6 
 7 //Server.
 8 #include <stdio.h>                   //用于printf等函数的调用
 9 #include <winsock2.h>                //Socket的函数调用 
10 #pragma comment (lib, "ws2_32")      //C语言引用其他类库时,除了.h文件外,还要加入对应的lib文件(这个不同于C#)
11 
12 int main()
13 {
14     /*
15         为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,
16         因此需要调用WSAStartup函数。使用Socket的程序在使用Socket之前必须调用WSAStartup函数。
17         该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;
18         操作系统利用第二个参数返回请求的Socket的版本信息。
19         当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。
20         以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。
21 ----------------------------------------------------------------------------------------------------------------------------------------
22         int WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData );
23         (1)wVersionRequested:一个WORD(双字节)型数值,在最高版本的Windows Sockets支持调用者使用,高阶字节指定小版本(修订本)号,低位字节指定主版本号。
24         (2)lpWSAData 指向WSADATA数据结构的指针,用来接收Windows Sockets 实现的细节
25 ----------------------------------------------------------------------------------------------------------------------------------------
26         本函数必须是应用程序或DLL调用的第一个Windows Sockets函数。
27     */
28     WSADATA wsaData;//用来接收Windows Sockets实现的细节
29     WSAStartup(MAKEWORD(2, 2), &wsaData);//完成对Winsock服务的初始化
30     SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);//地址类型:AF_INET;socket类型:SOCK_STREAM;协议类型:TCP
31     sockaddr_in sockaddr;//要绑定给sockfd的协议地址
32     sockaddr.sin_family = PF_INET;//AF_INET决定了地址类型要使用ipv4地址(32位)与端口号(16位)的组合
33     sockaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");   //需要绑定到本地的哪个IP地址|| 127.0.0.1是回送地址,指本地机
34     sockaddr.sin_port = htons(9000);                          //需要监听的端口
35     bind(s, (SOCKADDR*)&sockaddr, sizeof(SOCKADDR));        //进行绑定动作
36     listen(s, 1);                                           //启动监听
37     printf("listening on port [%d].\n", 9000);
38     int n;
39 
40         SOCKADDR clientAddr;
41         int size = sizeof(SOCKADDR);
42         SOCKET clientsocket;
43         clientsocket = accept(s, &clientAddr, &size);               //阻塞,直到有新tcp客户端连接
44         printf("***SYS***    New client touched.\n");
45         char* msg = "Hello, my client.\r\n";
46         char* revFlag = "Copy that!\r\n";
47         send(clientsocket, msg, strlen(msg) + sizeof(char), NULL);  //这里的第三个参数要注意,是加了一个char长度的
48         while (TRUE)
49         {
50             char buffer[MAXBYTE] = { 0 };
51             n=recv(clientsocket, buffer, MAXBYTE, NULL);//一直接收客户端socket的send操作
52             //recv只有接收到数据才会往下执行,否则一直等待
53             send(clientsocket, revFlag, strlen(revFlag) + sizeof(char), NULL);
54             buffer[n] = '\0';
55             printf("***Receive From Client***:    %s\n", buffer);
56         }
57 
58     closesocket(clientsocket);                                //关闭客户端socket
59     closesocket(s);//关闭监听socket
60 
61     WSACleanup();                                                //卸载
62     getchar();
63     exit(0);
64 }

 

客户端代码:

 1 /*
 2 2016-9-28 12:11:41
 3 @author:CodingMengmeng
 4 language:C;
 5 */
 6 
 7 //Client.
 8 #include <stdio.h>                      //用于输入、输出函数的调用,printf, gets
 9 #include <winsock2.h>                   //socket头文件
10 #include <Windows.h>                    //为了方便调试,所以加入了等待2秒才进行连接server,这里用到了sleep函数
11 #pragma comment (lib, "ws2_32")         //socket库文件
12 
13 int main()
14 {
15     Sleep(2000);                        //沉睡2秒再连接server
16     WSADATA wsaData;
17     WSAStartup(MAKEWORD(2, 2), &wsaData);
18     SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
19     sockaddr_in sockaddr;//描述服务器socket地址
20     sockaddr.sin_family = PF_INET;
21     sockaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
22     sockaddr.sin_port = htons(9000);
23     connect(s, (SOCKADDR*)&sockaddr, sizeof(SOCKADDR));//s:客户端的socket描述字;sockaddr:服务器的socket地址;sizeof(SOCKADDR):服务器socket地址的长度
24     char buffer[MAXBYTE] = { 0 };
25     recv(s, buffer, MAXBYTE, NULL);
26     printf("***SERVER*** SEND:%s", buffer);
27     while (TRUE)
28     {
29         memset(buffer, 0, sizeof(buffer));
30         char* mymsg = new char[100000];
31         printf("Say something to Server:\n");
32         gets(mymsg);//获取屏幕输入
33         send(s, mymsg, strlen(mymsg) + sizeof(char), NULL);//发送给服务器
34         recv(s, buffer, MAXBYTE, NULL);//接收服务器回传的响应,若未响应,则一直等待。
35         printf("***Receive From Server***:    %s",buffer);
36 
37             
38         /*
39         recv函数中的bufferlength参数是可以固定值的
40         send函数中的bufferlength参数不能固定值,需要看实际长度,并且考虑到'\0'字符串
41         */
42     }
43     closesocket(s);        //关闭客户端socket
44     WSACleanup();        //卸载
45     getchar();
46     exit(0);
47 }

 

  当然下边包车型地铁代码很简短,也有许多缺点,那就只是简短的以身作则socket的骨干函数使用。其实无论有多复杂的网络程序,都应用的这几个骨干函数。上边的服务器使用的是迭代方式的,即只有处理完2个客户端请求才会去处理下三个客户端的恳求,那样的服务器处理能力是很弱的,现实中的服务器都亟待有出现处理能力!为了须要出现处理,服务器供给创建四个新的进程只怕线程去处理请求等。

 

 

参考

1、吴秦:Linux
Socket编程(不限Linux)

2、Samaritans:简言之了然Socket