什么是字节序?
可参考这篇文章:数据存储:小端模式和大端模式——终于搞明白了!!!-CSDN博客https://blog.csdn.net/weixin_45633061/article/details/117421452在计算机中存储数据是以字节为最小单位的,而地址有高低之分,存储的数据也有高低位之分
在一些计算机中,将高位字节存储在高位地址空间中,低位字节存储在低位地址空间中,这是小端存储
在另一些计算机,将高位字节存储在低位地址空间中,低位字节存储在高位地址空间中,这是大端存储
而一般情况下,我们的PC采用小端存储,网络通信中采用大端存储,因此我们需要对数据进行转化。
Linux字节序转换函数
主机字节序<->网络字节序:
小端转大端:inet_pton(ip版本,需要转换的那个小端字符串,转换后的大端数据存放的地址)
成功则返回1
大端转小端:inet_ntop(ip版本,需要转换的那个大端字符串,转换后的小端数据存放的地址,第三个参数的大小sizeof(dst))
成功则返回1
流式传输:连接通道类水流,发送端和接受端处理数据的速度,在连接过程中可以一直改变
socket通信过程
补充:
ip用来定位主机,端口号用来定位主机的某一个进程
因此,socket需要绑定至主机的某个端口才能使用。
服务器端有两类文件描述符socket
一类只能用来监听,一类用来通信
客户端只有一类socket文件描述符,用了通信
socket文件描述符与缓存的关系
一个文件描述符指向两个缓冲区,一个读缓存区,一个写缓存区
当我们需要发送数据时,利用write函数(其参数有,操作的socket,要写入的数据,要写入的数据的大小),将数据写入写缓存区,根据参数列表我们便能看出一些端倪,要想写入数据,就得找到写缓存区,要想找到写缓存区,就得找到对应的socket文件描述符,因为前面说到了,一个文件描述符指向两个缓冲区,一个读缓存区,一个写缓存区。
当我们需要接受数据时,也是同样的道理。
服务器端-连接准备
socket函数
socket(ip版本,传输方式:流式/报式,协议)
成功返回0,错误返回-1。socket系列函数有个规律:错误则返回-1
bind函数
bind(用于监听的文件描述符,要绑定的IP和端口的结构体的地址,第二个参数结构体的大小)
成功返回0,错误返回-1。
注意绑定时ip和端口必须是大端
这里我们常用sockaddr_in来初始化,然后再强制转换为sockaddr传给bind函数
上述的结构体如下:
sockaddr_in的源代码
listen函数
listen(用于监听的文件描述符,listen一次处理的最大连接数(不超过128) )
accept函数
accept(用于监听的文件描述符,存储发送方ip和端口的结构体的地址,存放第二个结构体大小的地址)
这里需注意,第一个参数 socket(文件描述符) 用于跟对方主机进行连接
注意第2,3个参数为地址,我们需要给出一个这两地址
连接成功后,accept会把对方主机的sockaddr数据写入我们给的地址
因此第2,3个参数其实是用来接收数据的,称他为传出参数(利用值传递,引用传递达成这个目的)
accept的返回值为一个用于通信的socket(文件描述符),若错误则会返回-1
客户端-连接准备
socket函数
socket客户端只需要初始化一个socket文本描述符,用于通信
connect函数
connect(用于通信的socket文本描述符,目标主机的IP端口的结构体,第二个参数指向的内存的大小)
这里的目标主机就需要我们初始化了。
观察发现,客户端并没有bind这个流程,这是为什么呢?
其实很好解释
首先,客户端当然也绑定了端口号,只不过是系统替我们绑定了,不需要我们手动操作
那为什么服务器端需要绑定呢?
这你就你需要理解,客户端和服务器之间的区别
服务器端是服务方,是被动连接的一方,他需要先开启,再确定好端口号,然后开始监听
当有客户端连接时,他便提供服务。客户端是主动连接的一方,因此他需要服务器的ip和端口号,若服务器没有一个固定的端口号,客户端怎么找?
做个比喻
服务器是一个邮箱,若其他人要投递,则邮箱必须先确定,邮箱在哪个小区(ip地址),门牌号是多少(端口号),然后其他人才能投递过来。
而客户端就相当于投递信件的人,投递方不需要一个固定的位置,在哪都可以投递,因此端口号是随机分配的。
当然在服务器端接收到数据之后,便知道客户端的ip和端口了
好比你收到信件之后,就知道对面的地址了。
客户端-服务器 连接完成,实现通信
通过以上的函数,客户端和服务器便能实现连接了,接下来是关于通信的函数
有两组函数,send和recv其实是write和read的封装
发送:write(), send()
参数:
第一个参数为用于通信的socket文件描述符;第二个参数为指针,指向一片内存空间(其实就是tcp滑动窗口那的缓存),同时注意细节,这里加了const修饰符,代表我只是读取,不会改变这片内存的内容;第三个参数为发送的字符串的大小。
send有4个参数,第4个参数我也不知道什么意思,老师说填0就可以了。
返回值:
>0:发送的字符串的大小,与第三个参数len相同
==-1:发送失败
接受:read(), recv()
参数:
第一个参数为用于通信的socket文件描述符;第二个参数为指针,指向一片内存空间(其实就是tcp滑动窗口那的缓存),同时注意细节,这里没有加const修饰符,因为我要向这篇内存中写入数据,需要改变这片内存的内容;第三个参数为参数2指向的内存的大小。
recv有4个参数,第4个参数,依旧填0就可以了。
返回值:
>0:实际接受到的字节数
==0:对方断开连接
==-1:接收发生了错误
断开连接
若需要断开连接,客户端关闭socket即可
注意,一方关闭socket即可断开连接,一般是由客户端断开连接
因为服务器一般是一直运行的,不会关闭socket
关闭socket
通信完毕后,就需要调用close函数了。
close(int fd)
参数为要关闭的文件描述符对应的socket
成功返回0,失败则返回-1
以上内容整理自教学视频——01-概述_哔哩哔哩_bilibili爱编程的大丙