Makefile的main.c文件中的全局变量SONG song,要在fun.c文件里面写成extern SONG song
编译方法
第一次编写
或
网络编程
物理层的网线规定有八根,颜色不一样,功能不一样,光猫把光信号转换成电信号,光纤10Gb
WiFi叫无线局域网,一般也就50米左右,手机流量叫蜂窝网络,随时随地上网。
链路层:先格式化一下,网络可以通过交换机连接起来。
网络层:通过ip地址找到目标
传输层:数据以何种方式传输?tcp可靠传输,传输过程中数据准确的到达对方。udp可能会丢包 。一般下载文件上传文件多以tcp为主,一般视频,音频 (实时性重要)一般用udp
OSI模型
物理层
解决两个硬件之间怎么通信的问题,常见的物理媒介有光纤、电缆、中继器等。它主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。
它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特。
链路层(把设备连接起来)
在计算机网络中由于各种干扰的存在,物理链路是不可靠的。该层的主要功能就是:通过各种控制协议,将有差错的物理信道变为无差错的、能可靠传输数据帧的数据链路。
它的具体工作是接收来自物理层的位流形式的数据,并封装成帧,传送到上一层;同样,也将来自上层的数据帧,拆装为位流形式的数据转发到物理层。这一层的数据叫做帧。
网络层
计算机网络中如果有多台计算机,怎么找到要发的那台?如果中间有多个节点,怎么选择路径?这就是路由要做的事。
该层的主要任务就是:通过路由选择算法,为报文(该层的数据单位,由上一层数据打包而来)通过通信子网选择最适当的路径。这一层定义的是IP地址,通过IP地址寻址,所以产生了IP协议。
传输层
当发送大量数据时,很可能会出现丢包的情况,另一台电脑要告诉是否完整接收到全部的包。如果缺了,就告诉丢了哪些包,然后再发一次,直至全部接收为止。
简单来说,传输层的主要功能就是:监控数据传输服务的质量,保证报文的正确传输。
会话层(网络断开,连接的状态)
虽然已经可以实现给正确的计算机,发送正确的封装过后的信息了。但我们总不可能每次都要调用传输层协议去打包,然后再调用IP协议去找路由,所以我们要建立一个自动收发包,自动寻址的功能。于是会话层出现了:它的作用就是建立和管理应用程序之间的通信。
表示层(加密,解密)
表示层负责数据格式的转换,将应用处理的信息转换为适合网络传输的格式,或者将来自下一层的数据转换为上层能处理的格式。
应用层
应用层是计算机用户,以及各种应用程序和网络之间的接口,其功能是直接向用户提供服务,完成用户希望在网络上完成的各种工作。前端同学对应用层肯定是最熟悉的。
TCP/IP模型(也叫tcp/ip协议栈):专门描述互联网的模型(把OSI模型合并了一下)
TCP/IP协议族
- dns 域名解析 把域名转成ip地址
- DHCP 动态主机配置协议(路由器分配地址)
- http 超文本传输协议(使用网页就会用到这个协议)
- FTP 互联网远距离传输用ftp(可以断点续传)
- TFTP 简单文件传输协议(传输本地文件)---局域网内
- SNMP 网络管理 网络状态监测
- TCP 传输控制协议 一种可靠的传输方式 自带超时重传 必须要有应答 实时性比较差
- UDP 实时性好 会丢包 节省网络开销
- ARP 地址解析协议 在以太网环境中,数据的传输所依懒的是MAC地址而非IP地址,而将已知IP地址转换为MAC地址的工作是由ARP协议来完成的。
- RIP和OSPF都是路由协议
TCP编程基础知识
- 网关:也叫gate,跟个门一样,没出去就是局域网,出去就是互联网,默认是0.1(后两位)
- 广播:最后一位是255)给这个位发,所有人都接收到了(只能是局域网里,一个人发所有人收)
- 组播:让想收到的人收到(类似于群聊)
IP地址一般都是点分十进制(分为四段,每一段最小0最大255)
A类中第一段表示有多少个网络,是网络号,后面的数字表示有多少台主机(2的64次方)
B类中前两段表示有多少个网络(比如说128.0和128.1代表两个网络),后面数字表示有多少台主机(2的16次方)
C类前三段表示有多少个网络,后面数字表示有某一台主机(主机号)------我们一般用的是这种
linux的网络配置命令
配置完一定要重启sudo reboot 才会生效
ifconfig:查看网卡状态相关信息
硬件地址
手动设置ip地址,然后重启
查看网络状态(是否连接)
网络接口
- socket 套接字 是文件描述符(网络文件需要收发)
- ip地址是用来找主机的
- 端口号用来识别进程的
网络字节序
小端存储--数据的低位在低内存
- 计算机是小端 网络设备是大端
- ip地址和端口号要去找主机设备--------都要大小端转换一下
UDP(有服务端和客户端,一对多的关系)简单一点----无连接,不显示状态信息,不可靠
- socket类似于open,将网络设备打开,打开后拿到一个文件描述符
- 客户端找服务器需要ip地址和端口号,bind就是给套接字设置ip地址和端口号的
- 服务器先收,因为客户得先告诉服务器要啥
代码:(客户端找服务器)
固定的就选这个IPv4对应的
服务端:
SA是强转一下,转成下面这个类型
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#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);//第一步,可能会有少量丢包,0是默认方式if(-1 == sockfd){perror("socket");exit(1);}// man 7 ip struct sockaddr_in ser,cli;//in代表internet互联网,这个是对方和自己的地址结构体bzero(&ser,sizeof(ser));bzero(&cli,sizeof(cli));ser.sin_family = AF_INET;// 大小端转化 host to net short ser.sin_port = htons(50000);//小端转大端,50000是端口号ser.sin_addr.s_addr = inet_addr("192.168.203.128");//写自己ip,大端转小端,数字转字符串int ret = bind(sockfd,(SA)&ser,sizeof(ser));//第二步,绑定if(-1 == ret){perror("bind");exit(1);}socklen_t len = sizeof(cli);while(1){char buf[512]={0};recvfrom(sockfd,buf,sizeof(buf),0,(SA)&cli,&len);//第三步,收,0代表工作方式,有人发就接受,没人就等着time_t tm;time(&tm);//拿到时间sprintf(buf,"%s %s",buf,ctime(&tm));sendto(sockfd,buf,strlen(buf),0,(SA)&cli,len);//第四步,发出去,0是默认方式}close(sockfd);return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#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");exit(1);}struct sockaddr_in ser;bzero(&ser,sizeof(ser));ser.sin_family = AF_INET;// 大小端转化 host to net short ser.sin_port = htons(50000);ser.sin_addr.s_addr = inet_addr("192.168.203.128");while(1){char buf[512]="hello,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("buf is %s\n",buf);sleep(1);}close(sockfd);return 0;
}
结果:
UDP(用户数据报)(半双工,要么收要么发)
发完链路就自动释放了,比较节省空间,网络开销比较小,丢包就是因为没有维护链路的状态
数据报:数据和数据间是有间隔的,收发次数要对应,不然就丢了,并且在收的大小建议大于等于包的大小,即使小了,剩下的也不会再收到了
UDP特征
- 数据有边界
- 收发次数要对应
- recvfrom如果没有发送,就会阻塞
- sendto不会阻塞,他只管发,收不收都可以
UDP传文件的代码
服务端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
typedef struct sockaddr * (SA);
int main(int argc, char *argv[])
{int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(-1 == sockfd){perror("socket");exit(1);}// man 7 ip struct sockaddr_in ser,cli;bzero(&ser,sizeof(ser));bzero(&cli,sizeof(cli));ser.sin_family = AF_INET;// 大小端转化 host to net short ser.sin_port = htons(50000);//ser.sin_addr.s_addr = inet_addr("127.0.0.1");//活的地址,也代表自己的地址,本地回环,自己跟自己测,这个数据不会发出去ser.sin_addr.s_addr = INADDR_ANY;//代表本机任何一个可用的IP地址,自己跟自己也可以,跟外面也可以,不用大小端转换int ret = bind(sockfd,(SA)&ser,sizeof(ser));if(-1 == ret){perror("bind");exit(1);}socklen_t len = sizeof(cli);int fd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666);//trunc是清空,只要有创建后面就给666if(-1 == fd){perror("open");exit(1);}while(1){char buf[512]={0};int rd_ret = recvfrom(sockfd,buf,sizeof(buf),0,(SA)&cli,&len);if(0 == strcmp(buf,"^_^")){break;}write(fd,buf,rd_ret);//收多少写多少bzero(buf,sizeof(buf));strcpy(buf,"go on");//防止发的太快来不及写(收),所以控制他发的速度sendto(sockfd,buf,strlen(buf),0,(SA)&cli,len);}close(sockfd);close(fd);return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
typedef struct sockaddr * (SA);int main(int argc, char *argv[])
{int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(-1 == sockfd){perror("socket");exit(1);}struct sockaddr_in ser;bzero(&ser,sizeof(ser));ser.sin_family = AF_INET;// 大小端转化 host to net short ser.sin_port = htons(50000);ser.sin_addr.s_addr = inet_addr("192.168.203.128");//自己跟自己可以写any,跟别的必须写客户端IP地址int fd = open("/home/linux/1.png",O_RDONLY);if(-1 == fd){perror("open");exit(1);}char buf[512]={0};while(1){bzero(buf,sizeof(buf));int rd_ret = read(fd,buf,sizeof(buf));if(0==rd_ret){break;}sendto(sockfd,buf,rd_ret,0,(SA)&ser,sizeof(ser));bzero(buf,sizeof(buf));recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);}bzero(buf,sizeof(buf));strcpy(buf,"^_^");sendto(sockfd,buf,3,0,(SA)&ser,sizeof(ser));//走到这里证明发送结束了,告诉客户端发送结束了,因为无连接close(sockfd);close(fd);return 0;
}
结果:
练习:UDP的聊天室(一个人发,全都能收到)
服务端是负责转发的,除了不给发消息的人转发,其他人都转
服务端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr * (SA);
typedef enum {CMD_LOGIN,CMD_CHAT,CMD_LOGOUT}TYPE;
typedef struct
{TYPE type;char name[50];char context[128];}MSG;
typedef struct
{struct sockaddr_in cli;int flag; // 0 free 1 occu
}LIST;
#define MAX 10
LIST list[MAX]={0};//最多多少人
int do_login(int sockfd,MSG* msg,struct sockaddr_in* cli)
{int i = 0 ;for(i=0;i<MAX;i++){if(1 == list[i].flag )//等于一证明人在,没退出{sendto(sockfd,msg,sizeof(MSG),0,(SA)&list[i].cli,sizeof(list[i].cli));}}for(i=0;i<MAX;i++){if(0 == list[i].flag ){list[i].flag =1;//list[i].cli = *cli;memcpy(&list[i].cli,cli,sizeof(*cli));break;}}return 0;
}int do_chat(int sockfd, MSG* msg,struct sockaddr_in*cli)//转发
{int i = 0 ;for(i=0;i<MAX;i++){if(1 == list[i].flag && 0!=memcmp(&list[i].cli,cli,sizeof(*cli)) )//确保有人,并且发送方和接收方的ip地址不一样{sendto(sockfd,msg,sizeof(MSG),0,(SA)&list[i].cli,sizeof(list[i].cli));}}
}
int do_logout(int sockfd, MSG* msg,struct sockaddr_in*cli)
{return 0;
}
int main(int argc, char *argv[])
{int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(-1 == sockfd){perror("socket");exit(1);}// man 7 ip struct sockaddr_in ser,cli;bzero(&ser,sizeof(ser));bzero(&cli,sizeof(cli));ser.sin_family = AF_INET;// 大小端转化 host to net short ser.sin_port = htons(50000);ser.sin_addr.s_addr = inet_addr("127.0.0.1");int ret = bind(sockfd,(SA)&ser,sizeof(ser));if(-1 == ret){perror("bind");exit(1);}socklen_t len = sizeof(cli);MSG msg;while(1){bzero(&msg,sizeof(msg));recvfrom(sockfd,&msg,sizeof(msg),0,(SA)&cli,&len);switch(msg.type){case CMD_LOGIN:do_login(sockfd,&msg,&cli);break;case CMD_LOGOUT:do_logout(sockfd,&msg,&cli);break;case CMD_CHAT:do_chat(sockfd,&msg,&cli);break;}}close(sockfd);return 0;
}
客户端:(并行)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct