手把手写C++服务器(38):面试必背!Linux网络socket编程必会十问!

Posted 沉迷单车的追风少年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手写C++服务器(38):面试必背!Linux网络socket编程必会十问!相关的知识,希望对你有一定的参考价值。

 本系列文章导航: 手把手写C++服务器(0):专栏文章-汇总导航【更新中】

目录

1、说一下客户端和服务端socket建立连接和关闭连接的过程

2、如何将一个 socket设置成非阻塞模式

3、什么是socket三大属性?

4、阻塞模式下,send和recv函数行为是什么样子的?非阻塞模式下send/recv的返回值分别是什么?

5、客户端发起连接时,如何主动指定通过本地某个端囗号去连接?bind函数如果端口号设置为0是什么行为?

指定端口号连接

bind端口号设置为0

6、listen函数的 backlog参数用途是什么?

7、如何实现异步的connect函数?

8、accept函数调用时,三次握手是否已经完成?

9、如何实现半关闭状态?

10、select函数的第一个参数怎么设置? select函数的超时参数如果设置为NULL是什么行为?


1、说一下客户端和服务端socket建立连接和关闭连接的过程

2、如何将一个 socket设置成非阻塞模式

在调用socket()创建socket的时候,调用成功会返回一个socket文件描述符。

利用这个文件描述符fd,通常用fcntl函数来将文件描述符设置为非阻塞模式,不清楚fcntl的同学参看:手把手写C++服务器(27):五大文件描述符零拷贝、控制总结_沉迷单车的追风少年-CSDN博客

int setnoblocking(int fd) {
    int old_option = fcntl(fd, F_GETFL);  // 获取文件描述符旧状态
    int new_option = old_option | O_NOBLOCK;  // 设置非阻塞标志
    fcntl(fd, F_SETFL, new_option);  // 设置非阻塞模式
    return old_option;  // 返回旧的文件描述符以便日后恢复状态
}

3、什么是socket三大属性?

套接字的特性有三个属性确定,它们是:域(domain),类型(type),和协议(protocol)。

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);

1、domain:告诉系统使用哪个底层协议族。 对TCP/IP协议族而言, 该参数应该设置为PF_INET(Protocol Family of Internet, 用于IPv4) 或PF_INET6(用于IPv6) ; 对于UNIX本地域协议族而言, 该参数应该设置为PF_UNIX。
2、type:指定服务类型。 服务类型主要有SOCK_STREAM服务(流服务) 和SOCK_UGRAM(数据报) 服务。 对TCP/IP协议族而言, 值取SOCK_STREAM表示传输层使用TCP协议, 取SOCK_DGRAM表示传输层使用UDP协议。
3、protocol:是在前两个参数构成的协议集合下, 再选择一个具体的协议。 不过这个值通常都是唯一的(前两个参数已经完全决定了它的值) 。 一般设置为0, 表示使用默认协议。

  • 0:使用默认协议;
  • IPPROTO_TCP:使用TCP协议;
  • IPPROTO_UDP:使用UDP协议;

4、阻塞模式下,send和recv函数行为是什么样子的?非阻塞模式下send/recv的返回值分别是什么?

默认模式:阻塞模式。

send函数和recv函数都不是直接参与网络上的数据发送与接收!他俩将在缓冲区中读取/写入数据。在阻塞模式下,如果缓冲空间不足,会阻塞等待;但是在非阻塞模式下,会直接返回失败。
函数返回:

5、客户端发起连接时,如何主动指定通过本地某个端囗号去连接?bind函数如果端口号设置为0是什么行为?

指定端口号连接

socket提供了专用结构体,客户端以socketaddr_in为例,第二个参数sin_port代表端口号。

struct sockaddr_in {
    sa_family_t sin_family; //地址族
    u_int16_t sin_port;  // 端口号
    struct in_addr sin_addr; // IPV4地址结构体
};

一般请求框架中,通过sockaddr_in结构体帮顶端口号,但是要注意大小端问题。这些在本系列都详细讲过,不清楚的自行翻看。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
int main(){
    //创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
 
    //向服务器(特定的IP和端口)发起请求
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
   
    //读取服务器传回的数据
    char buffer[40];
    read(sock, buffer, sizeof(buffer)-1);
   
    printf("Message form server: %s\\n", buffer);
   
    //关闭套接字
    close(sock);
 
    return 0;
}

bind端口号设置为0

如果将 bind 函数中的端口号设置成0,那么操作系统会随机给程序分配一个可用的侦听端口,当然服务器程序一般不会这么做,因为服务器程序是要对外服务的,必须让客户端知道确切的ip地址和端口号。

6、listen函数的 backlog参数用途是什么?

listen创建一个监听队列以存放待处理的客户连接:

#include<sys/socket.h>
int listen(int sockfd,int backlog);

backlog用来提示内核监听队列的最大长度。如果监听队列的长度大于backlog,服务器将不受理新的客户连接,客户端也将收到ECONNREFUSED错误信息。

在linux 2.2版本后,只表示完全连接状态的socket的上限,处于半连接状态的上限由/proc/sys/net/ipv4/tcp_max_syn_backlog 内核参数定义。

backlog的典型值是5。

7、如何实现异步的connect函数?

在实际项目中,connect有同步连接和异步连接两种模式。
同步连接指的是,我们设置socket套接字为阻塞模式,调用connect之后,程序一直等待,直到该函数返回成功或者失败。如果连接过程中,发生了超时重传,接口的耗时时间有可能达127秒之久。假如我们的服务器程序只有一个网络线程,同步connect会阻塞该网络线程较长时间,在这段时间内将不能给其他连接提供服务。
异步连接指的是,我们设置socket套接字为非阻塞模式,调用connect之后,该函数会马上返回,如果连接立即成功,那么皆大欢喜,就不用进行下步操作了。如果连接没有立即成功,我们就用select或者epoll等待操作系统给我们通知,接到通知后,我们再判断连接成功与否。在高性能服务器程序中,我们优先使用异步连接这种模式。

所以异步connect实现的时候,需要:

  • 将该套接口加入epoll中,调用epoll_wait等待套接口的通知;
  • 如果连接成功,正常情况下epoll触发EPOLLOUT事件,不会触发EPOLLIN事件。但有一种情况,如果connect成功之后,服务端马上发送数据,此时客户端也会立刻得到EPOLLIN事件。如果连接失败,我们会得到EPOLLIN、EPOLLOUT、EPOLLERR和EPOLLHUP事件。

8、accept函数调用时,三次握手是否已经完成?

connect函数用于从listen监听队列中接受一个连接。

函数原型:

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);

注意:connect的作用只是从监听队列中取出连接,不论连接处于何种状态,也不关心网络状态的变化。

因此,accept函数调用的时候,三次握手已经完成。accept根本不参与三次握手。

9、如何实现半关闭状态?

close关闭连接只是将fd的引用计数减一,使用shutdown关闭socket是直接终止连接。

#include <sys/socket.h>
int shutdown(int sockfd, int howto);

其中,howto决定了shutdown的行为,具体值的含义如下:

含义
SHUT_RD关闭sockfd读的一半。缓冲区的数据都会被丢弃。
SHUT_WR关闭sockfd写的一半。缓冲区的数据会在真正关闭之前全部发送出去,应用程序不可以对socket文件描述符执行写的操作。即进入半关闭状态。
SHUT_RDWR同时关闭读写。

10、select函数的第一个参数怎么设置? select函数的超时参数如果设置为NULL是什么行为?

select的作用是在一段指定的时间内,监听用户感兴趣的文件描述符上的可读、可写、异常等事件。select函数原型:

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
                fd_set *exceptfds, struct timeval *timeout);

第一个参数指定被监听文件描述符总数。通常被设置为select监听的所有文件描述符中最大加一,因为文件描述符是从0开始计数的。

如果给timeout设置为NULL,select将会一直阻塞,直到某个文件描述符就位。

以上是关于手把手写C++服务器(38):面试必背!Linux网络socket编程必会十问!的主要内容,如果未能解决你的问题,请参考以下文章

手把手写C++服务器(17):自测!TCP协议面试经典十连问

手把手写C++服务器(17):自测!TCP协议面试经典十连问

手把手写C++服务器(33):Linux常用命令合集

手把手写C++服务器(21):Linux socket网络编程入门基础

手把手写C++服务器:Linux四大必备网络分析工具

手把手写C++服务器:Linux四大必备网络分析工具