1、概念
计算机网络:多个主机之间进行连接,形成一种互连互通的形式进行通信
结点:可以是计算机、集线器、路由器等在网络中进行通信的设备
链路:连接结点的称为链路,可以是光纤、铜缆、WiFi、卫星等
协议:两个实体之间的通信规则,协议规定了通信实体之间所交换的消息的格式、意义、顺序以及针对信息或发生的事情采取的动作
协议三要素:
语法(数据与控制信息的结构或格式、电平信号)
语义(需要发出何种控制信号、完成何种动作以及做出何种相应、差错控制)
时序(速度匹配、事件顺序)
实体:任何可发送或接受信息的硬件或软件进程
服务:在协议的控制下,本层向上一层提供服务,本层使用下一层所提供的服务
互联网:多个网络之间进行连接(WAN广域网、MAN城域网、LAN局域网、PAN个域网)
因特网:当前最大的一个互联网
通信方式:
1、C/S模式:客户端---服务器方式。客户端是服务的请求方,服务器是服务的提供方
2、B/S模式:浏览器---服务器方式。浏览器通过web访问服务器,进行请求进行数据交换
3、P2P模式:对等方式。通信在对等实体之间直接进行
网络新能参数:
1、速率:在网络中数据的传输速率,单位是 bit/s
2、带宽:在计算机网络中,网络带宽表示单位时间内网络信道所能通过的“最高数据率”
3、吞吐量:表示在发送端与接收端之间实际的传输数据速率
4、时延:指数据从网络的一段传输到另一端所需的时间
a、发送时延:是足迹或路由器发送数据帧所需要的时间
b、传播时延:是数据在信道中传播一定的距离所要花费的时间
c、处理时延:是主机或路由器在收到分组时要花费一点的时间进行处理
d、排队时延:在分组进入到路由器后要现在输入队列中排队等待
5、往返时间:从发送数据到接收数据,总共经历的时间
6、信道利用率:指某信道有%的时间是有数据通过
互联网的组成:
边缘部分:各种终端设备:主机、手机,位于网络的边缘;运行网络应用程序
核心部分:互联的路由器网络。关键功能:路由转发;交换机是在同一个子网内部进行转发数据;路由器是在不同的子网之间转发数据
主流的两种网络层次模型:
OSI网络模型(参考模型):7层,理论完整,实现复杂,但是没人使用
层次 | 名字 | 功能 |
第七层 | 应用层 | 由网络应用程序使用的,离用户最近的一层。组织应用数据(HTTP、HTTPS、FTP) |
第六层 | 表示层 | 从应用层接受数据,这些数据是字符或数字形式,表示层将这些数据字符,转换成机器能够理解的二进制格式 |
第五层 | 会话层 | 用于建立和管理连接、启用、发送和连接数据 |
第四层 | 传输层 | 实现 分段、流量控制和差错检测控制来控制通信的可靠性 |
第三层 | 网络层 | 传输层将数据传输到网络层,网络层用于将接收到的数据段从一台计算机传输到不同网络中的另一台计算机,网络层的功能是 进行 逻辑寻址、路由选择和路径确定 |
第二层 | 数据链路层 | 从网络层获取到数据包,数据包包含了发送方和接收方的地址。有两种寻址方式:逻辑寻址和物理寻址。逻辑寻址在网络层已经完成,即在网络数据包中添加了双方的地址。物理寻址就是在数据链路层中完成,实际的的一段传输位置(发送方和接收方的物理地址)-------帧 |
第一层 | 物理层 | 将数据链路层的帧的二进制序列转换成信号并在本地介质(铜缆、光纤、无线信号灯)上传输 |
TCP/IP网络模型:
层次 | 名字 | 作用 |
第四层 | 应用层 | |
第三层 | 传输层 | |
第二层 | 网络层 | |
第一层 | 网络接口层 |
TCP/IP体系结构(模型)传输数据:
数据的封装与解封装
2、层次
1、物理层
1、信号编码:不归零编码,慢切斯特编码
不归零编码:1为高电平、0为低电平
慢切斯特编码:1开始为高电平中间转为低电平,0开始为低电平中间转为高电平
2、影响信号失真
失真因数:传输距离、传输速率、传输介质、噪声干扰
3、传输介质:
双绞线:直通双绞线,交叉双绞线
同轴电缆
光纤:单模和多模
有低折射率的包层和高折射率的纤芯组成,光线在纤芯中通过全反射传输
宽带接入技术:
ADSL:使用现在的模拟电话用户线,采用频分复用
HFC
FTTx:
FTTH:光纤到户,光纤一致铺设到用户家庭
2、数据链路层
要理解数据链路层的作用:
什么是链路
从一个结点到相邻连接的结点的一段物理线路(有线或无线),中间没有任何其他交换结点
什么是数据链路
物理链路+通信协议
数据链路层的作用
在网络中的两个主机发送数据所经过的网络可以是多种不同类型的,不同类型网络的链路层可能采用的不同协议
三个基本问题:
封装成帧:在一端数据的前后分别添加首部和尾部,构成一个帧。帧定界符:SOH(帧开始符),EOT(帧结束符)
透明传输:若帧出现定界符,在其前面用字符填充
差错检测:循环冗余检测,在发送端,计算CRC冗余码,在待发送数据(k位)后再添加供差错检测用的CRC冗余码(n位),实际发送(k+n位);在接收端:利用n位CRC冗余码对收到的数据进行检查
用除数p在除去收到数,若余数为0,则证明帧无差错,接收
1、ppp帧
使用点对点信道的链路层
信道特点:点对点信道使用一对一的点对点通信方式。通常使用ppp协议,用户通过ppp协议接入isp,再接入互联网
2、MAC帧
局域网使用广播信道的链路层
多台主机共享局域网内软硬件资源
若多个设备在共享的广播信道上同时发送数据,则会彼此干扰,导致发送失败
网卡:计算机通过网络适配器(网卡)和局域网进行通信
MAC地址:
MAC地址是固话在网卡的ROM中,每个网卡全球唯一
MAC地址:6字节
前三个字节由IEEE注册管理机构分配,后三个字节厂商自行指派
通常MAC地址使用16进制表示:
xx:xx:xx:xx:xx:xx
xx-xx-xx-xx-xx-xx
3、网络层
网络连接:多个网络之间进行通信(解决跨网络之间的通信问题)
中继器:给物理层使用的设备
集线器:物理层使用的设备
交换机:数据链路层使用的设备
路由器:网络层使用的设备
在网络层中,对添加到网络中的设备,设置了一个网络地址------ip地址
通过ip地址标识设备在某个网络中
ip地址:由 网络号 + 主机号
网络号:在互联网中,当前整个网络的编号
主机号:在当前网络中设备的编号
分类ip地址(IPV4):32bit
ip地址用点分十进制表示:以一个字节为一个整数,通过 . 分开多个字节
A类地址:
8bit 网络号:0xxxxxxx
24bit 主机号
0.0.0.0-----127.255.255.255
B类地址:
16bit 网络号:10xxxxxx xxxxxxxx
16bit 主机号
128.0.0.0------191.255.255.255
C类地址:
24bit 网络号:110xxxxx xxxxxxxx xxxxxxxx
8bit 主机号
192.0.0.0----223.255.255.255
D类地址:
24bit 网络号:1110xxxx xxxxxxxx xxxxxxxx
8bit 主机号
224.0.0.0------239.255.255.255
E类地址:
240.0.0.0------255.255.255.255
主机号全为0:网络地址
主机号全为1:直接广播地址
网络号主机号全为1:有限广播地址
网络号为127,主机号全为0或全1的任何数,环回地址,用于本主机测试
划分子网:把原大的网络号,借用几位主机号来表示网络号
子网掩码:用于表示当前ip地址中有多少位表示网络号,有多少位表示主机号
子网掩码:网络号全为1,主机号全为0
在ip地址后 / 网络号位数
在互联网中,每个主机都有唯一的ip地址
通过ip地址确定网络中的主机
ip地址与硬件地址的关系:
ARP协议
在网络中传输数据时,目的ip地址不变,但是目的MAC地址会随着传输到不同的设备改变
ARP作用:已知主机或路由器接口的ip地址,找出其MAC地址
ARP缓存:每个主机都有一个ARP高速缓存,保存本局域网中ip地址到MAC地址的映射表
同一局域网使用ARP:当主机A向本局域网中主机B发送数据时,先查ARP高速缓存,若没有则运行ARP,查找B的MAC地址
ARP原理:主机A广播发送ARP请求分组,目标主机B收到请求后,向A发送ARP相应分组
跨网络使用ARP:判断目标IP和源IP不是同一个网络后,主机就要通过网关来传递信息了。信息首先发送到网关机上,再由网关机转发。在查找目标不在同一个网段后,目标ip改为网关机的ip。MAC地址为广播地址,发送信息时加上目标ip和MAC地址发送到网关中
IP数据报格式:
ICMP协议:
支持主机或路由器进行差错报告和网络探询
ping
4、传输层
运输层/传输层:向上面的应用层提供通信服务(提供端对端,进程到进程的通信服务),为运行在不同主机(host)上的进程提供逻辑通信,向高层用户屏蔽通信子网的细节
UDP和TCP的特点
UDP:
支持单播、多播、广播
无连接、不可靠
可以任意速率发送数据
发送数据以用户数据报为单位发送,接收也是以用户数据报为单位接收
TCP:
仅支持单播,不支持多播、广播
面向连接,可靠传输
发送时是按序传送,以字节流的形式传输
流量控制(发送速率不超过接收方的接收能力)
拥塞控制(网络过载时限制发送方的发送速率)
端口号:在进程中用于进行网络通信的标识,进程使用这个标识来表示当前进程进行网络通信,
UDP
传输层采用的UDP协议,实现端对端通信
套接字:
套接字API函数:
选择使用的网络通信的函数(加载对应的网络通信协议)
套接字文件:在进程中用于表示进行网络通信的文件(进程网络通信协议标准)
套接字文件描述符:在进程中使用网络通信协议进行通信的文件描述符
UDP通信:
步骤 | 函数 |
创建套接字 | socket() |
绑定套接字 | bind() |
进行通信 | sendto()/recvfrom() |
关闭套接字 | close() |
socket
功能 | 创建一个用于通信的套接字文件,获取套接字的文件描述符 |
头文件 | #include #include |
函数原型 | int socket(int domain, int type, int protocol); |
参数 | 参数1: int domain:地址族 AF_INET:ipv4地址族协议 参数2: int type:协议族 SOCK_STREAM:TCP SOCK_DGRAM:UDP SOCK_RAW:原始套接字 参数3: int protocol:选项 0:根据协议族自动选择 |
返回值 | 成功:返回新套接字 失败:返回-1 |
bind
功能 | 绑定套接字,为当前套接字对象添加绑定自己的网络信息(ip、port) |
头文件 | #include #include |
函数原型 | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
参数 | 参数1: int sockfd:套接字,表示绑定到哪个套接字上 参数2: const struct sockaddr *addr:地址,套接字要绑定的信息(ip、port)的结构体对象地址,在结构体中包含了ip、port等信息 参数3: socklen_t addrlen:结构体大小 |
返回值 | 成功:返回0 失败:返回-1 |
通用网络信息结构体:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
网络信息结构体需要根据不同的地址族,选择不同的结构体类型
AF_INET:------ipv4类型结构体
struct sockaddr_in {
sa_family_t sin_family; //地址族,说明网络信息的类型
in_port_t sin_port; //端口号port
struct in_addr sin_addr; //ip地址
};
/* Internet address. ipv4地址结构体*/
struct in_addr {
uint32_t s_addr; //ip地址,32bit
};
在存储端口时,存储大小端问题:
大端存储:低地址存储高字节数据,高地址存储低字节数据
小端存储:低地址存储低字节数据,高地址存储高字节数据
网络通信中:
主机字节序:主机中数据的存储方式(大小端)
网络字节序:在进行通信时,网络中以什么方式传输(大小端)-------大端方式传输
htonl
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);//主机字节序转网络字节序-----4字节
uint16_t htons(uint16_t hostshort);//主机字节序转网络字节序----2字节
uint32_t ntohl(uint32_t netlong);//网络字节序转主机字节序------4字节
uint16_t ntohs(uint16_t netshort);//网络字节序转主机字节序-----2字节
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);//把ipv4字符串地址,转换为对应的32bit ipv4地址
char *inet_ntoa(struct in_addr in);//把ipv4 32bit 地址,转换为字符串类型
sendto
功能 | 发送udp数据 |
头文件 | #include #include |
函数原型 | ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen); |
参数 | 参数1: int sockfd:通信套接字,使用通信套接字完成发送 参数2: const void *buf:指针,要发送的数据的首地址 参数3: size_t len:发送的长度,字节数 参数4: int flags:选项 0:阻塞发送 参数5: const struct sockaddr *dest_addr:通信接收数据的目的地址,网络信息结构体地址,发送的目标,需要根据通信的地址族选择结构体 参数6: socklen_t addrlen:长度,结构体大小 |
返回值 | 成功:返回发送的字节数 失败:返回-1 |
recvfrom
功能 | udp接收数据 |
头文件 | #include #include |
函数原型 | ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen); |
参数 | 参数1: int sockfd:用于接收的套接字 参数2: void *buf:指针,接收数据存储的地址 参数3: size_t len:想接收的大小 参数4: int flags: 0:阻塞接收 参数5: struct sockaddr *src_addr:指针,地址,用于获取发送方的ip、port,当接收数据时,就把发送方的信息存储到这个结构体地址中,如果不需要 写NULL 参数6: socklen_t *addrlen:指针,地址,用于获取结构体的大小,把结构体大小存储到这个地址中,不需要写 NULL |
返回值 | 成功:返回接收到的字节数 失败:返回-1 |
//send
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
int main()
{
//1、创建套接字
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
printf("error\n");
return -1;
}
//2、绑定套接字,为当前套接字添加当前进程的网络信息
//ip、port结构体
struct sockaddr_in addr;
addr.sin_family = AF_INET;//地址族
addr.sin_port = htons(8888);//port端口
addr.sin_addr.s_addr = inet_addr("192.168.124.113");//ip地址
if( bind(sockfd,(const struct sockaddr *)&addr,sizeof(addr)) < 0)
{
printf("error\n");
return -1;
}
char buf[20];
//发送的目标地址
struct sockaddr_in destaddr;
destaddr.sin_family = AF_INET;//地址族
destaddr.sin_port = htons(9999);//port端口
destaddr.sin_addr.s_addr = inet_addr("192.168.124.113");//ip地址
while(1)
{
fgets(buf,20,stdin);//从终端获取到数据
//3、发送数据---进行通信
sendto(sockfd,buf,strlen(buf)+1,0,(struct sockaddr *)&destaddr,sizeof(destaddr));
if(strcmp(buf,"quit\n") == 0)
break;
//3、接收数据
//ipv4结构体
struct sockaddr_in srcaddr = {0};
socklen_t addrlen;
//接收数据
recvfrom(sockfd,buf,20,0,(struct sockaddr *)&srcaddr,&addrlen);
printf("srcaddr:%s,port:%d--",inet_ntoa(srcaddr.sin_addr),ntohs(srcaddr.sin_port));
printf("%s",buf);
if(strcmp(buf,"quit\n")==0)
break;
}
//关闭通信
close(sockfd);
return 0;
}
//recv
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
int main()
{
//1、创建套接字
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
printf("error\n");
return -1;
}
//2、绑定套接字,为当前套接字添加当前进程的网络信息
//ip、port结构体
struct sockaddr_in addr;
addr.sin_family = AF_INET;//地址族
addr.sin_port = htons(9999);//port端口
addr.sin_addr.s_addr = inet_addr("192.168.124.113");//ip地址
if( bind(sockfd,(const struct sockaddr *)&addr,sizeof(addr)) < 0)
{
printf("error\n");
return -1;
}
char buf[20];
//发送的目标地址
struct sockaddr_in destaddr;
destaddr.sin_family = AF_INET;//地址族
destaddr.sin_port = htons(8888);//port端口
destaddr.sin_addr.s_addr = inet_addr("192.168.124.113");//ip地址
while(1)
{
//ipv4结构体
struct sockaddr_in srcaddr = {0};
socklen_t addrlen;
//接收数据
recvfrom(sockfd,buf,20,0,(struct sockaddr *)&srcaddr,&addrlen);
printf("srcaddr:%s,port:%d--",inet_ntoa(srcaddr.sin_addr),ntohs(srcaddr.sin_port));
printf("%s",buf);
if(strcmp(buf,"quit\n")==0)
break;
fgets(buf,20,stdin);//从终端获取到数据
//3、发送数据---进行通信
sendto(sockfd,buf,strlen(buf)+1,0,(struct sockaddr *)&destaddr,sizeof(destaddr));
if(strcmp(buf,"quit\n") == 0)
break;
}
//关闭通信
close(sockfd);
return 0;
}
//同传同收
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
int sockfd;
//线程进行接收数据
void * readdata(void * arg)
{
//sockfd;//
char buf[20];
while(1)
{
//接收数据
recvfrom(sockfd,buf,20,0,NULL,NULL);
printf("recv data is: %s",buf);
}
return NULL;
}
int main()
{
//1、创建套接字
sockfd = socket(AF_INET,SOCK_DGRAM,0);
//2、绑定套接字
//创建自己的网络信息ip、port
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = inet_addr("192.168.124.113");
bind(sockfd,&addr,sizeof(addr));
//3、进行通信
//创建线程,一个线程用于发送、一个线程用于接收
pthread_t tid;
pthread_create(&tid,NULL,readdata,NULL);
//主线程发送
char buf[20];
//发送的目标地址
struct sockaddr_in destaddr;
destaddr.sin_family = AF_INET;//地址族
destaddr.sin_port = htons(9999);//port端口
destaddr.sin_addr.s_addr = inet_addr("192.168.124.113");//ip地址
while(1)
{
fgets(buf,20,stdin);//从终端获取到数据
//3、发送数据---进行通信
sendto(sockfd,buf,strlen(buf)+1,0,(struct sockaddr *)&destaddr,sizeof(destaddr));
}
close(sockfd);
return 0;
}
//同传同收
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
int sockfd;
//线程进行接收数据
void * readdata(void * arg)
{
//sockfd;//
char buf[20];
while(1)
{
//接收数据
recvfrom(sockfd,buf,20,0,NULL,NULL);
printf("recv data is: %s",buf);
}
return NULL;
}
int main()
{
//1、创建套接字
sockfd = socket(AF_INET,SOCK_DGRAM,0);
//2、绑定套接字
//创建自己的网络信息ip、port
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = inet_addr("192.168.124.113");
bind(sockfd,&addr,sizeof(addr));
//3、进行通信
//创建线程,一个线程用于发送、一个线程用于接收
pthread_t tid;
pthread_create(&tid,NULL,readdata,NULL);
//主线程发送
char buf[20];
//发送的目标地址
struct sockaddr_in destaddr;
destaddr.sin_family = AF_INET;//地址族
destaddr.sin_port = htons(8888);//port端口
destaddr.sin_addr.s_addr = inet_addr("192.168.124.113");//ip地址
while(1)
{
fgets(buf,20,stdin);//从终端获取到数据
//3、发送数据---进行通信
sendto(sockfd,buf,strlen(buf)+1,0,(struct sockaddr *)&destaddr,sizeof(destaddr));
}
close(sockfd);
return 0;
}
TCP
在通信之前必须先建立链接
1、TCP首部:
TCP的可靠传输:
超时重传机制
超时计时器时间RTO:RTO=RTTs+4*RTTD
TCP流量控制(序号,确认号,确认标志位,窗口,死锁问题与持续计时器)
窗口:
序号:发送窗口里面的序号表示允许发送的序号
确认号:表示主机期望收到的下一个序号,而上一个序号位置的数据已经收到
死锁问题:发送方等待接收方通知窗口大小,接收方等待发送方发送数据
持续计时器:当发送方收到接收方的零窗口通知,启动持续计时器。若持续计时器到期,就发送一个零窗口探测报文段
TCP的拥塞控制:
网络拥塞的判断:重传定时超时
传输轮次与拥塞窗口大小的关系:
慢开始:
拥塞避免:
慢开始门限ssthresh
重传计时超时
三个重复ACK
应用数据被分割成TCP认为最适合发送的数据块
当TCP发送一个报文后,启动定时器,等待目的端口确认收到这个报文段。如果不能及时收到 对方的确认,将重发这个报文
当TCP收到来自TCP连接另一端的数据,它将发送一个确认。
TCP将保持它首部和数据的校验和
TCP报文以字节流进行传输,在IP数据报的到达可能失序,因此TCP报文到达也可能失序,TCP将对收到的数据进行重新排序
IP数据报可能会重复发送,TCP的接收端会丢弃重复的数据
通过TCP提供的流量控制,有对应的缓冲控制
TCP提供一种面向连接的、可靠的字节流服务
面向连接:两个使用TCP进行通信的应用,在彼此交换数据(通信)之前必须建立一个TCP连接(协商一些信息,通信细节)
2、TCP通信:
客户端 | 服务器 | ||||
有手机 | 创建套接字 | socket() | 有手机 | 创建套接字 | socket() |
有手机号 | 绑定套接字 | bind() | 有手机号 | 绑定套接字 | bind() |
拨打电话 | 建立连接 | connect() | 开启接听通话功能 | 监听套接字 | listen() |
接听电话 | 接收同意连接请求,建立连接 | accpet() | |||
进行通话 | 发送/接收数据 | read()/write() send()/recv() | 进行通话 | 发送/接收数据 | read()/write() send()/recv() |
挂机 | 关闭通信 | close() | 挂机 | 关闭套接字 | close() |
客户端:
建立连接请求
connect
功能 | 建立tcp连接 |
头文件 | #include #include |
函数原型 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
参数 | 参数1: int sockfd:要建立连接的套接字对象,哪个套接字要请求建立tcp连接 参数2: const struct sockaddr *addr:结构体,与哪个ip、port建立连接,向谁发起tcp连接请求 参数3: socklen_t addrlen:结构体大小 |
返回值 | 成功:返回0 失败:返回-1 |
服务器:
监听套接字
listen
功能 | 服务端监听自己的ip、prot的套接字对象,启用监听之后,如果有客户端的连接请求,放入监听队列中,表示有对应的连接请求 |
头文件 | #include #include |
函数原型 | int listen(int sockfd, int backlog); |
参数 | 参数1: int sockfd:要监听的套接字对象 参数2: int backlog:连接请求的队列大小(监听队列的大小) |
返回值 | 成功:返回0 失败:返回-1 |
连接客户端(接受客户端的连接请求)
accept
功能 | 接收客户端的连接请求,从监听队列中取出客户端的连接请求,建立连接,当监听队列没有客户端连接请求,就阻塞等待 |
头文件 | #include #include |
函数原型 | int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); |
参数: | 参数1: int sockfd:监听套接字,从监听的套接字中获取客户端的连接请求 参数2: struct sockaddr *addr:结构体指针,用于存储进行连接的客户端的网络信息ip、port 参数3: socklen_t *addrlen:指针,用于存储结构体的大小 |
返回值 | 成功:返回 与对应客户端进行通信的套接字 失败:返回 -1 |
//客户端进行通信----先发送,再接收
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
//1、创建套接字
//参数1:地址族 AF_INET---ipv4
//参数2:协议族 SOCK_STREAM----tcp
//参数3:协议 0:默认
int sockfd = socket(AF_INET,SOCK_STREAM,0);//创建一个TCP 套接字
//返回值:tcp 套接字,使用这个套接字就可以进行tcp操作
//2、绑定套接字
//对当前进程通信的tcp套接字添加自己的ip、port
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = inet_addr("192.168.124.113");
bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));
//3、建立连接,双方确定通信
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(10000);
serveraddr.sin_addr.s_addr = inet_addr("192.168.124.113");
connect(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
printf("ok\n");
//4、进行通信
char buf[20];
while(1)
{
int num = -1;
fgets(buf,20,stdin);
num = write(sockfd,buf,20);//发送
printf("size is %d\n",num);
num = read(sockfd,buf,20);//接收
printf("size is %d,data is %s\n",num,buf);
}
//5、关闭套接字
close(sockfd);
return 0;
}
//服务器,实现与客户端通信,先接收数据,然后发送数据
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <strings.h>
int main()
{
//1、创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
//2、绑定套接字
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
addr.sin_addr.s_addr = inet_addr("192.168.124.113");
bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));
//3、监听套接字,只要完成监听,套接字的功能变为监听套接字管理监听队列
if( listen(sockfd,10) < 0)
{
printf("error\n");
return -1;
}
//监听启动成功,一直进行监听,只要有客户端连接请求,放入监听队列
//
printf("linsten success\n");
//4、接收客户端连接请求---从监听队列中获取
//没有就会阻塞等待
int clientfd = accept(sockfd,NULL,NULL);
printf("accept success\n");
char buf[20];
int num = -1;
while(1)
{
bzero(buf,20);
num = recv(clientfd,buf,20,0);
if(num == 0)
{
printf("close\n");
break;
}
printf("%s\n",buf);
send(clientfd,"hello world\n",13,0);
}
close(sockfd);
close(clientfd);
return 0;
}
TCP建立连接与断开连接:
3、TCP服务器模型
对于TCP服务器而言,可以同时监听多个客户端的连接,TCP服务器可以与多个客户端进行连接通信
1、循环服务器
在TCP服务器中,每次与一个客户端进行连接,通信,通信结束后,再与下一个客户端连接通信;每次只与一个客户端进行通信
服务器一次只能接入一个客户端,处理完一个客户端的消息,再接入下一个客户端,如果该客户端不退出,其他客户端则无法接入
listenfd = scoket();
bind(listtenfd);
listen(listenfd);
while(1)
{
connfd = accept(listenfd);
while(1)
{
recv();
....
send();
}
close(connfd);
}
//循环服务器:先与第一个客户端进行通信,通信结束后与下一个客户端进行通信,依次与每一个客户端通信
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>
int main()
{
//1、创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
//2、绑定套接字
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
addr.sin_addr.s_addr = inet_addr("192.168.124.113");
bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));
//3、监听套接字,只要完成监听,套接字的功能变为监听套接字管理监听队列
if( listen(sockfd,10) < 0)
{
printf("error\n");
return -1;
}
//监听启动成功,一直进行监听,只要有客户端连接请求,放入监听队列
//
printf("linsten success\n");
while(1)//循环接收连接请求,与多个客户端进行通信
{
//4、接收客户端连接请求---从监听队列中获取
//没有就会阻塞等待
int clientfd = accept(sockfd,NULL,NULL);
//if()
printf("accept success\n");
char buf[20];
int num = -1;
//与客户端进行通信
while(1)
{//服务器先接收,再发送
bzero(buf,20);
num = recv(clientfd,buf,20,0);
if(num == 0 || strcmp("quit",buf)==0 || strcmp("quit\n",buf) == 0)//通信结束
{
printf("close\n");
break;
}
printf("recv data : %s\n",buf);
send(clientfd,buf,strlen(buf)+1,0);
}
close(clientfd);//关闭通信套接字
}
close(sockfd);
return 0;
}
2、并发服务器模型
当客户端接入服务器后(发起连接请求,建立连接),服务器可以创建多个任务同时与多个客户端建立连接进行通信
通过进程获取监听队列的连接请求,只要存在连接,创建进程或线程,让其执行通信任务,每个线程/进程执行对应的通信任务,实现并发通信
多线程并发服务器:
listenfd = scoket();
bind(listtenfd);
listen(listenfd);
while(1)
{
connfd = accept(listenfd);
pthread_create(thread_start);
}
thread_start()
{
recv();
....
send();
close(connfd);
}
//多线程并发服务器:通过多个线程,每个线程与客户端进行通信
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <pthread.h>
int clientfd;
void * client_handler(void * arg)
{
//arg;
int sockfd = *(int *)arg;
char buf[20];
int num = -1;
//与客户端进行通信
while(1)
{//服务器先接收,再发送
bzero(buf,20);
num = recv(sockfd,buf,20,0);
if(num == 0 || strcmp("quit",buf)==0 || strcmp("quit\n",buf) == 0)//通信结束
{
printf("client close\n");
break;
}
printf("recv data : %s\n",buf);
send(sockfd,buf,strlen(buf)+1,0);
}
close(sockfd);//关闭通信套接字
return NULL;
}
int main()
{
//1、创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
//2、绑定套接字
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
addr.sin_addr.s_addr = inet_addr("192.168.124.113");
bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));
//3、监听套接字,只要完成监听,套接字的功能变为监听套接字管理监听队列
if( listen(sockfd,10) < 0)
{
printf("error\n");
return -1;
}
//监听启动成功,一直进行监听,只要有客户端连接请求,放入监听队列
//
printf("linsten success\n");
while(1)
{
//4、接收客户端连接请求---从监听队列中获取
//没有就会阻塞等待
struct sockaddr_in clientaddr;
socklen_t len;
clientfd = accept(sockfd,(struct sockaddr *)&clientaddr,&len);//同意一个客户端连接
//if()
printf("accept success\n");
printf("client(%s:%d) connect ok\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
//创建线程-通过线程完成与客户端通信
pthread_t tid;
pthread_create(&tid,NULL,client_handler,&clientfd);
pthread_detach(tid);
}
close(sockfd);
return 0;
}
多进程并发服务器:
listenfd = scoket();
bind(listtenfd);
listen(listenfd);
while(1)
{
connfd = accept(listenfd);
if(fork()==0)
{
while(1)
{
recv();
....
send();
}
close(connfd);
exit(0);
}
else
{}
}
//多进程并发服务器:通过多个进程,每个进程与客户端进行通信
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <signal.h>
void client_handler(int sockfd)
{
char buf[20];
int num = -1;
//与客户端进行通信
while(1)
{//服务器先接收,再发送
bzero(buf,20);
num = recv(sockfd,buf,20,0);
if(num == 0 || strcmp("quit",buf)==0 || strcmp("quit\n",buf) == 0)//通信结束
{
printf("client close\n");
break;
}
printf("recv data : %s\n",buf);
send(sockfd,buf,strlen(buf)+1,0);
}
close(sockfd);//关闭通信套接字
}
void sig_handler(int sig)
{
if(sig == 17)
{
pid_t pid = waitpid(-1,NULL,WNOHANG);
printf("chlid pid is %d\n",pid);
}
}
int main()
{
//设置子进程的回收
//捕获子进程结束信号
signal(SIGCHLD,sig_handler);//设置捕获
//1、创建套接字
int listenfd = socket(AF_INET,SOCK_STREAM,0);
//2、绑定套接字
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
addr.sin_addr.s_addr = inet_addr("192.168.124.113");
bind(listenfd,(struct sockaddr *)&addr,sizeof(addr));
//3、监听套接字,只要完成监听,套接字的功能变为监听套接字管理监听队列
if( listen(listenfd,10) < 0)
{
printf("error\n");
return -1;
}
//监听启动成功,一直进行监听,只要有客户端连接请求,放入监听队列
//
printf("linsten success\n");
while(1)
{
//4、接收客户端连接请求---从监听队列中获取
//没有就会阻塞等待
struct sockaddr_in clientaddr;
socklen_t len;
int clientfd = accept(listenfd,(struct sockaddr *)&clientaddr,&len);//同意一个客户端连接
//if()
printf("accept success\n");
//与客户端进行连接-----clientfd
printf("client(%s:%d) connect ok\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
//创建子进程-通过进程完成与客户端通信
pid_t pid = fork();
//if(pid < 0)
if(pid == 0)
{
close(listenfd);
client_handler(clientfd);
exit(0);//当子进程结束时,会发送17) SIGCHLD 给父进程
}
else if(pid > 0)
{
close(clientfd);
}
}
close(listenfd);
return 0;
}
3、IO模型
1、阻塞IO模型
阻塞IO:在当前IO操作执行完之后,才能继续执行下一步操作,不能执行当前IO时,就阻塞等待
就是当应用发起IO操作,发起读取数据申请时,在数据还没准备好之前,应用就会一直处于等待数据状态,直到数据就绪可以进行读取,进程从调用IO操作操作开始,直到成功结束,这段时间内是被阻塞的,称为阻塞IO
在IO操作中,默认是阻塞IO
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>
int main()
{
//1、创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
//2、绑定套接字
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
addr.sin_addr.s_addr = inet_addr("192.168.124.113");
bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));
//3、监听套接字,只要完成监听,套接字的功能变为监听套接字管理监听队列
if( listen(sockfd,10) < 0)
{
printf("error\n");
return -1;
}
//监听启动成功,一直进行监听,只要有客户端连接请求,放入监听队列
//
printf("linsten success\n");
int sockfds[3];
for(int i = 0; i< 3;i++)
{
sockfds[i] = accept(sockfd,NULL,NULL);
}
printf("当前进程与3个客户端建立连接\n");
while(1)
{
char buf0[20];
int n0 = read(sockfds[0],buf0,20);
buf0[n0] = '\0';
printf("%s\n",buf0);
char buf1[20];
int n1 = read(sockfds[1],buf1,20);
buf1[n1] = '\0';
printf("%s\n",buf1);
char buf2[20];
int n2 = read(sockfds[2],buf2,20);
buf2[n2] = '\0';
printf("%s\n",buf2);
}
close(sockfd);
return 0;
}
2、非阻塞IO模型
非阻塞就是指,当进程进行IO操作,发起读取数据申请时,如果没有数据就绪准备好,就会立即告诉进程,不用在这里等待,立即结束本次IO操作
对于非阻塞而言,每次结束IO操作需要进行判断(是否收到数据)
通常,非阻塞需要循环进行IO操作
没收到数据时就跳过
循环检测IO操作,是否可执行
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
//1、创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
//2、绑定套接字
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
addr.sin_addr.s_addr = inet_addr("192.168.124.113");
bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));
//3、监听套接字,只要完成监听,套接字的功能变为监听套接字管理监听队列
if( listen(sockfd,10) < 0)
{
printf("error\n");
return -1;
}
//监听启动成功,一直进行监听,只要有客户端连接请求,放入监听队列
//
printf("linsten success\n");
int sockfds[3];
for(int i = 0; i< 3;i++)
{
sockfds[i] = accept(sockfd,NULL,NULL);
//把文件描述符设置为非阻塞IO
//得到文件的属性
int flags = fcntl(sockfds[0],F_GETFL);
flags = flags|O_NONBLOCK;
//设置文件属性
fcntl(sockfds[i],F_SETFL,flags);
}
printf("当前进程与3个客户端建立连接\n");
while(1)
{
char buf0[20];
int n0 = read(sockfds[0],buf0,20);//非阻塞,没有数据返回-1
if(n0 > 0)
{
buf0[n0] = '\0';
printf("0 : %s\n",buf0);
}
char buf1[20];
int n1 = read(sockfds[1],buf1,20);
if(n1 > 0)
{
buf1[n1] = '\0';
printf("1 : %s\n",buf1);
}
char buf2[20];
int n2 = read(sockfds[2],buf2,20);
if(n2 > 0)//<0表示,没有数据可读,跳过操作
{
buf2[n2] = '\0';
printf("2 : %s\n",buf2);
}
if(n0 == 0 && n1 == 0 && n2==0)
break;
}
close(sockfd);
return 0;
}
3、IO复用模型
由一个进程或线程,同时监控多个fd文件描述符,只需要一个或几个线程就可以完成数据状态询问,是否可以执行对应读写,执行IO操作
常用的IO多路复用函数:select、poll、epoll
进程/线程,通过将一个或多个fd传递给select,阻塞在select操作上,select帮我们侦测多个fd是否准备就绪(fd能否进行IO操作),当fd准备就绪时,select结束阻塞,返回数据可以进行IO操作状态,进程就调用对应的fd执行IO操作
复用IO基本思路:通过select或poll、epoll来监控多fd,来达到不必为每个fd创建一个对应的监控操作线程
select
功能 | IO多路复用,同时监视多个fd |
头文件 | #include |
函数原型 | int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); |
参数 | 参数1: int nfds:整数,文件描述符fd的监视范围==maxfd + 1 参数2: fd_set *readfds:监视读是否就绪的文件描述符表,如果没有为NULL 参数3: fd_set *writefds:监视写是否就绪的文件描述符表,如果没有为NULL 参数4: fd_set *exceptfds:监视异常的文件描述符表,如果没有为NULL 参数5: struct timeval *timeout:监视管理的时间,阻塞等待的时间,一直阻塞为 NULL |
返回值 | 成功:返回就绪的文件描述符的个数 超时:返回0表示超时 失败:返回-1 |
备注 | 成功,会把对应的监测表,清除未就绪,留下就绪的文件描述符 |
对select的表操作函数
void FD_CLR(int fd, fd_set *set);//从表 set中删除fd
int FD_ISSET(int fd, fd_set *set);//判断set表中是否有fd,返回值1为成功有,0失败
void FD_SET(int fd, fd_set *set);//往set表添加一个fd
void FD_ZERO(fd_set *set);//清空表
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>
int main()
{
//1、创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
//2、绑定套接字
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
addr.sin_addr.s_addr = inet_addr("192.168.124.113");
bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));
//3、监听套接字,只要完成监听,套接字的功能变为监听套接字管理监听队列
if( listen(sockfd,10) < 0)
{
printf("error\n");
return -1;
}
//监听启动成功,一直进行监听,只要有客户端连接请求,放入监听队列
//
printf("linsten success\n");
//创建读监视表
fd_set readfds;
//清空表
FD_ZERO(&readfds);
int maxfd = 0;
//添加套接字到表中
FD_SET(sockfd,&readfds);
if(maxfd < sockfd)
maxfd = sockfd;
/*
for(int i = 0; i< 3;i++)
{
int clientfd = accept(sockfd,NULL,NULL);
if(maxfd < clientfd)
maxfd = clientfd;
//添加套接字到表中
FD_SET(clientfd,&readfds);
}
printf("当前进程与3个客户端建立连接\n");
*/
while(1)
{
fd_set readtemp = readfds;
int num = select(maxfd+1,&readtemp,NULL,NULL,NULL);
if(num < 0)
{
printf("error\n");
}
printf("fd num is %d\n",num);
char buf[20];
for(int fd = 0;fd <= maxfd;fd++)
{
if( FD_ISSET(fd,&readtemp) == 0)
{
continue;
}
//说明fd在表中
if(fd == sockfd)
{//表示就绪的套接字是监听套接字
//有客户端连接
//接受客户端连接
int clientfd = accept(sockfd,NULL,NULL);
printf("建立新连接\n");
//添加套接字到表中
FD_SET(clientfd,&readfds);
if(maxfd < clientfd)
maxfd = clientfd;
}
else
{
bzero(buf,20);
if( read(fd,buf,20) == 0 )//客户端关闭套接字
{
printf("close\n");
FD_CLR(fd,&readfds);
close(fd);
continue;
}
printf("%s\n",buf);
write(fd,buf,strlen(buf)+1);
}
}
}
close(sockfd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024
int main() {
int server_fd, new_socket, max_fd, activity, i, valread, sd;
int client_sockets[MAX_CLIENTS];
fd_set readfds;
char buffer[BUFFER_SIZE];
struct sockaddr_in address;
socklen_t addrlen = sizeof(address);
// 创建服务器套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址和端口
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
// 绑定套接字到指定地址和端口
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}
// 初始化客户端套接字列表
for (i = 0; i < MAX_CLIENTS; i++) {
client_sockets[i] = 0;
}
// 设置服务器套接字为最大文件描述符
max_fd = server_fd;
printf("Waiting for connections...\n");
while(1) {
// 清空集合,添加服务器套接字和客户端套接字到集合中
FD_ZERO(&readfds);
FD_SET(server_fd, &readfds);
for (i = 0; i < MAX_CLIENTS; i++) {
sd = client_sockets[i];
if (sd > 0) {
FD_SET(sd, &readfds);
}
if (sd > max_fd) {
max_fd = sd;
}
}
// 等待可读事件发生
activity = select(max_fd + 1, &readfds, NULL, NULL, NULL);
if ((activity < 0) && (errno != EINTR)) {
printf("select error");
}
// 如果服务器套接字上有新连接请求,则接受连接
if (FD_ISSET(server_fd, &readfds)) {
if ((new_socket = accept(server_fd, (struct sockaddr*)&address, &addrlen)) < 0) {
perror("accept failed");
exit(EXIT_FAILURE);
}
// 输出客户端连接信息
printf("New connection, socket fd is %d, IP is %s, port is %d", new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));
// 将新的客户端套接字添加到列表中
for (i = 0; i < MAX_CLIENTS; i++) {
if (client_sockets[i] == 0) {
client_sockets[i] = new_socket;
break;
}
}
}
// 处理客户端套接字的数据
for (i = 0; i < MAX_CLIENTS; i++) {
sd = client_sockets[i];
if (FD_ISSET(sd, &readfds)) {
// 检查是否关闭连接
if ((valread = read(sd, buffer, BUFFER_SIZE)) == 0) {
// 获取关闭连接的客户端信息
getpeername(sd, (struct sockaddr*)&address, &addrlen);
// 输出客户端断开连接信息
printf("Host disconnected, IP is %s, port is %d", inet_ntoa(address.sin_addr), ntohs(address.sin_port));
// 关闭连接并移除客户端套接字
close(sd);
client_sockets[i] = 0;
} else {
// 回显接收到的消息
buffer[valread] = '\0';
send(sd, buffer, strlen(buffer), 0);
}
}
}
}
return 0;
}
4、信号驱动IO模型
5、异步IO模型
//服务器上传下载
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#define N 32
#define err_log(log)\
do{\
perror(log);\
exit(1);\
}while(0)
typedef struct sockaddr SA;
void process_list(int clientfd);
void process_get(int clientfd,char *filename);
void process_put(int clientfd,char *filename);
int main(int argc, const char *argv[])
{
int serverfd,clientfd;
struct sockaddr_in serveraddr,clientaddr;
char buf[N]={0};
ssize_t bytes;
socklen_t len=sizeof(SA);
bzero(&serveraddr,len);
bzero(&clientaddr,len);
//指定服务器的网络信息
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons();
serveraddr.sin_addr.s_addr=inet_addr();
if((serverfd=socket(AF_INET,SOCK_STREAM,0))<0)//创建套接字
{
err_log("fail to socket:");
}
if(bind(serverfd,(SA*)&serveraddr,len)<0)//绑定
{
err_log("fail to bind:");
}
if(listen(serverfd,5)<0)//监听
{
err_log("fail to listen:");
}
while(1)
{
if((clientfd=accept(serverfd,(SA*)&clientaddr,&len))<0)
{
perror("fail to accept:");
continue;
}
bytes=recv(clientfd,buf,sizeof(buf),0);
if(bytes<=0)
break;
switch(buf[0])
{
case 'L':
process_list(clientfd);
break;
case 'G':
process_get(clientfd,buf+2);
break;
case 'P':
process_put(clientfd,buf+2);
break;
}
close(clientfd);
}
close(serverfd);
return 0;
}
void process_list(int clientfd)
{
DIR *dp;
struct dirent *ent;
char buf[N]={0};
if((dp=opendir("."))==NULL)//打开目录获得目录流
{
perror("fail to opendir:");
strcpy(buf,"Fail");
send(clientfd,buf,sizeof(buf),0);
return;
}
else
{
strcpy(buf,"OK");
send(clientfd,buf,sizeof(buf),0);
while((ent=readdir(dp))!=NULL)//读取目录
{
if(ent->d_name[0]!='.')//屏蔽隐藏文件
{
strcpy(buf,ent->d_name);//拷贝文件名
send(clientfd,buf,sizeof(buf),0);
usleep(1000);//防止粘包 ms级延时
}
}
}
}
void process_get(int clientfd,char *filename)
{
int fd;
char buf[N]={0};
ssize_t bytes;
if((fd=open(filename,O_RDONLY))<0)
{
perror("fail to open:");
strcpy(buf,"Fail");
send(clientfd,buf,sizeof(buf),0);
return;
}
else
{
strcpy(buf,"OK");
send(clientfd,buf,sizeof(buf),0);
while(1)
{
bytes=read(fd,buf,sizeof(buf));//读文件
if(bytes<=0)
break;
send(clientfd,buf,bytes,0);//--------------------
usleep(1000);
}
close(fd);
return;
}
}
void process_put(int clientfd,char *filename)
{
int fd;
ssize_t bytes;
char buf[N]={0};
if((fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666))<0)
{
perror("fail to open:");
strcpy(buf,"Fail");
send(clientfd,buf,sizeof(buf),0);
return;
}
else
{
strcpy(buf,"OK");
send(clientfd,buf,sizeof(buf),0);
while(1)
{
bytes=recv(clientfd,buf,sizeof(buf),0);
if(bytes<=0)
break;
write(fd,buf,bytes);
}
close(fd);
return;
}
}
//客户端上传下载
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define err_log(log)\
do{\
perror(log);\
exit(1);\
}while(0)
#define N 32
typedef struct sockaddr SA;
// ./server 192.168.1.104 9999
void do_list(struct sockaddr_in serveraddr);//获得列表
void do_get(struct sockaddr_in serveraddr,char *filename);//下载文件
void do_put(struct sockaddr_in serveraddr,char *filename);//上传文件
int main(int argc, const char *argv[])
{
struct sockaddr_in serveraddr;
socklen_t len=sizeof(SA);
bzero(&serveraddr,len);
char buf[N]={0};
//指定服务器的网络信息
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons();
serveraddr.sin_addr.s_addr=inet_addr();
while(1)
{
puts("===============================================");
puts("1.list 2.get filename 3.put filename 4.quit");
puts("===============================================");
puts("input cmd>>>");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]='\0';//\n ----->\0
switch(buf[0])
{
case 'l':
do_list(serveraddr);
break;
case 'g':
do_get(serveraddr,buf+4);
break;
case 'p':
do_put(serveraddr,buf+4);
break;
case 'q':
exit(0);
default:
puts("cmd error!");
break;
}
}
return 0;
}
void do_list(struct sockaddr_in serveraddr)//获得列表
{
int sockfd;
char buf[N]={0};
buf[0]='L';
ssize_t bytes;
socklen_t len=sizeof(SA);
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("fail to socket:");
return;
}
if(connect(sockfd,(SA*)&serveraddr,len)<0)//请求链接服务器
{
perror("fail to connect:");
close(sockfd);
return;
}
send(sockfd,buf,sizeof(buf),0);//发送列表的请求
recv(sockfd,buf,sizeof(buf),0);//接受服务器的反馈
if(strncmp(buf,"OK",2)==0)
{
puts("-----------------------------------");
while(1)
{
bytes=recv(sockfd,buf,sizeof(buf),0);
if(bytes<=0)
break;
printf("%s\n",buf);
}
puts("------------------------------------");
puts("list ok!");
close(sockfd);
return;
}
else
{
puts("list fail!");
close(sockfd);
return;
}
}
void do_get(struct sockaddr_in serveraddr,char *filename)//下载文件
{
int sockfd;
char buf[N]={0};
int fd;
ssize_t bytes;
sprintf(buf,"G %s",filename);//拼接------------------------
socklen_t len=sizeof(SA);
if((fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666))<0)
{
perror("fali to open:");
return;
}
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("fail to socket");
return;
}
if(connect(sockfd,(SA*)&serveraddr,len)<0)
{
perror("fail to connect:");
close(sockfd);
return;
}
send(sockfd,buf,sizeof(buf),0);//发送下载请求
recv(sockfd,buf,sizeof(buf),0);//接收反馈信息
if(strncmp(buf,"OK",2)==0)
{
while(1)
{
bytes=recv(sockfd,buf,sizeof(buf),0);
if(bytes<=0)
break;
write(fd,buf,bytes);//---------------------
}
puts("get ok!");
close(sockfd);
close(fd);
return;
}
else
{
puts("get fail!");
close(sockfd);
close(fd);
return;
}
}
void do_put(struct sockaddr_in serveraddr,char *filename)//上传文件
{
int fd;
int sockfd;
char buf[N]={0};
ssize_t bytes;
sprintf(buf,"P %s",filename);//拼接------------------------
socklen_t len=sizeof(SA);
if((fd=open(filename,O_RDONLY))<0)
{
perror("fail to open:");
return;
}
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("fail to socket");
return;
}
if(connect(sockfd,(SA*)&serveraddr,len)<0)
{
perror("fail to connect");
close(sockfd);
return;
}
send(sockfd,buf,sizeof(buf),0);//发送上传文件请求
recv(sockfd,buf,sizeof(buf),0);
if(strncmp(buf,"OK",2)==0)
{
while(1)
{
bytes=read(fd,buf,sizeof(buf));
if(bytes<=0)
break;
send(sockfd,buf,bytes,0);//------------
usleep(1000);
}
puts("put ok!");
close(sockfd);
close(fd);
return;
}
else
{
puts("put fail!");
close(sockfd);
close(fd);
return;
}
}
3、数据库(sqlite)
大型数据库:Oracle、DB2
中型数据库:mysql,sqlserver
嵌入式数据库:sqlite
数据库文件
数据库管理系统
安装:
sudo apt install sqlite3 sqlite3-doc
命令操作数据库:
打开数据库管理工具:
命令行>> sqlite3
数据库操作命令:命令以 . 开头---操作数据库工具
.help:查看工具包含的命令说明
.quit:退出当前数据库管理工具
.open xx.db :打开数据库文件
.table:查看数据库中存在哪些表
数据库语句:sql语句-----操作数据库文件的表
必须现在数据库管理工具间中打开了数据库文件
//创建数据库文件 表
create table 表名(项1 , 项2, 项3 .....);
项:项名 类型 属性
create table userpasswd(id int not null,user text not null,passwd text);
//插入表
insert into 表名 values(值1 , 值2 , 值3);
insert into 表名(项名1,项名2) values(值1 , 值2);
//查询表
select 字段名(项名) from 表名 [where 查询条件];----- * 表示所有字段
//修改更新表
update 表名 set 字段名=值 [where 条件];
//删除表数据
delete from 表名 where 条件;
//删除整个表
drop table 表名;
C语言 API函数操作数据库:
sudo apt install libsqlite3-dev
1、打开数据库:
sqlite3_open
功能 | 打开数据库文件,操作数据库 |
头文件 | #include |
函数原型 | int sqlite3_open( const char *filename, sqlite3 **ppDb ); |
参数 | 参数1: const char *filename:打开数据库,指定操作数据库的文件 参数2: sqlite3 **ppDb:二级指针,一级指针的地址,一级指针存储打开的数据库,在调用函数时,在函数中就可以修改这个一级指针,用于存储打开的数据库 |
返回值 | 成功:返回0 失败:返回非0 |
2、关闭打开的数据库
sqlite3_close
功能 | 关闭打开的数据库 |
头文件 | #include |
函数原型 | int sqlite3_close(sqlite3 * pdb); |
参数 | 参数1: sqlite3 * pdb:关闭哪个打开的数据库 |
返回值 | 成功:返回0(SQLITE_OK) 失败:返回非0 |
3、执行sql语句
sqlite3_exec
功能 | 通过打开的数据库,执行sql语句操作数据库 |
函数原型 | int sqlite3_exec( sqlite3 * pdb, const char *sql, int (*callback)(void*,int,char**,char**), void * arg, char * * errmsg ); |
参数 | 参数1: sqlite3 * pdb:打开的数据库,表示要操作哪个数据库文件 参数2: const char *sql:要执行的sql语句字符串首地址 参数3: int (*callback)(void* arg,int num , char * * , char * * ):当执行sql语句时,如果存在结果,有一个结果就调用一次 参数4: void * arg:传递给结果函数(callback)作为参数 参数5: char * * errmsg:一级指针的地址,如果出错,则把错误信息字符串的首地址进行存储,通过一级指针的地址,在函数中进行设置(*errmsg) |
返回值 | 成功:返回0---SQLITE_OK 失败:返回非0 |
//当存在一个结果就调用一次
int callback(void *arg,int s_num, char ** val,char ** item)
void *arg:由sqlite3_exec()函数,的参数4传递
int s_num:调用时,传递 当前这一次结果有几项内容(字段数)
char ** item
char ** val:指针数组:存储多个指针,每个指针就是一项数据的地址
#include "sqlite3.h"
#include <stdio.h>
int sql_callback(void *arg,int s_size, char ** val,char ** item)
{
/*char * p[10];
p[0] = "zhangsan";
p[1] = "20";*/
for(int i = 0; i< s_size;i++)
{
printf("%s ",val[i]);
}
printf("\n");
return 0;
}
int main()
{
int ret;
sqlite3 *pdb;//标识数据库
ret = sqlite3_open("user.db",&pdb);
// printf("ret = %d\n",ret);
//创建数据库表
char * errmsg = NULL;
char sql[] = "create table student(id int not null,name text not null,age int)";
// ret = sqlite3_exec(pdb,sql,NULL,NULL,&errmsg);
if(ret != SQLITE_OK)
{
printf("%s\n",errmsg);
return -1;
}
//int sprintf(char *str, const char *format, ...);
//int printf(const char *format, ...);
//sprintf 把字符串写入到 str首地址中
int num;
scanf("%d",&num);
int id;
char name[20] = "";
int age;
switch(num)
{
case 1:
printf("输入id值:");
scanf("%d",&id);
printf("输入name值:");
scanf("%s",name);
printf("输入age值:");
scanf("%d",&age);
char sql[100];
sprintf(sql,"insert into student values(%d,'%s',%d)",id,name,age);
printf("%s\n",sql);
sqlite3_exec(pdb,sql,NULL,NULL,NULL);
break;
case 2://删除
//sqlite3_exec();
case 3://查找
//sprintf(sql,"select * from student where name=%s",name);
sqlite3_exec(pdb,"select name,age from student",sql_callback,NULL,NULL);
}
sqlite3_close(pdb);
return 0;
}
gcc -o sqlite sqlite.c -lsqlite3