1、协议概念
指定规则:先传输文件名,再传输文件大小,最后传输数据。如ftp协议,传输固定数据,遵守一定的格式。
标准协议:如http、tcp/ip,arp等。
2、网络设计模式
c/s架构:client/server
特点:要求开发客户端和服务端,协议采用自定义方式;必须先下载客户端,数据提前缓存好。
不足:安全性不高;开发工作量大
b/s架构:web/server
特点:不需要安装软件;工作量小,客户端基本采用浏览器方式
不足:要求遵守fttp协议,动态加载数据
3、网络基础
(1)OSI七层模型和TCP/IP模型区别在于划分标准不一致,实质一样。
1)物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型等,主要作用是传输比特流。
2)数据链路层:定义了如何让格式化数据以进行传输,主要作用为提供错误检测和纠正。
3)网络层:为两个主机系统之间提供连接和路径选择,即用于传输。
4)传输层:定义了一些传输数据的协议和端口号,如:TCP和UDP,TCP为点对点传输,UDP为数据传送到某个地方,再从该处进行传送,传送到某个地方的过程中容易出现数据丢失,及不定时传输的情况,故为不可靠传输。
(2)数据包封装-简单理解即为:
发送数据时,应用层先对数据进行处理,然后再到传输层,之后为网络层,最后链路层对数据进行前后处理,而在接收数据时端则是数据链路层先进行数据解包,之后网络层对数据包处理,接着传输层进行处理,再到应用层处理,最后将应用层数据交给应用程序处理。
(3)单向通信和双向通信
单向通信:使用用2个文件描述符即可;
双向通信:需在客户端设置发送端文件描述符和接收端文件描述符,服务端亦是如此。客户端发送端文件描述符对应服务端接收端文件描述符;客户端接收端文件描述符对应服务端发送端文件描述符。
4、网络通信过程
5、socket编程
(1)网络字节序转换函数
uint32_t htonl(uint32_t hostlong);//将ip地址转换为int类型,再转换为网络字节序
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h表示host,n表示network,l表示32位长整数,s表示16位短整数。
注:若主机为小端字节序,函数将参数做相应的大小端转换然后返回,若主机为大端字节序,函数不做转换,将参数原封不动地返回。
(2)IP地址和端口号
ip地址:对应主机,即在网络环境中,唯一识别一台主机。
端口号:端口相当于主机中的一个进程,即在主机中唯一识别一个进程。
(3)程序
//程序1 tcpserver.c
#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>
//定义服务器端口号
#define SERVER_PORT 8000
int main(void)
{
//定义结构体类型变量 服务端地址,客户端地址
struct sockaddr_in serveraddr,clientaddr;
//定义服务器端监听套接字,通信套接字
int sockfd,confd;
int addrlen;//客户端地址结构体变量所占空间大小
//自定义数组,变量
char ipstr[128];//存储客户端IP地址
char buf[1024];//储存从通信套接字读取到的数据
int len;//从通信套接字读取的数据字节数
int i=0;//普通循环变量=0
//1、创建socket套接字 参数:ipv4,TCP方式,默认协议
sockfd=socket(AF_INET,SOCK_STREAM,0);
//2、bind将监听套接字与服务器ip地址和端口绑定
bzero(&serveraddr,sizeof(serveraddr));//服务器端地址结构体变量清零
serveraddr.sin_family=AF_INET;//地址协议族为ipv4
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);//连接任意ip,字节序转换主机到网络
serveraddr.sin_port=htons(SERVER_PORT);//端口为8000
//参数 监听套接字,客户端地址结构体变量,转换后的客户端地址结构体变量所占空间大小
bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));//bind
//3、监听客户端,参数:监听套接字,监听上限数
listen(sockfd,128);
while(1)
{
addrlen=sizeof(clientaddr);//计算客户端地址结构体变量所占空间大小
//4、阻塞监听客户端连接请求 参数:监听套接字,客户端地址结构体变量,客户端地址结构体变量所占空间大小
confd=accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen);
printf("client ip %s \t port %d \n",inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ipstr,sizeof(ipstr)),ntohs(clientaddr.sin_port));
//5、读取通信套接字中数据,处理客户端请求 参数:通信套接字,数据缓存数组,需读取数据字节大小
len=read(confd,buf,sizeof(buf));
while(i<len)
{
buf[i]=toupper(buf[i]);//将读取到的字母变为大写
i++;
}
//数据写回到客户端 参数:通信套接字,存储读取到的数据数组,需写入数据字节大小
write(confd,buf,len);
close(confd);//关闭通信套接字
}
close(sockfd);//关闭监听套接字
return 0;
}
//程序2 tcpclient.c
#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>
//定义服务器端口
#define SERVER_PORT 8000
int main(int argc ,char *argv[])
{
//定义结构体类型变量 服务端地址
struct sockaddr_in serveraddr;
//定义客户器端通信套接字
int confd;
int addrlen;//服务端地址结构体变量所占空间大小
//自定义数组
char ipstr[]="192.168.60.128";//存储服务器IP地址
int len;//从通信套接字读取的数据字节数
char buf[1024];//储存从通信套接字读写的数据
//1、创建通信套接字 参数:ipv4,TCP方式,默认协议
confd=socket(AF_INET,SOCK_STREAM,0);
//2、bind将监听套接字与服务器ip地址和端口绑定
bzero(&serveraddr,sizeof(serveraddr));//服务器端地址结构体变量清零
serveraddr.sin_family=AF_INET;//地址协议族为ipv4
serveraddr.sin_port = htons(SERVER_PORT); //端口为8000
//IP地址字符串转为网络字节序
inet_pton(AF_INET,ipstr,&serveraddr.sin_addr.s_addr);
//3、连接服务器 参数:通信套接字,服务器端地址结构体变量,服务器端地址结构体变量所占空间大小
connect(confd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
//4、写数据到服务器
write(confd,argv[1],strlen(argv[1]));
//5、从服务器读取数据
len=read(confd,buf,sizeof(buf));
//显示到终端
write(STDOUT_FILENO,buf,len);
printf("\n");
return 0;
}
程序1和程序2结合使用,程序执行效果:
//程序3 tcpserver.c
#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>
//定义服务器端口号
#define SERVER_PORT 6666
int main(int argc,char *argv[])
{
//定义结构体类型变量 服务端地址,客户端地址
struct sockaddr_in serveraddr,clientaddr;
//定义服务器端监听套接字,通信套接字
int sockfd,confd;
int addrlen;//客户端地址结构体变量所占空间大小
//自定义数组
char ipstr[128];//存储客户端IP地址
char buf[1024];//储存从通信套接字读取到的数据
int len;//从通信套接字读取的数据字节数
//1、创建socket套接字 参数:ipv4,TCP方式,默认协议
sockfd=socket(AF_INET,SOCK_STREAM,0);
//2、bind将监听套接字与服务器ip地址和端口绑定
bzero(&serveraddr,sizeof(struct sockaddr_in));//服务器端地址结构体变量清零
serveraddr.sin_family=AF_INET;//地址协议族为ipv4
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);//连接任意ip,字节序转换主机到网络
serveraddr.sin_port=htons(SERVER_PORT);//端口为6666
//参数 监听套接字,客户端地址结构体变量,转换后的客户端地址结构体变量所占空间大小
bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(struct sockaddr));
//3、监听客户端,参数:监听套接字,监听上限数
listen(sockfd,5);
while(1)
{
addrlen=sizeof(clientaddr);//计算客户端地址结构体变量所占空间大小 //法1
//addrlen=sizeof(struct sockaddr_in);//法2
//4、阻塞监听客户端连接请求 参数:监听套接字,客户端地址结构体变量,客户端地址结构体变量所占空间大小
confd=accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen);
//显示请求的客户端IP地址
fprintf(stderr,"server get connect from %s\n",inet_ntoa(clientaddr.sin_addr));
//5、读取通信套接字中数据,处理客户端请求 参数:通信套接字,数据缓存数组,需读取数据字节大小
len=read(confd,buf,sizeof(buf));
buf[len]='\0';
printf("server received :%s\n",buf);
//关闭通信套接字
close(confd);
}
close(sockfd);//关闭监听套接字
return 0;
}
//程序4 tcpclient.c
#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>
//定义服务器端口
#define SERVER_PORT 6666
int main(int argc,char *argv[])
{
///定义结构体类型变量 服务端地址,客户端地址
struct sockaddr_in serveraddr,clientaddr;
//定义客户端套接字
int sockfd;
int addrlen;//客户端地址结构体变量所占空间大小
//自定义数组
int len;//从通信套接字读取的数据字节数
char buf[1024];//储存从通信套接字读取到的数据
//1、创建socket套接字 参数:ipv4,TCP方式,默认协议
sockfd=socket(AF_INET,SOCK_STREAM,0);
//2、bind将客户端套接字与客户端ip地址和端口绑定
bzero(&clientaddr,sizeof(struct sockaddr_in));//服务器端地址结构体变量清零
clientaddr.sin_family=AF_INET;//地址协议族为ipv4
clientaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
clientaddr.sin_port=htons(SERVER_PORT);//端口为6666
//参数 客户端套接字,客户端地址结构体变量,转换后的客户端地址结构体变量所占空间大小
bind(sockfd,(struct sockaddr *)&clientaddr,sizeof(struct sockaddr));
//客户程序填充服务端的资料
bzero(&serveraddr,sizeof(serveraddr));//服务器端地址结构体变量清零
serveraddr.sin_family=AF_INET;//地址协议族为ipv4
serveraddr.sin_port=htons(SERVER_PORT);//端口为6666
serveraddr.sin_addr.s_addr=inet_addr("127.0.0.1");
//3、连接服务器 参数:通信套接字,服务器端地址结构体变量,服务器端地址结构体变量所占空间大小
connect(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
//4、写数据到服务器
printf("please input char:\n");
fgets(buf,1024,stdin);//从键盘输入数据
write(sockfd,buf,strlen(buf));//写数据到服务端
//关闭客户端套接字
close(sockfd);
return 0;
}
程序3和程序4结合使用,程序执行效果:
注:
(1)服务器端程序中若监听放到while外面,只监听一次。
(2)客户端使用固定端口号时,可调用bind()。当客户端不需要固定的端口号,可以不调用bind(),客户端的端口号由内核自动分配。
(3)服务器端需要调用bind(),若服务器不调用bind(),内核会自动给服务器分配监听端口,则每次启动服务器时端口号都不一样,将导致客户端连接服务器出错。