最近两次面试情况都不好,尤其是腾讯。题目大部分都没有答上来,尤其是有些题目并不是很难。但是也有一些很麻烦的题目,一时半会也想不到特别好的解决方案。
接收端:
Q:TCP和UDP区别,在用TCP的时候发现什么问题
Q:用UDP怎么判断数据收到了?
- 包中增加编号,发送侧将发送过的包序号保存,接收侧将接收到的包的序号返回,发送方按照返回的序号将保存的序号清空,同时使用定时器,一段时间内没有返回的包认为丢失,进行重发。
- UDT
Q:用UDP无法用多线程怎么办?即UDP的并发处理。
并发的常见思路是使用多线程。服务器在读取一个新请求之后,可以交由一个线程处理,该线程在处理之后直接将响应内容发给客户端。另一方面,udp服务器和多个客户端交互,但是却没有多个socket。典型的解决方案是,服务器为每个客户端创建一个新的socket,并绑定一个新的端口。客户端以后就通过这个新的socket与服务器通信,获得响应。总结来说,udp并发服务器,针对多个客户端,可以创建多个socket;针对多个请求,可以使用多线程(线程池)进行处理。(我很怀疑这种做法,UDP)
基于asio(他内部是IOCP),在结构上如下实现:
1)对tcp,accept使用cpu数目个线程进行accept。
2)对udp以及tcp accept后的线程,使用统一的线程池处理。比如,对udp:这些线程同时投递很多async_recv_from。然后有了IOCP的事件通知后,进行工作处理,处理完成后直接发送,发送完成后再次进入等待async_recv_from。如果中间某个线程处理时,需要读文件或者sleep,此时如果还有其他网络数据需要处理,则自动由其它线程处理。 同时运行的线程数设置是cpu数目,但如果有人sleep,则可以有备选线程进入处理。erlang
如果是在linux平台的话,可以考虑多进程,大概思路:主进程bind一个端口,然后启动子进程,在子进程中完成recvfrom和sendto操作。如果要想提高吞吐量只需增加子进程个数就可以了,单台机器不够可以扩展到多台机器用域名来平衡即可。而且这个结构还有个好处,一个任务占用时间较多的话,只是一个子进程消耗时间,并不会影响其他子进程处理其他业务。
Q: 传输协议是怎么定的?
Q: 系统review,如果在使用UDP协议下判断节点是不是崩了?
- 网关发心跳包
- 超时判断
解决方案 - 个人感觉这不是一个很好的解决方案,因为iperf是客户端检查和sever有没有断开,而netcat貌似是不具备检查是不是断开的。利用现有的工具
netcat/iperf -
netstat查看:
netstat |grep 30010
查看30010上是不是有连接。测试后,假如是用netcat建立一个udpClient,查看后就是established。如果关闭的话,就没有这一条了。
Q:怎么用shell指令查看连接数?
Q:服务器最多有多少连接?是什么数量级的?1w,10w还是100w?
- port数(同一IP下)峰值为65535
- Windows 下为32762,不确定那个面试官问的是不是这个问题。
Q: 数据传的时候是大端模式还是小端模式?网络上是大端还是小端?接收端呢?
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。“小端低低”。
在网络上传输数据时,由于数据传输的两端对应不同的硬件平台,采用的存储字节顺序可能不一致。所以在TCP/IP协议规定了在网络上必须采用网络字节顺序,也就是大端模式。对于char型数据只占一个字节,无所谓大端和小端。而对于非char类型数据,必须在数据发送到网络上之前将其转换成大端模式。接收网络数据时按符合接受主机的环境接收。
大小端是由CPU而不是操作系统决定的。
The endian is a property of the CPU not the OS. An operating system can be ported to work on many hardware architectures, which means that an OS can work on different endians. ... Both Windows and the versions of Linux designed for those CPUs (such as Ubuntu and Fedora) have the same endian. 64bit has 4 bits per byte when the most significant bit of 4 bits get the most insignificant address this is big endianess, reversed would be small endianess..
LSB(least significant byte): The MSB in an 8-bit binary number represents a value of 128 decimal. The LSB represents a value of 1. In computing, the least significant bit (LSB) is the bit position in a binary integer giving the units value, that is, determining whether the number is even or odd.
Q: 为什么使用多线程而不是多进程?
- 多线程和多进程的选择和OS。对于UNIX和Linux,主要首选多进程,Win多线程。
- 进程比较稳定,但是fork一个进程处理请求往往要将主进程的很多东西 拷贝一份到fork的子进程,所以可能在并发量高的情况下,内存占用比较多
- 线程则针对高并发的环境
C++
Q: 函数里面的static和普通数据类型有什么区别?存储在哪里?
- 好奇怪哦,在函数里面的static不用初始化,如果初始化了,只会在函数第一次被调用时出现,下一次运行该程序这个值还是原来定义的那个哦,而不是重新再分配内存
- 每次函数都会改变值
- 函数运行次数也可以用这个方法
Q:vector存储在哪里?指针存储在哪里?
- vetctor数据在堆,指针在栈,指向的元素在堆中
Q:堆和栈有什么区别?堆内存和栈内存有哪些特性?
Q:用STL中队列实现栈,栈实现队列
\\java
import java.util.Stack;
/*题目要求: 用两个栈实现队列
* 思路: 每次push都push到Stack1中
* Pop的时候,先判断Stack2是不是空的,如果不是就Pop这个的
* (上次Stack1 pop出来的没弄完)
* 如果是空的话,全部将Stack1 pop出来,push进去(有点像汉罗塔)*/
public class StackToQueue {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if(!stack2.isEmpty())
return stack2.pop();
else{
while(!stack1.isEmpty())
stack2.push(stack1.pop());
return stack2.pop();
}
}
public static void main(String args[]){
StackToQueue stackQueue=new StackToQueue();
stackQueue.push(1);
stackQueue.push(2);
stackQueue.push(3);
System.out.println(stackQueue.pop());
System.out.println(stackQueue.pop());
stackQueue.push(4);
System.out.println(stackQueue.pop());
stackQueue.push(5);
System.out.println(stackQueue.pop());
System.out.println(stackQueue.pop());
}
}
\\C++ 用JAVA实现没什么意思,因为JAVA本身的LinkedList等都能删除队尾元素,也能得到队尾元素
\\C++ 队列有一点很烦人,pop时候只能pop不能顺便返回对头的值。
\\pop主要思想返回队列的尾元素,然后把剩下的全部放到另外一个队列中
#include<iostream>
#include<queue>
#include<stack>
using namespace std;
class queueToStack{
queue<int>q1;
queue<int>q2;
public:void push(int e){
q1.push(e);
}
public:int pop(){
int elem = 0;
if (q1.size() == 0 && q2.size() == 0)
return NULL;
if (!q1.empty()){
elem = q1.back();
while (q1.size() - 1 > 0){
q2.push(q1.front());
q1.pop();
}
q1.pop();
}
else{
elem = q2.back();
while (q2.size() - 1 > 0){
q1.push(q2.front());
q2.pop();
}
q2.pop();
}
return elem;
}
};
void main(){
queueToStack test;
test.push(1);
test.push(5);
cout << test.pop();
test.push(3);
cout << test.pop();
}
Q:hashmap和map是怎么实现的。
Q:内存对齐:sizeof相关,以及sizeof是干什么的:
\\No. 1: 结构体
struct node{
int a;
double b;
char c;
};\\Output: 64位系统:24=4+4+8+1+7;32位系统:4+(4+4)+1+3=16```
\No. 2: 类
class node{
int a;
int b;
char c;
};\Output: 4+4+1+3```
\\No. 3: 加上函数的类
class node{
int a;
int b;
char c;
int d(int c);
}\\Output: 不变滴```
\No. 4: 加上虚函数的类
class node{int a;
int b;
char c;
virtual int d(int c);
}\Output: 4+1+3+4```
\\No. 5: 加上自定义的类
class sensor{
char temp[33];
};
class node{int a;
int b;
char c;
}\\Output: 8+8+8+1+7+1+7```
\No. 6: union
union node{int a;
int b;
char c;
}\Output: 4```
sizeof对齐方式
为什么要内存对齐
32位和64位对齐有什么不同
总结如下:
- 内存对齐:即内存起始地址为它本身占用字节的整数倍,最后整体占用字节数为占用最大字节的变量的整数倍。
- 不同系统中, 对于64位系统,它有八个字节,而对于32位系统,是4个字节(64位char*为8字节,而32位为4字节,和寻址空间有关)。对于不同位的系统。例如64位node结构体,偏移量为0时存储a变量(4个字节),然后要存储b的时候发现偏移量为4,不是它本身的整数倍,因为给a补4个字节,再加上其本身的8个字节,这时候偏移量为16.然后c来了,16是sizeof(char)=1的整数倍,因此前面不用补。但是这时候这个结构体偏移量为17.不是max(sizeof(var))的整数倍,因此再补7个。但是因为32位系统只有四个字节,所以double是被截成两个4个字节的,所以就不太一样了。
- 内存对齐是以class中最大的那个基本类型为基准的,如果class中有自定义类型,则递归的取其中最大的基本类型来参与比较。在No.5内存块一个接一个的过来接走数据成员,一直到第5块的时候,BigData里只剩1个char了,将它放入内存块中,内存块还剩7个bytes,接下来是个int(4bytes),能够放下,所以也进入第5个内存块,这时候内存块还剩3bytes,而接下来是个double(8bytes),放不下,所以要等下一个内存快到来。因此,No.5的Data的size=33+4+(3)+8=48(64位)
- 类的普通成员、静态成员函数是不占类内存的,类中有虚函数的时候存在一个虚函数表指针,会增加4个字节。然后你再添虚函数结果还是一样了。
- 静态成员不属于类,所以sizeof不到静态成员。
- union,每次只能存取一个成员,因此就是等于对齐之后最大值。
Q: 函数调用次数统计(程序调用次数?记不清了)
- 函数外定义static,然后函数内每次递增(这种题都不会了,好搞笑哦)
操作系统
- 进程间通信方式
asdasd - 线程资源共享
其他:
游戏匹配模式设计
滴滴打车的用户如何快速匹配车主?需要用那些数据结构和算法?假如一个1w的用户,存储这些内存要占用多少?