先了解一些计算机网络的知识点:
国际标准化组织ISO于1977年成立,不久就提出一个试图使各种计算机在世界范围内互连成网的标准框架,即著名的开放系统互连基本参考模型 OSI/RM(Open Systems Interconnection Reference Model),简称 OSI 。
物理层:将数据转换为可通过物理介质传送的电子信号 (相当于邮局的搬运工人)
数据链路层:决定访问网络介质的方式,在此层将数据分帧,并处理流控制,指定拓扑结构并提供硬件寻址(相当于邮局的装拆箱工人)
网络层:使用数据路由经过大型网络(相当于邮局的排序工人)
传输层:提供终端到终端的可靠连接(相当于公司中跑邮局的送信职员)
会话层:允许用户使用简单易记的名称建立连接(相当于公司中收寄信、写信封和拆信封的秘书)
表示层:协商数据交换格式(相当于公司中替老板写信的助理)
应用层:用户的应用程序和网络之间的接口(老板)
分层带来的好处:(1)各层之间是独立的 (2)灵活性好 (3)结构上可分隔开 (4)易于实现和维护 (5)能促进标准化工作
OSI 的七层协议体系结构的概念清楚,理论也比较完整,但它既复杂又不实用。TCP/IP 四层体系结构则不同,得到广泛应用。
每层体系常用的协议有:
应用层 HTTP FTP(用于项目) DNS SMTP POP PING
传输层 TCP UDP (可以选择其一)
网络层 IP
数据链路层 ARP(地址解离协议) RARP(逆地址解离协议)
服务端 socket bind listen accept recv/send close
客户端 socket connect send/recv close
服务器端
#include
#include
int socket(int domain,int type,int pro); //函数功能创建socket(相当于买手机)
返回值为-1则出错 大于等于0的都是正确的 返回的是文件描述符 只不过是socket的文件(在linux上一切皆文件)
domain:协议簇 一般使用PF_INET(是TCP/IP协议簇 IPV4)
type:具体的协议 TCP:SOCK_STREAM(流式服务的) UDP:SOCK_UGRAM
pro:默认为0 (更具体的协议)
int bind(int sockfd,struct sockaddr *seraddr,int len);(相当于电话卡)
int sockfd:返回的是文件描述符
struct sockaddr *seraddr:服务器端的ip地址(关键这个参数怎么给?)唯一标识主机:IP地址+端口号
sockaddr 的结构如下:
struct sockaddr_in
{
sa_family_t sin_family; //地址族
u_int16_t sin_port; //无符号的16位short类型用来表示端口号
struct in_addr sin_addr;
};
主机字节序 :大端模式(高位存低地址) 小端模式(高位存高地址)
网络字节序 :大端模式
手机是大端模式, intel 处理器是小端模式。在主机字节序转换为网络字节序时,需将 intel 处理器从小端装换为大端,使用系统提供的端口号转换函数(有四种转换函数)
使用short htons 端口号转换函数
1—— 65535 为short范围,其中
1 —— 1024 系统预留专有端口号,普通用户无法使用
例:21:FTP端口号80:外部服务器接入端口号 443:HTTPS端口号
1024--5000 保留的端口号
5000以上普通用户使用
程序里面给IP地址的时候是 点分十进制的字符串
例如“192.168.1.120”四个字节,都是0--255 使用整型值保存下来
怎样将IP地址转换为整型值呢?使用地址转换函数,如下:
int listen(int sockfd,int size);//listen:启动监听 (相当于对方打电话自己等待接听)
//size:监听队列的大小(默认size+1)
//包含两个队列:已完成连接的队列 正在完成连接的队列
int accept(int sockfd,struct sockaddr* cliaddr,int *len);//(相当于来电显示 显示对方的IP 知道自己和谁通信)
返回值:返回维护本次连接的文件描述符
recv(int c,void* buff,int buffsize,int flag); //获取数据 (相当于听对方讲话)
//c从哪一个客户端维护链接上获取数据 buff读的数据在哪放 buffsize大小,
缓冲区一次读多少的大小 flag标记
send(int c,void *buff,int datasize,int flag) //发送数据 (相当于自己说话给对方)
TCP服务器端网络编程流程
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
//先创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in ser,cli;
memset(&ser,0,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr"127.0.0.1";//回环地址 自己访问本机的IP inet_addr将点分十进制的IP转换为整形
//绑定失败原因: 1.IP地址不对 2.端口号被占用或没权限使用
int res = bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));//将IP和端口号与当前进程绑定
assert(res != -1);
listen(sockfd,5);
while(1)
{
int len = sizeof(cli);//sizeof(cli)是一个常数 将此常数赋值给变量len,再对变量取地址
int c = accept(sockfd,(struct sockaddr*)&cli,&len);
if(c < 0)
{
printf("error\n");
return 0;
}
while(1)
{
char recvbuff[128] = {0};
int n = recv(c,recvbuf,127,0);//recv函数会阻塞运行
if(n <= 0)
{
printf("one client break\n");
break;
}
printf("%d:%s\n",c,recvbuf);
send(c,"ok",2,0);
}
close(c); //客户端每次连接断开,进入死循环 但服务器端不会断开
}
close(sockfd);
}
TCP的客户端编程流程
int socket(int domain,int type,int pro);//创建套接字
int connect(int sockfd,(struct sockaddr*)seraddr,int len); //发起连接 连接成功:返回>0 不成功:返回-1
send();
recv();
close();
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in ser,cli;
memset(&ser,0,sizeof(ser));//对ser做清空操作
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res != -1);
while(1)
{
printf("please input:");
fflush(stdout);//把输入的数据交给服务器,刷新一次
char buff[128] = {0};
fgets(buff,128,stdin);//获取数据放在buff
buff[strlen(buff) - 1] = 0;//将数据的/n去掉
if(strcmp(buff,"end") == 0))//如果buff与end比较相同则退出,否则发送send
{
break;
}
send(sockfd,buff,strlen(buff),0);
char recvbuff[128] = {0};
recv(sockfd,recvbuff,127,0);
printf("%s\n",buff);
}
close(sockfd);
}