建立TCP 服务器的系统调用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37964547/article/details/81429627

建立TCP服务器连接的过程中主要通过以下系统调用序列来获取某些函数,这些系统调用主要包括:socket(),bind(),listen(),accept(),send()和recv()。
如下图所示,
TCP应用程序进行的普通调用序列:
这里写图片描述
TCP调用的各个层:
这里写图片描述
(1)套接字层接收进行的任何 TCP 系统调用。套接字层验证 TCP 应用程序传递的参数的正确性。这是一个独立于协议 的层,因为尚未将协议连接到调用中。

(2)套接字层下面是协议层,该层包含协议的实际实现(本例中为 TCP)。当套接字层对协议层进行调用时,将确保对两个层之间共享的数据结构具有独占访问权限。这样做是为了避免任何数据结构损坏。

(3)各种网络设备驱动程序在接口层运行,该层从物理链路接收数据,并向物理链路传输数据。

(4)每个套接字具有一个套接字队列,并且每个接口具有一个用于数据通信的接口队列。不过,对于整个协议层,只有一个称为 IP 输入队列的协议队列。接口层通过此 IP 输入队列将数据输入到协议层。协议层使用相应的接口队列将数据输出到接口。

socket所处层次:
这里写图片描述
现在对每个系统调用进行解析:

1、socket接口

函数原型:

int socket(int protofamily, int so_type, int protocol);

参数解释:

  • protofamily: 指协议族,常见的值有:

    AF_INET,指定so_pcb中的地址要采用ipv4地址类型
    AF_INET6,指定so_pcb中的地址要采用ipv6的地址类型
    AF_LOCAL/AF_UNIX,指定so_pcb中的地址要使用绝对路径名

  • so_type:指定socket的类型,比较常用的类型有:

    SOCK_STREAM —– TCP类型,保证数据顺序及可靠性;
    SOCK_DGRAM ——UDP类型,不保证数据接收的顺序,非可靠连接;
    SOCK_RAW ——原始类型,允许对底层协议如IP或ICMP进行直接访问,不太常用

  • protocol: 指定具体的协议,也就是指定本次通信能接受的数据包的类型和发送数据包的类型,常见的值有:
    IPPROTO_TCP,TCP协议
    IPPROTO_UDP,UPD协议
    如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。

抽象的socket对外提供了一个统一、方便的接口来进行网络通信,但对内核来说,每一个接口背后都是及其复杂的,同一个接口对应了不同协议,而内核有不同的实现,幸运的是,如果确定了这三个参数,那么相应的接口的映射也就确定了。
函数返回值:根据这三个参数建立一个套接字,并将相应的资源分配给它,同时返回一个整型套接字号。

2、bind接口

函数原型

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

函数功能:将创建的socket绑定到指定的IP地址和端口上,通常是第二个调用的socket接口。当socket函数返回一个描述符时,只是存在于其协议族的空间中,并没有分配一个具体的协议地址(这里指IPv4/IPv6和端口号的组合),bind函数可以将一组固定的地址绑定到sockfd上。

参数解释:

  • sockfd : 是调用socket()函数创建的socket描述符
  • addr :指明要绑定的本地IP和端口号,使用网络字节序,即大端模式
  • addrlen :表示addr的长度
  • 返回值:0 – 成功,-1 – 出错。

需要注意的是:
通常服务器在启动的时候都会绑定一个众所周知的协议地址,用于提供服务,客户就可以通过它来接连服务器;而客户端可以指定IP或端口也可以都不指定,未分配则系统自动分配。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

3、listen接口

函数原型:

int listen(int sockfd, int backlog)

参数解释:

  • sockfd:是调用socket()函数创建的socket描述符(套接字号)
  • backlog:指定内核为此套接字维护的最大连接个数,该队列不能太长也不能没有,因为当队列太长时,需要耗费一定的资源进行维护和管理,没有的话也会降低操作系统的效率
  • 返回值:listen()成功时返回0,错误时返回-1。

函数功能:
listen()函数仅被TCP类型的服务器程序调用,实现监听服务,它实现2件事情:

(1) 当socket()创建1个socket时,被假设为主动式套接字,也就是说它是一个将调用connect()发起连接请求的客户端套接字;函数listen()将套接口转换为被动式套接字,指示内核接受向此套接字的连接请求,调用此系统调用后tcp 状态机由close转换到listen。
(2)第2个参数指定了内核为此套接字排队的最大连接个数。

listen用在bind()之后,accept()之后

4、connect接口

函数原型:

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen)

函数功能:
connect()通常由TCP类型客户端调用,用来与服务器建立一个TCP连接,实际是发起3次握手过程,连接成功返回0,连接失败返回1。
参数解释:

  • sockfd – 本地客户端额socket描述符;

  • serv_addr – 服务器协议地址;

  • addrlen – 地址缓冲区的长度。
    需要注意的是:

    (1) 可以在UDP连接使用使用connect(),作用是在UDP套接字中记住目的地址和目的端口。
    (2) UDP套接字使用connect后,如果数据报不是connect中指定的地址和端口,将被丢弃。没有调用connect的UDP套接字,将接收所有到达这个端口的UDP数据报,而不区分源端口和地址。

5、accept接口

函数原型:

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

函数功能:
accept()函数仅被TCP类型的服务器程序调用,从已完成连接队列返回下一个建立成功的连接,如果已完成连接队列为空,线程进入阻塞态睡眠状态。成功时返回套接字描述符,错误时返回-1。
参数解释:

  • sockfd – socket()函数返回的描述符;

  • addr – 输出的一个sockaddr_in变量地址,该变量用来存放发起连接请求的客户端的协议地址;

  • addrten – 作为输入时指明缓冲器的长度,作为输出时指明addr的实际长度。
    需要注意的是:
    如果accpet()执行成功,返回由内核自动生成的一个全新socket描述符,用它引用与客户端的TCP连接。通常我们把accept()第一个参数称为监听套接字(listening socket),把accept()功能返回值作为已连接套接字(connected socket)。一个服务器通常只有1个监听套接字,监听客户端的连接请求;服务器内核为每一个客户端的TCP连接维护1个已连接套接字,用它实现数据双向通信。

6、listen、connect、accept的执行流程

如图所示:
这里写图片描述
(1)服务器端在调用listen之后,内核会建立两个队列,SYN队列和ACCEPT队列,其中ACCPET队列的长度由backlog指定。
(2)服务器端在调用accpet之后,将阻塞,等待ACCPT队列直到有元素。
(3)客户端在调用connect之后,将开始发起SYN请求,请求与服务器建立连接,此时称为第一次握手。
(4)服务器端在接受到SYN请求之后,把请求方放入SYN队列中,并给客户端回复一个确认帧ACK,此帧还会携带一个请求与客户端建立连接的请求标志,也就是SYN,这称为第二次握手
(5)客户端收到SYN+ACK帧后,connect返回,并发送确认建立连接帧ACK给服务器端。这称为第三次握手
(6)服务器端收到ACK帧后,会把请求方从SYN队列中移出,放至ACCEPT队列中,而accept函数也等到了自己的资源,从阻塞中唤醒,从ACCEPT队列中取出请求方,重新建立一个新的sockfd,并返回。

7、send函数接口

函数原型:

int send(int sockfd, const void *msg, int len, int flags)

函数功能:主要是进行TCP类型的数据发送。
参数解释:

  • sockfd – 发送端套接字描述符(非监听描述符)。

  • msg – 待发送数据的缓冲区。

  • len – 待发送数据的字节长度。

  • flags – 一般情况下置为0。

8、recv()

函数原型:

int recv(int sockfd, void *buf, int len, unsigned int flags)

函数功能:主要用于TCP类型的数据接收。
参数解释:
- sockefd – 接收端套接字描述符(非监听描述符);

  • buf – 接收缓冲区的基地址;

  • len – 以字节计算的接收缓冲区长度;

  • flags – 一般情况下置为0。
    返回值:
    recv()从接收缓冲区拷贝数据。成功时,返回拷贝的字节数,失败返回-1。

  • 阻塞模式下,recv/recvfrom将会阻塞到缓冲区里至少有一个字节(TCP)/至少有一个完整的UDP数据报才返回,没有数据时处于休眠状态。
  • 若非阻塞,则立即返回,有数据则返回拷贝的数据大小,否则返回错误-1。

猜你喜欢

转载自blog.csdn.net/qq_37964547/article/details/81429627
今日推荐