手把手写C++服务器(38):面试必背!Linux网络socket编程必会十问!
Posted 沉迷单车的追风少年
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手写C++服务器(38):面试必背!Linux网络socket编程必会十问!相关的知识,希望对你有一定的参考价值。
本系列文章导航: 手把手写C++服务器(0):专栏文章-汇总导航【更新中】
目录
1、说一下客户端和服务端socket建立连接和关闭连接的过程
4、阻塞模式下,send和recv函数行为是什么样子的?非阻塞模式下send/recv的返回值分别是什么?
5、客户端发起连接时,如何主动指定通过本地某个端囗号去连接?bind函数如果端口号设置为0是什么行为?
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协议面试经典十连问