协议是一组规则,规定了如何发送数据。通信的双发都需要遵守该规则
物:物理层
数:数据链路层
网:网络层
传:传输层
会:会话层
表:表示曾
应:应用层
链:数据链路层(网络接口层):以太网帧协议,ARP协议
网:网络层:IP协议,ICMP协议,IGMP协议
传:传输层:TCP协议,UDP协议
应:应用层:HTTP,ftp,nfs,telnet,ssh
数据在没有被封装之前,是不能传输到网络上去进行通信的。
数据产生后,经由数据链路层、网络层、传输层、应用层,四层每一层都会对数据进行不同的封装,封装后才能传送到网络中进行通信。
到达目的端时,经由应用层、传输层、网络层、数据链路层,四层每一层都会解封装,从而最终得到通信数据。
以太网规定,数据包必须从一块网卡,传送到另一块网卡。
目的地址、源地址 —— mac地址(ifconfig命令可查看)。网卡编号。全球唯一。
ARP协议:
根据 IP地址, 获取 mac 地址。
ARP 包括请求和应答,请求携带发送端的 mac 地址,通过应答的信息来获取目的端的 mac 地址
TCP 优先建立好连接,后续消息全部在该路径上发送,UDP则是每次传输的路由路径不同
C/S 模式(client/server):
B/S 模式(browser/server):
在一次通信中,套接字socket必须成对出现
一个文件描述符指向一个套接字 ( 该套接字内部由内核借助两个缓冲区实现。)
小端法:(pc本地存储,IA架构)高位存高地址,低位存低地址。
大端法:(网络存储)高位存低地址,低位存高地址。
需要在做网络传输时,将 “网络字节序” —— “本机字节序” 相互转换。
#include
h表示host,n表示network,l表示32位长整数,s表示16位短整数。
// 本地字节序(IP) ---> 网络字节序(IP)
uint32_t htonl(uint32_t hostlong);
参数:要求是一个 int, 但 “192.168.6.108” 是 string。——> atoi() --> int --> htonl() --> 网络字节序。
// 本地字节序(port) ---> 网络字节序(port)
uint16_t htons(uint16_t hostshort);
// 网络字节序(IP) --->本地字节序(IP)
uint32_t ntohl(uint32_t netlong);
// 网络字节序(port) --->本地字节序(port)
uint16_t ntohs(uint16_t netshort);
#include
// 将 本机字节序 string 类型的 IP地址, 转换为 网络字节序 数值类型
int inet_pton(int af, const char *src, void *dst);
af:AF_INET(IPv4)、AF_INET6(IPv6)
src:传入。IP地址(string类型,点分十进制)
dst:传出。转换后的数值类型的 IP地址。
返回值:
1:成功。
0:语法正确, IP无效。
-1:失败。
// 将 网络字节序 数值类型, 转换回程 本机字节序 string。
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af:AF_INET(IPv4)、AF_INET6(IPv6)
src:传入。网络字节序 IP地址。
dst:传出。string 类型 IP
size:dst 的大小。
返回值:
成功:dst 传出值。string 类型的 IP地址。
失败:NULL
使用 man 7 ip 查看 struct sockaddr_in 结构体类型
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
// 举例:
struct sockaddr_in addr; // 定义地址结构(IP+port)变量
addr.sin_family = AF_INET;
addr.sin_port = htons(8000);
// #define INADDR_ANY 0 --- 取系统中任意一个 有效的 IP地址。
addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 模拟,使用 bind 绑定地址结构。
bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
#include 该头文件包含在
#include
// 创建一个新的套接字。
int socket(int domain, int type, int protocol);
domain: AF_INET、AF_INET6、AF_UNIX
type:SOCK_STREAM、SOCK_DGRAM
protocol:通常传 0
返回值:
成功:返回新套接字对应的 fd
失败:-1,errno
#include 该头文件包含在
#include
// 给 socket 绑定IP+port
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:socket函数的返回值。———— 监听套接字。
addr: 地址结构体。 ———— 类型应该是: struct sockaddr_in ,函数调用时需要强制类型转换
addrlen: sizeof(addr) 地址结构大小
返回值:
成功:0
失败:-1, errno
#include
#include
// 设置同时与服务器建立连接的 上限数。———— 该函数不会阻塞程序。
int listen(int sockfd, int backlog);
sockfd: socket 函数的返回值
backlog: 设置的监听最大数。 128
返回值:
成功:0
失败:-1, errno
// 阻塞等待客户端连接请求。 成功的话,返回一个与客户端成功建立连接的,用于通信的 socket文件描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd: socket 函数的返回值
addr: 传出参数。 成功与服务器建立连接的那个客户端地址结构。
addrlen:传入传出参数。
入:addr的大小。出:客户端 addr 的实际大小。
举例:socklen_t clt_addr_len = sizeof(addr); &clt_addr_len
返回值:
成功:能与客户端进行数据通信的 socket 对应的 文件描述符。
失败:-1, errno
// 使用现有的 socket 与服务器建立连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd: socket 函数的返回值
addr:传入参数。 服务器的地址结构。
addrlen: 服务器地址结构的大小。
返回值:
成功:0
失败:-1, errno
———— 客户端自己的地址结构,没有指定,由系统自动分配 --- "隐式绑定"
通信流程:
编码实现:
#include
#include
#include
#include
#include
#include
#include
#include
#define SRV_PORT 9527
#define SIZE 4096
int main()
{
int lfd = 0;
struct sockaddr_in srv_addr,clt_addr;
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//1.创建套接字
lfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == lfd)
{
perror("socket");
return 1;
}
//绑定IP和port
int ret = bind(lfd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
if(-1 == ret)
{
perror("bind");
return 1;
}
//listen 设置最大监听数
ret = listen(lfd,128);
if(-1 == ret)
{
perror("listen");
return 1;
}
printf("服务器阻塞等待客户端接入\n");
//accept 阻塞等待客户端接入
socklen_t clt_addr_len = sizeof(clt_addr_len);
int cfd = accept(lfd,(struct sockaddr*)&clt_addr,&clt_addr_len);
if(-1 == cfd)
{
perror("accept");
return 1;
}
char ip[SIZE] = {0};
printf("客户端接入:ip:%s,port:%d\n",
inet_ntop(AF_INET,&clt_addr.sin_addr.s_addr,ip,SIZE),
ntohs(clt_addr.sin_port));
char buf[SIZE] = {0};
while((ret = read(cfd,buf,SIZE)) != 0)
{
if('\0' != buf[strlen(buf)-1])
buf[strlen(buf)-1] = '\0';
for(int i=0 ;i<ret ;++i)
{
buf[i] = toupper(buf[i]);
}
write(cfd,buf,ret);
printf("client:%s\n",buf);
memset(buf,0,SIZE);
}
close(cfd);
close(lfd);
return 0;
}
通信流程:
编码实现:
#include
#include
#include
#include
#include
#include
#include
#include
#define SRV_PORT 9527
#define SIZE 128
int main()
{
int cfd;
cfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == cfd)
{
perror("socket");
return 1;
}
struct sockaddr_in srv_addr;
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
inet_pton(AF_INET,"127.0.0.1",&srv_addr.sin_addr.s_addr);
int ret = connect(cfd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
if(-1 == ret)
{
perror("connect");
return 1;
}
printf("客户端连接成功\n");
char buf[SIZE];
while(1)
{
fgets(buf,SIZE,stdin);
ret = write(cfd,buf,SIZE);
ret = read(cfd,buf,SIZE);
if(0 == ret)
{
printf("客户端退出\n");
break;
}
}
close(cfd);
return 0;
}