网络编程实验报告
本文最后更新于:几秒前
1 linux作业 | 网络编程
1.1 作业实验环境
- 硬件:
树莓派3b+
- 系统:
Raspbian GNU/Linux 11 (bullseye)
- 编辑器与连接方式:
vscode远程ssh
- 编译器:
gcc version 10.2.1 20210110 (Raspbian 10.2.1-6+rpi1)
1.2 作业概述
通过实际运行tcp/udp实例代码,了解linux网络编程,熟悉linux网络编程基本过程。本次作业报告分为两个部分:
- TCP回环测试
- UDP回环测试将分别记录各自过程
1.3 TCP回环实验测试
1.3.1 实验效果
如上图所示,左侧为 tcp_client
端,右侧为 tcp_server
端,对应为各自所运行的终端。
- 先运行右侧
server
端,传入参数为127.0.0.1 55566
,开始阻塞等待client
端连接 - 再运行左侧
client
端,传入参数一致,此时右侧server
端打印client
端ip
与port
,结果为127.0.0.1:41292
,可见client
端程序为其套接字分配了一个随机的未被占用的端口 - 此时左侧
client
端输入消息,将会在右侧server
端显示,并且收到server
端的回传 - 左侧
client
输入quit
后,将会将其发送给server
后退出程序,同样,server
接收到quit
后也会将自己关闭 - 另外,在
server
端输入的东西将会被存储,程序没有fget
,因此在退出终端之后,终端将会读取之前输入的命令下面逐步总结各自程序的过程
1.3.2 tcp_client端过程
1.3.2.1 定义变量
int sockfd;
- socket file descriptor - 套接字描述符- 用于存储后面创建的套接字
struct sockaddr_in serveraddr;
- 存储server
端信息所用结构体- 存储地址与端口
sockaddr_in
源码注释:/* Structure describing an Internet socket address. */
- 后面暂时看不懂,手敲了一下好像有四个成员变量( #need_to_search
)
- 地址长度和缓冲区略
1.3.2.2 异常处理
- 传入参数合法与否
- 调用
socket
函数,创建套接字AF_INET
- Address Family_Internet - IPv4地址族SOCK_STREAM
- 面向流的TCP套接字(?这里的stream和NanoPB的stream是不是一个东西 #need_to_search0
- 表示自动选择协议,一般为TCP- 若创建失败返回
-1
,并设置全局变量errno
来指示发生了什么错误。通常情况下,如果连接失败,可以通过调用perror("socket error");
来输出与连接失败相关的错误消息。
- 向
serveraddr
结构体中存储成员变量sin_family
- 用于指定地址族sin_port
与sin_addr
- 端口和地址- ?为啥
sin_addr
还要.s_addr
拓展搜索:
sin_addr.s_addr
表示了struct sockaddr_in
结构体中的 IP 地址字段。这样的结构设计是为了与网络编程中的网络字节序(大端Big-endian)相一致,同时也考虑了C语言的底层数据表示。inet_addr
- 点分十进制地址转换为32位整数atoi
和htons
- ASCII to Integer 和 Host to Network Short- 一个是把阿斯克码转整形
- 一个是把16位整数转网络字节序 (不是,那为啥port为啥不像addr一样分一个s_port啊
- ?为啥
- 调用
connect
函数,根据套接字
+地址信息
向server
端发送连接请求sockfd
- 存储的套接字(struct sockaddr *)&serveraddr
- 一个指向服务器地址信息的指针- 强制类型转换:
serveraddr
是前面定义的struct sockaddr_in
结构体,该函数需要的结构体类型为sockaddr
- 拓展搜索:
serveraddr
|serveraddr_in
|serveraddr_in6
之间的关系- 不管是
serveraddr_in
|serveraddr_in6
,都是serveraddr
的一个特殊形式,其中_in
给IPv4用,_in6
给IPv6用 - 这里的
in
不是输入的意思,而是Internet
的意思 sockaddr_in
表示 “Socket Address Internet”,用于表示IPv4地址。sockaddr_in6
表示 “Socket Address Internet version 6”,用于表示IPv6地址。
- 不管是
- 拓展搜索:
- 强制类型转换:
addrlen
- 长度- 若连接失败返回
-1
,并设置全局变量errno
来指示发生了什么错误。通常情况下,如果连接失败,可以通过调用perror("connect error");
来输出与连接失败相关的错误消息。
1.3.2.3 主循环
fget
- 获取用户输入,丢到buf
里面,删掉最后的换行符'\0'
- 调用
send
函数,传入套接字 + 内容 + 内容大小 + 可选标志- 前面三个参数没啥说的,主要是可选标志:
- 0(默认):通常情况下,将最后一个参数设置为0表示不使用特殊标志,即使用默认的发送行为。
MSG_DONTWAIT
:如果将参数设置为MSG_DONTWAIT
,则send
函数将以非阻塞方式发送数据。这意味着即使套接字的发送缓冲区已满,send
也会尽力发送尽可能多的数据,并立即返回,不会阻塞程序的执行。MSG_NOSIGNAL
:这个标志通常在Linux系统上使用,它告诉操作系统不要发送SIGPIPE
信号,即使套接字的远程端关闭了连接,也不会中断程序。这在某些情况下可以防止程序因为连接断开而崩溃。- 其他特定标志:
send
函数还可以接受其他特定于操作系统和套接字类型的标志参数,这些参数可以根据需要进行设置。这些标志通常用于高级用例,例如对套接字选项的设置或特殊的网络处理需求。
- 若发送失败返回
-1
,并设置全局变量errno
来指示发生了什么错误。通常情况下,如果连接失败,可以通过调用perror("send error");
来输出与连接失败相关的错误消息。
- 前面三个参数没啥说的,主要是可选标志:
strncmp
判断是否输入了quit
,是则退出- 调用
recv
函数,与send
类似,略 printf
在终端打印server回环的消息
没了
1.3.3 tcp_server端过程
1.3.3.1 定义变量
这部分和 client
端差不多,套接字描述符多了一个 acceptfd
,用于接受客户端用的,sockaddr_in 结构体也多了一个 clientaddr
,用于存储客户端的地址信息
bzero
函数将服务端和客户端的地址信息结构体初始化为零
1.3.3.2 异常处理
- 传入参数合法与否
- 创建套接字,同上
- 向
serveraddr
结构体中存储成员变量,同上 - 调用
bind
函数,将套接字和serveraddr
绑定,以便监听客户端的连接请求 - 调用
listen
函数,将套接字设置为监听模式,等待客户端的连接请求,5
表示等待连接队列的最大长度 - 调用
accept
函数,当有新的client
连接请求后,该函数将会为这个特指的client
返回一个新的套接字(?这里我的表述是不是有点问题)(struct sockaddr *)&clientaddr
:这是一个指向clientaddr
结构体的指针,用于存储客户端的地址信息。当accept
函数接受连接时,它会将客户端的地址信息填充到这个结构体中,以便后续使用。&addrlen
是一个指向addrlen
变量的指针,它用于指定clientaddr
结构体的大小,以便accept
函数知道要填充多少字节的地址信息。addrlen
在之前的代码中已经设置为sizeof(serveraddr)
,表示这个结构体的大小。- 这两个参数可以设置为
NULL
表示不关心client
的信息
拓展搜索:这个
accept
套接字和sockfd
有什么区别,为什么要设置两个套接字?
- 首先明确,网络通信中一般服务器往往会处理很多个客户端的通信,因此需要将用于监听连接请求的套接字和用于数据传输的套接字分开,也就是说
accept
套接字和sockfd
具有不同的角色和用途,这是为了支持服务器端的并发连接处理。- 所以,
sockfd
负责监听并接受多个客户端的连接请求,而acceptfd
用于与单个客户端进行数据通信。这种分离允许服务器并发处理多个客户端的连接和通信,提高了服务器的性能和效率。当一个客户端连接时,服务器会为其创建一个独立的acceptfd
,这种模型被称为并发服务器模型,它允许多个客户端同时与服务器通信,而不会相互干扰。
- 没啥问题,上面的都跑完了就打出客户端的
IP
和port
1.3.3.3 主循环
跟 client
端稍稍有点区别
- 接收消息还是调用
recv
函数,不过注意这里使用的套接字是acceptfd
,原因上面有记录- 没东西就
perror("no data");
- 客户端叫
quit
就退出,goto
操作,关闭两个套接字。 - 上面俩特例都没有,就正常打印客户端消息,并且
strcat
之后传回去给client
一个响应
- 没东西就
没了
1.4 UDP回环实验测试
1.4.1 实验效果
实验效果跟tcp大差不差,只是右侧的 server
端的显示每一次接收到消息都会打印出地址和端口
1.4.2 udp_client端过程
跟上面tcp的过程也差不多,主要留意几个区别的地方:
- UDP(User Datagram Protocol)套接字,与TCP(Transmission Control Protocol)套接字的创建有一些关键区别:
- 套接字类型:
- TCP套接字:在TCP通信中,使用
SOCK_STREAM
类型的套接字,它是一种面向连接的、可靠的、基于流的传输协议。TCP提供了数据的有序传输、错误检测和重传机制,适用于可靠性要求较高的应用。 - UDP套接字:在UDP通信中,使用
SOCK_DGRAM
类型的套接字,它是一种面向数据包的、不可靠的传输协议。UDP不保证数据的可靠性、有序性或重传,适用于需要低延迟和较少开销的应用。
- TCP套接字:在TCP通信中,使用
- 连接性:
- TCP套接字:TCP是面向连接的协议,它要求在通信前建立连接,有客户端和服务器之间的明确连接。使用
socket
、bind
、listen
、accept
等函数来建立连接。 - UDP套接字:UDP是非连接性的协议,通信不需要事先建立连接,可以直接发送数据包。UDP通信中的套接字不需要监听和接受连接请求。
- TCP套接字:TCP是面向连接的协议,它要求在通信前建立连接,有客户端和服务器之间的明确连接。使用
- 可靠性:
- udp没啥可靠的(),主要就是快,怎么快怎么来,数据包可能会丢失,但是适用于实时要求高的应用
- 套接字类型:
1.4.3 udp_server端过程
跟tcp差不多,主要有这个区别:
- 只开了
sockfd
一个套接字其他的略过
1.5 总结
在本次udp和tcp回环实验的过程中,发现之前学习的网络编程中有一个误解:之前认为一个套接字绑定的是一个进程,以前一直以为这个“进程”指的是一个.c文件或者这个.c文件编译出的可执行文件,但是通过tcp的实验发现,貌似并不是这样。