云服务器端源码 云服务器编程
- 注:以下代码是在观看网易云课堂,职坐标发布的Linux网络编程|人工智能物联网时,跟着视频写的,仅为学习使用。share=1&shareId=1020885091&courseId=1002913013&trace_c_p_k2=f61d55ffe1e84f9d8ec0dd31b371723c
1 用户建立连接,服务器发送系统时间
1.1 服务器
#include<netdb.h> #include<sys/socket.h> #include<unistd.h> #include<string.h> #include<stdio.h> #include<stdlib.h> #include<memory.h> #include<signal.h> #include<time.h> #include<arpa/inet.h> int sockfd; //套接字描述符 void sig_handler(int signo){ //信号处理函数 if(signo==SIGINT){ printf("serve close\n"); /*步骤6:关闭socket *这里关闭服务器socket */ close(sockfd); exit(1); } } void out_addr(struct sockaddr_in *clientaddr){ //输出连接上来的客户端相关信息 //将端口从网络字节序转化为主机字节序 int port=ntohs(clientaddr->sin_port); char ip[16]; memset(ip,0,sizeof(ip)); //将ip地址从网络字节序转化为点分十进制 inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip)); printf("client: %s(%d) connected\n",ip,port); } void do_service(int fd){ //向客户端发送系统时间 //获得系统时间 long t=time(0); //获得系统时间 char *s=ctime(&t); //将系统时间转化为字符串 size_t size=strlen(s)*sizeof(char); //获得字符串大小 //将服务端获得的系统时间返回到客户端 if(write(fd,s,size)!=size){ perror("write errpr"); //exit(1); } } int main(int argc,char* argv[]){ if(argc<2){ //如果没有输入端口号 printf("usage: %s #port\n",argv[0]); exit(1); } if(signal(SIGINT,sig_handler)==SIG_ERR){ printf("signal sigint error"); exit(1); } /*步骤1:创建socket(套接字) *注:socket创建在内核中,是一个结构体。 AF_INET:IPv4因特域 SOCK_STREAM:流式套接字,TCP协议 */ sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0){ perror("socket error"); exit(1); } /*步骤2:调用bind函数将socket和地址 *(包括ip、port)进行绑定 */ struct sockaddr_in serveraddr; //因特网专用地址 memset(&serveraddr,0,sizeof(serveraddr)); //初始化一下 //往地址中填入ip、port、internet地址族类型 serveraddr.sin_family=AF_INET; //IPv4 serveraddr.sin_port=htons(atoi(argv[1])); //端口,由命令行传入,需要用htons函数转成网络字节序 //serveraddr.sin_addr.s_addr="192.168.0.10"; //ip地址这样设置的话,ip地址就绑死了 serveraddr.sin_addr.s_addr=INADDR_ANY; //监听服务器上所有网卡的ip if(bind(sockfd,(struct sockaddr*)&serveraddr, sizeof(serveraddr))<0){ //将套接字与地址绑定,地址需要强转成通用地址形式 perror("bind error"); exit(1); } /*步骤3:调用listen函数启用监听(指定port监听) *通知系统去接管来自客户端的连接请求 *将接受到的客户端的连接请求放在对应的队列中 *listen的第二个参数是制定这个队列的大小 */ if(listen(sockfd,10)<0){ perror("listen error"); exit(1); } /*步骤4:调用acept函数从队列中获得一个客户端的请求连接 *并返回一个新的客户端描述符 *若没有客户端连接,则会阻塞,知道有一个客户端连接 */ struct sockaddr_in clientaddr; //用于保存客户端地址 socklen_t clientaddr_len=sizeof(clientaddr); //客户端地址长度 while(1){ int fd=accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len); if(fd<0){ perror("accept error"); continue; } /*步骤5:调用IO函数(read/write)和连接的客户端 *进行双向通讯 */ out_addr(&clientaddr); do_service(fd); /*步骤6:关闭socket *这里关闭客户端socket */ close(fd); } return 0; }1.2 客户端
#include<netdb.h> #include<sys/socket.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<memory.h> #include<unistd.h> #include<arpa/inet.h> int main(int argc,char *argv[]){ if(argc<3){ printf("usage: %s ip port\n",argv[0]); exit(1); } /*步骤1:创建socket */ int sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0){ perror("socket error"); exit(1); } //往serveraddr填入ip、port、地址族类型(IPv4) struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr)); serveraddr.sin_family=AF_INET; serveraddr.sin_port=htons(atoi(argv[2])); //将ip地址转化为网络字节序 inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr); /*步骤2:客户端调用connect函数连接到服务器端 */ if(connect(sockfd,(struct sockaddr*)&serveraddr, sizeof(serveraddr))<0){ perror("connect error"); exit(1); } /*步骤3:调用IO函数(read/write)和服务器 *进行双向通信 */ char buffer[1024]; memset(buffer,0,sizeof(buffer)); size_t size; if((size=read(sockfd,buffer,sizeof(buffer)))<0){ //读取服务器传过来的信息 perror("read error"); exit(1); } if(write(STDOUT_FILENO,buffer,size)!=size){ //将信息输出到屏幕上 perror("writer error"); } /*步骤4:关闭socket套接字 */ close(sockfd); return 0; }2 用户发送一个消息,服务器将消息返回(基于自定义协议)
2.1 协议
#ifndef __MSG_H__ #define __MSG_H__ #include <sys/types.h> typedef struct{ //协议头部 char head[10]; char checknum; //校验码 //协议体部 char buff[512]; //数据 }Msg; /* *发送一个基于自定义协议的message *发送的数据存放在buff中 */ extern int write_msg(int sockfd,char *buff,size_t len); /* *读取一个基于自定义协议的message *读取的数据存放在buff中 */ extern int read_msg(int sockfd,char *buff,size_t len); #endif#include "msg.h" #include<unistd.h> #include<string.h> #include<memory.h> #include<sys/types.h> /* *校验函数 */ static unsigned char msg_check(Msg *pMessage) //只在本文件使用 { unsigned char checknum=0; for(int i=0;i<sizeof(pMessage->head);++i){ checknum+=pMessage->head[i]; } for(int i=0;i<sizeof(pMessage->buff);++i){ checknum+=pMessage->buff[i]; } return checknum; } /* *发送一个基于自定义协议的message *发送的数据存放在buff中 */ int write_msg(int sockfd,char *buff,size_t len) { Msg message; memset(&message,0,sizeof(message)); strcpy(message.head,"Night00:"); //头部 memcpy(message.buff,buff,len); //数据 message.checknum=msg_check(&message); //校验码 if(write(sockfd,&message,sizeof(message))!=sizeof(message)) return -1; return 0; } /* *读取一个基于自定义协议的message *读取的数据存放在buff中 */ int read_msg(int sockfd,char *buff,size_t len) { Msg message; memset(&message,0,sizeof(message)); size_t size; if((size=read(sockfd,&message,sizeof(message)))<0) return -1; else if(size==0) return 0; //进行校验码验证 unsigned char checknum=msg_check(&message); if((checknum==(unsigned char)message.checknum) && (!strcmp("Night00:",message.head))){ memcpy(buff,message.buff,len); return sizeof(message); } return -1; }2.2 客户端
#include"msg.h" #include<netdb.h> #include<sys/socket.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<memory.h> #include<unistd.h> #include<arpa/inet.h> int main(int argc,char *argv[]){ if(argc<3){ printf("usage: %s ip port\n",argv[0]); exit(1); } /*步骤1:创建socket */ int sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0){ perror("socket error"); exit(1); } //往serveraddr填入ip、port、地址族类型(IPv4) struct sockaddr_in serveraddr; memset(&serveraddr,0,sizeof(serveraddr)); serveraddr.sin_family=AF_INET; serveraddr.sin_port=htons(atoi(argv[2])); //将ip地址转化为网络字节序 inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr); /*步骤2:客户端调用connect函数连接到服务器端 */ if(connect(sockfd,(struct sockaddr*)&serveraddr, sizeof(serveraddr))<0){ perror("connect error"); exit(1); } /*步骤3:调用IO函数(read/write)和服务器 *进行双向通信 */ char buff[512]; size_t size; char *prompt=">"; while(1){ memset(buff,0,sizeof(buff)); write(STDOUT_FILENO,prompt,1); //打印到屏幕 size=read(STDIN_FILENO,buff,sizeof(buff)); //输入字符串,read并不会将最后一位设置为'\0' char endChar[]="close socket"; //当输入close socket的时候退出循环 if(size==sizeof(endChar) && !strncmp(buff,endChar,strlen(endChar))){ break; } if(size<0) continue; buff[size-1]='\0'; //将数组最后一位作为结束符 if(write_msg(sockfd,buff,sizeof(buff))<0){ perror("write msg error"); //输入出错 continue; }else{ //输入成功,接受服务器端数据 if(read_msg(sockfd,buff,sizeof(buff))<0){ perror("read msg error"); //读取出错 continue; }else{ printf("%s\n",buff); //读取成功 } } } /*步骤4:关闭socket套接字 */ close(sockfd); return 0; }2.3 服务器(多进程)
#include<netdb.h> #include<sys/socket.h> #include<sys/types.h> #include<sys/wait.h> #include<unistd.h> #include<string.h> #include<stdio.h> #include<stdlib.h> #include<memory.h> #include<signal.h> #include<errno.h> #include<time.h> #include<arpa/inet.h> #include"msg.h" int sockfd; //套接字描述符 void sig_handler(int signo){ //信号处理函数 if(signo==SIGINT){ printf("serve close\n"); /*步骤6:关闭socket *这里关闭服务器socket */ close(sockfd); exit(1); } if(signo==SIGCHLD){ printf("child process deaded..."); wait(0); } } void out_addr(struct sockaddr_in *clientaddr){ //输出连接上来的客户端相关信息 //将端口从网络字节序转化为主机字节序 int port=ntohs(clientaddr->sin_port); char ip[16]; memset(ip,0,sizeof(ip)); //将ip地址从网络字节序转化为点分十进制 inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip)); printf("client: %s(%d) connected\n",ip,port); } void do_service(int fd){ //和客户端进行双向通信 /*和客户端进行读写操作(双向通信)*/ char buff[512]; while(1){ memset(buff,0,sizeof(buff)); printf("start read and write...\n"); size_t size; if((size==read_msg(fd,buff,sizeof(buff)))<0){ perror("protocal error"); //如果数据读取错误,则跳出循环 break; }else if(size==0){ /*可能有一种情况s,当客户端连接关闭 *即写端关闭,只留下读端,当缓冲区里 *的数据都被读完后,那么读取的大小就是0 *补充:若写端没有关闭,但是写端又不写入 *数据,那么读端就会阻塞。 */ break; }else{ //正常读取 if(write_msg(fd,buff,sizeof(buff))<0){ if(errno==EPIPE){ /*当服务端想向客户端写入数据时,若客户端断开连接 *就类似与管道的读端关闭,只留下写段,这样会产生 *一个SIGPIPE信号,同时errno会等于EPIPE */ break; } perror("protocal error"); } printf("%s\n",buff); } } } int main(int argc,char* argv[]){ if(argc<2){ //如果没有输入端口号 printf("usage: %s #port\n",argv[0]); exit(1); } if(signal(SIGINT,sig_handler)==SIG_ERR){ printf("signal sigint error"); exit(1); } if(signal(SIGCHLD,sig_handler)==SIG_ERR){ perror("signal sigchld error"); exit(1); } /*步骤1:创建socket(套接字) *注:socket创建在内核中,是一个结构体。 AF_INET:IPv4因特域 SOCK_STREAM:流式套接字,TCP协议 */ sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0){ perror("socket error"); exit(1); } /*步骤2:调用bind函数将socket和地址 *(包括ip、port)进行绑定 */ struct sockaddr_in serveraddr; //因特网专用地址 memset(&serveraddr,0,sizeof(serveraddr)); //初始化一下 //往地址中填入ip、port、internet地址族类型 serveraddr.sin_family=AF_INET; //IPv4 serveraddr.sin_port=htons(atoi(argv[1])); //端口,由命令行传入,需要用htons函数转成网络字节序 //serveraddr.sin_addr.s_addr="192.168.0.10"; //ip地址这样设置的话,ip地址就绑死了 serveraddr.sin_addr.s_addr=INADDR_ANY; //监听服务器上所有网卡的ip if(bind(sockfd,(struct sockaddr*)&serveraddr, sizeof(serveraddr))<0){ //将套接字与地址绑定,地址需要强转成通用地址形式 perror("bind error"); exit(1); } /*步骤3:调用listen函数启用监听(指定port监听) *通知系统去接管来自客户端的连接请求 *将接受到的客户端的连接请求放在对应的队列中 *listen的第二个参数是制定这个队列的大小 */ if(listen(sockfd,10)<0){ perror("listen error"); exit(1); } /*步骤4:调用acept函数从队列中获得一个客户端的请求连接 *并返回一个新的客户端描述符 *若没有客户端连接,则会阻塞,知道有一个客户端连接 */ struct sockaddr_in clientaddr; //用于保存客户端地址 socklen_t clientaddr_len=sizeof(clientaddr); //客户端地址长度 while(1){ int fd=accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len); if(fd<0){ perror("accept error"); continue; } /*步骤5:fork一个子进程 *调用IO函数(read/write)和连接的客户端 *进行双向通讯 */ pid_t pid=fork(); if(pid<0){ continue; }else if(pid==0){ //子进程 out_addr(&clientaddr); do_service(fd); /*步骤6:关闭socket *这里关闭客户端socket */ close(fd); }else{ //父进程 /*当fork一个子进程的时候,子进程与父进程的环境相同 *因此,套接字的描述符fd也会复制一份,而内核对fd有 *一个类似于引用计数的东西,当父进程和子进程都close *掉这个fd时,这个套接字才会真正关闭 */ close(fd); } } return 0; }2.4 服务器(多线程)
(有问题,待更新)
#include<netdb.h> #include<sys/socket.h> #include<sys/types.h> #include<sys/wait.h> #include<unistd.h> #include<string.h> #include<stdio.h> #include<stdlib.h> #include<memory.h> #include<signal.h> #include<errno.h> #include<time.h> #include<arpa/inet.h> #include<pthread.h> #include"msg.h" int sockfd; //套接字描述符 void sig_handler(int signo){ //信号处理函数 if(signo==SIGINT){ printf("serve close\n"); /*步骤6:关闭socket *这里关闭服务器socket */ close(sockfd); exit(1); } } void out_addr(struct sockaddr_in *clientaddr){ //输出连接上来的客户端相关信息 //将端口从网络字节序转化为主机字节序 int port=ntohs(clientaddr->sin_port); char ip[16]; memset(ip,0,sizeof(ip)); //将ip地址从网络字节序转化为点分十进制 inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip)); printf("client: %s(%d) connected\n",ip,port); } void do_service(int fd){ //和客户端进行双向通信 /*和客户端进行读写操作(双向通信)*/ char buff[512]; while(1){ memset(buff,0,sizeof(buff)); printf("start read and write...\n"); size_t size; if((size==read_msg(fd,buff,sizeof(buff)))<0){ perror("protocal error"); //如果数据读取错误,则跳出循环 break; }else if(size==0){ /*可能有一种情况s,当客户端连接关闭 *即写端关闭,只留下读端,当缓冲区里 *的数据都被读完后,那么读取的大小就是0 *补充:若写端没有关闭,但是写端又不写入 *数据,那么读端就会阻塞。 */ break; }else{ //正常读取 if(write_msg(fd,buff,sizeof(buff))<0){ if(errno==EPIPE){ /*当服务端想向客户端写入数据时,若客户端断开连接 *就类似与管道的读端关闭,只留下写段,这样会产生 *一个SIGPIPE信号,同时errno会等于EPIPE */ break; } perror("protocal error"); } printf("%s\n",buff); } } } void out_fd(int fd){ //通过套接字输出客户端信息 struct sockaddr_in clientaddr; socklen_t len=sizeof(clientaddr); if(getpeername(fd,(struct sockaddr*)&clientaddr,&len)<0){ perror("getpeername error"); return; } //将端口从网络字节序转化为主机字节序 int port=ntohs(clientaddr.sin_port); char ip[16]; memset(ip,0,sizeof(ip)); //将ip地址从网络字节序转化为点分十进制 inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ip,sizeof(ip)); printf("client: %s(%d) closed\n",ip,port); } void* th_fn(void *arg){ //线程运行函数 int fd=*(int*)arg; do_service(fd); //和客户端进行双向通信 out_fd(fd); //输出一个客户端相关信息 close(fd); return (void*)0; } int main(int argc,char* argv[]){ if(argc<2){ //如果没有输入端口号 printf("usage: %s #port\n",argv[0]); exit(1); } if(signal(SIGINT,sig_handler)==SIG_ERR){ printf("signal sigint error"); exit(1); } /*步骤1:创建socket(套接字) *注:socket创建在内核中,是一个结构体。 AF_INET:IPv4因特域 SOCK_STREAM:流式套接字,TCP协议 */ sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0){ perror("socket error"); exit(1); } /*步骤2:调用bind函数将socket和地址 *(包括ip、port)进行绑定 */ struct sockaddr_in serveraddr; //因特网专用地址 memset(&serveraddr,0,sizeof(serveraddr)); //初始化一下 //往地址中填入ip、port、internet地址族类型 serveraddr.sin_family=AF_INET; //IPv4 serveraddr.sin_port=htons(atoi(argv[1])); //端口,由命令行传入,需要用htons函数转成网络字节序 //serveraddr.sin_addr.s_addr="192.168.0.10"; //ip地址这样设置的话,ip地址就绑死了 serveraddr.sin_addr.s_addr=INADDR_ANY; //监听服务器上所有网卡的ip if(bind(sockfd,(struct sockaddr*)&serveraddr, sizeof(serveraddr))<0){ //将套接字与地址绑定,地址需要强转成通用地址形式 perror("bind error"); exit(1); } /*步骤3:调用listen函数启用监听(指定port监听) *通知系统去接管来自客户端的连接请求 *将接受到的客户端的连接请求放在对应的队列中 *listen的第二个参数是制定这个队列的大小 */ if(listen(sockfd,10)<0){ perror("listen error"); exit(1); } /*步骤4:调用acept函数从队列中获得一个客户端的请求连接 *并返回一个新的客户端描述符 *若没有客户端连接,则会阻塞,知道有一个客户端连接 */ struct sockaddr_in clientaddr; //用于保存客户端地址 socklen_t clientaddr_len=sizeof(clientaddr); //客户端地址长度 //设置线程的分离属性 pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); while(1){ //主控线程负责accept /*这里的clientaddr可以获得客户端的地址 *但是也可以不这样做,可以从fd中获得客 *户端地址,使用getpeername函数 */ int fd=accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len); //int fd=accept(sockfd,NULL,NULL); if(fd<0){ perror("accept error"); continue; } out_addr(&clientaddr); /*步骤5:启动子线程(以分离状态启动,子线程运行结束 *资源自动回收,主线程不用阻塞来等待它回收资源) *调用IO函数(read/write)和连接的客户端 *进行双向通讯 */ pthread_t th; int err; if((err=pthread_create(&th,&attr,th_fn,(void *)&fd))!=0){ perror("pthread create error"); } pthread_attr_destory(&attr); } return 0; }