目录
1.协议
通信双方约定的一套标准
2.国际网络通信协议标准:
1.OSI协议:(过于冗余)
应用层 发送的数据内容
表示层 数据是否加密
会话层 是否建立会话连接
传输层 数据传输的方式
网络层 数据的路由
数据链路层 局域网内部通信
物理层 物理介质的连接
2.TCP/IP协议模型:
应用层 发送的数据内容
传输层 数据传输的方式
网络层 数据由一台主机到达另一台主机
网络接口层 物理介质连接
1.应用层:
FTP 文件传输协议 (基于TCP )
TFTP 简单文件传输协议 (基于UDP)
HTTP 超文本传输协议
HTTPS 安全超文本传输协议
SMTP 简单邮件传输协议
TELNET 网络终端登录协议 (远程登录一台电脑)
DNS 域名系统
..
1.HTTP
HTTP协议:
www.nowapi.com
APPKey: 44923
sign: 5432c8efd2fc919d409b01241b70c9f4
HTTP超文本传输协议
应用层
万维网:大型的信息联网存储所
统一资源定位符:简称URL
协议://主机:端口号/资源路径
https://www.baidu.com
协议:https 加密
http 非加密
主机:IP地址
端口号: 80
443
资源路径: 默认为 / 主页
短连接:想要通信时建立链接
长连接:通信前建立链接,通过新过程中链接一直保持
客户端如何拿到服务器中的网页文件?
1.客户端向主机发送TCP链接请求
2.服务器收到请求后,与客户端链接成功
3.客户端向发送HTTP请求报文,告诉服务器想要的数据
4.服务器回复HTTP响应报文,将客户端要的数据发回
5.双方关闭通信
通信报文:
GET /?app=weather.today&weaId=1&appkey=44923&sign=5432c8efd2fc919d409b01241b70c9f4&format=json HTTP/1.1
Host: api.k780.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 17 Aug 2024 08:08:35 GMT
Content-Type: application/json; charset=utf-8;
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *
{"success":"1","result":{"weaid":"1","days":"2024-08-17","week":".........","cityno":"beijing","citynm":"......","cityid":"101010100","temperature":"30.../23...","temperature_curr":"32...","humidity":"62%","aqi":"45","weather":"...............","weather_curr":"......","weather_icon":"http://api.k780.com/upload/weather/d/1.gif","weather_icon1":"","wind":".........","winp":"2...","temp_high":"30","temp_low":"23","temp_curr":"32","humi_high":"0","humi_low":"0","weatid":"2","weatid1":"","windid":"5","winpid":"2","weather_iconid":"1"}}
HTTP请求报文格式:
1.方法:
GET
2.资源路径:
/?app=weather.today&weaId=1&appkey=44923&sign=5432c8efd2fc919d409b01241b70c9f4&format=json
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.设置虚拟机网络模式:
NAT模式是只要windows有网,linux就有网,linux是虚拟地址,可以发出去,但是别人发不进来
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 //自动获取IP地址
auto ens33
auto
: 这个关键字告诉系统在启动时(或执行ifup -a
命令时)自动启用指定的网络接口。
ens33
: 这是你要自动启用的网络接口的名称。在大多数现代 Linux 系统中(尤其是使用 systemd 和 udev 的),以太网接口通常命名为ensX
(如ens33
)、enpXsY
或ethX
(较旧系统)。含义:
auto ens33
表示当系统启动网络服务时,它会自动尝试激活ens33
这个网卡。
iface ens33 inet dhcp
iface
: 是 "interface" 的缩写,表示开始定义一个网络接口的配置。
ens33
: 指定要配置的网络接口名称,与上面的auto
行一致。
inet
: 指定该接口使用的地址族是 IPv4 (inet6
则表示 IPv6)。
dhcp
: 这是最关键的部分。它指定该接口将通过 DHCP (Dynamic Host Configuration Protocol) 协议来自动获取其 IP 地址、子网掩码、默认网关和 DNS 服务器地址。含义: 这一行定义了接口
ens33
使用 IPv4,并且它的所有网络配置(IP地址、网关、DNS等)都从网络中的 DHCP 服务器自动获取。你不需要(也不应该)在这个文件里手动设置 IP 地址、网关等。总结起来,这两行的意思就是:
在系统启动时,自动启用名为
ens33
的网络接口,并为该接口通过 DHCP 协议自动获取 IPv4 地址及相关网络配置(网关、DNS等)。
3.保存退出
:wq
5.重启网络服务
sudo /etc/init.d/networking restart
sudo reboot // 所有配置类的都可以重启,不止网络。
6.测试与局域网内其余IP地址是否能够连通
ping 192.168.0.167
ping www.baidu.com
7.netstat -anp | less
查看进程的端口号
8.网络的字节序
大端存储
将数字转换顺序:也就是端口号的转换函数uint32_t htonl(uint32_t hostlong);
hton(50000)
主机转网络:uint32_t htonl(uint32_t hostlong);
ipv4 192.168.0.1 1~65535
uint16_t htons(uint16_t hostshort);
网络转主机:host to net
net to host
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
字符串转换函数:
主机转网络:in_addr_t inet_addr(const char *cp);
inet_addr("192.168.1.20");
cli.sin_addr
网络转主机:char *inet_ntoa(struct in_addr in);
4.UDP编程:
1.套接字:
理解为: 1.网络文件的文件描述符。 或者 2.用于网络通信的一组接口函数。
实现Linux系统下的网络通信
套接字:一次通信对象的抽象
2.socket
//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
//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
sendaddr.sin_addr.s_addr = inet_addr("192.168.0.171");
in_addr_t inet_addr(const char *cp);
功能:
将字符串的IP地址转换为32位的地址类型
5.htons
sendaddr.sin_port = htons(20000);
uint16_t htons(uint16_t hostshort);
功能:
将本地字节序(小端)转换成网络大端字节序
6.bind
//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);
功能:
如果该函数在服务器端调用,则表示将参数1相关的文件描述符文件与参数2 指定的接口地址关联,用于从该接口接受数据。
如果该函数在客户端调用,则表示要将数据从参数1所在的描述符中取出并从参数2所在的接口
设备上发送出去。
注意:如果是客户端,则该函数可以省略,由默认
接口发送数据。
参数:
addr:绑定地址结构体空间首地址
addrlen:绑定地址空间大小
struct sockaddr ////通用地址结构 { u_short sa_family; ////地址族 char sa_data[14]; ////地址信息 }; 转换成网络地址结构如下: struct _sockaddr_in ///网络地址结构 { u_short sin_family; ////地址族 u_short sin_port; ///地址端口 struct in_addr sin_addr; ///地址IP char sin_zero[8]; ////占位 }; struct in_addr { in_addr_t s_addr; }
返回值:
成功返回0
失败返回-1
注意:
只能绑定自己的IP地址
7.recvfrom
//3.接收数据
nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
if (-1 == nsize)
{
perror("fail to recvfrom");
return -1;
}
printf("接收内容: %s\n", tmpbuff);
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
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == sockfd)
{
perror("socket");
return 1;
}
2.定义存放客户端和服务端,端口,ip,类型等信息的结构体
struct sockaddr_in ser,cli;//client man 7 ip
3.给服务器结构体中的端口,地址,网络类型赋值,注意小端转大端------- htons,inet_addr
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000); // host to net short
ser.sin_addr.s_addr = inet_addr("192.168.116.130");// 小端转大端
4.服务端与套接字绑定自己的IP和端口。注意转换服务器结构体网络(ser)的类型
typedef struct sockaddr * (SA)
int ret = bind(sockfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("bind");
return 1;
}
将struct sockaddr_in ser;转换为struct sockaddr * 类型;
5.接收来自客户端的信息,从而获得客户端的ip和端口,并把获得信息存在客户端结构体中。
socklen_t len = sizeof(cli);
char buf[256]={0};
recvfrom(sockfd,buf,sizeof(buf),0,(SA)&cli,&len);
这里的recvfrom最后一个参数的类型是地址,sizeof是常量不能取地址,所以先定义一个变量去接收sizeof的大小,然后再去取这个变量的地址。
将struct sockaddr_in ser;转换为struct sockaddr * 类型;
6.向客户端发送信息
sendto(sockfd,buf,strlen(buf),0,(SA)&cli,sizeof(cli));
7.关闭套接字
close(sockfd);
代码示例1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr * (SA);
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == sockfd)
{
perror("socket");
return 1;
}
struct sockaddr_in ser,cli;//client man 7 ip
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000); // host to net short
ser.sin_addr.s_addr = inet_addr("192.168.116.130");// 小端转大端
int ret = bind(sockfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("bind");
return 1;
}
socklen_t len = sizeof(cli);
while(1)
{
char buf[256]={0};
recvfrom(sockfd,buf,sizeof(buf),0,(SA)&cli,&len);
time_t tm;
time(&tm);
struct tm * tminfo = localtime(&tm);
sprintf(buf,"%s %d:%d:%d\n",buf,tminfo->tm_hour,tminfo->tm_min
,tminfo->tm_sec);
sendto(sockfd,buf,strlen(buf),0,(SA)&cli,sizeof(cli));
}
return 0;
}
客户端流程:
1.创建用来通信的套接字------socket
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == sockfd)
{
perror("socket");
return 1;
}
2.定义存放客户端和服务端的端口,ip,类型等信息的结构体
struct sockaddr_in ser,cli;//client man 7 ip
3.给服务器结构体中的端口,地址,网络类型赋值,注意小端转大端------- htons,inet_addr
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000); // host to net short
ser.sin_addr.s_addr = inet_addr("192.168.116.130");// 小端转大端
客户端可以绑定自己的IP和端口,也可以不绑定,默认的端口发出。
4.向绑定的服务器发送信息,注意转换服务器结构体网络的类型(函数要求)------- sendto
typedef struct sockaddr * (SA);
char buf[256]={0};
strcpy(buf,"this is udp test");
sendto(sockfd,buf,strlen(buf),0,(SA)&ser,sizeof(ser));
将struct sockaddr_in ser;转换为struct sockaddr * 类型;
5.接收来自套接字的信息
bzero(buf,sizeof(buf));
recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
5.关闭套接字------close
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <time.h>
#include <unistd.h>
typedef struct sockaddr * (SA);
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == sockfd)
{
perror("socket");
return 1;
}
struct sockaddr_in ser,cli;//client man 7 ip
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000); // host to net short
ser.sin_addr.s_addr = inet_addr("192.168.116.130");// 小端转大端
socklen_t len = sizeof(cli);
while(1)
{
char buf[256]={0};
strcpy(buf,"this is udp test");
sendto(sockfd,buf,strlen(buf),0,(SA)&ser,sizeof(ser));
bzero(buf,sizeof(buf));
recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
printf("server:%s",buf);
sleep(1);
}
close(sockfd);
return 0;
}
⭐⭐⭐
流程:
服务:创建套接字-----与套接字绑定自己的ip,端口-----接收来自客户端的信息从而获得客户端的ip和端口,赋值给客户端结构体-----给客户端发送信息------关闭套接字。
客户:创建套接字----给服务端结构体ip和端口赋值-----发送信息给服务端-----接收来自服务端的信息-----关闭套接字。
服务端与套接字绑定ip和端口意思就是从这个端口获得信息,客户端不绑定是默认端口发出信息,也可以指定端口,那就需要bind绑定,客户端发送信息给ser结构体中存放的端口和ip,接收信息的时候就不需要存放来自服务端的端口和IP,因为已经知道了。而服务器接收到来自客户端的信息后要将客户端的端口和ip存放在客户端结构体cli中,从而才能给客户端发信息。
其中关键的关系:
服务端套接字和服务端的ip和端口,服务端与 (客户端的ip和端口)
客户端的套接字与客户端的ip和端口,客户端与(服务端的端口和ip)
服务端的套接字要绑定服务端的ip和端口,是因为刚开始要接收来自客户端的信息,客户端已经知道服务端的ip和端口,能给服务端的端口和ip发送信息,如果不绑定服务端的套接字和服务端的ip和端口那么服务端的套接字就在不知道去哪接收信息,所以用bind。而客户端不绑定是因为可以用默认的端口发出。
刚开始服务端不知道客户端的ip和端口,只有接收到了客户端的信息,才能知道客户端的ip和端口,从而才能给客户端发信息。所以recvfrom中后俩个参数给cli赋值,将接收到的信息(包含中的ip和端口)赋值给cli。而客户端的recvfrom就不要给ser赋值,因为已经知道,并且在刚开始的时候就已经赋值了。所以后俩个参数填NULL空。
这就是客户端与服务端中,bind有无,recvfrom有无后俩位参数的原因。
示例代码:
发送端:
#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;
}
#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;
}
#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.编写两个程序,一个发送端,一个接
(1)
发:
#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;
}
收:
#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;
}
(2).利用线程,udp套接字实现两台主机全双工通信
send.c
#include "../head.h"
int sockfd = 0;
pthread_t tid1;
pthread_t tid2;
struct sockaddr_in recvaddr;
void *thread1(void *arg)
{
char tmpbuff[1024] = {0};
ssize_t nsize = 0;
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
fgets(tmpbuff, sizeof(tmpbuff), stdin);
tmpbuff[strlen(tmpbuff)-1] = '\0';
nsize = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nsize)
{
perror("fail to sendto");
return NULL;
}
if (!strcmp(tmpbuff, ".quit"))
{
break;
}
}
pthread_cancel(tid2);
return NULL;
}
void *thread2(void *arg)
{
char tmpbuff[1024] = {0};
ssize_t nsize = 0;
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
if (-1 == nsize)
{
perror("fail to recvfrom");
return NULL;
}
if (!strcmp(tmpbuff, ".quit"))
{
break;
}
printf("RECV:%s\n", tmpbuff);
}
pthread_cancel(tid1);
return NULL;
}
int main(void)
{
//1.创建套接字
char tmpbuff[1024] = {"hello"};
ssize_t nsize = 0;
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(RECV_PORT);
recvaddr.sin_addr.s_addr = inet_addr(RECV_IP);
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//2.发送一次
nsize = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nsize)
{
perror("fail to sendto");
return -1;
}
//3.创建两个线程
pthread_create(&tid1, NULL, thread1, NULL);
pthread_create(&tid2, NULL, thread2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
close(sockfd);
return 0;
}
receve.c
#include "../head.h"
int sockfd = 0;
pthread_t tid1;
pthread_t tid2;
struct sockaddr_in recvaddr;
struct sockaddr_in sendaddr;
void *thread1(void *arg)
{
char tmpbuff[1024] = {0};
ssize_t nsize = 0;
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
fgets(tmpbuff, sizeof(tmpbuff), stdin);
tmpbuff[strlen(tmpbuff)-1] = '\0';
nsize = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&sendaddr, sizeof(sendaddr));
if (-1 == nsize)
{
perror("fail to sendto");
return NULL;
}
if (!strcmp(tmpbuff, ".quit"))
{
break;
}
}
pthread_cancel(tid2);
return NULL;
}
void *thread2(void *arg)
{
char tmpbuff[1024] = {0};
ssize_t nsize = 0;
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
if (-1 == nsize)
{
perror("fail to recvfrom");
return NULL;
}
if (!strcmp(tmpbuff, ".quit"))
{
break;
}
printf("RECV:%s\n", tmpbuff);
}
pthread_cancel(tid1);
return NULL;
}
int main(void)
{
//1.创建套接字
char tmpbuff[1024] = {"hello"};
ssize_t nsize = 0;
int ret = 0;
socklen_t addrlen = sizeof(sendaddr);
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(RECV_PORT);
recvaddr.sin_addr.s_addr = inet_addr(RECV_IP);
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
ret = bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == ret)
{
perror("fail to bind");
return -1;
}
//2.接收一次
nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, (struct sockaddr *)&sendaddr, &addrlen);
if (-1 == nsize)
{
perror("fail to recvfrom");
return -1;
}
//3.创建两个线程
pthread_create(&tid1, NULL, thread1, NULL);
pthread_create(&tid2, NULL, thread2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
close(sockfd);
return 0;
}
(3)利用进程,udp套接字实现两台主机全双工通信
ser:
cli:
8.wireshark 抓包工具
1.虚拟机能够上网
2.apt-get配置好
3.sudo apt-get install wireshark
4.sudo wireshark (启动)
1.开启抓包
2.开始通信
3.停止抓去并筛选通信信息
双击any
太多内容,可以根据源进行过滤
或者直接搜索过滤
找到了数据包
len=12是应用层数据包的长度,也就是hello world\n
length 是56,包括:
UDP包头:(8个字节)
1.源端口号
2.目的端口号
3.长度
4.校验和
5.TCP编程
发端: 收端:
socket socket
bind
listen
connect accept
send recv
recv send
close close
1.函数接口:
1.socket
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
ret = listen(sockfd, 10);
if (-1 == ret)
{
perror("fail to listen");
return -1;
}
int listen(int sockfd, int backlog);
功能:
监听发送三次握手连接的套接字,并放入等到处理队列中
参数:
sockfd:套接字文件描述符
backlog:等待队列的大小(最多存放尚未被处理的三次握手请求的个数)
返回值:
成功返回0
失败返回-1
listen不会阻塞,accept会阻塞。
3.accept
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
sockfd是不和任意的去通信,他的目的是接收三次握手连接,通信是和对应的套接字连接,也就是accept产生的新的套接字,去通信。
4.connect
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
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
服务端send和recv的套接字是accept返回的那个通信套接字,不是lisen的那个套接字,listen的套接字只是用来监听,不是用来通信的。
对于客户端,只有一个套接字,那就是通信套接字。
6.recv
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
7.close
关闭套接字,要关闭俩个,一个是通信套接字,一个是监听套接字,这里关闭了监听套接字之前已经接收的还会继续,之后已经找不到监听套接字了,就没办法继续了。
发送端流程:
1.创建用来通信的套接字------socket
2.给接收端(目的地)IP地址和端口号赋值
3.给目的地发送连接请求------connect
4.向目的地址发送数据-------send
5.接收目的地发来的数据-------recv
5.关闭套接字------close
#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
#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;
}
发端是客户端,收端是服务端。
TCP和UDP比较:
1.socket不同:
UDP: socket(AF_INET, SOCK_DGRAM, 0);
TCP: socket(AF_INET, SOCK_STREAM, 0);//流式
2.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和端口信息是因为在之前就已经建立 了连接。
3.recvfrom 和 recv的比较:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
这里recv不需要写参数接收端的IP和端口信息是因为在之前就已经建立 了连接。
UDP发送文件:
send.c
#include "../head.h"
int main(void)
{
ssize_t nsize = 0;
ssize_t nret = 0;
char filepath[1024] = {0};
char tmpbuff[4096] = {0};
struct sockaddr_in recvaddr;
int fd = 0;
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(RECV_PORT);
recvaddr.sin_addr.s_addr = inet_addr(RECV_IP);
//1.创建套接字
int sockfd = 0;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//2.接收发送的文件路径
printf("请输入发送的文件路径:\n");
scanf("%s", filepath);
//3.发送文件名
nsize = sendto(sockfd, filepath, strlen(filepath)+1, 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nsize)
{
perror("fail to sendto");
return -1;
}
//4.发送文件内容
fd = open(filepath, O_RDONLY);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
while (1)
{
nsize = read(fd, tmpbuff, sizeof(tmpbuff));
if (nsize <= 0)
{
break;
}
nret = sendto(sockfd, tmpbuff, nsize, 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nret)
{
perror("fail to sendto");
return -1;
}
}
sprintf(tmpbuff, ".quit");
nret = sendto(sockfd, tmpbuff, strlen(tmpbuff)+1, 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nret)
{
perror("fail to sendto");
return -1;
}
//4.关闭文件和套接字
close(fd);
close(sockfd);
return 0;
}
recv.c:
#include "../head.h"
int main(void)
{
//1.创建套接字
int sockfd = 0;
struct sockaddr_in recvaddr;
ssize_t nsize = 0;
char tmpbuff[4096] = {0};
int ret = 0;
int fd = 0;
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;
}
//3.接收文件名
nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
if (-1 == nsize)
{
perror("fail to recvfrom");
return -1;
}
fd = open(tmpbuff, O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
while (1)
{
//4.接收文件内容
nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
if (-1 == nsize)
{
perror("fail to recvfrom");
return -1;
}
if (!strcmp(tmpbuff, ".quit"))
{
break;
}
//5.将内容写入文件中
write(fd, tmpbuff, nsize);
}
//6.关闭文件和套接字
close(fd);
close(sockfd);
return 0;
}
TCP发送文件:
send.c:
#include "../head.h"
int main(void)
{
int sockfd = 0;
int ret = 0;
ssize_t nsize = 0;
ssize_t nret = 0;
char filepath[1024] = {0};
char tmpbuff[4096] = {0};
int fd = 0;
struct sockaddr_in recvaddr;
//1.创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//2.发送连接请求
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(RECV_PORT);
recvaddr.sin_addr.s_addr = inet_addr(RECV_IP);
ret = connect(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == ret)
{
perror("fail to connect");
return -1;
}
//3.发送文件名
fgets(filepath, sizeof(filepath), stdin);
filepath[strlen(filepath)-1] = '\0';
nsize = send(sockfd, filepath, strlen(filepath), 0);
if (-1 == nsize)
{
perror("fail to send");
return -1;
}
sleep(1);
//4.发送文件内容
fd = open(filepath, O_RDONLY);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
while (1)
{
nsize = read(fd, tmpbuff, sizeof(tmpbuff));
if (nsize <= 0)
{
break;
}
nret = send(sockfd, tmpbuff, nsize, 0);
if (-1 == nret)
{
perror("fail to send");
return -1;
}
}
//5.关闭套接字
close(fd);
close(sockfd);
return 0;
}
recv.c:
#include "../head.h"
int main(void)
{
int sockfd = 0;
int ret = 0;
int confd = 0;
char tmpbuff[4096] = {0};
char filepath[1024] = {0};
ssize_t nsize = 0;
ssize_t nret = 0;
int fd = 0;
struct sockaddr_in recvaddr;
//1.创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//2.绑定IP地址和Port
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;
}
//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, filepath, sizeof(filepath), 0);
if (-1 == nsize)
{
perror("fail to recv");
return -1;
}
//6.创建文件并写入文件内容
fd = open(filepath, O_WRONLY | O_TRUNC | O_CREAT, 0664);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
while (1)
{
nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
if (0 == nsize)
{
printf("链接关闭\n");
break;
}
else if (-1 == nsize)
{
printf("链接出错\n");
break;
}
write(fd, tmpbuff, nsize);
}
//7.关闭文件和套接字
close(fd);
close(confd);
close(sockfd);
return 0;
}
6.TCP粘包问题:
TCP发送数据是连续的,两次发送的数据可能粘连成一包被接收到
1.解决粘包问题方法:
1.接收指定长度:(不稳定)
发送5个字节 接收5个字节
2.睡眠:(效率低)
让每次发送间设定时间间隔
3.将两次数据包间添加间隔标志(文件名后面加\r\n)
(1)在发送端将文件名拼接一个\r\n
(2)接受端用fdopen 通过文件描述符获得文件流指针
(3)接收端fgets将文件名\r\n获取
(4)strtok截取文件名
send.c:
#include "../head.h"
int main(void)
{
int sockfd = 0;
int ret = 0;
ssize_t nsize = 0;
ssize_t nret = 0;
char filepath[1024] = {0};
char tmpbuff[4096] = {0};
int fd = 0;
struct sockaddr_in recvaddr;
//1.创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//2.发送连接请求
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(RECV_PORT);
recvaddr.sin_addr.s_addr = inet_addr(RECV_IP);
ret = connect(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == ret)
{
perror("fail to connect");
return -1;
}
//3.发送文件名
fgets(filepath, sizeof(filepath), stdin);
filepath[strlen(filepath)-1] = '\0';
sprintf(tmpbuff, "%s\r\n", filepath);//在文件名字后面添加\r\n
nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
perror("fail to send");
return -1;
}
//4.发送文件内容
fd = open(filepath, O_RDONLY);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
while (1)
{
nsize = read(fd, tmpbuff, sizeof(tmpbuff));
if (nsize <= 0)
{
break;
}
nret = send(sockfd, tmpbuff, nsize, 0);
if (-1 == nret)
{
perror("fail to send");
return -1;
}
}
//5.关闭套接字
close(fd);
close(sockfd);
return 0;
}
recv.c:
#include "../head.h"
int main(void)
{
int sockfd = 0;
int ret = 0;
int confd = 0;
char tmpbuff[4096] = {0};
char filepath[1024] = {0};
ssize_t nsize = 0;
ssize_t nret = 0;
char *pfilepath = NULL;
int fd = 0;
FILE *fp = NULL;
struct sockaddr_in recvaddr;
//1.创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//2.绑定IP地址和Port
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;
}
//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.接收文件名
fp = fdopen(confd, "r+");//fdopen通过文件描述符获得文件流指针
if (NULL == fp)
{
perror("fail to fdopen");
return -1;
}
//src.jpg\r\n
fgets(filepath, sizeof(filepath), fp);//将文件名\r\n全部接收
pfilepath = strtok(filepath, "\r");//只要文件名字
if (NULL == pfilepath)
{
return -1;
}
//6.创建文件并写入文件内容
fd = open(pfilepath, O_WRONLY | O_TRUNC | O_CREAT, 0664);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
while (1)
{
nsize = fread(tmpbuff, 1, sizeof(tmpbuff), fp);
if (0 == nsize)
{
break;
}
write(fd, tmpbuff, nsize);
}
//7.关闭文件和套接字
close(fd);
fclose(fp);
close(confd);
close(sockfd);
return 0;
}
利用tcp套接字实现两台主机全双工通信
send.c:
#include "../head.h"
int sockfd = 0;
pthread_t tid1;
pthread_t tid2;
void *thread1(void *arg)
{
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
MY_GETS(tmpbuff);
nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
perror("fail to send");
return NULL;
}
if (!strcmp(tmpbuff, ".quit"))
{
break;
}
}
close(sockfd);
return NULL;
}
void *thread2(void *arg)
{
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
perror("fail to recv");
return NULL;
}
else if (0 == nsize)
{
break;
}
if (!strcmp(tmpbuff, ".quit"))
{
break;
}
printf("RECV:%s\n", tmpbuff);
}
pthread_cancel(tid1);
close(sockfd);
return NULL;
}
int main(void)
{
int ret = 0;
struct sockaddr_in recvaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("fail t socket");
return -1;
}
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(RECV_PORT);
recvaddr.sin_addr.s_addr = inet_addr(RECV_IP);
ret = connect(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == ret)
{
perror("fail to connect");
return -1;
}
pthread_create(&tid1, NULL, thread1, NULL);
pthread_create(&tid2, NULL, thread2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
recv.c:
#include "../head.h"
int sockfd = 0;
int confd = 0;
pthread_t tid1;
pthread_t tid2;
void *thread1(void *arg)
{
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
MY_GETS(tmpbuff);
nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
perror("fail to send");
return NULL;
}
if (!strcmp(tmpbuff, ".quit"))
{
break;
}
}
close(confd);
return NULL;
}
void *thread2(void *arg)
{
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
perror("fail to recv");
return NULL;
}
else if (0 == nsize)
{
break;
}
if (!strcmp(tmpbuff, ".quit"))
{
break;
}
printf("RECV:%s\n", tmpbuff);
}
pthread_cancel(tid1);
close(confd);
return NULL;
}
int main(void)
{
int ret = 0;
struct sockaddr_in recvaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("fail t socket");
return -1;
}
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;
}
ret = listen(sockfd, 10);
if (-1 == ret)
{
perror("fail to listen");
return -1;
}
confd = accept(sockfd, NULL, NULL);
if (-1 == confd)
{
perror("fail to accept");
return -1;
}
pthread_create(&tid1, NULL, thread1, NULL);
pthread_create(&tid2, NULL, thread2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
close(sockfd);
return 0;
}
UDP MTU最大传输单元(理论:65535 实际:1500)
TCP包头(待补充)
源端口
目的端口
序号:发送数据的编号
确认号:接收到数据的编号(只有当ACK为1时,该位有效)、确认号即想要让对方下次发送数据的序号
数据偏移:拆包组包过程中标识该包的偏移量
SYN:请求应答
ACK:确认应答
FIN:结束连接
RST:重置连接
PSH:数据包
URG:加急
窗口:滑动串口,用来完成流量控制和拥塞控制
滑动窗口:用来控制接收和发送窗口的大小,来实现对流量的控制
校验和
紧急指针
本次发送的序号,为上次收到的确认号
本次发送的确认号,为收到的序号 + 实际接收到的数据长度
1.TCP三次握手:
SYN
SYN + ACK
ACK
2.四次挥手:
FIN
ACK + FIN
ACK
FIN
ACK
FIN
ACK
3.传输过程中通过序号和确认号保障数据传输的完整性
PSH
ACK
TCP和UDP传输方式:
1.UDP实现方式简单
资源开销比较小
UDP不安全、不可靠
UDP是无连接的,面向数据包的传输方式
2.TCP实现方式复杂
资源开销比较大
TCP安全、可靠
TCP是面向连接的,面向字节流传输方式
7.进程实现tcp的不阻塞
server:
#include "head.h"
int CreateListenSocket(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(pip);
ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
ret = listen(sockfd, 10);
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int confd)
{
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
sprintf(tmpbuff, "%s --- echo", tmpbuff);
nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
return nsize;
}
void handler(int signo)
{
wait(NULL);
return;
}
int main(void)
{
int sockfd = 0;
int confd = 0;
int ret = 0;
pid_t pid;
signal(SIGCHLD, handler);
sockfd = CreateListenSocket(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("创建监听套接字失败\n");
return -1;
}
while (1)
{
confd = accept(sockfd, NULL, NULL);
if (-1 == confd)
{
printf("处理连接失败\n");
return -1;
}
pid = fork();
if (-1 == pid)
{
perror("fail to fork");
return -1;
}
if (0 == pid)
{
while (1)
{
ret = HandleConnection(confd);
if (-1 == ret)
{
printf("接收异常!\n");
break;
}
else if (0 == ret)
{
printf("关闭连接!\n");
break;
}
}
close(confd);
close(sockfd);
exit(0);
}
}
close(sockfd);
return 0;
}
client:
#include "head.h"
int CreateTcpConnection(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(SER_IP);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int sockfd)
{
char tmpbuff[4096] = {0};
static int cnt = 0;
ssize_t nsize = 0;
sprintf(tmpbuff, "hello world --- %d", cnt);
nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
cnt++;
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
return nsize;
}
int main(void)
{
int sockfd = 0;
int ret = 0;
sockfd = CreateTcpConnection(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("连接服务器异常\n");
return -1;
}
while (1)
{
ret = HandleConnection(sockfd);
if (-1 == ret)
{
printf("连接出错!\n");
break;
}
else if (0 == ret)
{
printf("连接关闭\n");
break;
}
sleep(1);
}
close(sockfd);
return 0;
}
8.线程实现tcp
server:
#include "head.h"
int CreateListenSocket(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(pip);
ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
ret = listen(sockfd, 10);
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int confd)
{
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
sprintf(tmpbuff, "%s --- echo", tmpbuff);
nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
return nsize;
}
void *threadfun(void *arg)
{
int ret = 0;
int confd = (int)arg;
while (1)
{
ret = HandleConnection(confd);
if (-1 == ret)
{
printf("接收异常!\n");
break;
}
else if (0 == ret)
{
printf("关闭连接!\n");
break;
}
}
return NULL;
}
int main(void)
{
int sockfd = 0;
int confd = 0;
int ret = 0;
pthread_t tid;
pthread_attr_t attr;
sockfd = CreateListenSocket(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("创建监听套接字失败\n");
return -1;
}
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
while (1)
{
confd = accept(sockfd, NULL, NULL);
if (-1 == confd)
{
printf("处理连接失败\n");
return -1;
}
pthread_create(&tid, &attr, threadfun, (void *)confd);
}
close(sockfd);
return 0;
}
client:
#include "head.h"
int CreateTcpConnection(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(SER_IP);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int sockfd)
{
char tmpbuff[4096] = {0};
static int cnt = 0;
ssize_t nsize = 0;
sprintf(tmpbuff, "hello world --- %d", cnt);
nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
cnt++;
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
return nsize;
}
int main(void)
{
int sockfd = 0;
int ret = 0;
sockfd = CreateTcpConnection(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("连接服务器异常\n");
return -1;
}
while (1)
{
ret = HandleConnection(sockfd);
if (-1 == ret)
{
printf("连接出错!\n");
break;
}
else if (0 == ret)
{
printf("连接关闭\n");
break;
}
sleep(1);
}
close(sockfd);
return 0;
}
8.HTTP
HTTP协议:
www.nowapi.com
APPKey: 44923
sign: 5432c8efd2fc919d409b01241b70c9f4
HTTP超文本传输协议
应用层
万维网:大型的信息联网存储所
统一资源定位符:简称URL
协议://主机:端口号/资源路径
https://www.baidu.com
协议:https 加密
http 非加密
主机:IP地址
端口号: 80 (http)
443 (https)
资源路径: 默认为要的信息是 / 主页
短连接:想要通信时建立链接
长连接:通信前建立链接,通过新过程中链接一直保持
客户端如何拿到服务器中的网页文件?
1.客户端向主机发送TCP链接请求 (http使用的是tcp协议,必然不能是udp了)
2.服务器收到请求后,与客户端链接成功
3.客户端向发送HTTP请求报文,告诉服务器想要的数据
4.服务器回复HTTP响应报文,将客户端要的数据发回
5.双方关闭通信
用抓包工具获取信息
1.打开抓包工具
2.用虚拟机的浏览器打开网站
3.获得抓到的信息
通信报文:
请求报文:客户端向服务端
GET /?app=weather.today&weaId=1&appkey=44923&sign=5432c8efd2fc919d409b01241b70c9f4&format=json HTTP/1.1
Host: api.k780.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
HTTP/1.1:版本号
CRLF:\r\n
Host:主机
User-Agent:用的是哪个浏览器
Accept:想要的数据类型
Accept-Language:想要啥语言的
Accept-Encoding:要什么格式,压缩(可以去掉这行)
Connection:连接,保持链接
Upgrade-Insecure-Requests: 缓存的东西(去掉)
响应报文:服务端向客户端
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 17 Aug 2024 08:08:35 GMT
Content-Type: application/json; charset=utf-8;
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *
(主体)
{"success":"1","result":{"weaid":"1","days":"2024-08-17","week":".........","cityno":"beijing","citynm":"......","cityid":"101010100","temperature":"30.../23...","temperature_curr":"32...","humidity":"62%","aqi":"45","weather":"...............","weather_curr":"......","weather_icon":"http://api.k780.com/upload/weather/d/1.gif","weather_icon1":"","wind":".........","winp":"2...","temp_high":"30","temp_low":"23","temp_curr":"32","humi_high":"0","humi_low":"0","weatid":"2","weatid1":"","windid":"5","winpid":"2","weather_iconid":"1"}}
Server:服务器
Date:时间
Content-Type:数据的格式
Transfer-Encoding:传递的模式,连续的模式
HTTP请求报文格式:
1.方法:
GET
2.资源路径:
/?app=weather.today&weaId=1&appkey=44923&sign=5432c8efd2fc919d409b01241b70c9f4&format=json
weaId:当前城市的id号
appkey:用户的账号
sign:标识,用来看有没有权限
format:想要的数据格式,为json格式
9.数据的传输
应用层
传输层
网络层
网络接口层
1.以太网 V2的 MAC 帧格式
MAC物理地址,也就是硬件的地址,每一个硬件的地址都是独一的,网卡的地址
以太网的MAC帧包括:目的地址,源地址,上一层协议类型,数据,FCS检验
2.IP数据报的格式
分为俩部分:首部,数据部分
表示用的是IPV4还是IPV6
首部长度占四位,但是能表示十进制的数是15,十进制1代表一个字节,十进制的15就是60个字节,也就是首部长度占4位,可以表示60字节,就好比,一箱酒有12瓶,那么四箱酒就有60瓶。这里用四个位表示有多少个4字节大小的,4位最多表示有15个4字节。比如说:
0001
(二进制)→ 表示首部长为 1 × 4 = 4 字节
0101
(二进制)→ 表示首部长为 5 × 4 = 20 字节
1111
(二进制)→ 表示首部长为 15 × 4 = 60 字节(最大)首部长度字段本身是4位(bit);
它的单位是 4字节;
所以它能表示的实际首部长度范围是:
最小:4 × 1 = 4字节
最大:4 × 15 = 60字节
总长度是首部+数据的长度,占16位,也就是十进制表示2^16-1 = 65535,每个十进制代表一个字节,也就是单位是1字节。
标识,就是分片后方便拼接在一起。
标志,就是设定是否可以分片。
MTU:最大传输单元,以太网的限制,最大1.5k,超过就会数据分片。(ifconfig里面可以看到)
TTL:生存时间,和互联网结点有关,结点去找某个网站,通过查找信息传递给相连的结点,互相传递去找,有最短路径和最佳路径,最后找到最佳路径,假如去找某个不存在的结点,那么TTL(默认64),结点找的时候向下个结点传递一次的时候TTL会自动减1,当减到0的时候,结点就停止工作,不找了。
传输层,比如说用的是UDP协议或者是TCP协议,那么IP数据报就会发到相应的协议去处理。
源地址:从哪里来
目的地址:到哪里去