(已拿offer)(已离职)
UNIX域套接字用于同一台pc上运行的进程之间通信,它仅仅复制数据,不执行协议处理,不需要增加删除网络报头,无需计算校验和,不产生顺序号,无需发送确认报文。
unix域套接字地址结构如下定义:
#include
struct sockaddr_un {
sa_family_t sun_family;/* AF_LOCAL */
char sun_path[104];/* 以空字符结尾的字符串 */
}
没有端口和IP地址等信息
tcp使用connect函数,经过三次握手建立连接
UDP可以使用,在UDP收发数据有两种方式:
UDP socket使用connect仅仅是指定了唯一的IP地址和端口号,没有三次握手。这样做的目的是限制socket仅能与一个对端交换数据报。
可以使用read或recv,recv函数的声明如下
/* Read N bytes into BUF from socket FD.
Returns the number read or -1 for errors.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t recv (int __fd, void *__buf, size_t __n, int __flags);
返回值有:
while(1)
{
cnt = (int)recv(m_socket, pBuf,RECVSIZE, 0);
if( cnt >0 )
{
//正常处理数据
}
else
{
if((cnt<0) &&(errno == EAGAIN||errno == EWOULDBLOCK||errno == EINTR))
//这几种错误码,认为连接是正常的,继续接收
{
continue;//继续接收数据
}
break;//跳出接收循环
}
}
常见的错误码errno有
在功能上,read/write是recv/send的子集。read/wirte是更通用的文件描述符操作,而recv/send在socket领域则更“专业”一些。
如果有如下几种需求,则read/write无法满足,必须使用recv/send:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xuWfm0OF-1582258488789)(https://blog.jiar.vip/2017/08/24/TCP%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B%E7%AE%80%E4%BB%8B/tcp_hand_wave_detail.png)]
TIME_WAIT 状态,超时时间占用了 2MSL(Maximum segment lifetime) ,在 Linux 上固定是 60s
有这个状态是两方面的原因
一个数据报在发送途中或者响应过程中有可能成为残余的数据报,因此必须等待足够长的时间避免新的连接会收到先前连接的残余数据报,而造成状态错误。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-19RImwR0-1582258488790)(http://blog.qiusuo.im/images/duplicate-segment.png)]
确保被动关闭方已正常关闭
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XS74ohqW-1582258488790)(http://blog.qiusuo.im/images/last-ack.png)]
但是这样也造成了主动关闭方进入 TIME-WAIT 状态后,无论对方是否收到 ACK ,都需要等待 60s 。耗费内存、CPU及端口。为了解决这个问题,TCP协议推出了一个扩展,在 TCP Header 中可以添加2个4字节的时间戳字段,第一个是发送方的时间戳,第二个是接受方的时间戳。这样就可以避免上面两种情况。
关于TIME_WAIT数量太多。从上面的描述我们可以知道,TIME_WAIT是个很重要的状态,但是如果在大并发的短链接下,TIME_WAIT 就会太多,这也会消耗很多系统资源。只要搜一下,你就会发现,十有八九的处理方式都是教你设置两个参数,一个叫tcp_tw_reuse,另一个叫tcp_tw_recycle的参数,这两个参数默认值都是被关闭的,后者recyle比前者resue更为激进,resue要温柔一些。另外,如果使用tcp_tw_reuse,必需设置tcp_timestamps=1,否则无效。
epoll是实现I/O多路复用的一种方法,有水平触发(level trigger,LT,默认)和边缘触发(edge trigger,ET)两种工作模式,区别在于两种模式的返回就绪状态的时间不同。
epoll之所以高效,是因为epoll将用户关心的文件描述符放到内核里的一个事件表中,而不是像select/poll每次调用都需要重复传入文件描述符集或事件集。比如当一个事件发生(比如说读事件),epoll无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入就绪队列的描述符集合就行了。
new
/delete
是C++操作符,malloc
/free
是库函数new
自行计算需要空间的大小,malloc
需要指定大小new
从自由存储区分配内存,malloc
从堆上分配内存new
在分配内存时调用构造函数,delete
在释放内存时调用析构函数,malloc
只分配内存不做初始化new
抛出bac_alloc异常,malloc
则返回NULL不能,struct对象由于内存对齐会有内存间隙,所以就算所以成员变量都相等,内存比较还是得到不相等的结果。
我觉得可以先memset初始化之后再使用对象,然后就可以用memcmp来对比。(有指针的话不行)
或者选择重载==运算符,一一比较所有成员变量是否相等
如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。
C调C++函数:
// C++ code:
extern "C" void f(int);
void f(int i){...}
混合调用时,在C++的.h和.cpp文件中写入
#ifdef __cplusplus
extern "C" {
#endif
//一段代码
#ifdef __cplusplus
}
#endif
malloc
定义全局变量,其他文件extern声明该变量
不能使用值来传递
如果拷贝构造函数中的参数不是一个引用,即形如CClass(const CClass c_class),那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数的参数必须是一个引用。
不是,算法书上有介绍一个双递归Ackerman函数就没有非递归方式的定义
A ( 1 , 0 ) = 2 A ( 0 , m ) = 1 A ( n , 0 ) = n + 2 A ( n , m ) = A ( A ( n − 1 , m ) , m − 1 ) A(1,0)=2\\ A(0,m)=1\\ A(n,0)=n+2\\ A(n,m)=A(A(n-1,m),m-1) A(1,0)=2A(0,m)=1A(n,0)=n+2A(n,m)=A(A(n−1,m),m−1)
但是理论上所有递归可以改写成循环,也可以避免栈溢出
int main(int argc, char const *argv[])
{
cout << argv[0] << endl;
return 0;
}
void my_printf(char *val, ...)
{
int ch;
va_list arg;
va_start(arg, val);
while (*val != '\0')
{
switch (*val)
{
case '%': //遇到%执行switch case语句
{
if (*(val + 1) == 'c') //输出字符
{
ch = va_arg(arg, char);
putchar(ch);
val++; //指针变量向下偏移一个单位
}
else if (*(val + 1) == 'd')
{
ch = va_arg(arg, char); //输出整形
printd(ch);
val++;
}
else if (*(val + 1) == 's') //输出字符串
{
char *p = va_arg(arg, char *);
while (*p != '\0')
{
putchar(*p);
p++;
}
val++; //指向头一变量的下一个字符
}
else
putchar('%');
break;
}
default:
{
putchar(*val);
break;
}
}
val++;
}
va_end(arg);
}
struct A
{
char a;
long b;
char c;
double d;
};
struct B
{
char a;
char c;
long b;
double d;
};
sizeof(A)=24,sizeof(B)=16
在Linux下两者没有区别,换行模式为\n
但是在Windows下换行模式为\r\n
,若使用文本模式打开,系统会将所有\r\n
转换为\n
,写入时则将所有\n
转换为\r\n
写入,如果以二进制读写就不会发生这样的转换。
所以以
斐波那契数列:f[n]=f[n-1]+f[n-2]
可以用递归或循环,也可以用公式
F ( n ) = 1 5 ∗ ( ( 1 + 5 2 ) n + 1 − ( 1 − 5 2 ) n + 1 ) F(n)=\frac{1}{\sqrt{5}}*((\frac{1+\sqrt{5}}{2})^{n+1}-(\frac{1-\sqrt{5}}{2})^{n+1}) F(n)=51∗((21+5)n+1−(21−5)n+1)
如果查询次数很少则用单源最短路Dijkstra,查询次数很多则用Floyd
贪心。每次选择不在集合中的最近的点加入集合,然后对其他不在集合中的点进行松弛操作,直到所有点都加入集合
int arr[maxn];
int n;
int bs(int x)
{
int lo=0,hi=n-1,mid;
while(lo<=hi)
{
mid=lo+((hi-lo)>>1);
if(arr[mid]==x)
return mid;
if(arr[mid]>x)
hi=mid-1;
else
lo=mid+1;
}
return -1;
}
int的取值范围是 − 2 31 至 2 31 − 1 -2^{31}至2^{31}-1 −231至231−1,我们可以连续申请 2 32 b i t = 2 30 b y t e = 1 G 2^{32} bit = 2^{30} byte = 1G 232bit=230byte=1G内存,这样,每个bit都可以对应一个int数字,将这1G内存初始化为0,接下来每次处理一个数字,就给它所对应的bit设为1,查询时就只要看其对应的bit值,当然0会有两个bit对应所以特殊处理一下。
正向考虑每分钟出现车的概率:
P = p + ( 1 − p ) ∗ p + ( 1 − p ) ∗ ( 1 − p ) ∗ p P=p+(1-p)*p+(1-p)*(1-p)*p P=p+(1−p)∗p+(1−p)∗(1−p)∗p
反向考虑:
P = 1 − ( 1 − p ) 3 P=1-(1-p)^3 P=1−(1−p)3
如果不考虑随机化,每次选择当前范围的第一个数作为标杆,然后再将这个范围的所有比它小的数放到他左边,大的放到他右边,由这个标杆的现在位置划分出两个范围,分别对这两个范围的数再重复这样的操作,直到范围大小为1
DP
堆是一颗完全二叉树,每次建堆的完成后,堆顶(根节点)为最小值,将堆顶取出,把二叉树的最后一个节点移到根节点,再次建堆,重复此过程直到堆的大小为1
第一次建堆是从最后一个非叶子节点向前遍历,以后都从根节点
建堆的过程:由此节点开始,对比左右子节点,若有比根节点大的,则交换这两个节点,并从被交换的节点继续迭代
BFS