一.协议
1.1协议的概念
什么是协议?
协议:规定了通信双方之间发送和接收报文时所使用的格式和顺序
1.2网络编程中经常使用的两个协议
1.2.1 TCP
TCP:传输层协议
特点;有面向连接,提供可靠性通信(即在传输过程中数据无误、数据无丢失、数据无失序、
数据无重复到达的通信))
使用情况:适合对传输质量要求较高,传输大量数据时的通信
1.2.2 UDP
UDP:传输层协议
特点:
1.无连接,提供不可靠服务(即在传输过程中可能会造成数据丢失,数据失序等)
2.因为在传输数据时不需要进行连接,即UDP传输效率是高效的
适用情况:在广播、组播以及实时通信时可以采用UDP方式进行实时数据的传输
二.网路体系结构
2.1基本概念
1.为什么需要分层:
数据在传输过程时是一个庞大而复杂的过程,而分层可以将这一个复杂的过程解析层一个个小的步骤,
每一个步骤被都可以被某一层进行处理,这样就可以提升计算机处理数据的效率,当某一个数据在某一层出
错时直接可以找到对应的层次进行处理,而不是从头来过。
2.2 OSI分层模型
2.3 说明
1.每一层实现不同的功能,以上图片只是简单的列举了每一层所做的某一部分的功能
2.每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,
同时使用下层提供的服务
三.网络编程基础知识
注意:
1.计算机网路是一个庞大而复杂的知识体系,这里只是简单列举一些网络编程所使用到的网络
的基础知识
3.1字节序
在不同类型的朱继忠内存存储字节的顺序也不同,这里分别介绍计算机所存储数据的两种顺序。
1.小端序:即低字节存储在低地址位。Intel、AMD等采用的是这种方式;
2.大端序:即高字节存储在低址值位。由ARM、Motorola等所采用
如何判断自己主机的字节序:
方法一:使用;联合体
#include <stdio.h>
union test
{
char a;
int b;
};
int main(int argc, const char *argv[]){
union test t1;//定义一个联合体变量
t1.b=0x123456;//以十六进制进行检查,较为方便
if (0x56==t1.a)//若是低字节存储在低地址位那么便是小段存储,否者便是大端序
{
printf("小端序\n");
}
else if (0x12==t1.a)/
{
printf("大端序\n");
}
return 0;
}
方法二:使用指针
#include <stdio.h>
int main(int argc, const char *argv[]){
int a=0x123456;
char *p=(char*)&a;
if (0x56==*p)
{
printf("小端序\n");
}
else if (0x12==*p)
{
printf("大端序\n");
}
return 0;
}
为什么要判断字节序:在通信时如果不确定与之通信主机的字节序,那么通信之时会造成发送
数据与接收数据不同的情况,为了确保通信双方在通信时接收与发送数据时数据的一致性网络字节
序应运而出。
网路字节序:网络字节序就是大端序,通信双方在进行通信时需要将数据转换成网络字节序而后
进行数据的处理
3.2 网络字节序说明
1.多字节整型数据需要进行字节序的处理,字符串不需要
2.字节序转换的几个函数
字节序转换的函数:
头文件 :
#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字节无符号整数从网络字节序转换为主机字节序
例如:
htons(8888) 其中在使用htons函数将端口号转换为网络字节序(大端字节序)。
四.IP地址
4.1IP是什么
主机在网络中的编号,这个编号就是IP地址。
IP地址的分类:IPV4 与 IPV6
IP地址的组成:网络号与主机号
IPV4地址的分类:
网络号 主机号 规定最高位 范围 使用单位
A 1字节 3字节 0 0.0.0.0 - 127.255.255.255 政府/大公司/学校
B 2字节 2字节 10 128.0.0.0-191.255.255.255 中等规模的公司
C 3字节 1字节 110 192.0.0.0-223.255.255.255 个人
D 1110 [224-239] 组播
E 11110 [240-255] 保留测试用的
4.2 IP地址的转换
//将点分十进制的字符串 转换成 网络字节序的 无符号四字节整型
in_addr_t inet_addr(const char *cp);
//将网络字节序的无符号四字节整型的ip地址 转换成 点分十进制的字符串
char *inet_ntoa(struct in_addr in);
五.TCP网络编程
5.1 网络编程模型
C/S模型:客户端服务器模型
B/S模型:浏览器服务器模型
5.2 TCP网络编程流程
5.2.1 简单的服务器流程:
1.创建流式套接字--socket()
2.填充服务器网络信息结构体
3.将套接字与服务器网络信息结构体绑定--bind()
4.将套接字设置成被动监听状态--listen()
5.阻塞等待客户端连接--accept()
6.收发数据 read/write
7.关闭套接字--close()
5.2.2 简单的客户端流程
1.创建流式套接字--socket()
2.填充服务器网络信息结构体
3.与服务器建立连接--connect()
4.收发数据 read/write
5.关闭套接字--close()
5.3 函数说明
5.3.1 socket函数
头文件:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
函数原型:int socket(int domain, int type, int protocol);
函数功能:创建套接字,套接字是网络通信的一种基本机制。通过套接字,计算机可以在网络上进行
数据的传输和接收。套接字是一种抽象的通信端点,它可以在不同的计算机之间建立通信连接,实现
数据的传输。
参数说明:
1.int domain 指定套接字使用的协议族 ,常见的有
AF_INET IPV4使用
AF_INET6 IPV6使用
2.int type 指定通信时所使用的协议
3.int protocol 附加协议,若是没有附加协议设置为0即可
返回值 :成功范湖文件描述符(套接字)
失败返回-1,并且重置错误码
5.3.2 bind函数
头文件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数功能:将套接字与网络信息结构体绑定,绑定操作通常用于服务器端
参数说明:
1.int sockfd 表示套接字(文件描述符)
2.const struct sockaddr *addr 网络信息结构体
//这个结构体只是用于转换的
struct sockaddr {
sa_family_t sa_family;//结构体参数说明typedef unsigned short sa_family_t
char sa_data[14];
}
//实际所使用的结构体,使用时需要把这个结构体强制装换成struct sockaddr类型的
struct sockaddr_in {
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 网络字节序的端口号 */
struct in_addr sin_addr; /* ip地址 */
};
3.socklen_t addrlen addr的长度
返回值:
成功 0
失败 -1 重置错误码
5.3.3 listen函数
头文件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:
int listen(int sockfd, int backlog);
函数功能:
将套接字设置成被动监听状态,然后才能使用accept函数等待客户端连接
参数说明:
int sockfd:套接字
backlog:半连接队列的长度 (只要不是 0 就行,一般是5到10)
返回值:
成功 0
失败 -1 重置错误码
5.3.4 accept函数
头文件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
函数功能:
提取挂起队列中的第一个连接(阻塞等待客户端连接)
参数说明:
int sockfd:已经bind和listen过的套接字
struct sockaddr *addr:用来保存客户端的信息缓冲区的首地址,也可以传NULL
socklen_t *addrlen:addr的大小
返回值:
成功 返回一个新的文件描述符专门用于和当前的客户端通信
失败 -1 重置错误码
5.3.5 connect函数
头文件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
函数功能:
与服务器建立连接
参数:
int sockfd:套接字
const struct sockaddr *addr:要连接的服务器的网络信息结构体的首地址
socklen_t addrlen:addr的大小
返回值:
成功 0
失败 -1 重置错误码
六.代码编写
6.1服务器代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h> //使用时 struct sockaddr_in 需要加这个头文件
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#define ERR_LOG(msg) do{ printf("%s %s %d\n",__FILE__,__func__,__LINE__);perror(msg);exit(EXIT_FAILURE);}while(0)
int main(int argc, const char *argv[]){
/*1.创建套接字,主要用于接受客户端的连接请求。*/
int sock_id=0;
if (-1==(sock_id=socket(AF_INET,SOCK_STREAM,0)))//创建套接字
{ERR_LOG("socket error");}
printf("%d..\n",sock_id);
/*2.填充服务器信息结构体*/
struct sockaddr_in service_information;//注册服务器信息
memset(&service_information,0,sizeof(service_information));
service_information.sin_family=AF_INET;//设置为AF_INET,代表使用的是TCP协议
service_information.sin_port=htons(8888);//写服务器的端口号
service_information.sin_addr.s_addr=inet_addr("127.0.0.1");//sin_addr是服务器的IP地址。struct in_addr的s_addr成员以网络字节序包含主机接口地址。
/*3.绑定套接字与服务器信息结构体*/
const struct sockaddr *__addr=(struct sockaddr *)&service_information;//将service_information结构体的指针转换为struct sockaddr类型的指针,并将结果保存在__addr中。
if (-1==(bind(sock_id,__addr,sizeof(service_information))))
ERR_LOG("bind error");
/*4.将套接字设置为被动监听状态*/
if(-1==(listen(sock_id,5)))//5是半连接队列的长度
ERR_LOG("listen error");
/*5.等待客户端连接*/
printf("等待客户端连接..\n");
int accept_id=0;//accept()函数返回的文件描述符可以用于与客户端进行数据传输
if(-1==(accept_id=accept(sock_id,NULL,NULL)))
ERR_LOG("listen error");
printf("客户端连接成功..\n");
/*6.对客户端传输的数据进行处理*/
char str[128]={0};
char buf[128]={0};
int read_num=0;
int num_1=0;
int num_2=0;
char symbol;
int result=0;
memset(str,0,sizeof(str));//将清理数组内的脏数据
memset(buf,0,sizeof(buf));//将清理数组内的脏数据
while (true)
{
read_num=read(accept_id,str,sizeof(str));//读取客户端发送的信息,然后存到str里面
if (!strcmp(str,"1"))
{
break;
}
sscanf(str,"%d%c%d",&num_1,&symbol,&num_2);//将str里面的数据按照%d%c%d的格式解析,然后将解析完的数据依次存放到&num_1,&symbol,&num_2里面
memset(str,0,sizeof(str));
switch (symbol)
{
case '+':
result=num_1+num_2;
break;
case '-':
result=num_1-num_2;
break;
case '*':
result=num_1*num_2;
break;
case '/':
result=num_1/num_2;
break;
case '%':
result=num_1%num_2;
break;
default:
write(accept_id,"只支持加减乘除运算",sizeof("只支持加减乘除运算"));
break;
}
sprintf(buf,"%d",result);
result=0;
write(accept_id,buf,sizeof(buf));
memset(buf,0,sizeof(buf));//数据处理完之后,清理数组
}
close(accept_id);
close(sock_id);
return 0;
}
6.2 客户端代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#define ERR_LOG(msg) do{ printf("%s %s %d\n",__FILE__,__func__,__LINE__);perror(msg);exit(EXIT_FAILURE);}while(0)
int main(int argc, const char *argv[]){
/*1.获取套接字(文件描述符)*/
int socket_fd=0;
if (-1==(socket_fd=socket(AF_INET,SOCK_STREAM,0)))//AF_INET代表使用的是IPV4,SOCK_STREAM表示使用TCP协议
ERR_LOG("socket error");
/*2.填充服务器网络信息结构体*/
struct sockaddr_in service_information;
memset(&service_information,0,sizeof(struct sockaddr_in));
service_information.sin_family=AF_INET;
service_information.sin_port=htons(6677);//服务器的端口号
service_information.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器的IP地址
/*3.连接服务器*/
if (-1==connect(socket_fd,(struct sockaddr *)&service_information,sizeof(service_information)))
ERR_LOG("connect error");
printf("连接服务器成功..\n");
/*4.进行数据的收发*/
char str[128]={0};//用来保存发送给服务器的内容
while (true)
{
fgets(str,sizeof(str),stdin);//从终端获取数据进行运算,支持加,减,乘,除,取余
str[strlen(str)-1]='\0';//将'\n'替换为'\0'
write(socket_fd,str,strlen(str));
if (!strcmp(str,"1"))
{
break;
}
memset(str,0,sizeof(str));//清空数组中的数据
read(socket_fd,str,sizeof(str));
printf("计算结果为: %s\n",str);
}
//关闭套接字
close(socket_fd);
return 0;
}