当前位置: 首页 > article >正文

Linux-c 网络socket练习1

要求

1、基于tcp/ip,自定义通讯协议,c-s架构,client和server端均用sh终端运行;

2、实现用户登录和注册功能,服务端返回操作码,如果失败,返回失败信息;

3、server端,使用链表实现用户数据在内存中的存储方式,使用文件IO实现服务端持久化;

扩展要求

1、单点登录,并实现退出登录功能;

2、互斥,多客户端同时注册(同一用户名)。

一、程序设计

1、通讯协议

1.1、注册与登录数据报(定长报文):

客户端--->服务端
序号字段名称占用字节数说明
1报文头1固定为: 0x11
2操作码11-登录,2-注册,其他-错误码
3用户名20ASCII字符串
4密码20ASCII字符串
5报文尾1

固定为: 0x22

服务端 ---> 客户端
序号字段名称占用字节数说明
1报文头1固定为: 0x11
2操作返回码11-成功,其他-错误
3错误描述50ASCII字符串
4报文尾1

固定为: 0x22

2、持久化存储结构

使用以下结构体,将内存中的字节存储方式,直接写入到文件中,读取亦然。

typedef struct u{
    char username[20];
    char passwd[20];
}userinfo_t;

3、客户端的功能

1)带参数化运行 如 ./a.out 服务器ip 服务器端口号。启动时启用tcp连接服务器,如果失败,提升信息,退出程序;

2)成功连接上服务器后,显示操作界面及提示信息,简单的命令行终端即可(1-登录,2-注册,Q-退出客户端)

3)登录:输入用户名和密码,密码不可见,且用户名、密码不超过10位

4)注册:输入用户名、密码和确认密码,要求密码和确认密码一致,且用户名、密码不超过10位

5)若服务端主动断开连接,显示原因,退出程序

4、服务端的功能

1)带参数化的运行

2)用户名、密码持久化存储,注册成功,追加

3)操作失败,向客户端返回失败原因

4)若客户端断开连接,关闭对应的连接,但不退出程序

5)注册,用户名不允许重复

6)内存中的用户信息存储使用链表


5、目录结构

二、公共部分实现-头文件("接口")

1、userinfo.h

 定义了用户信息的结构体

        内存中用链表存储用户信息,及相关操作函数

        文件持久化存储用户信息,及相关函数

typedef struct u{
	char username[20];
	char passwd[20];
}userinfo_t;

typedef struct n{
	union{
		userinfo_t data;
		int len;
	};
	struct n *next;
}link_t,node_t;

// 链表操作函数
link_t* link_create();
void link_destroy(link_t* L);
void link_insertHead(link_t* L, userinfo_t user);
node_t* link_findByUsername(link_t* L, char* username);

// 文件操作函数
link_t* file_load();
void file_appendUser(userinfo_t user);

2、mynet.h 

        定义了客户端与通讯段的通讯报文 (未来如果需要添加断包与拆包的功能,也应该添加在此)

typedef struct{
	char head;
	char cmd;
	char username[20];
	char passwd[20];
	char end;
}msg_send_t;

typedef struct{
	char head;
	char ret;
	char errmsg[50];
	char end;
}msg_ret_t;


void msg_send_init(msg_send_t* msg, char cmd, char* username,char* passwd);
void msg_ret_init(msg_ret_t* msg);

3、相关实现

3.1、userinfo.c

link_t* link_create(){
	link_t* L=(link_t*)malloc(sizeof(link_t));
	if(L == NULL){
		fprintf(stderr,"链表初始化失败,malloc error\n");
		return NULL;
	}
	L->len=0;
	L->next=NULL;
	return L;
}

void link_destroy(link_t* L){
	node_t* p=L->next;
	for(int i=0;i<L->len;i++){
		node_t* temp=p;
		p=p->next;
		free(temp);
	}
	free(L);
}

void link_insertHead(link_t* L, userinfo_t user){
	node_t *p=(node_t*)malloc(sizeof(node_t));
	p->next=L->next;
	L->next=p;
	memcpy(&p->data, &user, sizeof(userinfo_t));
	L->len++;
	printf("链表:节点插入成功\n");
}

node_t* link_findByUsername(link_t* L, char* username){
	node_t *p=L->next;
	for(int i=0;i<L->len;i++){
		printf("1:[%s],2:[%s]\n",p->data.username,username);
		if(strcmp(p->data.username, username)==0){
			return p;
		}
		p=p->next;
	}
	return NULL;
}

link_t* file_load(){
	link_t* L=link_create();

	//打开文件
	if(access(FILE_PATH, F_OK)){//不存在
		printf("文件不存在,新建文件\n");
		//创建文件
		int fd=open(FILE_PATH, O_WRONLY|O_CREAT|O_TRUNC, 0666);
		close(fd);
		//肯定无数据
		return L;
	}
	printf("文件存在,读取文件数据\n");
	int fd=open(FILE_PATH, O_RDONLY);
	if(fd == -1){
		perror("open error");
		return NULL;
	}

	//读取数据
	userinfo_t u;
	ssize_t cnt;
	while((cnt=read(fd,&u,sizeof(u)))>0 ){
		//将读取数据插入到链表
		printf("读取一次,读取了[%ld]个字节\n",cnt);
		link_insertHead(L, u);
	}
	printf("read completed. cnt=[%ld]\n", cnt);
	
	//关闭文件
	close(fd);
	return L;
}

void file_appendUser(userinfo_t user){
	//打开文件, 程序运行到此处,文件一定存在
	int fd=open(FILE_PATH, O_WRONLY|O_CREAT|O_APPEND, 0666);
	if(fd ==-1){
		perror("file_appenUser:文件打开失败");
		return;
	}
	//追加数据
	ssize_t cnt=write(fd, &user, sizeof(user));
	printf("cnt=[%ld]个字节已经写入\n", cnt);

	//关闭文件
	close(fd);

}

3.2、mynet.c

void msg_send_init(msg_send_t* msg, char cmd, char* username,char* passwd){
	msg->head=0x11;
	msg->cmd=cmd;
	strcpy(msg->username,username);
	strcpy(msg->passwd,passwd);
	msg->end=0x22;
}
void msg_ret_init(msg_ret_t* msg){
	msg->head=0x11;
	//
	msg->end=0x22;
}

三、客户端实现

3.1 client.c

void login(int sk, msg_ret_t* msgRet);
void reg(int sk, msg_ret_t* msgRet);

//函数转移表
void (*funcArr[])(int,msg_ret_t*)={login,reg};

int main(int argc, const char *argv[])
{
	if(argc != 3){//argv[1]:服务端ip argv[2]:服务端port
		fprintf(stderr,"参数有误\n");
		return -1;
	}
	//与服务端建立连接
	//1.1 创建socket文件
	int sk=socket(AF_INET, SOCK_STREAM, 0);
	if(sk==-1){
		perror("与服务端的连接建立失败 socket error");
		return -1;
	}

	//1.2 connect
	addr_in_t addr;
	addr.sin_family=AF_INET;
	addr.sin_port=htons(atoi(argv[2]));
	addr.sin_addr.s_addr=inet_addr(argv[1]);
	socklen_t addrlen=sizeof(addr);

	if(connect(sk, (struct sockaddr*)&addr, addrlen)==-1){
		perror("connect error");
		return -1;
	}
	printf("与服务端成功建立连接\n");


	//操作界面 
	while(1){
		printf("*******************\n");
		printf("\t1-登录\n");
		printf("\t2-注册\n");
		printf("\tQ-退出\n");
		//int cmd;
		//fscanf(stdin,"%d", &cmd);
		int cmd=getchar();
		getchar();//吸回车
		//
		msg_send_t msgSend={0};
		msg_ret_t msgRet={0};
		//调用函数转移表
		printf("cmd=[%d],[%ld]\n", cmd, sizeof(funcArr)/sizeof(void*));
		if(cmd=='Q'){
			break;
		}
		if(cmd<49||cmd>49+sizeof(funcArr)/sizeof(void*)){
			printf("错误的指令!请重新输入\n");
			continue;
		}
		//调用
		funcArr[cmd-49](sk, &msgRet);
		if(msgRet.ret == 1){
			printf("操作成功\n");
			continue;
		}
		printf("操作失败!错误原因[%d][%s]\n", msgRet.ret,msgRet.errmsg);
	}	
	close(sk);
	printf("客户端已经关闭\n");
	//
	return 0;
}

void login(int sk, msg_ret_t* msgRet){
	printf("\t\t【登录】\n");
	char user[20];
	printf("\t\t用户名:");
	fgets(user,sizeof(user),stdin);
	user[strlen(user)-1]=0;

	char passwd[20];
	printf("\t\t密码:");
	fgets(passwd,sizeof(passwd),stdin);
	passwd[strlen(passwd)-1]=0;

	msg_send_t msg;
	msg_send_init(&msg, 1, user,passwd);

	ssize_t cnt=send(sk, &msg, sizeof(msg),0);
	printf("发送[%ld]个字节\n", cnt);
	if(cnt == -1){
		perror("send error");
		return;
	}

	cnt=recv(sk,msgRet,sizeof(msg_ret_t),0);
	printf("收到[%ld]个字节\n", cnt);
}

void reg(int sk, msg_ret_t* msgRet){
	printf("\t\t【注册】\n");
	char user[20];
	printf("\t\t用户名:");
	fgets(user,sizeof(user),stdin);
	user[strlen(user)-1]=0;

	char passwd[20];
	printf("\t\t密码:");
	fgets(passwd,sizeof(passwd),stdin);
	passwd[strlen(passwd)-1]=0;

	char passwd2[20];
	printf("\t\t确认密码:");
	fgets(passwd2,sizeof(passwd2),stdin);
	passwd2[strlen(passwd2)-1]=0;

	if(strcmp(passwd,passwd2)!=0){
		msgRet->ret=2;
		strcpy(msgRet->errmsg,"两次输入的密码不一致\n");
		return;
	}

	msg_send_t msg;
	msg_send_init(&msg, 2, user,passwd);

	ssize_t cnt=send(sk, &msg, sizeof(msg),0);
	printf("发送[%ld]个字节\n", cnt);
	if(cnt == -1){
		perror("send error");
		return;
	}

	cnt=recv(sk,msgRet,sizeof(msg_ret_t),0);
	printf("收到[%ld]个字节\n", cnt);
}

3.2、客户端的Makefile

因为涉及.c文件在不同目录下的编辑,为了方便与练习,贴出Makefile:

OUT=client.out
CC=gcc
OBJS=${patsubst %.c,%.o,${wildcard *.c} ../common/mynet.c ../common/userinfo.c}

all:${OBJS}
	gcc -o ${OUT} $^

%.o:%.c
	gcc -c -o $@ $^

clean:
	${RM} -f ${OBJS} ${OUT}

四、服务端实现

4.1 server.c

void handle(link_t* L,msg_send_t* msg, msg_ret_t* ret){
	msg_ret_init(ret);
	switch(msg->cmd){
	case 1:{
		//login
		//node_t* p=link_findByUsername(L, msg->username);
		node_t *p=link_findByUsername(L, msg->username);
		if(p == NULL){//用户不存在
			printf("111\n");
			ret->ret=2;
			strcpy(ret->errmsg,"该用户不存在");
			return;
		}
		printf("222\n");
		//用户存在,校验用户密码
		if(strcmp(p->data.passwd, msg->passwd)==0){
			ret->ret=1;
			strcpy(ret->errmsg,"登录成功");
			return;
		}
		ret->ret=2;
		strcpy(ret->errmsg,"密码错误");
		return;
	}
	case 2:{//reg
		//查询用户是否已经存在
		node_t *p=link_findByUsername(L, msg->username);
		if(p!=NULL){
			ret->ret=2;
			strcpy(ret->errmsg,"注册失败,该用户已经存在");
			return;
		}

		//若不存在,将用户添加到文件中
		userinfo_t uuu;
		strcpy(uuu.username, msg->username);
		strcpy(uuu.passwd,msg->passwd);
		file_appendUser(uuu);

		//将用户信息,追加到内存中
		link_insertHead(L, uuu);
		ret->ret=1;
		strcpy(ret->errmsg,"注册成功");
		break;
		}
	default://不支持的操作
		ret->ret=2;
		strcpy(ret->errmsg,"不支持的操作,目前仅支持1-登录,2-注册操作");
		break;
	}
}


int main(int argc, const char *argv[])
{
	//入参校验
	if(argc != 3){
		fprintf(stderr,"输入的参数有误\n");
		return -1;
	}
	int port=atoi(argv[2]);
	//
	//从文件读取存储信息
	link_t* L=file_load();
	//
	//创建服务端socket文件
	int serverFD=socket(AF_INET, SOCK_STREAM, 0);
	printf("服务端套接字文件创建完成,serverFD=[%d]\n", serverFD);
	//
	//ip信息与socket文件绑定
	addr_in_t addr={0};
	addr.sin_family=AF_INET;
	addr.sin_port=htons(port);
	addr.sin_addr.s_addr=inet_addr(argv[1]);
	
	if(bind(serverFD, (addr_t*)&addr, sizeof(addr))==-1){
		perror("bind error");
		return -1;
	}
	//listen
	if(listen(serverFD, 10)==-1){
		perror("listen error");
		return -1;
	}
	while(1){
		//accept获取客户端socket文件
		addr_in_t client_addr={0};
		socklen_t client_len=sizeof(client_addr);
		int clientFD=accept(serverFD, (addr_t*)&client_addr, &client_len);
		//printf("客户端[%d][%s]已经建立连接\n", clientFD, inet_ntoa(client_addr.sin_addr.s_addr));
		//业务逻辑
		char buf[64];
		while(1){
			//ssize_t cnt=recv(clientFD,buf,sizeof(buf),0);
			//printf("cnt=[%ld],收到消息:%s\n", cnt,buf);
			//if(cnt==0){
			//	if(close(clientFD)==-1){
			//		perror("clientFD关闭失败");
			//		break;
			//	}
			//	printf("clientFD=[%d]已经关闭\n", clientFD);
			//	break;
			//}
			//读取报文
			msg_send_t msg;
			ssize_t cnt=recv(clientFD, &msg, sizeof(msg),0);
			printf("从客户端收到了[%ld]个字节\n", cnt);
			//业务处理
			msg_ret_t ret;
			handle(L,&msg, &ret);
			
			//返回处理结果
			cnt=send(clientFD, &ret, sizeof(ret),0);
			printf("ret=[%d],errmsg=[%s]\n", ret.ret, ret.errmsg);
			printf("向客户端发送了[%ld]个字节的数据\n", cnt);
		}

		//close客户端socket文件
	}

	return 0;
}

4.1 服务端的Makefile

注意与客户端的Makefile的区别:

OUT=server.out
CC=gcc
OBJS=${patsubst %.c,%.o,${wildcard *.c} ../common/mynet.c ../common/userinfo.c}

all:${OBJS}
	gcc -o ${OUT} $^

%.o:%.c
	gcc -c -o $@ $^

clean:
	${RM} -f ${OBJS} ${OUT}

五、运行效果

附:完成的源码


http://www.kler.cn/a/377287.html

相关文章:

  • stm32定时器输出比较----驱动步进电机
  • 【python高级】342-TCP服务器开发流程
  • Linux 常见用例汇总
  • 重温设计模式--外观模式
  • python 内存管理
  • Retrofit源码分析:动态代理获取Api接口实例,解析注解生成request,线程切换
  • C++_STL_xx_番外01_关于STL的总结(常见容器的总结;关联式容器分类及特点;二叉树、二叉搜索树、AVL树(平衡二叉搜索树)、B树、红黑树)
  • C#与C++结构体的交互
  • 北京迅为iTOP-LS2K0500开发板快速使用编译环境虚拟机Ubuntu基础操作及设置
  • Javaweb梳理3——SQL概述+DDL语句1
  • Maven(22)如何使用Maven进行单元测试?
  • 面试经典 150 题:189、383
  • 2024年,Rust开发语言,现在怎么样了?
  • 6、显卡品牌分类介绍:技嘉 - 计算机硬件品牌系列文章
  • java项目之文理医院预约挂号系统源码(springboot)
  • 实战:基于 Next.js+MoonShot API 开发一个 Github Trending 总结助手
  • 远程连接服务
  • matlab模拟小球平抛
  • oracle insert忽略主键冲突,忽略重复记录
  • C++_day3
  • LeetCode3226题. 使两个整数相等的位更改次数(原创)
  • CSS 动画:网页设计的动态之美
  • ubuntu df -h分配的磁盘空间小于物理磁盘
  • mysql8 window 免安装
  • 【Qt聊天客户端-min_Bug】客户端请求失败分析
  • 杂货 | 每日资讯 | 2024.11.1