【Linux网络 (二)】套接字编程

一、前言

 本篇文章会介绍端口号、网络字节序等网络编程中的基本概念,以及udp/tcp套接字编程API。

二、端口号 (port)

1)port、套接字概念

 网络通信的本质不同主机上的两个进程进行间通信,并且两进程会看到同一块资源——网络。并且我们用ip是用于表示互联网中不同主机的地址;端口号(port)这是用于标识一台主机上的不同进程。所以ip + port既可以用于标识互联网中的唯一进程,我们也将ip + port称为套接字!!

2)端口号 vs 进程id

 端口号用于标识同一台主机上进程的唯一性,但进程id作用同样如此。那为啥在网络中不复用进程id知识,而是引入端口号呢?

  1. 其一是为了让网络模块和系统模块进行接藕,减低两者间的耦合度!
  2. 其二是并不是所有的进程都有通信的需求。而port专用于网络通信!

3)端口号和进程关系

  1. 一个进程可以绑定多个端口号,但一个端口号不能被多个进程绑定!

三、认识TCP/Udp协议

 我们先对TCP和UDP有一个大概的认识,在后面再详细讲解它的协议内容。

TCP协议:

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

UDP协议:

  • 传输层协议
  • 没有连接
  • 不可靠传输
  • 面向数据报

1)连接性解释

 tcp在通信前需要先建立连接,也就是大名鼎鼎的3次握手,2次挥手;而UDP通信什么都不用提前做。我们可以将tcp通信比喻成打电话时,我们在通信前需要先获得对方同意,对方同意接你电话;将tcp想象为寄快递,我只需要知道对方地址即可!

2)可靠性解释

 tcp是可靠,Udp不可靠。那生活中我们采用tcp就可以了,为啥需要Udp?

 原因在于tcp的可靠性是有代价的(数据丢包,超时重传、数据粘包等问题需要处理),而udp认为只要将即数据从传输层发给网络层就认为数据发生成功了,所以tcp通信比udp慢。并且可靠、不可靠描述的是两种协议的特点,并没有谁好谁坏。在当前网络环境中,数据丢包的概率极小。
 针对不同的场景,可以选择不同的通信方式。如果对数据可靠性要求高的场景,我们可以采用tcp,比如支付过程。反之采用udp协议,在满足需求的前提下,大大提高通信速度。比如直播,偶尔出现数据丢包,对我们影响不大就可以采用udp协议!

3)面向数据报/字节流解释

 面向数据报是指报文之间是有边界的,应用层交给UDP多长的报文,UDP原样发送,既不拆分也不合并。读端每次只读取一个完整报文!
 面向字节流是指数据向流水一样,是没有明显边界的。对于读端,读端并不关系写端写了多少次,读到的数据是否完整,而是尽可能将数据全部读取上来交给上层!

四、网络字节序

 内存中的多字节数据相对于内存地址有大端和小端之分。这也意味着数据从A主机经网络发给B主机,B主机收到的数据可能是反的。所以网络规定,所有发送到网络中的数据必须是大端!!先发出的数据是低地址,后发出的数据是高地址。

 但人为大小端数据转换非常复杂,所以系统也提高了一系列转换接口:
在这里插入图片描述

五、struct sockaddr类型介绍

 网络通信时,socket有很多类型,主要有如下3种:

  1. 域间套接字Unix socket:主要用于本主机内部通信。域间套接字通过套接字文件来进行通信,进程可以通过打开这个文件来进行读写操作,从而实现通信。
  2. 网络socket:通过ip + port来进行网络通信。
  3. 原始socket:原始套接字绕过传输层,直接通过网络层进行通信。主要用于编写一些网络工具。

 网络中套接字种类很多,理论而言,我们要为每一种套接字设计对应的一套接口。但这无疑加重程序员的负担,为了统一所有套接字API,网络通用地址类型struct sockaddr类型诞生!!
在这里插入图片描述

六、TCP 通信API

在这里插入图片描述

1)socket()解释

 socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符。应用程序可以像读写文件一样用read/write在网络上收发数据。如果socket()调用出错则返回-1。

domain(协议家族):即socket()函数采用的网络协议种类,在网络通信中一般选择AF_INET
type(套接字类型):对于tcp而言,面向字节流,选择SOCK_STREAM
protocol(协议):通常前面两个参数就明确的通信协议种类,所以一般填0

2) bind()解释

 bind 函数用于将一个套接字(socket)与特定的IP地址和端口号关联起来。(将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号)

  1. 对于服务端ip和port是固定的,但我们一般只绑定port,而ip选择任意地址。原因在于一台机器上可能存在多张网卡,即一台主机从多个ip(网卡)读取数据都应该交给上层指定端口(port)   但对于客户端而言,虽然也需要绑定ip+port,但不需要显示绑定。原因在于客户端程序很多,如果直接由用户显示绑定,可能会导致某些时刻端口号冲突,导致一些程序无法正常启动!所以客户端在首次发送信息时,OS会自动绑定。
  2. bind调用时,如果短时间内重复调用会失败,这和tcp断开连接时会进入TIME_WAIT状态有关。我们只需要通过地址复用即可,具体做法如下:
    int opt = 1; setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR| SO_REUSEPORT, &opt, sizeof(opt)); // 地址复用

2.1 setsockopt()函数

int setsockopt(int sockfd, int level, int option_name, const void *option_value, socklen_t option_len);
  • socockfd:要设置选项的套接字描述符。
  • level:指定了选项所在的协议层级。套接字层选SOL_SOCKET
  • option_name:设置的选项的名称。主要有两选项:SO_REUSEADDR允许地址和端口的复用;SO_REUSEPORT与 SO_REUSEADDR 类似,但提供了更细粒度的控制。
  • option_value需要设置的选项值。
  • option_len:option_value指针指向的变量长度。

2.2 服务端struct sockaddr结构体填充样式

在这里插入图片描述

3)listen()解释

 tcp在通信前需要先建立连接,但服务端如何得知客户端发起了请求开始建立连接呢?所以我们需要通过listen()函数将套接字设置为监听状态!!

 listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略。listen()成功返回0,失败返回-1;

4)accept()解释

 三次握手完成后, 服务器调用accept()接受连接,并返回一个新的文件描述符! 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。   addr是一个传出参数,accept()返回时传出客户端的地址和端口号;如果给addr 参数传NULL,表示不关心客户端的地址。

如何理解返回一个新的文件描述符:

 通过listen()将套接字设置为监听状态,我们可以帮监听套接字理解为饭店外进行拉客的工作人员!他的工作只是将客人拉到字节饭店即可,并不真正提高服务;而是重新叫一名服务员进行招待后,出去重新拉客。
 所以监听套接字就好比拉客的工作人员,他只是用于监听等待客户端发起连接。  而返回的新的文件描述符才是真正和客户端进行交互的。后续可客户端和服务端进行网络通信,都是通过新的文件描述符来进行!!

5)connect()解释

 客户端需要调用connect()连接服务器;connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;!!
 connect()成功返回0,出错返回-1。

6)tcp读写

 tcp客户端和服务端通信和文件一样,通过read、write进行!

7)Udp通信API

 UDp只需要将建socket文件文件描述符(本质是创建文件细节,将网卡文件打开),然后绑定ip+port即可通信。

 UDP通信消息收发通过recvfrom、sendto完成!
在这里插入图片描述
在这里插入图片描述

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小白debug~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值