linux之socket编程
源IP地址与目的IP地址
任何主机想要进行网络通信,首先就要拥有IP地址!因为每台主机都有网络地址
就注定了有——源IP与目的IP
消息从哪里发送——发送主机的IP就是源IP
消息要到哪里——接收消息的主机的IP就是目的IP!
在IP报头里面就包含了源IP与目的IP
端口号
当我们从数据从A主机送到B主机是我们的目的么?——不是!
因为真正通信不是这两台机器!
例如:我们平时使用抖音要进行数据通信的时候,根本不是我们的手机在和抖音所在的主机进行沟通!而是我们使用抖音这个app,而对方的主机上搭建了一个抖音的服务器 ——是这个app和抖音的服务器在进行通信(==是这两台机器上的软件在进行通信!==)
==我们真正的目的是要将一个数据从一个软件送到另一个软件——让软件进行通信!==
但是,应用层上面也有很多的协议!每一个软件都有自己特定的协议!
例如服务器主机上,也可能存在很多的服务!例如:可能一台服务器主机上,运行这抖音服务器,其他软件的服务器
我们自己的手机上,也运行着很多的软件
==那么现在我们遇到了一个问题!——数据有IP(公网)标识一台唯一的主机,可是应用层上面有如此多正在运行的进程!该如何保证A主机上的进程的数据,能够传给B主机上的特定进程呢?==
==用谁来表示各自主机上客户或者服务进程的唯一性呢?==
就好比我们再刷抖音,为什么那些视频是在抖音的app上展示,而不是在其他的软件上展示呢?
如何表示主机上运行的进程的唯一性呢?
==如果主机的唯一性能够得到保证,主机上的进程的唯一性能够保证!——那么我们就可以用这两个唯一性,来最终这两个主机上的服务来进行互相通信!==
==为了能够更好的标识一台主机上服务进程的唯一性!——我们采用端口号(port)来表示服务器进程和客户端进程==
端口号是==传输层协议==的内容!——但是可以在应用层被使用!
- 端口号是一个2字节16位的整数:
- 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理:
- P地址+端口号能够标识网络上的某一台主机的某一个进程:
- 一个端口号只能被一个进程占用。
就像是我们要去访问一个淘宝的页面!我们本地是没有这个数据的!但是我们最终还是看到了!——这个数据从哪里来?不就是对方给的吗!那么对于对方来说我们不就是在read数据吗?
我们现在要去一个网站注册,我们就要把我们的信息提交到远端的服务器上!
==我们可以知道两个主机的IP(公网)肯定是不一样的!因为要把保证全网唯一——但是port能不能重复呢?可以!==
因为port保证的是主机内部的唯一——两个进程通信的时候,只要IP不一致!哪怕端口号一样是关系的!
但是如果是在同一台机器上!那么说明IP是一样的!那么此时端口号就不能一样!
==进程已经有pid为什么还要有port呢?——我们不是已经有pid保证该进程在主机上的唯一性吗?==
如果是从技术角度,那么这是可行的!
但是系统是系统,网络是网络!——pid是由系统来指定的!
如果在网络中使用pid标识该进程的唯一性!这就注定了网络和系统是强耦合的!
万一我们进程中的pid改变了!那么网络也要跟着一起变!
所以如果单独设计就没有这个问题!
==所以首要的目的就是——将系统和网络进行解耦!==
一般我们在网络通信的时候,大多时候都是客户端主动发起请求的!
例如:我们平时使用客户端 ,正常情况是不会出现客户端自动打开的!就像我们很久没有使用了,那个客户端也不会自动打开说,你好久没有使用我了!快打开用一用
往往都是我们主动的打开它!所以一般都是客户端先发送数据,进行数据请求
而客户端发送数据有个问题,需要找到服务器进程!——这就是决定了服务器的唯一性不能有任何改变!——即IP+port不能随意改变!一旦确定下来就要一直保持不变!
就像是打警察电话,就要一直保证他是不变的!如果今天一变,明天一变!那么人们群众就很难快速的找到警察!
==所以这是不能随意改变的!——那么就不能使用轻易会改变的值!而进程的pid,进程一开一关,pid就发生了变化了!每次还都不一样!port端口号就不会随意改变!==
所以这就是为什么不使用pid的原因之二
==进程+port 我们就可以找到这台主机上对应的网络服务!==
但是我们计算机里面有很多的软件,不是所有的软件都是需要联网的!
不是所有的进程都需要提供网络服务或者请求的!但是所有的进程都是需要pid的!
==端口号是在传输层提供的功能,而传输层是属于内核的!内核里有对应的端口信息!——问题是这些端口信息是怎么样找到应用层曾经启动的某个进程呢?==
底层OS是如何根据port找到指定的进程?
每一个进程在操作系统内部都有PCB,我们假设port是一个unit16类型的数据!
也就是说现在的问题如何根据这个unit16类型的数据找到 task_struct这个PCB结构体!
操作系统是使用的是hash方案!——操作系统内部维护了一张基于端口号做key值的hash表,然后value就是对应的PCB的地址!
这样子就可以根据port找到这个进程,找到这个进程就可以找到这个进程的文件描述符表!找到这个文件描述符就可以找到这个文件对象!找到这个文件对象就可以找到文件对象的缓冲区,这样子我们就可以将数据放入这个缓冲区里面!——这就相当于把网络数据放在文件里面了!就可以如同读取文件一样!将数据读取出来!
==另外,一个进程可以绑定多个端口号;但是一个端口号不能被多个进程绑定==
为什么呢?——因为从端口号的进程必须是唯一的!这样子数据才能找到进程!
但是从多个端口号到一个进程也是可以的!
例如:我们经常听说软件后门,其实就是绑定了多个端口号,一个给我们用户用的!另一个是给另一个人,但是我们不知道!另一个人通过这个端口号获取信息或者发送指令
源端口和目的端口
传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号。就是在描述"数据是谁发的,要发给谁";
==所以我们在网络通信的过程中,IP+port表示唯一性!如果今天client将数据发送server!那么我们要不要将自己的ip和端口发送给对方呢?——因为因为我们还要发回来!==
==服务端还要给客户端传回消息!==
这就决定了,未来发送数据的时候一定会"多发"一部分数据!——以协议的形式呈现!
像是我们日常使用好像都没有见到过!那是因为写服务的和提供服务器都是同一个公司!在软件里面就内置好了ip+port!一般来说就不会再改变了!
==我们现在说的ip都是属于公网ip!==
认识TCP协议
在传输层有两种协议——一个是TCP,一个是UDP
TCP是Transmission Control Protocol 传输控制协议其特点有
- 传输层协议
- 有连接——在通信之间要先建立连接
- 可靠传输
- 面向字节流
在认识可靠之前,我们得先知道什么叫做不可靠?
比方说我们在发送数据的时候,因为一些原因导致了多传了一次!这导致了对方收到了两个报文!或者因为网络问题导致的数据丢失——这些都是不可靠的!
==而传输层的协议就是用来解决可靠性的==
但是我们一般是感知不到的!用户也不关心这个方面!所以对于这个可靠传输我们其实是很难体会到的!
什么是面向字节流?后面我们会讲
认识UDP协议
UDP是User Datagram Protocol 用户数据报协议
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
TCP协议面临的网络问题UDP也是会面临的!但是UDP是不可靠的!
==为什么TCP怎么好我们还要用到UDP呢?——这里我们得先纠正一个观念!可靠和不可靠这里都是属于一个中性词!不是说可靠就好!也不是说不可靠就不好!==
==可靠和不可靠描述的是一种物理特征!==
虽然从直觉上来说可靠是好的!——但是我们也要认识一点!可靠是要付出代价的!
因为计算机中数据丢包,网络出问题,这些都是客观存在的问题!为了缓解这些问题就必须提供更多的策略,花费更多精力和时间!
==这就注定了这个协议写起来是比较复杂的!——复杂是体现维护上和编码上!==
==不可靠则意味着,没有成本比较简单!——维护和使用也是简单的==
所以我们要挑选合适的场景!
例如:TCP协议一般用在银行系统,淘宝,京东这些电商类的!
UDP协议适合场景则是在网络通信的时候允许丢包,也能控制传送数据的速度或者流量!例如:网络直播!或者DNS,广告推送!
网络字节序
为什么要有网络字节序
什么是网络字节序?
我们都知道,计算机是分为大端机和小端机的!而数据都是是内存里面存的!
而数据都是有自己的高权值的!例如1234,1虽然数字很小!但是权值位很高!数字也是有高位和低位的区别的!
一个整数,要开辟4字节的内存空间去存储!而地址也是有高地址和低地址之分的
我们可以选择把高权值的数据,存在高地址的地方!也可以选择放在低地址的地方!——这就是大端和小端的区别!==(大小端的存储是按照字节为单位的!不是按照bit位为单位的!)==
小端就是高权值的数据放在高地址处,低权值位的数据数据放在低地址处!
大端就是高权值的数据排放在内存的低地址端,低权值位的数据排放在内存的高地址端。
如果出现了——数据从大端机传给小端机,一个整形四个字节是按照大端来存的!发的时候也是按照大端来发的!最后小端收的是一个大端的数据!那么最后是不是会出现将数据解释反了的情况?
==但是这样不是问题!因为我们可以从算法上将其逆序就好了!——真正的问题是我们该如何判断我们收到的数据是大端还是小端的!我们是无从知道的!==
因为大小端存在的时间是很长的!所以也衍生出了很多的解决方案!但是我们可以使用一个最简单的解决方案!——==我们规定网络中的数据都是大端的!==
大端的数据那么就直接发!小端的数据那么就转换一下再发送!!——这样子就解决了我们收到的数据是大端还是小端的判断问题!
==这个按照大端存储的数据我们就称之为网络字节序!==
网络字节序的使用
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。那么如何定义网络数据流的地址呢?
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出:
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存:
- 因此,网络数据流的地址应这样规定**:先发出的数据是低地址,后发出的数据是高地址**
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
- 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据:
- 如果当前发送主机是小端,就需要先将数据转成大端:否则就忽略,直接发送即可;
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行可以调用以下库函数做网络字节序和主机字节序的转换。
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
h表示host,n表示network,l表示32位长整数,s表示16位短整数——则htonl就是host to network long将一个32位的长整数从主机序列转为网络序列!ntohl就是network to host long 将一个32位的长整数从网络序列转换为主机序列!
至于主机序列是大端还是小端,函数内部会自行处理!
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
==可能会有疑惑!这里只有32/16位的整数!如果我们以后要发送char,double/float,这些类型该怎么转换呢?——不用担心!我们网络上发送的所有数据都是字符串!==
未来我们使用一些接口,会自动的对字符串进行转换!
socket网络编程
常见的API接口
我们所写的代码都是在应用层的!所以这就是注定了!我们要调用各种的系统调用接口!
所以操作系统给我们提供了常见的接口!
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器) int socket(int domain, int type, int protocol); // 绑定端口号 (TCP/UDP, 服务器) int bind(int socket, const struct sockaddr *address,socklen_t address_len); // 开始监听socket (TCP, 服务器) int listen(int socket, int backlog); // 接收请求 (TCP, 服务器) int accept(int socket, struct sockaddr* address,socklen_t* address_len); // 建立连接 (TCP, 客户端) int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockaddr结构
实际上在网络通信的时候,套接字的种类是很多的!
有网络套接字,原始套接字,Unix域间套接字等等
网络套接字是用于跨主机键通信的同时还支持本地通信!
域间套接字只支持本地通信!——用法其实和网络套接字一样!类似于管道
我们目前进行网络编程是在应用层调用传输层的接口!但是原始套接字能够绕过传输层去访问网络层/数据链路层各种协议的有效数据!
原始套接字一般是用于抓包软件/网络侦测的工具
==我们可以发现,每一种套接字的使用场景是不一样的!如果未来我们想使用它,那么我就应该有三套不同接口!——但是思想上都是一样的!功能也是类似的!==
但是这样对于接口的设计者来说是很麻烦的!有没有什么办法只用一套的接口!通过不同的参数,解决所有的网络或者其他场景下的通信问题?
==所以就设计了sockaddr这个类型用于专门解决这个问题!==
这个struct sockaddr_in(这个in是指inet——说明这个结构体是适用于网络通信的!)
struct sockaddr_un(un是指unix——说明这个结构体是用于域间套接字的)
我们可以很明显的看出来!这两个类型是完全不一样的!如果直接使用,设计接口就只能设计两套不同的接口!
==为了能够用一套接口来解决!——于是有了struct sockaddr类型!==
==但是在函数的内部看到类型都是struct sockaddr类型!——但是它不管是什么!首先都会提取这个结构体的前面两个字节!然后根据前面两个字节来判断到底是AF_INET还是AF_UNIX,如果是AF_INET那么就在函数内部强转回struct sockaddr_in,如果是AF_UNIX则强转回struct sockaddr_un==
用这样的方式就可以设计出一套统一接口来进行使用!
==如果理解C++,我们就可以看出来这其实就是一个多态的实现!——struct sockaddr就是一个基类!而其他两个类型就是子类!==
为什么设计者不使用void*来设计接口呢?这样不是更加的优雅吗?——因为那时候C语言标准还没有void *
==我们还可以看一下sockaddr_in的真实样子==
==这些类型一般在该头文件下面!==
#include<arpa/inet.h>
总结
- IPv4和IPv6的地址格式定义在netinet/.in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位1P地址。
- IPv4、IPv6地址类型分别定义为常数AF_INET、AF INET6.这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容
- socket API可以都用struct sockaddr*类型表示,在使用的时候需要强制转化成sockaddr_in;这样的好处是程序的通用性,可以接收IPv4,IPv6,以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数
字符串转整形和整形转字符串
为什么IP的类型是unit32_t呢?——其实在我们写代码的时候我们一把会用string/字符串类型的变量来表示IP例如:“192.168.0.0”
一个IP地址是按照点十分制——这种字符串形式的点分十进制的其实没有什么意义,就是可读性比较而已好,除此之外一无是处
用一个4字节的类型是足够用来存储的!即unit32_t ip——这种整数类型的风格一般就是网络中使用的!网络是不会使用我们上面写的字符串的!因为使用字符串还要解析,转换,比较很慢!而且占用空间还大!
==但是系统会有接口会帮我们将字符串风格IP转换为整数风格的IP——也可以转回来!==
原理也复杂——例如:整数转字符串
如果是字符串转整形——也很简单就是按照点来分割即可!
==我们使用字符串类型来传参的意义就是在于——它可读性好!但是实际网络通信不会使用!==
==我们可以使用系统的接口来帮助我们进行转换!——这个请使用系统提供的接口!而不要自己去写——因为我们自己去转换,转换为整形ip还是主机序列!我们还得再一次转换让其变成网络字节序!==
让我们自己太麻烦了而且不一定安全!所以操作系统也给我们提供了对应接口
in_addr_t inet_addr(const char* op);
这个函数内部会完成两件事情,首先就是将点分十进制的字符串IP地址转化为unit32_t类型,然后将主机序列转化为网络字节序!——将我们的需要的工作都一起完成了!
我们看到有很多的struct in_addr和in_addr_t其实就是和sockaddr_in类型里面的是一样的!
in_addr_t其实就是一个unit32_t,struct in_addr其实就是一个封装了in_addr_t的结构体!
char* inet_ntoa(struct in_addr in);
==这个in_addr就是一个封装了in_addr_t类型的结构体!in_addr_t就是unit32_t是ip==
如果是我们自己将整形IP转换为点分十进制!那么首先我们要将整形从网络字节序转化为主机字节序!然后将整形从转化为点分十进制!——这很麻烦!
==有了这个函数就可以直接一次性完成这两个工作了!==
socketAPI接口详解
socket
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器) int socket(int domain, int type, int protocol);
==我们可以看到这个函数的描述是创建一个通信的一段!==
在linux下一切皆文件!——所以在通信之前!我们是不是要先让OS先把网卡文件打开?(其实怎么说有错误!其实网卡文件早就打卡了!)——其实socket是打开了一个文件与网卡设备相关联起来!
但是我们可简单的理解为创建socket(套接字)就是打开网卡
domain参数
——这个英文单词的中文意思就是域——对应的就是未来这个套接字未来是想要网络通信还是本地通信!可以填的参数有:
但是我们实际上大多数用的是头两个——AF_UNIX,AF_LOCAL与AF_INET
而这个AF_UNIX/AF_LOCA其实就是域间通信即本地通信!
而AF_INET就是使用的是IPV4的网络通信!
==但是我们可以看到,我们这样就可以用一个接口来创建不同的套接字类型!==
domain参数的是固定的写法!
type参数
——这个参数表示的是套接字提供的服务类型!什么是套接字服务的类型?
==SOCK_DGRAN就是数据报——就是说UDP的方式来进行通信!==
==SOCK_STREAM就是代表,我们未来的这个套接字是一个流式的套接字!直白的说就是在底层打开了一个TCP的策略!以TCP的方式来通信==
==上面这两个就是我们经常使用的!==
SOCK_RAW就是原始套接字等等读者有兴趣可以自己去了解!
==总之这个参数就是给我们提供的是socket的能力类型!==
protocol参数
——这个参数代表的是使用的是什么协议!例如:TCP_protocol或者UDP协议!但是这个参数一般为0即可!**为什么呢?——因为如果我们在domain参数选了网络通信,type选择了数据流/数据包,那么默认采用的协议也就固定了!**所以一般就不填,设为0即可!
返回值
==我们可以看到成功!那么返回一个新的socket的文件描述符!失败则返回-1!且错误码被设置!==
==所以我们看到这个socket接口的时候!我们就可以将其和文件操作中的open画上等号!——我们可以使用操作文件的方式来操作网络!我们读写网络其实就可以等于读写文件!==
我们以后读写网络——就是通过文件操作来进行读写!
bind
// 绑定端口号 (TCP/UDP, 服务器) int bind(int socket, const struct sockaddr *address,socklen_t address_len);
这个函数的作用就是绑定一个“名字”给套接字!
socket是打开了一个socket文件!但是这个文件要给谁用呢?这时候就要用到bind的!
sockfd参数
就是打开的socket的返回值!——指要绑定到那个socket文件上面!
addr参数
——类型就是我们上面说的struct sockaddr!这是一个通用的接口!我们上面说过——我们不是直接使用这个类型!
==如果要进行网络通信我们应该是使用struct sockaddr_in这个类型!如果是本地通信那么就是struct sockaddr_un这个类型!==
addrlen参数
这个参数表示的是未来传的结构体的长度!因为我们知道struct sockaddr_in和struct sockaddr_un这两类结构体的大小是不一样的!所以要传入这个结构体长度!
#include<arpa/inet.h>
==这些类型一般都是在这个头文件下面!==
==sockaddr是有填充字段的!但是我们最好让填充字段都变成0是最好的!==
void bzero(void* s,size_t n);
这个函数的作用就是按字节位单位向缓冲区里面写0
==在使用bind绑定之前!我们可以使用bzero先将sockaddr的变量都清零==
返回值
成功返回0,错误则返回-1!且错误码被设置!
listen
// 开始监听socket (TCP, 服务器) int listen(int socket, int backlog);
==这函数的作用就是修改我们套接字的状态!让其变成listen的状态!只有套接字的是listen的状态的时候!那么它才能一直帮我们获取新连接,接收新的连接请求!——这个是在TCP中使用的!==
就像是你是一个移动公司的老板,为了给用户提供更好的服务,手底有1w个接线员, 当客户给10086打电话的,如何保证能够及时的被接听,建立连接呢?——那就要保证电话的旁边有人,此时==这个人就是listen状态==
sockfd参数
——就是套接字的文件描述符!
backlog参数
——这个是底层全连接队列的长度+1这个数值可以设置为5,10,20之类的但是一般不要设置太大一般是不太长的数据
返回值
成功返回0,失败返回-1,错误码被设置
accept
// 接收请求 (TCP, 服务器) int accept(int socket, struct sockaddr* address,socklen_t* address_len);
这个函数的作用就是接收一个连接!——在使用tcp的时候!就要让服务器一直进行accept,用来获取连接,等到获取了一个连接,才能进行发送信息
sockfd参数
——从这个套接字里面获取新连接
addr参数
——是一个输入输出型参数,里面有将来是谁对我们发起的连接的主机的ip地址和port
addrlen参数
——是一个输入输出型参数,表示上面的addr参数的实际大小!返回值
函数调用成功,则返回一个整数,整数是一个已经接收连接的套接字的文件描述符
==为什么也是一个文件描述符?——sockfd也是文件描述符,这两个文件描述符有什么差别么?==
未来一个服务器,被几百个人同时连接,那么是不是这就意味着要建立几百个文件描述符?
我们举个例子:你和朋友去一个美食街,美食街上有很多的美食店——这些店的门口都站着一个人!这些人我们就称之为拉客的人,把别人拉进自己的店面里面
你和朋友走着走着,被一个烧烤店的拉客人,张三给叫住,邀请你们去吃,你和朋友此时恰好也饿了,刚好又好久没有吃烧烤了,于是张三就招呼你们进去,张三打开门后让你们自己进去,**他自己是不进去的!**然后等你进去后,他就一嗓子说“客人,来了!来个服务员,照顾一下”
然后一个服务员李四来了,然后接下来就是由这个李四来给你们端茶送水,给你们服务
当你们在吃烧烤的时候,这时候张三哪里去了?——张三还在继续拉客!
你们吃着吃着,张三又拉来了一个人,照样哄了一嗓子,服务员王五出来了,给新的一组人提供服务!
张三不断的拉客,每次拉进来一个客人,就会有一个新的服务员出来,专门这人服务
无论有多少人——张三只做一件事就是拉客
而李四,王五,这些服务员的工作就是提供服务!
==而这样的模型下——张三就是参数sockfd这个文件描述符——只负责建立新连接!==
==而服务员就是——这个listen函数的返回值的这个文件描述符——用于给这些连接好的客户端对外提供服务==
==所以我要进行TCP是不能直接使用sockfd这个文件描述符进行通信的!而是要使用listen函数的返回值!==
connect
// 建立连接 (TCP, 客户端) int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
==这个函数的作用就是在套接字初始化一个连接——即发起连接==
sockfd参数
——要通过那个套接字发起连接,客户端的套接字
addr参数
——一个输入型参数,里面要填我们要向那个客户端发起连接的ip和port
addrlen
——就是addr参数的大小!connect调用后如果没有绑定ip和port会自动的去绑定!
返回值
如果连接成功返回0,失败返回-1且错误码被设置!——我们可以看到如果连接成功,该函数会自动帮我们的binding的!
socket读写数据接口
recvfrom
当我们要从socket里面读取数据的时候我们就要要用到这个接口
sockfd参数
——就是我们要从呢个套接字去读取
buf参数
——就是要将数据读取到那个缓冲区里面!
len参数
——就是缓冲区的长度
flags参数
——我们一般默认为0这里我们只说几个
MSG_WAITALL: 这个参数允许我们在没有数据可用时阻塞。这对于确保接收到所有数据很有用。
MSG_DONTWAIT: 这个参数允许我们在没有数据可用时立即返回。这对于非阻塞I/O很有用。
==一般来说每一个选项的用法==
MSG_WAITALL: 用于确保接收到所有数据,即使这需要阻塞。
MSG_DONTWAIT: 用于非阻塞I/O,在没有数据可用时立即返回。
==flags为0的时候一般来说表示默认行为!——会进行阻塞式等待!==
src_addr参数
——如果我们今天读取到了一个消息!我们想不想知道是谁给我们发送的消息,**肯定是要知道!**那么如果我们想要知道那么肯定得知道对方的socket是多少!==这个参数是一个输出型参数,这个参数返回的是消息是从哪一个client发出来的!(这个输出型参数是要我们提前构造一个struct sockaddr_in 来接收的!)里面就会有这个client的ip是谁,ip地址是什么,前提条件是需要使用UPD/TCP协议进行网络通信==
addrlen参数
——也是一个输出型参数,就是说上面的结构体长度src_addr参数的结构体长是多少!返回值
返回值就是读取到的字节数,如果出错就是返回-1,且错误码被设置
如果是UDP一般是不会收到0的!因为不面向连接!只有TCP会收到0
sendto
标签:sockaddr,socket,主机,编程,参数,linux,接字,我们 From: https://blog.51cto.com/u_15835985/9513276当我们想要给套接字发送数据的时候!我们可以使用这个接口!
sockfd参数
——就是我们想要向那个套接字发送!
buf参数
——就是要将数据读取到那个缓冲区里面!
len参数
——就是缓冲区的长度
flags参数
——我们一般默认为00就是有数据就发,没有数据就等!想了解详细参数的读者可以自行查找!
des_addr参数
==我们要进行网络通信的时候,我们就要只要要向谁发!——那么就要知道对端的ip和port,所以我们创建一个struct sockaddr_in类型的变量!因为我们要向这个结构体里面填入信息!==
addrlen参数
——就是这个结构体的长度!这是一个输入型参数,所以我们可以看到是没有指针的!
返回值
成功返回发出去的字节数,错误返回-1,然后错误码设置!