计算机网络

1.协议

通信双方需要遵守的规则。

范式:进行数据库设计的时候需要遵循的规则

2.OSI七层模型

d8204aeb41d25d43f9b33209d3af9328.png

3.协议格式

每一层的协议格式以了解为准,如果给了包,给了格式,要分析出对应的位置对应的是什么。

4.TCP协议

TCP协议是一个传输层的协议,面向连接的协议,可靠的协议,全双工的协议,字节流的协议,可以进行流量控制的协议

三次握手

e44e75685cc066a87ea9ecf881950edc.png

四次挥手

ad90f8b8e3b19fd2684176bd9f1134cf.png

查看网络状态

1
netstat -apn | grep xxxx

xxxx表示端口号

状态迁移图(没太懂)

8e40d98283386bfb0300f1fe4ff8ef2e.png

网络编程

1.基础

在网路环境中,可以通过IP找到对应的主机,但是要找到进程,还需要加上端口号,所以就需要用到ip加端口号的组合

2.字节序

数据在本机存储会有:主机字节序

数据在网络传输时候会有:网络字节序

引出来会有大端存储和小端存储的问题

主机字节序:小端存储:低位存储在低地址处

网络字节序:大端存储:高位存储在低地址处

1
对应的函数

网络编程相关八股文

1、TCP链接建立的过程需要三次握手,为什么?

TCP建立的过程需要三次是因为:

建立三次握手是为了确保双方都能够正确的收发数据。

具体步骤:(1).第一次握手:客户端发送连接请求报文,包含客户端自己的序列号SYN,并进入SYN_SENT状态,等待服务端确认

​ (2).第二次握手:服务端收到客户端的请求之后,如果同意连接,会发送一个确认报文ACK,同时还包含服务端自己的序列号SYN,进入SYN_RCVD状态

​ (3)第三次握手,客户端收到服务端的确认后,也发送一个确认报文段ACK,确认序列号,双方进入ESTABLISHED状态,连接建立完成

2、TCP链接断开的过程需要四次挥手,为什么?

TCP断开连接需要四次挥手是因为在数据传输结束后,双方都希望能够确认对方已经不再发送数据,这样做是为了确保双方都能确认批次的关闭意图,并且在关闭后的一段时间内仍然能够处理可能在网络中滞留的延迟数据包:

具体步骤:(1)第一次挥手:客户端发送一个带有FIN标志的报文段,表示客户端已经没有数据要发送了,进入FIN_WAIT_1状态

(2)第二次挥手:服务端收到客户端的FIN后,发送一个确认报文段ACK,表示已经接收到了客户端的关闭请求,,但自己可能还有数据需要发送,进入CLOSE_WAIT状态

(3)第三次挥手:服务端确认自己的数据已经发送完毕后,发送一个带有FIN标志的报文段给客户端,表示服务端也没有数据要发送了,进入LAST_ACK状态

(4)第四次挥手客户端收到服务器端的FIN后,发送一个确认报文段(ACK),进入TIME_WAIT状态,等待2MSL(最长报文段寿命)后,进入CLOSED状态

3、 关闭链接时,服务器端可不可以主动断开链接?为什么?

关闭连接时,服务器端是可以主动断开连接的,TCP连接的关闭过程中,任何一方都可以主动发起关闭请求,另一方收到后会响应,

4、为什么需要TIME_WAIT状态,该状态可以删除吗?

需要TIME_WAIT状态,该状态一般不能被删除因为:

TIME_WAIT状态在TCP连接的关闭过程中起重要的作用:

(1)确保在网络中延迟的最后一个ACK报文段能够被端正确接收

(2)防止出现之前连接的残留报文段被新连接误认为时属于自己的报文段

(3)允许老的连接信息在网络中完全消逝,以确保不会与新的连接产生混淆

5、TCP协议和UDP协议有啥区别?

TCP与UDP的区别是,TCP是可靠传输,UDP是不可靠传输

TCP是一种面向连接的,可靠的基于字节流的传输协议,提供错误检测、流量控制和拥塞控制等机制,适用于需要数据完整性和顺序性的应用,如HTTP,FTP

UDP是无连接的,不可靠的传输协议,数据以数据报的形式发送,没有拥塞控制和流量控制,适用于实时应用和传输速度较为关键的场景,如DNS,视频流等

6、网络IO复用模型有哪些?它们之间的异同是什么?

常见的网络IO复用模型有:select poll epoll

异同点:

效率:在连接数较大时,epoll通常比select和poll更高效

可扩展性:epoll支持更大的并发连接数,适合并发环境

跨平台型:select和poll在多平台上都有支持,而epoll更依赖特定的操作系统

7、网络IO模型有哪些?

阻塞IO,非阻塞IO,IO多路复用,信号驱动IO,异步IO

主要区别在于应用程序如何等待IO操作的完成已经处理IO事件的方式

8、什么是同步、异步?什么是阻塞、非阻塞?

同步:发起一个操作后,必须等待操作完成后才能继续后续操作

异步:发起一个操作后,不必等待操作完成,可以同时做其他操作,当操作完成后会得到通知或回调

阻塞:调用者发起一个操作后,调用不会立即返回,而是一直等待操作完成后才返回

非阻塞:调用者发起一个操作后,调用会立即返回,无序等待操作完成,可以通过轮询或者回调等方式来获取操作完成的状态或结果

Reactor V1

类图

79e4ff01c3bc8baa1f265e40e45d865c.png

Socket.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef SOCKET
#define SOCKET
class Socket
{
public:
Socket() ;
explicit Socket(int fd);
~Socket() ;
int getFd();

Socket(const Socket & rhs) = delete ;
Socket & operator=(const Socket & rhs) = delete ;

private:
int _fd;
};

#endif

Socket.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "Socket.hpp"
#include <my_header.h>

Socket::Socket(){
_fd = socket(AF_INET,SOCK_STREAM,0);
if(_fd < 0){
perror("_fd error");
return;
}
}


Socket::Socket(int fd)
:_fd(fd)
{}

Socket::~Socket() {
close(_fd);
}

int Socket::getFd(){
return _fd;
}

InetAddress.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef INETADDRESS
#define INETADDRESS
#include <string>
#include <my_header.h>
using namespace std;

class InetAddress
{
public:
InetAddress(const string & ip,unsigned short port);
InetAddress(const struct sockaddr_in &);
~InetAddress();
string IP();
unsigned short port();

struct sockaddr_in * getInetAddress();


private:
struct sockaddr_in _addr;
};

#endif

InetAddress.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "InetAddress.hpp"

InetAddress::InetAddress(const string & ip,unsigned short port)
{
memset(&_addr,0,sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_port = htons(port);
_addr.sin_addr.s_addr = inet_addr(ip.c_str());
}


InetAddress::InetAddress(const struct sockaddr_in & addr)
:_addr(addr)
{}


InetAddress::~InetAddress(){}


string InetAddress::IP(){
return string(inet_ntoa(_addr.sin_addr));
}


unsigned short InetAddress::port(){
return ntohs(_addr.sin_port);
}


struct sockaddr_in * InetAddress::getInetAddress(){
return &_addr;
}

Acceptor.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#ifndef ACCEPTOR
#define ACCEPTOR
#include "InetAddress.hpp"
#include "Socket.hpp"


class Acceptor
{
public:
Acceptor(const string &ip, unsigned short port);
~Acceptor() {}

int accept();
void ready();
private:
void setReuseAddr();
void setReusePort();

void bind();
void listen();



private:
Socket _sock;
InetAddress _addr;
};

#endif

Acceptor.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include "Acceptor.hpp"

Acceptor::Acceptor(const string &ip, unsigned short port)
:_sock()
,_addr(ip,port)
{}

void Acceptor::setReuseAddr(){
int opt = 1;
int ret = setsockopt(_sock.getFd(),SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(-1 == ret){
perror("setsockopt ip error");
return;
}
}


void Acceptor::setReusePort(){
int opt = 1;
int ret = setsockopt(_sock.getFd(),SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(-1 == ret){
perror("setsockopt ip error");
return;
}
}

void Acceptor::bind(){
int ret = ::bind(_sock.getFd(),(struct sockaddr *)_addr.getInetAddress(),sizeof(struct sockaddr));
if(-1 == ret){
perror("bind error");
return;
}
}

void Acceptor::listen(){
int ret = ::listen(_sock.getFd(),128);
if(-1 == ret){
perror("listen error");
return;
}
}

void Acceptor::ready(){
setReuseAddr();
setReusePort();
Acceptor::bind();
Acceptor::listen();
}

int Acceptor::accept(){
//接受客户端发来的连接请求
int connetFd = ::accept(_sock.getFd(),nullptr,nullptr);
if(-1 == connetFd){
perror("accept error");
return -1;
}
return connetFd;
}

TcpConnection.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef TCPCONNECTION
#define TCPCONNECTION
#include <string>
#include "SocketIO.hpp"
using std::string;


class TcpConnection
{
public:
explicit TcpConnection(int fd);
~TcpConnection() {}

string receive();
void send(const string & msg);

private:
SocketIO _socketIO;
};


#endif

TcpConnection.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "TcpConnection.hpp"

TcpConnection::TcpConnection(int fd)
:_socketIO(fd)
{}

string TcpConnection::receive(){
char buf[65535] = {0};
_socketIO.readLine(buf,sizeof(buf));
return string(buf);
}

void TcpConnection::send(const string & msg){
_socketIO.writen(msg.c_str(),msg.size());//这里第三个参数用sizeof会出现乱码
//因为sizeof会多统计一个字符

}

SocketIO.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef SOCKETIO
#define SOCKETIO


class SocketIO
{
public:
explicit SocketIO(int fd);
~SocketIO() {}

int readn(char * buf,int len);

int readLine(char *buf,int len);

int writen(const char *buf,int len);

private:
int _fd;
};


#endif

SocketIO.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include "SocketIO.hpp"
#include <my_header.h>
SocketIO::SocketIO(int fd)
:_fd(fd)
{}

int SocketIO::readn(char * buf,int len){
int left = len;
char *pstr = buf;
int ret = 0;
while(left > 0){
ret = read(_fd,pstr,left);
if(-1 == ret && errno == EINTR){
//碰到中断错误,属于可恢复的错误
continue;
}else if(-1 == ret){
perror("read error");
return -1;
}else if(0 == ret){
//客户端断开连接
break;
}else{
pstr += ret;
left -= ret;
}
}

return len - left;
}

int SocketIO::readLine(char *buf,int len){
//涉及到read和recv的区别,recv可以将内核的数据拷贝一份,而read会直接将内核的数据给读出来,
//并且把内核这部分数据移除,这俩读一行,可能第一个读的长度已经包含好几行了,所以
//用read不行,会把别的行的数据误删掉,所以使用recv先拷贝一份,然后一个一个遍历
//找到\n就把之前的数据read出来
int left = len - 1;//换行符?
char *pstr = buf;
int ret = 0,total = 0;
while(left > 0){
ret = recv(_fd,pstr,left,MSG_PEEK);
if(-1 == ret && errno == EINTR){
continue;
}else if(0 == ret){
break;
}else if(-1 == ret){
perror("read error");
return -1;
}else{
for(int i = 0; i < ret; ++i){
if(pstr[i] == '\n'){
int sz = i + 1;
readn(pstr,sz);
pstr += sz;
*pstr = '\0';
//读取完一行数据
//
return total + sz;
}
}

readn(pstr,ret);
total += ret;
pstr += ret;
left -= ret;
}
}
*pstr = '\0';

return total - left;
}

int SocketIO::writen(const char *buf,int len){
int left = len;
const char * pstr = buf;
int ret = 0;
while(left > 0){
ret = write(_fd,pstr,left);
if(-1 == ret && errno == EINTR){
continue;
}else if(-1 == ret){
perror("write error");
break;
}else if(0 == ret){
break;
}else{
left -= ret;
pstr += ret;
}
}
return len - left;

}

测试文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include "TcpConnection.hpp"
#include "Acceptor.hpp"

void test(){
Acceptor acc("127.0.0.1",8888);
acc.ready();
TcpConnection tcpc(acc.accept());
cout << "连接建立!" << endl;
while(1){
cout << ">> client :" <<tcpc.receive() << endl;
tcpc.send("i am server!\n");
}
}





int main()
{
test();
return 0;
}