1.协议
通信双方约定的一套标准
2.国际网络通信协议标准:
1.OSI协议:(过于冗余)
应用层 发送的数据内容
表示层 数据是否加密
会话层 是否建立会话连接
传输层 数据传输的方式
网络层 数据的路由
数据链路层 局域网内部通信
物理层 物理介质的连接
2.TCP/IP协议模型:
应用层 发送的数据内容
传输层 数据传输的方式
网络层 数据由一台主机到达另一台主机
网络接口层 物理介质连接
1.应用层:
FTP 文件传输协议 (基于TCP )
TFTP 简单文件传输协议 (基于UDP)
HTTP 超文本传输协议
HTTPS 安全超文本传输协议
SMTP 简单邮件传输协议
TELNET 网络终端登录协议 (远程登录一台电脑)
DNS 域名系统
..
2.传输层:
TCP 传输控制协议 (可以严格控制,建立好链接才发送)
UDP 用户数据报协议 (目的地存在就发过去,不存在就丢失掉了)
UDP:不安全、不可靠的传输方式
UDP机制简单
UDP占用的资源开销比较小
TCP:安全、可靠的传输方式
TCP机制复杂
TCP占用的资源开销比较大
三次握手建立连接,确认双方能够通信
通信过程中保障数据传输的完整性
四次挥手断开连接,确保数据传输的完整
TCP:
1.三次握手,来确立链接成功
A---->B : SYN 请求应答信号。
B---->A : ACK+SYN 回复应答,并且请求应答。
A---->B : ACK 回复应答。
2.通信中确保信号的完整
A--->B : PSH 发送数据
B---->A : ACK 收到信号,若是信号不完整,发剩下的信号
3.四次挥手告别
A---->B : FIN 请求终止。
B---->A : ACK 回复请求。
B---->A : 等待B给A发送完后也发起终止信号。
A---->B : ACK 回复。
3. 网络层:
1. IPV4协议
8位,所以最多是2的8次方,范围就是 0~255,最大255,由于0,和1 特殊,所以最多可以用253个
1. IP地址
管理员IP地址形式:192.168.0.167
内存IP地址形式: 11000000.10101000.00000000.10100111
IP地址 = 网络位 + 主机位
网络位:IP地址所属的网段(局域网的编号)
主机位:局域网中的第几台主机
网段号:网络位不变,主机位全为0
广播号:网络位不变, 主机位全为1
子网掩码:每个IP地址都会搭配一个子网掩码,用来区分IP地址的网络位及主机位
子网掩码展开成二进制,1对应的部分就是IP地址的网络位,0对应的部分就是IP地址的主机位
192.168.0.167
255.255.255.0
11000000.10101000.00000000.10100111
11111111.11111111.11111111.00000000
192.168.0.0
192.168.0.255
2. IP地址的划分:
公有地址
私有地址
A类:1.0.0.0 ~ 126.255.255.255
子网掩码:255.0.0.0
管理超大规模型网络
私有地址:10.0.0.0 ~ 10.255.255.255
B类:128.0.0.0 ~ 191.255.255.255
子网掩码:255.255.0.0
管理大中规模型网络
私有地址:172.16.0.0 - 172.31.255.255
C类:192.0.0.0 ~ 223.255.255.255
子网掩码:255.255.255.0
管理中小规模型网络
私有地址:192.168.0.0 ~ 192.168.255.255
D类:224.0.0.0 ~ 239.255.255.255
用于组播:255.255.255.0
E类:240.0.0.0 ~ 255.255.255.255
用于实验和研究:255.255.255.0
3.MAC地址
:设备自带网卡的地址(该地址是唯一的)
4.端口号:
找到同一台主机不同的应用程序
3.设置虚拟机网络模式:
1.ifconfig
2.将虚拟机IP地址设置为桥接模式:
1.点击"虚拟机"
2.点击"设置"
3.选择"网络适配器"
4.点击"桥接模式"
5.点击"确定"
3.将虚拟机桥接到无线网卡上去
1.点击"编辑"
2.点击"虚拟网络编辑器"
3.点击"更改设置"
4.已桥接至选择无线网卡
5.点击"确定"
4.修改网卡配置文件
1.sudo vim /etc/network/interfaces
2.修改文件内容为:
auto lo
iface lo inet loopback
auto ens33
iface ens33 inet dhcp
3.保存退出
:wq
5.重启网络服务
sudo /etc/init.d/networking restart
6.测试与局域网内其余IP地址是否能够连通
ping 192.168.0.167
ping www.baidu.com
4.UDP编程:
1.套接字:
实现Linux系统下的网络通信
套接字:一次通信对象的抽象
2.socket
cpp
//1.创建用来通信的套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
int socket(int domain, int type, int protocol);
功能:
创建套接字
参数:
domain: AF_INET 表示IPV4协议
type:套接字类型
SOCK_STREAM:流式套接字
SOCK_DGRAM:数据报套接字
SOCK_RAW:原始套接字
protocol:
TCP和UDP协议:0
返回值:
成功返回用来通信的文件描述符
失败返回-1
3.sendto
cpp
//3.为目的地址赋值
recvaddr.sin_family = AF_INET; //协议族
recvaddr.sin_port = htons(30000); //端口号(将本地字节序转换为网络字节序)
recvaddr.sin_addr.s_addr = inet_addr("192.168.0.171"); //IP地址(将字符串类型转换为二进制地址类型)
//4.向目的地址发送数据
nsize = sendto(sockfd, "hello world", 12, 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nsize)
{
perror("fail to sendto");
return -1;
}
printf("发送成功!\n");
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:
发送信息
参数:
sockfd:套接字文件描述符
buf:发送数据空间首地址
len:发送数据长度
flags:发送属性 默认为0
dest_addr:目标地址存放空间首地址
addrlen:目的地址的长度
返回值:
成功返回发送字节数
失败返回-1
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 */
};
如果sendto对应的套接字没有绑定端口,则sendto绑定一个随机端口完成发送功能
4.inet_addr
cpp
sendaddr.sin_addr.s_addr = inet_addr("192.168.0.171");
in_addr_t inet_addr(const char *cp);
功能:
将字符串的IP地址转换为32位的地址类型
5.htons
cpp
sendaddr.sin_port = htons(20000);
uint16_t htons(uint16_t hostshort);
功能:
将本地字节序(小端)转换成网络大端字节序
6.bind
cpp
//2.将发送端套接字与IP地址和端口号绑定
sendaddr.sin_family = AF_INET;
sendaddr.sin_port = htons(20000);
sendaddr.sin_addr.s_addr = inet_addr("192.168.0.171");
ret = bind(sockfd, (struct sockaddr *)&sendaddr, sizeof(sendaddr));
if (-1 == ret)
{
perror("fail to bind");
return -1;
}
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:
将套接字与IP地址和端口进行绑定
参数:
addr:绑定地址结构体空间首地址
addrlen:绑定地址空间大小
返回值:
成功返回0
失败返回-1
注意:
只能绑定自己的IP地址
7.recvfrom
cpp
//3.接收数据
nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
if (-1 == nsize)
{
perror("fail to recvfrom");
return -1;
}
printf("接收内容: %s\n", tmpbuff);
cpp
socklen_t addrlen = sizeof(sendaddr);
nsize = sendto(sockfd, tmpbuff, strlen(tmpbuff)+1, 0, (struct sockaddr *)&sendaddr, sizeof(sendaddr));
if (-1 == nsize)
{
perror("fail to sendto");
return -1;
}
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:
接收信息
参数:
sockfd:套接字文件描述符
buf:接收数据空间首地址
len:接收数据长度(是最多接收多少,不是实际接受多少,实际接受多少从返回值返回)
flags:接收的属性 默认为0
src_addr:存放发送方地址空间的地址
addrlen: 要接收的发送方地址的长度
返回值:
成功返回实际接收字节数
失败返回-1
发送端流程:
1.创建用来通信的套接字------socket
2.将发送端套接字与IP地址和端口号绑定-------bind
3.为目的地址赋值-------htons,inet_addr
4.向目的地址发送数据-------sendto
5.关闭套接字------close
cpp
#include "../head.h"
int main(void)
{
int sockfd = 0;
ssize_t nsize = 0;
int ret = 0;
struct sockaddr_in recvaddr;
struct sockaddr_in sendaddr;
//1.创建用来通信的套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//2.将发送端套接字与IP地址和端口号绑定
sendaddr.sin_family = AF_INET;
sendaddr.sin_port = htons(20000);
sendaddr.sin_addr.s_addr = inet_addr("192.168.0.171");
ret = bind(sockfd, (struct sockaddr *)&sendaddr, sizeof(sendaddr));
if (-1 == ret)
{
perror("fail to bind");
return -1;
}
//3.为目的地址赋值
recvaddr.sin_family = AF_INET; //协议族
recvaddr.sin_port = htons(30000); //端口号(将本地字节序转换为网络字节序)
recvaddr.sin_addr.s_addr = inet_addr("192.168.0.171"); //IP地址(将字符串类型转换为二进制地址类型)
//4.向目的地址发送数据
nsize = sendto(sockfd, "hello world", 12, 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nsize)
{
perror("fail to sendto");
return -1;
}
printf("发送成功!\n");
//5.关闭套接字
close(sockfd);
return 0;
}
接收端流程:
1.创建套接字
2.绑定IP和Port
3.接收信息
4.关闭套接字
cpp
#include "../head.h"
int main(void)
{
int ret = 0;
int sockfd = 0;
struct sockaddr_in recvaddr;
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
//1.创建套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//2.绑定IP和Port
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(30000);
recvaddr.sin_addr.s_addr = inet_addr("192.168.0.171");
ret = bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == ret)
{
perror("fail to bind");
return -1;
}
//3.接收数据
nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
if (-1 == nsize)
{
perror("fail to recvfrom");
return -1;
}
printf("接收内容: %s\n", tmpbuff);
close(sockfd);
return 0;
}
cpp
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <time.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
作业:
1.自己实现发送端,从终端接收一个字符串发送给接收端
自己实现接收端,从网络中接收到一个字符串并打印
2.编写两个程序,一个发送端,一个接
发:
cpp
#include "../head.h"
int main(void)
{
char tmpbuff[1024] = {0};
struct sockaddr_in recvaddr;
ssize_t nsize = 0;
//1.创建套接字
int sockfd = 0;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
while (1)
{
fgets(tmpbuff, sizeof(tmpbuff), stdin);
tmpbuff[strlen(tmpbuff)-1] = '\0';
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(RECV_PORT);
recvaddr.sin_addr.s_addr = inet_addr(RECV_IP);
//2.发送信息
nsize = sendto(sockfd, tmpbuff, strlen(tmpbuff)+1, 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nsize)
{
perror("fail to sendto");
return -1;
}
nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
if (-1 == nsize)
{
perror("fail to recvfrom");
return -1;
}
printf("RECV:%s\n", tmpbuff);
}
//3.关闭套接字
close(sockfd);
return 0;
}
收:
cpp
#include "../head.h"
int main(void)
{
//1.创建套接字
int sockfd = 0;
int ret = 0;
char tmpbuff[1024] = {0};
ssize_t nsize = 0;
struct sockaddr_in recvaddr;
struct sockaddr_in sendaddr;
socklen_t addrlen = sizeof(sendaddr);
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//2.绑定IP和端口
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(RECV_PORT);
recvaddr.sin_addr.s_addr = inet_addr(RECV_IP);
ret = bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == ret)
{
perror("fail to bind");
return -1;
}
while (1)
{
//3.接收数据
nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, (struct sockaddr *)&sendaddr, &addrlen);
if (-1 == nsize)
{
perror("fail to recvfrom");
return -1;
}
printf("%s:%d->%s\n", inet_ntoa(sendaddr.sin_addr), ntohs(sendaddr.sin_port), tmpbuff);
fgets(tmpbuff, sizeof(tmpbuff), stdin);
tmpbuff[strlen(tmpbuff)-1] = '\0';
nsize = sendto(sockfd, tmpbuff, strlen(tmpbuff)+1, 0, (struct sockaddr *)&sendaddr, sizeof(sendaddr));
if (-1 == nsize)
{
perror("fail to sendto");
return -1;
}
}
//4.关闭套接字
close(sockfd);
return 0;
}
5.TCP编程
发端: 收端:
socket socket
bind
listen
connect accept
send recv
recv send
close close
1.函数接口:
1.socket
cpp
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
UDP: socket(AF_INET, SOCK_DGRAM, 0);
TCP: socket(AF_INET, SOCK_STREAM, 0);
2.listen
cpp
ret = listen(sockfd, 10);
if (-1 == ret)
{
perror("fail to listen");
return -1;
}
int listen(int sockfd, int backlog);
功能:
监听发送三次握手连接的套接字,并放入等到处理队列中
参数:
sockfd:套接字文件描述符
backlog:等待队列的大小(最多存放尚未被处理的三次握手请求的个数)
返回值:
成功返回0
失败返回-1
3.accept
cpp
confd = accept(sockfd, NULL, NULL);
if (-1 == confd)
{
perror("fail to accept");
return -1;
}
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:
处理等待队列中的第一个套接字
参数:
sockfd:套接字文件描述符
addr:存放发送方IP地址的空间首地址
addrlen:存放发送方IP地址的空间大小
返回值:
成功返回一个新的文件描述符(这个描述符是与接收端相对应的新的套接字)
失败返回-1
4.connect
cpp
ret = connect(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == ret)
{
perror("fail to connect");
return -1;
}
printf("连接成功\n");
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:
向接收方发起连接请求
参数:
sockfd:套接字文件描述符
addr:接收方的IP地址和端口号
addrlen:接收方的IP地址和端口号的大小
返回值:
成功返回0
失败返回-1
5.send
cpp
fgets(tmpbuff, sizeof(tmpbuff), stdin);
tmpbuff[strlen(tmpbuff)-1] = '\0';
nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
perror("fail to send");
return -1;
}
printf("发送成功!\n");
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:
向接收方发送数据
参数:
sockfd:套接字文件描述符
buf:要发送的数据的首地址
len:要发送的数据的长度
flags:标志位
返回值:
成功返回发送字节数
失败返回-1
6.recv
cpp
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
perror("fail to recv");
return -1;
}
printf("实际接收 %ld个字节, 内容:%s\n", nsize, tmpbuff);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:
接收发送方发送的数据
参数:
sockfd:套接字文件描述符
buf:接收数据的缓冲区首地址
len:接收数据的缓冲区的大小
flags:标志位
返回值:
成功返回实际接收字节数
失败返回-1
对方关闭返回0
发送端流程:
1.创建用来通信的套接字------socket
2.给接收端(目的地)IP地址和端口号赋值
3.给目的地发送连接请求------connect
4.向目的地址发送数据-------send
5.接收目的地发来的数据-------recv
5.关闭套接字------close
cpp
#include "../head.h"
int main(void)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in recvaddr;
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(RECV_PORT);
recvaddr.sin_addr.s_addr = inet_addr(RECV_IP);
//1.创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//2.发送连接请求
ret = connect(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == ret)
{
perror("fail to connect");
return -1;
}
printf("连接成功\n");
//3.发送数据
fgets(tmpbuff, sizeof(tmpbuff), stdin);
tmpbuff[strlen(tmpbuff)-1] = '\0';
nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
perror("fail to send");
return -1;
}
printf("发送成功!\n");
//4.接收数据
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
perror("fail to recv");
return -1;
}
printf("实际接收 %ld个字节, 内容:%s\n", nsize, tmpbuff);
//5.关闭
close(sockfd);
return 0;
}
接收端流程:
1.创建套接字 -----socket
2.给自己的IP和端口赋值
2.绑定套接字(只能绑定自己的)IP和Port -------bind
3.监听是否收到连接请求------listen
4.处理连接请求 ---------accept
5.接收数据---------recv
6.发送数据--------send
. 7.关闭俩个------confd和sockfd
cpp
#include "../head.h"
int main(void)
{
int sockfd = 0;
int confd = 0;
int ret = 0;
char tmpbuff[4096] = {0};
struct sockaddr_in recvaddr;
ssize_t nsize = 0;
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(RECV_PORT);
recvaddr.sin_addr.s_addr = inet_addr(RECV_IP);
//1.创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//2.绑定IP地址和端口号
ret = bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == ret)
{
perror("fail to bind");
return -1;
}
//3.监听
ret = listen(sockfd, 10);
if (-1 == ret)
{
perror("fail to listen");
return -1;
}
//4.处理连接请求
confd = accept(sockfd, NULL, NULL);
if (-1 == confd)
{
perror("fail to accept");
return -1;
}
//5.收发
nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
perror("fail to recv");
return -1;
}
printf("RECV:%s\n", tmpbuff);
memset(tmpbuff, 0, sizeof(tmpbuff));
fgets(tmpbuff, sizeof(tmpbuff), stdin);
tmpbuff[strlen(tmpbuff)-1] = '\0';
nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
perror("fail to recv");
return -1;
}
//6.关闭
close(confd);
close(sockfd);
return 0;
}
sendto 和 send的比较:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
这里send不需要写参数接收端的IP和端口信息是因为在之前就已经建立 了连接。
recvfrom 和 recv的比较:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
这里recv不需要写参数接收端的IP和端口信息是因为在之前就已经建立 了连接。