Day07_网络编程20250721(网络编程考试试卷)
网络编程考试试卷
澎乐多航运科技
一、选择题(每题2分,共20分)
1.以下IP地址中,属于c类地址的是(C)
A、 112.213.12.23
B、156.123.32.12
C、23.123.213.23
D、210.123.23.12
2.在UDP协议中,客户端调用connect()函数的作用是:(A)A.将ip和pont 写入内核
B.发起与服务器的连接
C.监听端准口
D.接受连接请求
3.以下哪个子网掩码是错误的(C)
A、255.255.255.0B、255.255.240.0
C、255.255.240.255
D、255.255.255.224
4.设置套接字选项SOREUSEADDR的主要目的是:(A)
A.允许地址和端口立即重用B.设置接收超时时间
C.启用广播功能
D.调整发送缓冲区大小
5.在网络中,inta=10是以以下哪种形式进行存储的(C)
A、0000 0000 1010 0000
B、0000 1010 0000 0000
C、0000 0000 0000 1010
D、1010 0000 0000 0000
6.有一些端口号是保留给常用的服务器应用程序的,也被称为熟知端口,其范围是(B)
A、0~1024
B、0~1023
C、1~1024
D、1~1023
7.关于 modbus 协议,以下哪种说法不正确(B)A、读普通线圈的功能码是01
B、读多个线圈的功能码是02(离散线圈,没有多个线圈这个概念)
C、modbus 协议签第 2~3 个字节表示 modbus 协议的标识符D、modbus协议中,那些一个字节的区域不需要使用htons进行转换
8.以下关于 modbus 的说法,哪一个是正确的()
A、写单个线圈的时候,协议长度固定为6B、写单个线圈的时候,线圈值为htons(0xff00)--->htons(0x00ff),大端存储
C、modbus协议标识符固定为0xff00--->0x0000
D、写线圈和写寄存器的时候,都需要提前准备请求体和响应体--->写线圈不需要提前准备响应体
9.以下哪个行为可能触发 SIGPIPE?(A)
A.客户端断开连接后继续写入数据
B.服务器绑定端口失败
C.接收缓冲区溢出
D.套接字未设置为非阻塞
10.以下哪个结构体是用来接入udp广播组时候,setsockopt 函数需要用到的结构体(C)
A、struct sockaddr in;
B、struct sockaddr;
C、struct ip mreqn
D、struct sockaddr_un
二、简答题(每题10分,共60分)1、有如下UDP单对单通信发送端模型代码,有3处错误,请指出并修改
------------------------------------------------------------------------------------------------------------------------
答案:
int sender=socket()//参数省略 struit Sockodd_instruct sockaddr_un addr ={0}; addr.sin_family=AF_UNIX; addr.sin port = htons(8888); addr.sin addr.s_addr= 0; char* str = “hello"; send(sender,str,6,0);
//方法一: int sender=socket()//参数省略 struit Sockodd_instruct sockaddr_in addr ={0}; addr.sin_family=AF_INET; addr.sin port = htons(8888); addr.sin addr.s_addr= 0; char* str = “hello"; sendto(sender,str,6,0);//方法二 int sender=socket()//参数省略 struit Sockodd_instruct sockaddr_in addr ={0}; addr.sin_family=AF_INET; addr.sin port = htons(8888); addr.sin addr.s_addr= 0; if(connect(sender,(struct sockaddr*)&addr,sizeof(addr)) == -1){perror("bind"); } char* str = “hello"; send(sender,str,6,0);
2、请详细描述 select、poll、epoll 模型的优缺点------------------------------------------------------------------------------------------------------------------------
答案:
01.select优点:
简单好用,直观
02.select缺点:
缺点:
1:监视列表 fd_set 类型 其本质是一个 栈空间数组
这就意味着,fd_set 监视列表能够监视的描述符,存在上限且无法动态扩容
2:select 还将数组包装成结构体,并且提供操作数组的数组的函数
这就导致,用户本身无法利用该数组来记录一些必要描述符
所以,用户需要自己定义一个数组去记录一些必要的描述符
然后判断这些必要的描述符是否激活的时候,就会造成双重循环,程序运行效率低
3:select的激活列表会覆盖监视列表,所以用户还需要额外准备一个激活列表,并且每次重新使用监视列表去赋值导致更多的多余操作以及降低程序的运行效率03.poll优点:
改进了select的几个缺点
1:虽然监视列表和激活列表还是同一张
但是不会覆盖了。所以用户无需额外操作,也无需反复大量赋值,浪费系统资源
2:select监视列表是一个无法扩容的栈空间数组
poll模型的监视列表很灵活,不想扩容,就写成栈空间数组。想扩容,就写成堆空间的动态数组
04.poll缺点:
poll模型的监视列表,虽然能扩容了,但是由于是一个数组,每次监视到有描述符激活的时候,需要从头遍历,修改监视列表里面的revents在查询激活的描述符的效率方面,poll模型还是比较低的,epoll模型就是效率最高的
05.epoll优点:
监视列表是二叉树,查询效率特别高,并且能够自动扩容
并且由于监视列表是文件实现的,所以能够监视的描述数量理论上是无限的
select 模型:一个固定值
poll模型:监视列表虽然可扩容,但是由于监视列表存在于代码中,允许监视的描述符数量,取决于系统允许的描述符上限
激活列表是额外的一个数组,和监视列表天然分离
epoll 额外存在边缘触发模式
06.epoll缺点:
比 select 和 poll 模型复杂,学习成本高
------------------------------------------------------------------------------------------------------------------------
3、请详细描述TCP和UDP的区别?------------------------------------------------------------------------------------------------------------------------
答案:
tcp 是一个可靠的,基于连接的,字节流协议
tcp 拥有流量控制功能,顺序控制功能,应答重发功能,以保证在网络不拥堵的时候,所有数据都能正确发送
udp协议由于非连接,没有可靠的应答手段
所以 udp协议传输效率高于tcp协议,传输的稳定性低于tcp协议
udp协议容易丢包,但是速度是真的快
------------------------------------------------------------------------------------------------------------------------
4、请详细描述,什么是心跳包,以及详细介绍心跳包的编写逻辑流程------------------------------------------------------------------------------------------------------------------------
答案:
心跳包:udp服务器客户端,检测客户端是否断连
1:发送端开启一个线程
线程里面每隔 1 秒向服务器发送一个心跳包
2:服务器接收到每一个客户端的心跳包之后,需要在后台记录以下
记录:发送心跳包的客户端的 ip + port
记录这个目的:服务器能够准确的区分出,当前心跳包到底是哪个客户端发的
记录:发送这个心跳包的时间戳记录这些东西,使用一个数组或者链表即可
注意:
一开始:服务器数组中肯定是没有任何数据
当接受到一个客户端发来心跳包的时候,要做以下2件事情
1:判断这个客户端是否是第一次发来心跳包
2:如果是第一次发来心跳包,将该客户端的 ip + port + 当前系统时间戳 存入数组中
3:如果不是第一次发来心跳包,根据该心跳包的 ip 和 port 在数组中找到对应的客户端,更新其保存的时间戳即可3:服务器每隔一段时间,检测数组中所有客户端的最后一次心跳包的时间
------------------------------------------------------------------------------------------------------------------------
5、请详细描述TCP 三次握手与四次挥手------------------------------------------------------------------------------------------------------------------------
答案:
01.三次握手的过程:
第一次握手:
客户端向服务器发送了一个 SYN 握手包
此时 Seq = 0 ,Len = 0
说明:客户端向服务器发送数据的时候,从第0个字节开始发送,数据长度为0
此次握手过后,没有验证任何事情
第二次握手:
服务器向客户端发送了一个 SYN 握手包 和一个 ACK应答包
SYN 握手包中
Seq = 0,Len = 0
说明服务器也要像客户端发送数据,从第0个字节开始发送,数据长度为0
ACK应答包中
Ack = 1
说明,服务器接受到了客户端发来的0个字节的数据,要求客户端下次从第1个字节开始发送数据
此次握手验证了2件事情:
1:客户端的数据发送能力正常
2:服务器的数据接受能力正常
第三次握手
客户端向服务器发送的一个 ACK应答包
其中:
Seq = 1
因为上一次握手里面,服务器要求客户端本次发送数据从第1个字节开始发,所以客户端的Seq是1
Len = 0
说明从第1个字节开始发送数据,数据长度为0
Ack = 1
说明:客户端同样的要求服务器下次发送数据的时候,从第1个字节开始发送
此次握手验证了2件事情:
1:服务器的数据发送能力正常
2:客户端的数据接受能力正常
第一次握手:
客户端向服务器发送了一个 SYN 握手包
此时 Seq = 0 ,Len = 0
说明:客户端向服务器发送数据的时候,从第0个字节开始发送,数据长度为0
此次握手过后,没有验证任何事情
第二次握手:
服务器向客户端发送了一个 SYN 握手包 和一个 ACK应答包
SYN 握手包中
Seq = 0,Len = 0
说明服务器也要像客户端发送数据,从第0个字节开始发送,数据长度为0
ACK应答包中
Ack = 1
说明,服务器接受到了客户端发来的0个字节的数据,要求客户端下次从第1个字节开始发送数据
此次握手验证了2件事情:
1:客户端的数据发送能力正常
2:服务器的数据接受能力正常
第三次握手
客户端向服务器发送的一个 ACK应答包
其中:
Seq = 1
因为上一次握手里面,服务器要求客户端本次发送数据从第1个字节开始发,所以客户端的Seq是1
Len = 0
说明从第1个字节开始发送数据,数据长度为0
Ack = 1
说明:客户端同样的要求服务器下次发送数据的时候,从第1个字节开始发送
此次握手验证了2件事情:
1:服务器的数据发送能力正常
2:客户端的数据接受能力正常
4 四次挥手
第一次挥手
主动断开方向被动方发送一个 FIN 挥手包,请求断开链接
Seq = 1,Ack = 1,Len = 0
表明:自己从第1个字节发送数据,数据长度是0,并要求对方下次发送数据的时候,从第1个字节开始发送
第二次挥手
被动断开放接收挥手包之后,立刻回复了一个 ACK 应答包
注意:此时只回了一个Ack应答包,并没有回复 FIN 挥手包
表明:被动方此时不允许断开链接,仅仅是通知主动方,我明白你要断开链接,但是此时还有数据在传输中(即使没有数据),不能断开链接第三次挥手
当所有数据传输完毕之后,被动方才回复了一个 FIN 挥手包和 ACK应答包
表示现在数据传输完毕了,允许断开链接第四次挥手
当主动方接收到被动方的FIN挥手包之后
立刻明白,此时是时候断开链接了
立刻回复了 Ack 应答包,表明收到,正式断开链接------------------------------------------------------------------------------------------------------------------------
6、请详细描述 TCP 的粘包现象以及原因------------------------------------------------------------------------------------------------------------------------
答案:
tcp协议为了提高数据的发送效率
会将短时间内, 连续发送的数据,粘连在一起,一并发送
因为:tcp底层有一个 1500字节的缓存区,tcp每次发送数据,其实就是固定发送了1500个字节
这就导致2个现象:
1:为了提高效率,短时间内连续发送的 <1500字节的数据,会被粘连在一起,粘在一个1500字节的包中进行发送
2:如果粘连的数据总大小 > 1500 之后,超出1500的部分会被分散在后一个 1500中发送,这就会造成总有数据会被阶段,我们把这种线程成为 分包或者拆包------------------------------------------------------------------------------------------------------------------------
三、编程题(每题10分,共20分)
1、请使用UDP完成一个广播的代码07_broadcast_send
#include <25051head.h> int main(int argc, const char *argv[]) {if(argc<2){printf("请输入端口号:\n");return 1;}short port=atoi(argv[1]);int sender=socket(AF_INET,SOCK_DGRAM,0);struct sockaddr_in addr={0};addr.sin_family=AF_INET;addr.sin_addr.s_addr=inet_addr("192.168.141.255");addr.sin_port=htons(port);int is_broadcast=1;//允许广播1,不允许广播为0setsockopt(sender,SOL_SOCKET,SO_BROADCAST,&is_broadcast,4);//udp协议中的connect不是真正的建立连接//而是将目标ip和port写入内核//内核中一旦有目标ip和port,就可以不用sendto了,write也可以if(connect(sender,(struct sockaddr*)&addr,sizeof(addr))==-1){perror("connect_error0");}//用epoll模型发送和接收消息//01.创建epoll模型int epfd=epoll_create1(EPOLL_CLOEXEC);struct epoll_event epoll_sender={.events=EPOLLIN,.data.fd=sender};struct epoll_event epoll_stdin={.events=EPOLLIN,.data.fd=0};epoll_ctl(epfd,EPOLL_CTL_ADD,sender,&epoll_sender);epoll_ctl(epfd,EPOLL_CTL_ADD,0,&epoll_stdin);int eplen=2;#if 0int is_reuseaddr=1;setsockopt(sender,SOL_SOCKET,SO_REUSEADDR,&is_reuseaddr,4); #endifwhile(1){struct epoll_event list[20]={0};int count=epoll_wait(epfd,list,eplen,-1);for(int i=0;i<count;i++){int fd=list[i].data.fd;if(fd==0){char buf[128]="";printf("请输入:");scanf("%s",buf);getchar();//sendto(sender,buf,strlen(buf),0,(struct sockaddr*)&addr,sizeof(addr));send(sender,buf,strlen(buf),0);//write(sender,buf,strlen(buf));continue;}if(fd==sender){char buf[128]="";recv(sender,buf,128,0);printf("接收到消息:%s\n",buf);}}}return 0; }
08_broadcast_reciver
#include <25051head.h> int main(int argc, const char *argv[]) {if(argc<2){printf("请输入端口号:\n");return 1;}short port=atoi(argv[1]);int reciver=socket(AF_INET,SOCK_DGRAM,0);struct sockaddr_in addr={0};addr.sin_family=AF_INET;addr.sin_addr.s_addr=inet_addr("0.0.0.0");addr.sin_port=htons(port);if(bind(reciver,(struct sockaddr*)&addr,sizeof(addr))==-1){perror("bind_error");return 1;}while(1){//准备一个套接字结构体,用来存放发送端(客户端)ip和portstruct sockaddr_in sender_addr={0};int sender_len=sizeof(sender_addr);char buf[128]="";//read(reciver,buf,128);recvfrom(reciver,buf,128,0,(struct sockaddr*)&sender_addr,&sender_len);printf("读取到udp的消息:%s\n",buf);//新需求:要求接收端发送给发送端回复消息sendto(reciver,buf,strlen(buf),0,(struct sockaddr*)&sender_addr,sender_len);}return 0; }
25051head.h
#ifndef __25051HED_H__ #define __25051HED_H__ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <ctype.h> #include <sys/types.h>//引入open函数 #include <sys/stat.h>//stat lstat: #define _GNU_SOURCE #include <fcntl.h> //dup3 #include <unistd.h>//引入 getdtablesize函数,获取文件描述符个数,包含close函数 dup,dup2 #include <time.h> #include <sys/wait.h> #include <pthread.h>//引入线程 #include <semaphore.h>//引入信号量 #include <signal.h>//引入管道通信信号 #include <sys/ipc.h>//ftok shmget #include <sys/msg.h>//msgget #include <sys/shm.h>//引入共享内存的 #include <sys/sem.h>//semget引入信号灯集#include <arpa/inet.h>//大端转换函数--->网络编程 #include <sys/socket.h> #include <netinet/in.h> #include <sys/socket.h>//引入套接字#include <sys/select.h> #include <poll.h> #include <sys/epoll.h> #include <sqlite3.h>//引入sqlite3数据库int P(int sem_num,int sem_op,int semid); int V(int sem_num,int sem_op,int semid);#if 0 typedef struct sockaddr_in addr_in_t; typedef struct sockaddr addr_t; typedef struct sockaddr_un addr_un_t; #endif#define ERRLOG(msg) do{printf("__%d__",__LINE__);fflush(stdout);perror(msg);return -1;}while(0) #endif
2、编写一个TCP并发服务器,要求使用EPOLL模型完成方法一:
07_epoll_server#include <25051head.h> #define BUFFER_SIZE 1024 //处理客户端要求 void handle_client_request(int client_fd,sqlite3* db) {char buffer[BUFFER_SIZE]={0};ssize_t bytes_received=read(client_fd,buffer,BUFFER_SIZE-1);if(bytes_received<=0){printf("客户端断开连接..\n");//移除客户端描述符return;}char action[10],name[100],pswd[100];sscanf(buffer,"%s %s %s",action,name,pswd);if(strcmp(action,"register")==0){char sql[256]="";snprintf(sql,sizeof(sql),"insert into user_info(name,pswd) values('%s','%s')",name,pswd);char* err_msg=NULL;int rc=sqlite3_exec(db,sql,0,0,&err_msg);if(rc!=SQLITE_OK){send(client_fd,"该账号已存在",strlen("该账号已存在"),0);sqlite3_free(err_msg);}else{send(client_fd,"注册成功",strlen("注册成功"),0);}}else if(strcmp(action,"login")==0){char sql[256]="";snprintf(sql,sizeof(sql),"SELECT pswd from user_info where name='%s'",name);sqlite3_stmt *stmt;int rc=sqlite3_prepare_v2(db,sql,-1,&stmt,NULL);if(rc==SQLITE_OK){if(sqlite3_step(stmt)==SQLITE_ROW){const unsigned char* stored_pswd=sqlite3_column_text(stmt,0);if(strcmp((const char*)stored_pswd,pswd)==0){send(client_fd,"登录成功",strlen("登录成功"),0);}else{send(client_fd,"该账号不存在",strlen("该账号不存在"),0);}sqlite3_finalize(stmt);}}} }int main(int argc, const char *argv[]) {if(argc<2){printf("请输入端口号:\n");return 1;}short port=atoi(argv[1]);//"abc123"--->0int server=socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr={0};addr.sin_family=AF_INET;addr.sin_port=htons(port);addr.sin_addr.s_addr=inet_addr("0.0.0.0");if(bind(server,(struct sockaddr*)&addr,sizeof(addr))==-1){perror("bind");return 1;}listen(server,10);//01.打开数据库sqlite3 *db=NULL;char* err_msg=NULL;int rc=sqlite3_open("./user.db",&db);if(rc!=SQLITE_OK){fprintf(stderr,"sqlite3_open_error%s\n",sqlite3_errmsg(db));sqlite3_close(db);return 1;}//02.创建table表char* sql="create table if not exists user_info(name text primary key,pswd text not null);";rc=sqlite3_exec(db,sql,0,0,&err_msg);if(rc!=SQLITE_OK){fprintf(stderr,"sql_create_error:%s\n",err_msg);sqlite3_free(err_msg);sqlite3_close(db);close(server);return 1;}//03.准备一个epoll模型的监视列表int epfd=epoll_create1(EPOLL_CLOEXEC);//准备一下想要监视的描述符:目前来说,只需要监视0和serverstruct epoll_event epoll_stdin={.events=EPOLLIN,.data.fd=0};struct epoll_event epoll_server={.events=EPOLLIN,.data.fd=server};epoll_ctl(epfd,EPOLL_CTL_ADD,0,&epoll_stdin);epoll_ctl(epfd,EPOLL_CTL_ADD,server,&epoll_server);int eplen=2;while(1){//提前准备一个激活列表struct epoll_event list[20]={0};int count=epoll_wait(epfd,list,eplen,-1);for(int i=0;i<count;i++){//list里面全都是激活的描述符,最多判断一下,以何种方式激活//将激活的描述符单独取出int fd=list[i].data.fd;if(fd==0){char buf[1024]="";scanf("%s",buf);getchar();printf("键盘输入数据:%s\n",buf);continue;}if(fd==server){printf("有客户端连接..\n");struct sockaddr_in client_addr={0};int client_len=sizeof(client_addr);int client=accept(server,(struct sockaddr*)&client_addr,&client_len);printf("新连接的客户端ip=%s\n",inet_ntoa(client_addr.sin_addr));printf("新连接的客户端port=%d\n",ntohs(client_addr.sin_port));struct epoll_event epoll_client={.events=EPOLLIN,.data.fd=client};epoll_ctl(epfd,EPOLL_CTL_ADD,client,&epoll_client);eplen++;continue;}//剩下的都是客户端描述符handle_client_request(fd,db);}}//04.关闭数据库sqlite3_close(db);return 0; }
08_pthread_client
#include <25051head.h>#define BUFFER_SIZE 1024int client_fd;// 接收服务器消息的线程函数 void *receive_messages(void *arg) {char buffer[BUFFER_SIZE];while (1) {ssize_t bytes_received = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);if (bytes_received <= 0) {// 服务器断开连接printf("与服务器的连接已断开。\n");close(client_fd);exit(EXIT_SUCCESS);}buffer[bytes_received] = '\0';printf("%s\n", buffer);}return NULL; }// 发送用户请求的线程函数 void *send_requests(void *arg) {char action[10], name[100], pswd[100];char buffer[BUFFER_SIZE];while (1) {printf("1: 注册 2: 登录 3: 退出\n");int choice;scanf("%d", &choice);getchar(); // 消耗换行符if (choice == 3) {close(client_fd);exit(EXIT_SUCCESS);}printf("请输入账号: ");fgets(name, sizeof(name), stdin);name[strcspn(name, "\n")] = 0;printf("请输入密码: ");fgets(pswd, sizeof(pswd), stdin);pswd[strcspn(pswd, "\n")] = 0;if (choice == 1) {snprintf(action, sizeof(action), "register");} else if (choice == 2) {snprintf(action, sizeof(action), "login");}snprintf(buffer, sizeof(buffer), "%s %s %s", action, name, pswd);send(client_fd, buffer, strlen(buffer), 0);}return NULL; }int main(int argc, char *argv[]) {if (argc != 3) {printf("用法: %s <服务器IP> <服务器端口>\n", argv[0]);return 1;}// 创建 socketclient_fd = socket(AF_INET, SOCK_STREAM, 0);if (client_fd == -1) {perror("socket");return 1;}// 初始化服务器地址struct sockaddr_in server_addr = {0};server_addr.sin_family = AF_INET;server_addr.sin_port = htons(atoi(argv[2]));server_addr.sin_addr.s_addr = inet_addr(argv[1]);// 连接服务器if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("connect");close(client_fd);return 1;}// 创建接收消息和发送请求的线程pthread_t recv_thread, send_thread;if (pthread_create(&recv_thread, NULL, receive_messages, NULL) != 0) {perror("pthread_create: receive_messages");close(client_fd);return 1;}if (pthread_create(&send_thread, NULL, send_requests, NULL) != 0) {perror("pthread_create: send_requests");close(client_fd);return 1;}// 等待线程结束pthread_join(recv_thread, NULL);pthread_join(send_thread, NULL);return 0; }
老师的代码:
01_epoll_server
#include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <pthread.h> #include <semaphore.h> #include <wait.h> #include <signal.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/ipc.h> #include <sys/sem.h> #include <semaphore.h> #include <sys/msg.h> #include <sys/shm.h> #include <sys/un.h> #include <sys/epoll.h> #include <sqlite3.h>typedef struct sockaddr_in addr_in_t; typedef struct sockaddr addr_t; typedef struct sockaddr_un addr_un_t;enum Type{TYPE_REGIST,TYPE_LOGIN };enum Err{SUCCESS,ERR_NAME,ERR_PSWD };typedef struct Pack{enum Type type;enum Err err;char name[16];char pswd[16]; }pack_t;void regist(pack_t pack,int client,sqlite3* db){sqlite3_stmt* stmt = NULL;char* errmsg = NULL;char* sql = "insert into users(name,pswd) values(?,?)";sqlite3_prepare_v2(db,sql,-1,&stmt,NULL);sqlite3_bind_text(stmt,1,pack.name,-1,NULL);sqlite3_bind_text(stmt,2,pack.pswd,-1,NULL);int res = sqlite3_step(stmt);if(res == SQLITE_DONE){// 注册成功pack.err = SUCCESS;}else{// 注册失败pack.err = ERR_NAME;}//pack.type = TYPE_REGIST;write(client,&pack,sizeof(pack));}void login(pack_t pack,int client,sqlite3* db){sqlite3_stmt* stmt = NULL;char* errmsg = NULL;char* sql = "select pswd from users where name = ?";sqlite3_prepare_v2(db,sql,-1,&stmt,NULL); sqlite3_bind_text(stmt,1,pack.name,-1,NULL);int res = sqlite3_step(stmt);if(res == SQLITE_DONE){pack.err = ERR_NAME; }else{const char* db_pswd = sqlite3_column_text(stmt,0);if(strcmp(db_pswd,pack.pswd) == 0){pack.err = SUCCESS;}else{pack.err = ERR_PSWD;}}write(client,&pack,sizeof(pack)); }int main(int argc, const char *argv[]) {//printf("%d\n",__FD_SETSIZE / __NFDBITS);if(argc < 2){printf("请输入端口号\n");return 1;}short port = atoi(argv[1]);// "abc123" -> 0int server = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET; addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr("0.0.0.0");if(bind(server,(struct sockaddr*)&addr,sizeof(addr)) == -1){perror("bind");return 1;}listen(server,10);//01.打开数据库sqlite3 *db=NULL;char* err_msg=NULL;int rc=sqlite3_open("./user.db",&db);if(rc!=SQLITE_OK){fprintf(stderr,"sqlite3_open_error%s\n",sqlite3_errmsg(db));sqlite3_close(db);return 1;}//02.创建table表char* sql="create table if not exists users(name text primary key,pswd text not null);";rc=sqlite3_exec(db,sql,0,0,&err_msg);if(rc!=SQLITE_OK){fprintf(stderr,"sql_create_error:%s\n",err_msg);sqlite3_free(err_msg);sqlite3_close(db);close(server);return 1;}// 创建epoll监视列表int epfd = epoll_create1(EPOLL_CLOEXEC);// 将 0 和 server 添加进入监视列表//struct epoll_event epoll_stdin = {.events = EPOLLIN ,data:{fd:server}};struct epoll_event epoll_stdin = {.events = EPOLLIN , .data.fd = 0};struct epoll_event epoll_server = {.events = EPOLLIN , .data.fd = server};epoll_ctl(epfd,EPOLL_CTL_ADD,0,&epoll_stdin);epoll_ctl(epfd,EPOLL_CTL_ADD,server,&epoll_server);int eplen = 2;while(1){// 提前准备一个激活列表struct epoll_event list[20] = {0};int count = epoll_wait(epfd,list,eplen,-1);for(int i=0;i<count;i++){// list里面全都是激活的描述符,最多判断一下 ,以何种方式激活的// 将激活的具体描述符单独取出int fd = list[i].data.fd;if(fd == 0){char buf[1024] = "";scanf("%s",buf);getchar();printf("键盘输入数据:%s\n",buf);continue;}if(fd == server){printf("有客户端连接\n");struct sockaddr_in client_addr = {0};int client_len = sizeof(client_addr);int client = accept(server,(struct sockaddr*)&client_addr,&client_len);printf("新连接的客户端的ip = %s\n",inet_ntoa(client_addr.sin_addr));printf("新连接的客户端的port = %d\n",ntohs(client_addr.sin_port));struct epoll_event epoll_client = {.events = EPOLLIN | EPOLLET , .data.fd = client};epoll_ctl(epfd,EPOLL_CTL_ADD,client,&epoll_client);eplen ++;continue;}// 剩下的都是客户端描述符pack_t pack = {0};int res = read(fd,&pack,sizeof(pack));if(res == 0){printf("客户端断开连接\n");epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);continue;}switch(pack.type){case TYPE_REGIST:{regist(pack,fd,db);break;}case TYPE_LOGIN:{login(pack,fd,db);break;}}}}return 0; }
02_client.c
#include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <pthread.h> #include <semaphore.h> #include <wait.h> #include <signal.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/ipc.h> #include <sys/sem.h> #include <semaphore.h> #include <sys/msg.h> #include <sys/shm.h> #include <sys/un.h>typedef struct sockaddr_in addr_in_t; typedef struct sockaddr addr_t; typedef struct sockaddr_un addr_un_t;enum Type{TYPE_REGIST,TYPE_LOGIN };enum Err{SUCCESS,ERR_NAME,ERR_PSWD };typedef struct Pack{enum Type type;enum Err err;char name[16];char pswd[16]; }pack_t;void* thread_main(void* arg){int client = *(int*)arg;pack_t pack = {0};while(1){read(client,&pack,sizeof(pack));switch(pack.type){case TYPE_REGIST:{if(pack.err == SUCCESS){printf("\n注册成功\n");}else{printf("\n该账号已存在\n");}break;}case TYPE_LOGIN:{if(pack.err == SUCCESS){printf("\n登录成功\n");}else if(pack.err == ERR_NAME){printf("\n该账号不存在\n");}else{printf("\n密码错误\n");}break;}}} }int main(int argc, const char *argv[]) {if(argc < 2){printf("请输入端口号\n");return 1;}short port = atoi(argv[1]);// atoi 函数,将字符串类型转换成整形int client = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET; addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr("127.0.0.1");connect(client,(struct sockaddr*)&addr,sizeof(addr));pthread_t id;pthread_create(&id,0,thread_main,&client);pthread_detach(id); // 客户端发送账号密码给服务器,实现注册/登录操作int ch = 0;while(1){pack_t pack = {0};printf("1:注册\n");printf("2:登录\n");printf("请选择:");scanf("%d",&ch);getchar();switch(ch){case 1:{printf("请输入账号:");scanf("%s",pack.name);getchar();printf("请输入密码:");scanf("%s",pack.pswd);getchar();pack.type = TYPE_REGIST;write(client,&pack,sizeof(pack));break;}case 2:{printf("请输入账号:");scanf("%s",pack.name);getchar();printf("请输入密码:");scanf("%s",pack.pswd);getchar();pack.type = TYPE_LOGIN;write(client,&pack,sizeof(pack));break;}default:{break;}}}return 0; }