Linux 网络应用编程
写在前面:写了好几天的代码,写前甚至根本不会运行这种代码啊啊啊!网上也搜不到太多的参考,所以超痛苦,一定要总结下这艰难的日子
一、首先是基本框架
包含套接字相关以及客户端和服务器的基本架构,这里主要是参考的老师给出的大框架以及这几篇文章的框架。
TCP 简单回声
这篇超有用,因为感觉要求和代码都很适合我们的实验,(甚至怀疑真的是我们之前的学长学姐的实验?雾)基本框架和语句都是在这学的。
网络编程基本框架
这篇是刚开始就搜到的,感觉框架也很全,但是实在是不知道是完成啥的也就没仔细研究。
这部分(最开始写的时候)我的重点是相关语句,刚拿到手根本不知道有些句子是干啥的,这时候的目的就是写个能运行的玩意跑一下,但是,但是连个能运行的都不知道咋写,甚至不知道咋运行。
唯一运行的线索是第一篇的编译运行给出的代码(第五条这里)
但是,不知道咋在一个终端里面初始化完服务器直接链接客户端,没错,后来划水去小b搜了一下网络编程,发现人家,打开了两个终端,没错,非常合理,一个服务器,另一个客户端,所以终于搞出了一个能运行的代码。
这么运行!:
此时我的全部目的就是能运行,所以五则运算这里的逻辑是直接传了字符串(简单回声实验里面传的是字符串,所以只会传字符串了)
但还有一个问题就是简单回声的代码我没有办法正确connect,此时的搜索包括不限于:
发现问题后,
就诞生了第一版抽象,但是勉强正确运行的代码:
服务器:
#include<stdio.h> #include<unistd.h> #include<stdint.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<signal.h> #include<stdlib.h> #include<sys/types.h> int sigint_flag = 0; void handle_sigint(int sig){ printf("[srv] SIGINT is coming!\n"); sigint_flag = 1; } void usage(const char *str) { printf("%s usage:<IP><PORT><veri_code>\n", str); } void srv_biz(int connfd); int main(int argc,char **argv){ if(argc != 3){ usage(argv[0]); return 1; } struct sigaction sa; sa.sa_flags = 0; sa.sa_handler = handle_sigint; sigemptyset(&sa.sa_mask); sigaction(SIGINT,&sa,NULL); char ip_adress[20]; strcpy(ip_adress,argv[1]); int port = atoi(argv[2]); int listenfd = socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in serv_addr,cli_addr; memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充 memset(&cli_addr, 0, sizeof(cli_addr)); serv_addr.sin_family = AF_INET; //使用IPv4地址 serv_addr.sin_addr.s_addr = inet_addr(argv[1]); //具体的IP地址 serv_addr.sin_port = htons(port); socklen_t clilen; char cli_ip[1024]; printf("[srv] server [%s:%d] is initializing!\n",ip_adress,port); int chec=0; chec=bind(listenfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)); if(chec<0){ perror("bind error"); return 1; } chec=listen(listenfd,10); if(chec<0){ perror("listen error"); return 1; } while(!sigint_flag){ int clilen = sizeof(cli_addr); int connfd = accept(listenfd,(struct sockaddr*) &cli_addr,&clilen); if(connfd<0){ if(errno == EINTR) continue; else perror("accept error"); return 1; //print error exit; } inet_ntop(AF_INET,&(cli_addr.sin_addr),cli_ip,sizeof(cli_ip)); int cli_port=ntohs(cli_addr.sin_port); printf("[srv] client[%s:%d] is accepted!\n",cli_ip,cli_port); srv_biz(connfd); printf("[srv] client[%s:%d] is closed!\n",cli_ip,cli_port); close(connfd); } close(listenfd); printf("[srv] listenfd is closed!"); printf("[srv] server is going to exit!"); return 0; } void srv_biz(int connfd){ char message[1024]={0}; char ret_message[1024]={0}; while(1){ memset(message,0,140); ssize_t size_read = read(connfd,message,sizeof(message)-1); if(size_read <= 0||strncmp(message,"EXIT",4)==0){ return ; } int i=4,a=0,b=0,sum=0,j=0; while(message[i]<='9'&&message[i]>='1'){ ret_message[j]=message[i]; a=a*10+message[i]-'0'; i++; j++; } i++; ret_message[j]=32; j++; if(strncmp(message,"ADD",3)==0){ ret_message[j]='+';} if(strncmp(message,"MUL",3)==0){ ret_message[j]='*';} if(strncmp(message,"MOD",3)==0){ ret_message[j]='%';} if(strncmp(message,"DIV",3)==0){ ret_message[j]='/';} if(strncmp(message,"SUB",3)==0){ ret_message[j]='-';} j++; ret_message[j]=32; j++; while(message[i]<='9'&&message[i]>='1'){ ret_message[j]=message[i]; b=b*10+message[i]-'0'; i++; j++; } //printf("%d,%d\n",a,b); if(strncmp(message,"ADD",3)==0){ sum=a+b;} if(strncmp(message,"MUL",3)==0){ sum=a*b;} if(strncmp(message,"MOD",3)==0){ sum=a%b;} if(strncmp(message,"DIV",3)==0){ sum=a/b;} if(strncmp(message,"SUB",3)==0){ sum=a-b;} ret_message[j]=32; j++; char string[32]={0}; // while(sum!=0){ //itoa(sum, string, 10); sprintf(string,"%d",sum); strcat(ret_message,string); ret_message[strlen(ret_message)] = '\0'; //puts(ret_message); ssize_t size_write = write(connfd,ret_message,strlen(ret_message)+2); }
客户端:
#include<stdio.h> #include<unistd.h> #include<stdint.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<signal.h> #include<stdlib.h> #include<sys/types.h> void cli_biz(int connfd); int main(int argc,char *argv[]){ char ip_str[20] = {0}; int port = atoi(argv[2]); int connfd = socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in serv_addr; serv_addr.sin_family = AF_INET; //使用IPv4地址 serv_addr.sin_addr.s_addr = inet_addr(argv[1]); //具体的IP地址 serv_addr.sin_port = htons(port); if(connect(connfd,(struct sockaddr*) &serv_addr,sizeof(serv_addr))<0){ perror("connect failed"); return 1; } else { inet_ntop(AF_INET, &serv_addr.sin_addr, ip_str, sizeof(ip_str)); printf("[cli] server[%s:%d] is connected\n", ip_str , ntohs(serv_addr.sin_port)) ; } cli_biz(connfd); close(connfd); return 0; } void cli_biz(int connfd){ char message[1024]; while(1){ //获取用户输入 fgets(message,sizeof(message),stdin); //如果输入EXIT/SIGINT,退出 if(strcmp(message,"EXIT\n")==0) return ; if(strcmp(message,"SIGINT\n")==0) return ; //解析输入数据,执行业务逻辑 //发送+接收+解析自定义应用层协议PDU ssize_t size_write = write(connfd,message,strlen(message)); ssize_t size_read = read(connfd,message,sizeof(message)-1); printf("[rep_rcv] %s\n",message); } }
二、正确完成报文建立和传输
这里要求是共20字节报文传输,4字节数字代表操作符,两个8字节的操作数字。
所以,本人又经过了使用数组传递,很成功的运行出了结果,但是,并不符合要求!伟大的舍友聪明的舍友已经满分,并提示我要用结构体传输!(永远的神)
所以这时目的就变成了使用read和write传一个20字节的结构体,这里需要用到的搜索包括但不限于:
这里共有几个问题:
1.结构体怎么传,read函数原型里面中间的缓冲区是void * buf,直接写read(connf,buf,sizeof(buf))(buf为结构体,分别含int32_t op,int64_t a,b)会报错,这里要在buf前加个“&”,不能直接传
2.结构体对齐问题,结构体存储会对齐,导致这里其实sizeof(buf)是24,(这里如果不懂可以搜索下原因),但是这样子不合题意,所以要取消结构体自动对齐(这个也可以自行搜索取消对齐的办法)
3.字节序转换,注意正确使用字节序转换函数(多少位用多少的转换函数!!!!建议搜索16、32、64位字节转换函数并选择合适函数)
所以到此会有更为成功的一版代码:
核心部分:(更符合要求)
void cli_biz(int connfd){ while(1){ char message[1024]=""; struct mess buf={0,0,0}; struct mess buff={0,0,0}; //获取用户输入 scanf("%s",message); //如果输入EXIT/SIGINT,退出 if(strcmp(message,"EXIT")==0){ printf("[cli] command EIXT received\n"); return ; } if(strcmp(message,"SIGINT")==0) return ; //解析输入数据,执行业务逻辑 int64_t rebuf=0; scanf("%ld%ld",&buf.a,&buf.b); //int64_t buf[4]={0},rebuf[4]={0}; //if(strncmp(message,"EXIT",4) == 0){ buf[0]=0; } //else{ if(strncmp(message,"ADD",3) == 0){ fu='+';buf.op=0x1; } if(strncmp(message,"MUL",3) == 0){ fu='*';buf.op=0x4; } if(strncmp(message,"MOD",3) == 0){ fu='%';buf.op=0x10;} if(strncmp(message,"DIV",3) == 0){ fu='/';buf.op=0x8; } if(strncmp(message,"SUB",3) == 0){ fu='-';buf.op=0x2; } // while(message[i]<='9'&&message[i]>='0'){ // buf[1]=buf[1]*10+message[i]-'0'; // i++; // } // i++; // while(message[i]<='9'&&message[i]>='0'){ // buf[2]=buf[2]*10+message[i]-'0'; // i++; // } //} //发送+接收+解析自定义应用层协议PDU //printf("[rep_rcv] %ld %ld %ld\n",buf[1],buf[2],buf[0]); buff.op=htonl(buf.op); buff.a=htobe64(buf.a); buff.b=htobe64(buf.b); ssize_t size_write = write(connfd,&buff,sizeof(buff)); //printf("%ld",sizeof(buf)); ssize_t size_read = read(connfd,&rebuf,sizeof(rebuf)); rebuf=be64toh(rebuf); printf("[rep_rcv] %ld %c %ld = %ld\n",buf.a,fu,buf.b,rebuf); } }
void srv_biz(int connfd){ while(1){ struct mess buf={0,0,0}; int64_t rebuf=0; ssize_t size_read = read(connfd,&buf,sizeof(buf)); if(size_read <= 0){ return ; } //printf("%ld\n",sizeof(buf)); buf.op=ntohl(buf.op); buf.a=be64toh(buf.a); buf.b=be64toh(buf.b); if(buf.op==0x1){ fu='+';rebuf=buf.a+buf.b;} if(buf.op==0x2){ fu='-';rebuf=buf.a-buf.b;} if(buf.op==0x4){ fu='*';rebuf=buf.a*buf.b;} if(buf.op==0x8){ fu='/';rebuf=buf.a/buf.b;} if(buf.op==0x10){ fu='%';rebuf=buf.a%buf.b;} printf("[rqt_res] %ld %c %ld = %ld\n",buf.a,fu,buf.b,rebuf); rebuf=htobe64(rebuf); //printf("%ld\n",sizeof(rebuf)); ssize_t size_write = write(connfd,&rebuf,sizeof(rebuf)); } }
由于现在(2024/6/27)作业还没截止,所以这个总结先存个草稿,等下次想起来再发。