一、基础知识
就是要知道OSI开放系统互联参考模型,以及四层模型和五层模型

7. 应用层
作用:为应用程序提供网络服务接口,用户交互
协议:HTTP、HTTPS、FTP、TFTP、SMTP、POP3、DNS、Telnet、SNMP
数据:报文
- 表示层
作用:翻译、加密解密、编码解码、压缩解压,解决两个主机数据格式不一样的问题,确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。
例子:JPG、GIF 编码、SSL 加密、ASCII/EBCDIC 字符转换
注意:HTTPS 的 SSL/TLS 属于表示层功能
- 会话层
作用:建立、管理、断开会话连接;同步、断点续传即通过传输层建立数据传输的通路
例子:登录验证、会话断点恢复
- 传输层
作用:端到端(进程‑进程)通信,差错校验、流量控制、端口寻址,定义传输数据的协议和端
协议:TCP(可靠、面向连接)、UDP(不可靠、无连接)
设备:网关、防火墙;逻辑标识:端口号
数据单元:段 Segment
- 网络层
作用:主机‑主机通信;路由选择、IP 寻址、分组转发。传输数据包,为位于不容位置的网络中的两个主机系统之间提供连接和路径选择就是将一个局域网和另一个局域网连接起来。
协议:IP、ARP、ICMP、IGMP、RIP、OSPF
典型设备:路由器、三层交换机
数据单元:分组(数据包)Packet
- 数据链路层
作用:帧封装即让格式化数据以帧为单位进行传输,MAC 物理地址寻址;差错检测,重传;相邻节点传输
协议:以太网 Ethernet、PPP ‑ 典型设备:网卡、网桥、二层交换机
数据单元:帧 Frame
- 物理层
作用:传输原始比特流 0/1即以2进制传输光信号或电信号,定义硬件电气、机械标准
设备:集线器 HUB、中继器、网线、光纤、RJ45 接口
数据单元:比特 Bit
| 层级 | 作用 | 示例 |
|---|---|---|
| 应用层 | 给程序用的业务数据 | HTTP、API、JSON |
| 传输层 | 负责两个程序之间可靠传输数据 | TCP / UDP |
| 网络层 | 负责找到对方电脑 IP 地址 | IP 协议 |
| 数据链路层 | 局域网传输 | 网卡、以太网 |
| 物理层 | 网线、无线电信号 | 硬件 |
反正就是都要记住!!!!
二、TCP协议
TCP(transmit control protocol):类似打电话,是一种可靠传输。
特点:a.面向连接:就是在进行通信之前,必须建立好一条逻辑上的通路
b.提供可靠传输:四个"无",无丢失,无失序,无差错,无重复(因为会对每个字节都进行编号,所以出错的话就很容易找出来)
c.字节流:有好处有问题
1.建立连接的过程
tcp通过三次握手来建立连接
(客户端) (服务器)
client --------------------------------- server
1 --------- 我要通话(拨号 )------->
2 <------喂(服务器接听电话)-----
3 -------------------喂 ----------------->
这个过程其实就是相当于测试服务器能收到客户端的信息,客户端能收到服务器的信息。
2.tcp通信模型
CS 模型 (Client Server 客户端服务器模型)
BS 模型 (Browser Server 浏览器服务器模型)
P2P模型 (Peer to Peer 点对点)
3.一些小知识
socket(套接字)是操作系统给你的 C 语言程序,访问 TCP/UDP 通道的工具,它不是网络层协议,它是程序和操作系统网络模块之间的接口 API。
类比:TCP 是水管,socket 就是你家水龙头;你拧开水龙头 (socket),水流 (TCP 数据) 就能进出。
API 指服务器对外开放、允许外部程序访问的功能入口。访问这个 API,需要使用 HTTP 协议,通过 TCP 传输。
调用socket(),拿到套接字 fd(水龙头)->connect(fd,服务器地址) 三次握手,建立 TCP 连接(管道打通)->send(fd,http报文) → 通过已经建好的 TCP 管道,发送 HTTP‑GET 请求,去调用 API接收recv(fd,R_buf,...) → TCP 管道把服务器返回的 JSON 数据传回你的程序->关闭套接字,断开 TCP 连接。
4.通信过程
(1)socket 买个了手机
bind 可选的,一般不绑定
(2)connect 拨打电话
(3)read/write 说话
(4)close 挂电话
服务器端:
(1)socket 买个了手机
(2)bind 插了SIM卡 --- 绑定服务器地址信息
(3)listen 待机 --- 监听是否有客户端连接
(4)accept 接听电话 --- 处理客户端的连接 完成连接建立
(5)read/write 说话
(6)close 挂电话
5.客户端具体函数
(1)socket
头文件:#include <sys/types.h> #include <sys/socket.h>
形式int socket(int domain, int type, int protocol);
功能:创建通信的一端
参数:domain 有三种选择,我们一边选择IPv4
AF_UNIX, AF_LOCAL Local communication
AF_INET IPv4 Internet protocols
AF_INET6 IPv6 Internet protocols
type :SOCK_STREAM:流式套接字,提供双向,有序,可靠,面向连接的字节流(其实就是TCP)
SOCK_DGRAM:数据报套接字,提供无连接,不可靠的数据报(其实就是UDP)
protocol: 协议(tpc或udp),给 0 表示默认协议就是流式是TCP,报文是UDP。
返回值:成功时返回socket的文件描述符,失败时返回-1并设置errno。
(2)connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:发起连接请求
参数:sockfd:用到的socket的fd
addr:要通信地址,对于客户端来说,就是服务器的地址(ip+端口号)
addrlen:表示的式 addr 参数地址的大小
返回值:成功时返回0,失败时返回-1并设置errno
&ser_addr 本来是 struct sockaddr_in*,前面加 (struct sockaddr *) 强制转换成 connect 要求的指针类型。
struct sockaddr_in {
sa_family_t sin_family; AF_INET(IPV4)
in_port_t sin_port; 端口号
struct in_addr sin_addr; ip地址
};
它就是嵌套在 struct sockaddr_in 里面,专门存放 IPv4 IP 地址的子结构体
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
(3)将端口号 转换为网络字节序的端口号
形式:uint16_t htons(uint16_t hostshort);
功能:将hostshort值转换为网络字节序返回
(4)将ip地址转换为网络字节序的ip地址
形式:in_addr_t inet_addr(const char *cp);
功能:将字符串形式ip地址cp(点分十进制) 转换为 网络字节序形式的IP地址返回
(5)将结构体中的内容清空
形式:void bzero(void *s, size_t n);

6.网络中的地址addr:解决怎么在找到一台主机以及这台主机上进程
(1)IP地址:是网络中主机的唯一标识,它由网络地址和主机号组成。网络地址是子网的唯一标识,类似于电话号码的区号;主机号是子网内每台主机的编号。
ip的本质:在现在(IPv4)的网络中,IP地址是32bit的二进制数,采用大端字节序.
(2)表示方法(点分十进制):直接使用二进制数,不容易记忆,为了方便记忆,将32位二进制码划分为4个字节,每个字节转化成相应的十进制数,字节之间用"."来分隔,这种表示方法,称之为"点分十进制表示法"。
例:192 .168 .1 .1
11000000 10101000 00000001 00000001
(3)Ip地址分类 (Ipv4)
| 类别 | 首字节十进制 | 开头比特 | 默认掩码 |
|---|---|---|---|
| A | 0‑127 | 0 | 255.0.0.0 |
| B | 128‑191 | 10 | 255.255.0.0 |
| C | 192‑223 | 110 | 255.255.255.0 |
| D | 224‑239 | 1110 | 无 |
| E | 240‑255 | 1111 | 无 |

(4)子网掩码
作用:区分一个 IP 地址哪一部分是网络号,哪一部分是主机号,IPv4 地址 = 网络号 + 主机号
在网络中找到一台主机用ip来找,找到主机中进程用端口号来找,那么ip号+端口号能确定武罗中进程的唯一地址。
端口号组成由16位的数据也就是0~65535组成
1~1023是知名端口号
80 http
1024~50000是系统注册的因为不是所有网站都是知名网站
>=50000是动态的我们写代码一般用这一部分。
7.服务器端
服务器端使用的socket和connect是一样的
(1)int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:给sockfd 绑定一个名字也就是地址信息
参数:sockfd是要绑定的socket
addr是绑定服务器自己的地址
addrlen是服务器地址大小
返回值:成功时返回0,失败时返回-1并设置errno
(2)int listen(int sockfd, int backlog);
功能:将sockfd设置为监听套接字(被动套接字)
参数:sockfd为要设置的socket,backlog表示等待队列的大小(他的大小决定他能监听几个套接字)
返回值:成功时返回0,失败返回-1并设置errno
(3)int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:从listen的等待队列中提取第一个连接请求,完成连接,同时创建一个socket 表示已经连接好的socket,最终返回这个已连接好的socket的fd
参数:sockfd时监听套接字的fd,addr是用来保存对端地址信息,addrlen是值结果参数,必须初始化,sizeof(客户端地址类型)
返回值:成功返回已经连接好的socket的fd,失败时返回-1并errno被设置
如果想知道客户端的地址信息:
struct sockaddr_in cliaddr;
socklen_t addrlen = sizeof(cliaddr);
accept(sockfd, ( struct sockaddr *)&cliaddr, &addrlen)
char *inet_ntoa(struct in_addr in);
功能:将网络字节序的ip地址in 转换为 字符串形式的ip地址 返回
注意:客户端只有一个socket,服务器有两个socket,一个监听的socket,一个通信的socket

三、UDP协议
UDP(User Datagram Protocol):(类似 生活中的广播)
特点:不提供可靠传输,在数据发送时,不需要建立连接
(1)无连接
(2)不保证可靠传输
(3)数据报 --- 一包一包
应用:(1).小数据,但是对速度要求较高(QQ.及时文本信息,语音等),实时性要求较高场合!
(2).广播,组播 //电子教师(vnc --- 广播)
(3).无线网的传输 //udp
1.函数
(1)发送
ssize_t send (int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:sendto 将数据发送到指定的地址中,通过sockfd
参数:sockfd --- 表示socket对应的fd
buf --- 存放数据的一块内存空间
len --- 表示要发送的字节数
flags --- 发送的标志 MSG_DONWAIT //非阻塞 --- 不在等 0 ---默认方式
dest_addr --- 目标地址 --- 代表要发送到的目的地址
addrlen --- 代表 目标地址数据的长度 sizeof(dest_addr)
返回值:成功 返回发送出去的字节数,失败 -1 && errno被设置
(2)接收
ssize_t recv (int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:从socket中接收消息
参数:sockfd --- 表示socket对应的fd
buf --- 存放数据的一块内存空间
len --- 表示打算读取的字节数
flags --- 读取的标志 MSG_DONWAIT //非阻塞 --- 不在等
src_addr --- 表示数据的源地址
addrlen --- 源地址大小 ,值结果参数:socklen_t addrlen = sizeof(src_addr);
返回值:成功 返回读取到的字节数,失败 -1 && errno被设置
例:实现服务器和客户端相互收发
(1)服务器
cpp
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <strings.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
struct sockaddr_in srcaddr;
void * do_send (void *arg)
{
int serfd = *(int *)arg;
char buf[1024];
while (1)
{
printf("> ");
fgets(buf,sizeof(buf),stdin);
sendto(serfd,buf,strlen(buf)+1,0,(const struct sockaddr *)&srcaddr,sizeof(srcaddr));
if (strncmp(buf,"quit",4) == 0)
{
close(serfd);
exit(0);
}
}
return NULL;
}
void* do_recv(void *arg)
{
char buf[1024];
int serfd = *(int *)arg;
while (1)
{
recvfrom(serfd,buf,sizeof(buf),0,NULL,NULL);
printf("buf = %s\n",buf);
if (strncmp(buf,"quit",4) == 0)
{
close(serfd);
exit(0);
}
}
return NULL;
}
int main(int argc, const char *argv[])
{
int serfd = socket(AF_INET,SOCK_DGRAM,0);
if (serfd < 0)
{
perror("socket fail");
return -1;
}
//bind
struct sockaddr_in seraddr;
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = INADDR_ANY;
seraddr.sin_port = htons(50000);
if (bind(serfd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
{
perror("bind fail");
return -1;
}
//recvfrom 函数
socklen_t len = sizeof(srcaddr);
char buf[1024];
recvfrom(serfd,buf,sizeof(buf),0,(struct sockaddr*)&srcaddr,&len);
printf("-------------------\n");
printf("ip = %s\n",inet_ntoa(srcaddr.sin_addr));
printf("port = %d\n",ntohs(srcaddr.sin_port));
printf("-------------------\n");
printf("buf = %s\n",buf);
pthread_t tid[2];
int ret = pthread_create(&tid[0],NULL,do_send,&serfd);
if (ret != 0)
{
errno = ret;
perror("pthread_create fail");
return -1;
}
pthread_detach(tid[0]);
ret = pthread_create(&tid[1],NULL,do_recv,&serfd);
if (ret != 0)
{
errno = ret;
perror("pthread_create fail");
return -1;
}
pthread_detach(tid[1]);
pthread_exit(NULL);
return 0;
}
(2)客户端
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <strings.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <pthread.h>
struct sockaddr_in seraddr;
void * do_send (void *arg)
{
int clifd = *(int *)arg;
char buf[1024];
while (1)
{
printf("> ");
fgets(buf,sizeof(buf),stdin);
sendto(clifd,buf,strlen(buf)+1,0,(const struct sockaddr *)&seraddr,sizeof(seraddr));
if (strncmp(buf,"quit",4) == 0)
{
close(clifd);
exit(0);
}
}
return NULL;
}
void* do_recv(void *arg)
{
char buf[1024];
int clifd = *(int *)arg;
while (1)
{
recvfrom(clifd,buf,sizeof(buf),0,NULL,NULL);
printf("buf = %s\n",buf);
if (strncmp(buf,"quit",4) == 0)
{
close(clifd);
exit(0);
}
}
return NULL;
}
int main(int argc, const char *argv[])
{
int clifd = socket(AF_INET,SOCK_DGRAM,0);
if (clifd < 0)
{
perror("socket fail");
return -1;
}
char buf[1024] = "udp!\n";
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
seraddr.sin_port = htons(50000);
sendto(clifd,buf,strlen(buf)+1,0,(const struct sockaddr *)&seraddr,sizeof(seraddr));
pthread_t tid[2];
int ret = pthread_create(&tid[0],NULL,do_send,&clifd);
if (ret != 0)
{
errno = ret;
perror("pthread_create fail");
return -1;
}
pthread_detach(tid[0]);
ret = pthread_create(&tid[1],NULL,do_recv,&clifd);
if (ret != 0)
{
errno = ret;
perror("pthread_create fail");
return -1;
}
pthread_detach(tid[1]);
pthread_exit(NULL);
return 0;
}