【网络编程】一、socket编程详解


在这里插入图片描述

前言 – 知识铺垫

一、理解源IP地址和目的IP地址

​ 因特网上的每台计算机都有一个唯一的 IP 地址,如果一台主机上的数据要传输到另一台主机,那么对端主机的 IP 地址就应该作为该数据传输时的目的 IP 地址。但仅仅知道目的 IP 地址是不够的,当对端主机收到该数据后,对端主机还需要对该主机做出响应,因此对端主机也需要发送数据给该主机,此时对端主机就必须知道该主机的 IP 地址。因此一个传输的数据当中应该涵盖其IP地址目的IP地址,目的 IP 地址表明该数据传输的目的地,源 IP 地址作为对端主机响应时的目的 IP 地址。

​ 在数据进行传输之前,会先自顶向下贯穿网络协议栈完成数据的封装,其中在网络层封装的 IP 报头当中就涵盖了源 IP 地址和目的 IP 地址。而除了源 IP 地址和目的 IP 地址之外,还有源 MAC 地址和目的 MAC 地址的概念。

二、socket通信的本质

​ 首先我们需要明确的是,两台主机之间通信的目的不仅仅是为了将数据发送给对端主机,而是为了访问对端主机上的某个服务。比如我们在用百度搜索引擎进行搜索时,不仅仅是想将我们的请求发送给对端服务器,而是想访问对端服务器上部署的百度相关的搜索服务。

​ 现在通过 IP 地址和 MAC 地址已经能够将数据发送到对端主机了,但实际我们是想将数据发送给对端主机上的某个服务进程,此外,数据的发送者也不是主机,而是主机上的某个进程,比如当我们用浏览器访问数据时,实际就是浏览器进程向对端服务进程发起的请求。

在这里插入图片描述

​ 也就是说,socket 通信本质上就是两个进程之间在进行通信,只不过这里是跨网络的进程间通信。比如逛淘宝和刷抖音的动作,实际就是手机上的淘宝进程和抖音进程在和对端服务器主机上的淘宝服务进程和抖音服务进程之间在进行通信。

​ 因此进程间通信的方式除了管道、消息队列、信号量、共享内存等方式外,还有套接字,只不过前者是不跨网络的,而后者是跨网络的。

三、端口号

​ 实际在两台主机上,可能会同时存在多个正在进行跨网络通信的进程,因此当数据到达对端主机后,必须要通过某种方法找到该主机上对应的服务进程,然后将数据交给该进程处理。而当该进程处理完数据后还要对发送端进行响应,因此对端主机也需要知道,是发送端上的哪一个进程向它发送的数据请求。

  • 端口号(port)的作用实际就是标识一台主机上的一个进程。告诉操作系统,当前的这个数据要交给哪一个进程来处理。
  • 端口号是传输层协议的内容。
  • 端口号是一个 2 byte,也就是 16 位的整数。
  • 在一台主机中,一个端口号只能被一个进程占用
  • 一个进程可以绑定多个端口号,但是一个端口号不能被多个进程同时绑定
  • 由于 IP 地址能够唯一标识公网内的一台主机,而端口号能够唯一标识一台主机上的一个进程,因此用 IP地址 + 端口号 就能够唯一标识网络上的某一台主机的某一个进程。

​ 当数据在传输层进行封装时,就会添加上对应源端口号和目的端口号的信息。这时通过 源IP地址 + 源端口号 就能够在网络上唯一标识发送数据的进程,通过目的 IP地址 + 目的端口号 就能够在网络上唯一标识接收数据的进程,此时就实现了跨网络的进程间通信。

💥注意: 因为端口号是隶属于某台主机的,所以 端口号可以在两台不同的主机当中重复,但是 在同一台主机上进行网络通信的进程的端口号不能重复

四、TCP和UDP的区别

  • UDP:传输层协议、无连接、不可靠传输、面向数据报
  • TCP:传输层协议、有连接、可靠传输、面向字节流

Ⅰ. 网络字节序

一、网络中的大小端问题

计算机在存储数据时是有大小端的概念的:

  • 大端模式:数据的高字节内容保存在内存的低地址处,数据的低字节内容保存在内存的高地址处。
  • 小端模式:数据的高字节内容保存在内存的高地址处,数据的低字节内容保存在内存的低地址处。

在这里插入图片描述

​ 如果编写的程序只在本地机器上运行,那么是不需要考虑大小端问题的,因为同一台机器上的数据采用的存储方式都是一样的,要么采用的都是大端存储模式,要么采用的都是小端存储模式。但如果涉及网络通信,那就必须考虑大小端的问题,否则对端主机识别出来的数据可能与发送端想要发送的数据是不一致的。

​ 如下图,现在两台主机之间在进行网络通信,其中发送端是小端机,而接收端是大端机。发送端将发送缓冲区中的数据按内存地址从低到高的顺序发出后,接收端从网络中获取数据依次保存在接收缓冲区时,也是按内存地址从低到高的顺序保存的。

在这里插入图片描述

​ 但由于发送端和接收端采用的分别是小端存储和大端存储,此时对于内存地址从低到高为 44332211 的序列,发送端按小端的方式识别出来是 0x11223344,而接收端按大端的方式识别出来是 0x44332211,此时接收端识别到的数据与发送端原本想要发送的数据就不一样了,这就是由于大小端的偏差导致数据识别出现了错误。

​ 由于我们不能保证通信双方存储数据的方式是一样的,因此 网络当中传输的数据必须考虑大小端问题。因此 TCP/IP 协议规定,网络数据流采用大端字节序,即低地址高字节。无论是大端机还是小端机,都必须按照 TCP/IP 协议规定的网络字节序来发送和接收数据。

  • 如果发送端是小端,需要先将数据转成大端,然后再发送到网络当中。
  • 如果发送端是大端,则可以直接进行发送。
  • 如果接收端是小端,需要先将接收到数据转成小端后再进行数据识别。
  • 如果接收端是大端,则可以直接进行数据识别。

​ 在这个例子中,由于发送端是小端机,因此在发送数据前需要先将数据转成大端,然后再发送到网络当中,而由于接收端是大端机,因此接收端接收到数据后可以直接进行数据识别,此时接收端识别出来的数据就与发送端原本想要发送的数据相同了。

在这里插入图片描述

​ 需要注意的是,这些大小端的转化工作是由操作系统来完成的,因为该操作属于通信细节,不过也有部分的信息需要我们自行进行处理,比如端口号和IP地址。具体的接口函数会在下面讲到

二、为什么网络字节序采用的是大端,而不是小端❓❓❓

​ 网络字节序采用的是大端,而主机字节序一般采用的是小端,那为什么网络字节序不采用小端呢?如果网络字节序采用小端的话,发送端和接收端在发生和接收数据时就不用进行大小端的转换了。

​ 该问题有很多不同说法,下面列举了两种说法:

  • 说法一TCPUnix 时代就有了,以前 Unix 机器都是大端机,因此网络字节序也就采用的是大端,但之后人们发现用小端能简化硬件设计,所以现在主流的都是小端机,但协议已经不好改了。
  • 说法二: 大端序更符合现代人的读写习惯。

Ⅱ. 网络编程常用接口

一、套接字常用 API

​ 这里简单的列出来,下面会具体讲解它们!

#include <sys/types.h> 
#include <sys/socket.h>

// 创建套接字(TCP/UDP,客户端+服务器)
int socket(int domain, int type, int protocol);

// 绑定端口号(TCP/UDP,服务器)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 监听套接字(TCP,服务器)
int listen(int sockfd, int backlog);

// 接收请求(TCP,服务器)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

// 建立TCP连接(TCP,客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 发送信息
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, 
               			const struct sockaddr *dest_addr, socklen_t addrlen);

// 接收信息
 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

#include <unistd.h>
int close(int fd); // 用于关闭套接字文件

​ 其实我们可以看到参数中有一个结构体 struct sockaddr,这是什么呢,请看下面!

💥收发数据接口的一些区别

​ 这里提一嘴收发数据函数中的一些区别,这里以发送函数为例,也就是 send() 函数和 sendto() 函数的区别,它们都是用于通信之间传递数据的,但是它们的细节不太一样,接收数据函数也是一样的

  1. 参数数量: send() 函数接受的参数比 sendto() 函数少。具体而言,send() 只需传入套接字描述符、数据缓冲区和数据长度三个参数。而 sendto() 则额外需要指定目标地址和目标端口等参数。
  2. 使用范围:send() 函数适用于连接类型的套接字,如 TCP 套接字。它可以在已经连接的套接字上发送数据,系统会根据连接状态选择传输的路径。
  3. 目标地址: sendto() 函数适用于无连接类型的套接字,如 UDP 套接字。它需要指定数据报的目标地址和目标端口,每次发送数据时都可以指定不同的目标地址。
  4. 阻塞行为:在默认情况下,send()sendto() 都是阻塞的函数。调用这两个函数时,如果发送缓冲区没有足够的空间,则函数会阻塞等待直到能够发送数据。可以使用设置非阻塞模式或设置超时时间的方式来改变其行为。
  5. 返回值:两个函数的返回值含义略有区别。send() 函数返回实际发送的字节数量,如果发送失败则返回 -1。而 sendto() 函数返回实际发送的字节数量或者发送错误码,也可能返回 -1

​ 另外还有一个 sendmsg() 函数也是用于发送数据的,但是它比上面两个函数更加的灵活,它提供了更强大的灵活性和功能。函数原型如下:

ssize_t sendmsg
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

利刃大大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值