网络应用程序设计模式
C/S (client/server)客户端/服务器(数据量大,稳定性好)
优点:协议选用灵活、QQ、LOL、先下载客户端、数据可提前缓存
缺点:对用户安全造成威胁、开发工作量大
B/S(浏览器/服务器模式)(数据量小)
优点:使用计算机自带浏览器,较安全,只需开发服务器,客户端采用计算机自带浏览器
缺点:协议不可变http、QQ农场、可以跨平台(windows\linux)
socket基本概念
客户端(client)
或称为用户端,是指与服务器相对应,为客户提供本地服务的程序。产品或服务所指的终点、软件。
服务器(server)
对客户端发送的请求做出处理并将结果返回给客户端
端口号和IP
- Socket成对出现,数据接收端、数据发送端
- 端口号:在主机中唯一标识一个进程
- IP:在网络环境中唯一标识一台主机
- 端口号+IP:在网络环境中唯一标识一个进程(socket)
- 使用socket必须确认IP,端口号
网络字节序
网络中的数据以二进制字节码流传输
存储方式
- 大端:低地址存在高位上,高地址存在低位
- 小端:低地址存在低位上,高地址存在高位上(计算机)
字节序转换
网络数据流应采用大端字节序,计算机选用的是小端存储,当计算机传数据时,从小端到大端,所以要进行字节序转换。
库函数(数字转网络字节序)
- htonl(host to network long):主机到网络IP32位
- ntohl(network to host long):网络到主机IP32位
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong);//32位IP,从主机到网络 uint16_t htons(uint16_t hostshort);//16位端口号 uint32_t ntohl(uint32_t netlong);//从网络到主机 uint16_t ntohs(uint16_t netshort);
IP地址转换函数(字符串转网络字节序)
- 点分十进制字符串转换成网络字节序 inet_pton(IP to network)
- 网络字节序转换成点分十进制字符串 inet_ntop(network to IP)
#include <arpa/inet.h> //af指定所用IP地址的形式(IPV4:AF_INET,IPV6:AF_INET6) //src指定源地址IP //dst指定目的地址IP int inet_pton(int af, const char *src, void *dst); const char *inet_ntop(int af,const void *src, char *dst, socklen_t size);
socket编程的实现
socket框架
服务器
- socket() // 建立套接字
- 对套接字进行初始化
- bind() //绑定端口号和IP(struct sockaddr_in addr)
- listen() //指定最大同时发起数
- accept() //阻塞等待客户端发起连接
- read() //读取客户端发送来的信息(对应客户端第4步)
- 对客户端信息进行处理
- Write() //将处理好的信息发送给客户端
- Close(); //关闭套接字
客户端
- socket();
- bind();//可以隐式绑定
- connect();//发起连接
- write();//写发送给服务器的信息
- read(); //读从服务器返回的信息(对应服务器端第7步)
- close();
注:程序启动时先启动服务器端,由服务器执行到第4步阻塞等待客户端发送信息,再启动客户端
客户端与服务器通信实现
- 初始化缓冲区char buf[]
- 通过缓冲区buf传信息
- 在服务器端对buf进行清零操作
- 每个write()对buf重新赋值,read()对应另一方的上一个write()
- 客户端write()在buf写信息
- 服务器read()读客户端buf信息
框架函数实现
头文件
#include <sys/types.h>
#include<sys/socket.h>
socket()
创建套接字
int socket(int domain, int type, int protocol);//创建套接口
- Domain:函数socket()的参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义
- Type: 函数socket()的参数type用于设置套接字通信的类型,有SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据包套接字)
- Protocol: 函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
初始化套接字
//套接字数据结构
Struct sockaddr_in{
_kernel_sa_family_t sin_family;
_be16 sin_port;
Struct in addr sin_addr;
}
//初始化
Struct sockaddr_in addr;
Addr.sin_family =AF_NET/NET6;
Addr.sin_port =htops/ntops;
Addr.sin_addr.s_addr =htops;
bind()
对套接字绑定端口号、IP,需要进行类型强转
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- Sockfd:socket文件描述符
- Addr:构造出IP地址加端口号(1000以下的端口给系统用,不能大于65535)
- Addrlen:sizeof(addr)长度
- 返回值:成功返回0,失败返回-1
listen()
同时可以有多少个客户端与服务器建立连接,对套接字进行操作
int listen(int sockfd, int backlog);//backlog最大值为128
accept()
阻塞等待客户端发起连接,需进行类型强转
int accept(int sockfd, struct sockaddr *addr,socklen_t *addrlen);
connect()
需进行类型强转
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
socket编程实例
客户端向服务器发送请求,服务器向客户端返回当前时间,并记录访问客户端的IP号、端口号
服务器端
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <memory.h>
#include <ctype.h>
int sockfd;//全局套接字描述符
char IP[16];
char *str;
//控制ctrl c产生的信号量处理函数
void signal_handler(int signno){
if(signno== SIGINT){
printf("服务器关闭\n");
close(sockfd);
exit(1);
}
}
//输出客户端相关信息
char * out_addr(struct sockaddr_in *client_addr){
//转换客户端port字节序
int port = htons(client_addr->sin_port);
memset(IP,0,sizeof(IP));
//将ip地址从网络字节序转换成点十分进制
inet_ntop(AF_INET,&client_addr->sin_addr.s_addr,IP, sizeof(IP));
printf("用户:%s(%d) 已连接\n",IP,port);
return IP;
}
//获取时间
char * do_service(int cfd){
long t = time(0);//获得系统时间
str = ctime(&t);
size_t size = strlen(str) * sizeof(char);
//将时间写入客户端sockaddr中
if(write(cfd,str,size)!=size){
printf("写入出错");
}
return str;
}
int main(int argc,char *argv[]){
//在命令行中传递端口号
if(argc <2){
printf("usage:%s #port\n",argv[0]);
exit(1);
}
//当客户端按下ctrl c时服务器端自动结束,否则服务器会一直循环
//设置信号量进行控制
if(signal(SIGINT, signal_handler)== SIG_ERR){
perror("信号量错误");
exit(1);
}
//创建文件
FILE *f;
//创建套接字,SOCK_STREAM使用TCP
sockfd = socket(AF_INET,SOCK_STREAM,0);
//在绑定函数之前对套接字进行初始化操作
struct sockaddr_in server_addr;//套接字地址
struct sockaddr_in client_addr;
memset(&server_addr,0,sizeof(server_addr));//对套接字地址进行清空操作
server_addr.sin_family =AF_INET;//与套接字协议保持一致,IPV4
server_addr.sin_port = htons(atoi(argv[1]));//将端口号转换字节序,命令行传入的第二个参数为端口号,atoi将其强制转换为整形
server_addr.sin_addr.s_addr = INADDR_ANY;//响应所有接口的链接请求
//绑定端口号、IP
int bindno;//用于错误处理
bindno = bind(sockfd,(struct sockaddr *)&server_addr, sizeof(server_addr));
if(bindno < 0){
perror("绑定出错");
exit(1);
}
//在指定端口进行监听
int listenno;//错误处理
listenno = listen(sockfd,128);//最多同时允许128个客户端请求建立链接
if(listenno <0){
perror("监听出错");
exit(1);
}
//调用accept函数阻塞直到等待客户端请求连接
socklen_t client_addr_len = sizeof(client_addr);//定义客户端套接字长度
while(1){
int cfd;//客户端文件描述符
cfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
if(cfd <0){
perror("接收失败");
continue;//继续获得
}
//read函数读取客户端信息
out_addr(&client_addr);//获取客户端地址信息
do_service(cfd);//对客户端文件进行相关操作
f= fopen("client_IP.txt","a+");
fprintf(f,"用户IP:%s,请求时间:%s",IP,str);
fclose(f);
close(cfd);//关闭客户端套接字
}
fclose(f);
return 0;
}
客户端
#include <stdlib.h>
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <memory.h>
#include <unistd.h>
int main(int argc, char *argv[]){
if(argc<3){
printf("用户:%s IP #port\n",argv[0]);
exit(1);
}
//创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0){
perror("套接字出错");
exit(1);
}
struct sockaddr_in server_addr;
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));//转换字节序
//将IP地址转换成网络字节序
inet_pton(AF_INET,argv[1],&server_addr.sin_addr.s_addr);
//调用connect请求与服务器建立连接
int a;
a = connect(sockfd,(struct sockaddr *)&server_addr, sizeof(server_addr));
if(a<0){
perror("连接出错");
exit(1);
}
//调用IO函数与服务器进行通信
//提供一个缓冲区给服务器放置时间
char buf[1024];
memset(&buf,0,sizeof(buf));//清空缓冲区
size_t size;
size = read(sockfd, buf,sizeof(buf));
if(size<0){
perror("读取错误");
exit(1);
}
//将日期写在屏幕上
write(STDOUT_FILENO,buf,size);
//关闭套接字
close(sockfd);
return 0;
}