本节重点
认识ip地址,端口号,网络字节序等网络编程中的基本概念
学习scoket,api的基本用法
能够实现一个简单的udp客户端/服务端
能够实现一个简单的tcp客户端/服务器(但链接版本,多进程版本,多线程版本)
理解tcp服务器建立连接,发送数据,断开连接的过程
目录
1. 理解ip地址和目的ip地址
在ip数据包头部中,有两个地址,分别叫做源ip地址和目的ip地址
思考,我们光有ip地址就可以完成通信了吗?想象一下qq发消息的例子,有了ip地址就能够把消息发送到对方的机器上,还需要有一个其他的表示来区分出,这个数据要给哪一个程序进行解析
2. 端口号
网络协议中的下三层主要解决的是数据安全可靠的发送到远端机器
用户使用应用层软件,完成数据发送和接收,需要先把软件启动起来,软件就是进程
日常在一个应用中通信,发送端从这个软件发出,接收端同样需要这个软件收到解析。这个本质就是进程间通信,进程间通信需要两个进程看到同一份共享资源,这个就是网络,管道等都有创建管道,挂接管道,关闭管道。同样,网络也需要提供一些系统调用和接口,这个就是网络协议栈
信息传递后还要面临一个问题,就是这个信息应该交给上层的哪一个进程,怎么区分是这个进程不是另外的进程。这时就需要端口号,可以唯一表示该主机上的网络应用层的进程
端口号(port)是传输层协议的内容
- 端口号是一个2字节16位的整数
- 端口号是用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理
- ip地址+端口号能够标识网络上的某一台主机的某一个进程
- 一个端口号只能被一个
用ip和port两个设备通讯可以标识全网唯二的两台设备,这个方式就是socket
端口号和进程id
想要标识唯一的进程,pid也可以,为什么还要使用端口号
1.不是所有的进程都要网络通信,但是所有进程都要有pid
2.pid每次启动都会改变
3.系统和网络功能解耦。如果用pid网络开发,势必会让网络部分和进程部分强关联,进程的pid如果有改动,那么网络也需要改动
抖音发送出获取视频的请求,在传输层加上了源端口号和目标端口号。传到对方传输层时,可以根据hash表寻找,里面存的都是进程的pcb,找到需要传递的进程
一个进程可以绑定多个端口号,一个端口号不可以被多个进程绑定
3. TCP协议
(Transmission Control Protocol 传输控制协议)
传输层协议
有连接
可靠传输
面向字节流
4. UDP协议
(User Datagram Protocol 用户数据报协议)
传输层协议
无连接
不可靠传输
面向数据报
所谓可靠传输tcp会确认沟通是否成功,直到对方收到消息前都会维护这个数据,比如打电话之前会“喂喂”
udp面向数据报,像邮件一样,直接将数据丢给下层,至于是否成功发送不关心,数据会直接丢掉
可靠传输也是伴随着成本的增加才做到的,两者没有优劣之分
5. 网络字节序
内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大小端之分,网络数据流也有,如何定义地址呢?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出
接收主机把从网络上接到的字节一次保存在缓冲区中,也是按内存地址从低到高的顺序保存
网络数据流的地址规定先发出的数据是低地址,后发出的是高地址
TCP/IP协议规定,网络数据流采用大端字节序,低地址高字节
不管这台主机是大端还是小端,都按照规定的字节序发送接收,如果当前主机是小端,就会先进行转换
为使字节序有可移植性,同样的c代码在大端和小端机都可以运行,可以调用下面的库函数做字节序和主机字节序的转换
这些函数名,h表示host,n表示network,l表示32位长整数,s表示16位短整数,ip地址32位,端口号16位
htonl表示将32位长整数从主机字节序转换为网络字节序,将IP地址转换后发送
如果主机是小端,会做相应转换返回,大端原封不动返回
6. socket编程接口
常见API
//创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket (int domain, int type, int protocol) ;
//绑定端口号 (TCP/UDP, 服务器)
int bind (int socket, cosnt struct sockaddr* address, socklen_t address_len) ;
//开始监听 socket (TCP, 服务器)
int listen (int socket, int backlog) ;
//接收请求 (TCP, 服务器)
int accept (int socket, struct sockaddr* address, soclen_t* address_len) ;
//建立连接 (TCP, 客户端)
int connect (int sockfd, cosnt struct sockaddr* addr, socklen_t addrlen) ;
sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket,然而,各种网络协议的地址格式并不相同
套接字编程种类:
1.域间套接字:同一个机器内
2.原始套接字:可以接收网卡或底层的数据,用来做网络工具
3.网络套接字:用户间的网络通信
上图的_un是域间套接字,_in是网络套接字,开发者希望将接口统一为一种类型,所以引出了sockaddr类型,也就是所有参数里传入的类型,它内部会判断是网络还是域间等,调用不同的功能。前16位和其他两个一样,后面的内容是填充字段,14字节的地址信息
IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr-in结构体表示,包括16位我地址类型,16位端口号和32位IP地址
IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6,这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体的内容
socket API可以都用struct sockaddr*类型表示,在使用的时候需要强制转化为sockaddr_in,这样的好处是程序的通用性,可以接收IPv4、IPv6,以及UNIX Domain Socket各种类型的sockaddr结构体指针作为参数
虽然socket api接口是sockaddr,但真正的基于ipv4编程时,使用的数据结构是sockaddr_in,这个结构里主要有三部分信息:地址类型,端口号,IP地址
in_addr用来表示一个ipv4的ip地址,起始就是一个32位的整数