一、思维导图
二、模拟面试题
什么是IP地址? | IP地址是主机在网络中的唯一标识。 分为IPv4和IPv6, IPv4由4字节32位二进制数组成,通常使用点分十进制表示,例如192.168.117.85 ,其中每个十进制数的范围都在0-255. IPv6由16字节128位二进制数组成,通常使用8组6进制的数字用冒号隔开表示。 IPv6主要是为了解决IPv4分配网络地址不足的问题。 |
IP地址和MAC地址的区别 | 1、IP地址是在网络中用来标识设备的,使得数据正确接收和发送,提供了逻辑地址,使得数据包在网络中能够正确的路由;MAC地址用于在局域网内唯一标识设备的物理地址,是网络适配器(网卡)的固定地址,用于局域网内数据帧的传输。 2、IP地址主要应用于网络层,负责数据包在互联网到局域网之间的路由; MAC地址主要应用于数据链路层,是局域网内进行数据帧传输和识别接收者发送者的依据。 3、IP地址可以根据需求重新分配; MAC地址通常在设备生产中分配,固定不可改变。 |
当电脑从一个网络切换到另一个网络时,哪个地址变,哪个地址不变? | IP地址会改变,MAC地址不变。(具体如上述IP地址和MAC地址区别) |
什么是端口号? | 端口号本质上是一个两字节无符号整数,用于标识网络中特殊的服务或者进程,在网络通信中,每个通过网络传输的数据包都包含源端口号和目标端口号。 端口号的范围从0到65535,其中0到1023端口号被系统保留,用于一些知名的网络服务,如HTTP(80端口)、FTP(21端口)、SSH(22端口)、SMTP(25端口)等。1024到49151的端口号是注册端口,用于常见的应用程序或服务,49152到65535的端口号是动态或私有端口,用于临时分配给客户端程序。 |
TCP通信过程中服务器端实现流程 | 1、创建套接字:服务器端首先需要通过socket函数创建一个套接字文件用于接收客户端的连接请求。创建套接字时需要指定通信的协议族(如IPv4或IPv6)和通信类型(如TCP或UDP) 2、绑定地址和端口:服务器端需要通过系统函数 bind 将套接字绑定到一个特定的IP地址和端口号上,以便客户端能够通过这个地址和端口与服务器建立连接通信。 3、监听连接请求:服务器端通过调用 listen 函数开始监听来自客户端的连接请求。在监听状态下服务器可以接收客户端发送的连接请求并进行处理。 4、接收连接请求:当服务器接收到客户端的连接请求时,通过调用 accept 函数接受该连接请求,并创建一个新的套接字用于与客户端进行通信,通常称为连接套接字。 5、接收和发送数据:通过 accept 函数创建的连接套接字,服务器可以接收来自客户端的数据,同时服务器也可以通过连接套接字向客户端发送数据。(read/write send/recv sendto/recvfrom ) 6、关闭连接 当通信完成或客户端主动断开连接时, 服务器端需要通过调用 close 函数关闭套接字文件,释放资源并终止与客户端的通信。 |
TCP通信的客户端流程 | 1、创建套接字:客户端首先需要通过socket函数创建一个套接字文件用于接收客户端的连接请求。创建套接字时需要指定通信的协议族(如IPv4或IPv6)和通信类型(如TCP或UDP) 2、绑定地址和端口:客户端需要知道要连接的服务器的IP地址和端口号,以便客户端能够通过这个地址和端口与服务器建立连接通信。 3、发起连接请求:客户端使用创建的套接字调用连接(connect)函数,指定服务器的地址和端口号,以发起连接请求。连接请求发送到服务器后,服务器会响应确认连接,建立客户端与服务器之间的通信通道。 4、接收和发送数据:一旦连接建立成功,客户端可以通过套接字进行数据的发送和接收。同时服务器也可以通过连接套接字向客户端发送数据(read/write send/recv sendto/recvfrom ) 5、关闭连接:当客户端与服务器的通信结束时,需要关闭连接以释放资源。客户端可以调用关闭(close)函数来关闭套接字连接。 |
TCP通信和UDP通信的区别 | 1、连接or无连接: TCP是面向连接的协议,通信前需要先建立连接,然后才能进行数据传输,确保数据的可靠性和顺序性。 UDP是无连接的协议,通信不需要事先建立连接,可以直接发送数据报,但不保证数据的可靠性和顺序性。 2、可靠性: TCP提供可靠的数据传输,通过确认、重传、超时等机制来确保数据的可靠性,保证数据的完整性和顺序性。 UDP不提供数据传输的可靠性保证,数据报可能丢失、重复或乱序,需要应用层自行处理。 3、传输方式: TCP提供面向流的传输,数据以字节流的形式进行传输,保证数据的顺序和完整性,适用于需要可靠传输的场景,如文件传输、网页浏览等。 UDP提供数据报传输,数据被封装成数据报并独立传输,不保证数据的顺序和完整性,适用于实时性要求高、容忍数据丢失的场景,如音频、视频传输、实时游戏等。 4、连接管理: TCP通过三次握手建立连接,四次挥手释放连接,并维护连接状态信息,包括拥塞控制、流量控制等。 UDP不维护连接状态,每个数据报都是独立的,没有连接建立和关闭的过程。 5、效率: TCP的可靠性和连接管理带来了一定的开销,包括握手延迟、拥塞控制、流量控制等,适用于对数据完整性和顺序性要求高的场景。 UDP的无连接特性和简单性使其开销较小,适用于实时性要求高、数据丢失可以容忍的场景。 |
TCP通信中的三次握手 | 1、客户端向服务器发送同步序列编号(SYN)的数据包,表示请求建立连接,并选择一个初始序列号(ISN)。 2、服务器收到客户端的SYN请求后,会发送一个带有SYN和ACK标志的数据包作为应答,确认客户端的SYN,并选择自己的初始序列号,并对客户端的初始序列号进行确认。 3、客户端收到服务器的确认后,会发送一个带有ACK标志的数据包作为确认,表示连接建立成功。 |
TCP通信中的四次挥手 |
1、客户端已经发送完所有数据,想要关闭连接,于是发送一个带有FIN标志的数据包给服务器,表示不再发送数据。 2、服务器收到客户端的FIN后,会发送一个带有ACK标志的数据包作为确认,表示已收到关闭请求,但仍可以发送数据。 3、服务器发送完所有数据后,也想要关闭连接,于是发送一个带有FIN标志的数据包给客户端,表示不再发送数据。 4、客户端收到服务器的FIN后,会发送一个带有ACK标志的数据包作为确认,表示已收到关闭请求,连接终止。 |
UDP中是否可以使用connect函数进行连接 | 1、UDP通信中可以使用connect函数,将服务器与某个客户端建立一个唯一通道。 2、优点:传输效率高,稳定性高,数据干扰较小。 3、在服务器端使用connect与某个特定的客户端建立连接后,服务器就不再接收其他客户端的消息了。 4、如果想要断开,需要再使用一次connect函数,但是需要将地址信息结构体中的sin_addr改成AF_UNSPEC。 5、在UDP中可以多次使用connect函数与其他客户端建立连接,但是在TCP中只能进行一次连接。 6、当UDP中使用了connect与某个特定的客户端建立连接后,就可以正常使用read/write、send/recv函数完成通信。 |
三、
1、tcpforkSer
#include<myhead.h>
#define SER_PORT 8888 //服务器端口号
#define SER_IP "192.168.241.128" //服务器IP
//定义信号处理函数
void handler(int signo)
{
if(signo == SIGCHLD)
{
//以非阻塞的形式回收僵尸进程
while(waitpid(-1,NULL,WNOHANG) > 0);
}
}
/******************主程序*********************/
int main(int argc, const char *argv[])
{
//将子进程退出时发射的SIGCHLD信号绑定到信号处理函数中
if(signal(SIGCHLD,handler) == SIG_ERR)
{
perror("signal error");
return -1;
}
//1、创建一个套接字
int sfd = -1;
sfd = socket(AF_INET,SOCK_STREAM,0);
//参数1:表示创建的是网络通信的套接字
//参数2:表示使用的是TCP通信协议
//参数3:参数2指定了协议,参数3填0即可
if(sfd == -1)
{
perror("socket error");
return -1;
}
printf("%d success sfd = %d\n",__LINE__,sfd); //3
//设置端口号快速重用
int reuse = 1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) == -1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");
//2、绑定IP和端口号
//2.1填充地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //地址族
sin.sin_port = htons(SER_PORT); //端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //IP地址
//2.2绑定
if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin))==-1)
{
perror("bind error");
return -1;
}
printf("%d bind success\n",__LINE__);
//3、将套接字设置成被监听状态
if(listen(sfd,128) == -1)
{
perror("listen error");
return -1;
}
//4、阻塞等待客户端的连接请求
int newfd = -1;
//定义结构体变量接受客户端地址信息结构体
struct sockaddr_in cin;//接收客户端信息结构体
socklen_t addrlen = sizeof(cin);//接收客户端结构体大小
//定义进程号变量
pid_t pid = -1;
while(1)
{
//父进程执行连接操作
if((newfd = accept(sfd,(struct sockaddr*)&cin,&addrlen)) == -1)
{
perror("accept error");
return -1;
}
printf("[%s %d]请求连接 newfd = %d\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);
//创建子进程
pid = fork();
if(pid > 0)
{
//关闭与客户端通信的套接字
close(newfd);
}
else if(pid == 0)
{
//关闭sfd
close(sfd);
//5、收发数据
char rbuf[128] = "";//接收客户端发送的信息
while(1)
{
//将容器清空
bzero(rbuf,sizeof(rbuf));//memset(rbuf,0,sizeof(rbuf));
//从套接字中读取数据
int res = recv(newfd,rbuf,sizeof(rbuf)-1,0);
if(res == 0)
{
printf("客户端已下线\n");
break;
}
printf("[%s %d]:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),rbuf);
//加个收到再回回去
strcat(rbuf," <Got it>!");
send(newfd,rbuf,strlen(rbuf),0);
printf("回复成功\n");
}
//关闭与客户端通信的套接字
close(newfd);
//退出子进程
exit(EXIT_SUCCESS);
}
}
//6、关闭服务器
close(sfd);
return 0;
}
2、udpSer
#include<myhead.h>
#define SER_PORT 6666
#define SER_IP "192.168.117.103"
#define CLI_PORT 7777
#define CLI_IP "192.168.117.103"
int main(int argc, const char *argv[])
{
//1、创建用于通信的套接字文件描述符
int cfd = socket(AF_INET,SOCK_DGRAM,0);
if(cfd == -1)
{
perror("socket error");
return -1;
}
printf("socket success cfd = %d\n",cfd);
//2、绑定IP地址和端口号
//2.1填充客户端地址信息结构体
struct sockaddr_in cin;
cin.sin_family = AF_INET;
cin.sin_port = htons(CLI_PORT);
cin.sin_addr.s_addr = inet_addr(CLI_IP);
//2.2、绑定
if(bind(cfd,(struct sockaddr*)&cin,sizeof(cin)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//3、数据收发
char wbuf[128] = "";
//填充服务器端地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr = inet_addr(SER_IP);
while(1)
{
printf("请输入>>>");
fgets(wbuf,sizeof(wbuf),stdin); //从终端获取一个字符串
wbuf[strlen(wbuf)-1] = 0; //将换行换成'\0'
//判断输入的是否为退出
if(strcmp(wbuf,"quit") ==0)
{
break;
}
//将数据发送给服务器
sendto(cfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&sin,sizeof(sin));
printf("发送成功\n");
//接收服务器发送来的消息
bzero(wbuf,sizeof(wbuf)); //清零
read(cfd,wbuf,sizeof(wbuf)-1);
printf("收到服务器的消息为:%s\n",wbuf);
}
//4、关闭套接字
close(cfd);
return 0;
}
四、项目
1、TCP机械臂测试
通过w(红色臂角度增大)s(红色臂角度减小)d(蓝色臂角度增大)a(蓝色臂角度减小)按键控制机械臂
注意:关闭计算机的杀毒软件,电脑管家,防火墙
1)基于TCP服务器的机械臂,端口号是8888, ip是Windows的ip;
查看Windows的IP:按住Windows+r 按键,输入cmd , 输入ipconfig
2)点击软件中的开启监听;
3)机械臂需要发送16进制数,共5个字节,协议如下
0xff 0x02 x y 0xff 0xff:起始结束协议,固定的; 0x02:控制机械手臂协议,固定的; x:指定要操作的机械臂 0x00 红色摆臂 0x01 蓝色摆臂 y:指定角度
#include<myhead.h>
#define SER_PORT 8888
#define SER_IP "192.168.117.85"
#define CLI_PORT 9999
#define CLI_IP "192.168.241.129"
int main(int argc, const char *argv[])
{
//1、创建用于连接的客户端套接字
int cfd = socket(AF_INET,SOCK_STREAM,0);
if(cfd == -1)
{
perror("socket error");
return -1;
}
printf("socket sucess cfd = %d\n",cfd); //3
//设置端口号快速重用
int reuse = 1;
if(setsockopt(cfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) == -1)
{
perror("socket error");
return -1;
}
printf("端口号快速重用成功\n");
//2、绑定端口号和IP地址(非必须)
//2.1填充客户端地址信息结构体
struct sockaddr_in cin;
cin.sin_family = AF_INET;
cin.sin_port = htons(CLI_PORT);
cin.sin_addr.s_addr = inet_addr(CLI_IP);
//2.2、绑定端口号和IP
if(bind(cfd,(struct sockaddr*)&cin,sizeof(cin)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//3、连接服务器
//3.1、填充要连接服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //地址族
sin.sin_port = htons(SER_PORT); //服务器端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //服务器IP地址
//3.2、连接服务器
if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
{
perror("connect error");
return -1;
}
printf("连接成功\n");
//定义控制机械臂的容器
char buf_red[5] = {0xff,0x02,0x00,0x00,0xff}; //红色臂容器
unsigned char buf_bule[5] = {0xff,0x02,0x01,0x00,0xff}; //蓝色臂容器
//将数据发送给服务器初始化
send(cfd,buf_red,sizeof(buf_red),0);
sleep(1); //防止沾包
send(cfd,buf_bule,sizeof(buf_bule),0);
//定义一个读取数据的结构体变量
struct input_event ie;
//打开 /dev/input/event1文件
int fd = open("/dev/input/event1",O_RDONLY);
if(fd == -1)
{
perror("open error");
return -1;
}
//收发数据
char ctrl = 0; //用户输入的控制键
while(1)
{
// scanf("%c",&ctrl); //用户输入键
// getchar();
//从文件中读取当前文件中的数据
read(fd,&ie,sizeof(ie));
//输出从键盘事件文件中读取的数据
//printf("ie.type = %d,ie.code = %d,ie.value =%d\n",ie.type,ie.code,ie.value);
//对输入字符进行多分支选择
switch(ie.code*ie.value)
{
case 17:
{
//红色臂角度增大
buf_red[3] +=2;
if(buf_red[3] >= 90)
{
buf_red[3] = 90;
}
//将数据发送给服务器
send(cfd,buf_red,sizeof(buf_red),0);
}
break;
//红色臂角度减小
case 31:
{
//红色臂角度减小
buf_red[3] -=2;
if(buf_red[3] <= -90)
{
buf_red[3] = -90;
}
//将数据发送给服务器
send(cfd,buf_red,sizeof(buf_red),0);
}
break;
case 30:
{
//蓝色臂角度增大
buf_bule[3] +=2;
if(buf_bule[3] >= 180)
{
buf_bule[3] =180;
}
//将数据发送给服务器
send(cfd,buf_bule,sizeof(buf_bule),0);
}
break;
case 32:
{
//蓝色臂角度减小
buf_bule[3] -=2;
if(buf_bule[3] <= 0)
{
buf_bule[3] = 0;
}
//将数据发送给服务器
send(cfd,buf_bule,sizeof(buf_bule),0);
}
break;
case 16:goto END;
}
}
END:
//关闭套接字文件
close(cfd);
return 0;
}
2、基于UDP的TFTP文件传输
1)tftp协议概述
简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输
特点:
是应用层协议
基于UDP协议实现
数据传输模式
octet:二进制模式(常用)
mail:已经不再支持
2)tftp下载模型
TFTP通信过程总结
- 服务器在69号端口等待客户端的请求
- 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
- 每个数据包的编号都有变化(从1开始)
- 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
- 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。
3)tftp协议分析
差错码:
0 未定义,差错错误信息
1 File not found.
2 Access violation.
3 Disk full or allocation exceeded.
4 illegal TFTP operation.
5 Unknown transfer ID.
6 File already exists.
7 No such user.
8 Unsupported option(s) requested.