网络编程:json多客户端服务器实现

json多客户端服务器实现
原理图:
在这里插入图片描述

服务器代码:

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include "cJSON.h"
#include <stdbool.h>
#define MAXSIZE 100

void *client_thread(void *arg);
sem_t sm;//定义一个信号量

struct ClientInfo{

	int sockfd;//套接字
	char id[16];
};

/*定义一个结构体数组
作用:来一个客户端,就把客户端的信息存在这个数组里面
memset()对其初始化*/
struct ClientInfo cinfo[MAXSIZE];

int main(void)
{
	//初始化信号量:wq
	sem_init(&sm, 0, 0);

	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
	{
		perror("socket fail");
		return -1;
	}
	
	//2.绑定
	struct sockaddr_in addr;  //定一个存储地址,端口号的替代结构体
	//struct sockaddr_in *p = (struct sockaddr_in*)&addr;
	memset(&addr, 0, sizeof(addr)); //初始化为0
	addr.sin_family = AF_INET;//初始化地址族IPV4
	addr.sin_port = htons(8989); //设置端口号(网络字节序号)
	addr.sin_addr.s_addr = INADDR_ANY;//初始化绑定地址, 用INADDR_ANY--表示绑定本机地址
	int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
	if(ret < 0)
	{
		perror("bind fail:");
		return -1;
	}

	//3.监听
	ret = listen(sockfd, 5);
	if(ret < 0)
	{
		perror("listen error");
		return -1;
	}

	//4.接受链接, 没有客户端链接的时候->此函数阻塞
	struct sockaddr_in clientaddr; //保存客户端地址
	socklen_t len = sizeof(clientaddr);//保存地址长度

	//初始化客户端信息结构体数组
	memset(cinfo, 0, sizeof(cinfo));//所有数据都清零

	//主线程只负责接受客户端链接,不收发数据->收发数据在线程中实现
	while(1)
	{
		int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
		if(clientfd < 0)
		{
			perror("accept error");
			return -1;
		}

		//创建一个线程
		pthread_t id = 0;
		pthread_create(&id, NULL, client_thread, (void *)&clientfd);
		//分离线程
		pthread_detach(id);

		//把客户端套接字描述符保存(链表, 数组)
		//把套接字保存在最小未使用的数组元素中
		for(int i=0; i<MAXSIZE; i++)
		{
			if(cinfo[i].sockfd == 0)
			{
				cinfo[i].sockfd = clientfd;//保存客户端套接字
				break;
			}
		}
		//获取信号量资源
		sem_wait(&sm);//目的是等待->线程执行完int clientfd = *((int *)arg)这句再循环上面的内容
	}

	sem_destroy(&sm);//销毁信号量
	//关闭套接字
	close(sockfd);
	return 0;
}

/*
json数据格式:通用
作用:解析{"id":"99999", "to":"88888", data:"hello world"}此对象
		这对象的99999/888888/helloworld等字符串
(用字符串方法解析比较麻烦)
*/
bool parse_data(char *srcData, char *fromid,  char *toid, char *data)
{
	//字符串转cjson对象
	cJSON *root = cJSON_Parse(srcData);
	if(root == NULL) return false;
	//解析to
	cJSON *toObj = cJSON_GetObjectItem(root,"to"); //根据键获取对应的值
	printf("toId:%s\n", toObj->valuestring);
	sprintf(toid, "%s", toObj->valuestring);
	
	//解析id
	cJSON *idObj = cJSON_GetObjectItem(root,"id"); //根据键获取对应的值
	printf("Id:%s\n", idObj->valuestring);
	sprintf(fromid,"%s", idObj->valuestring);
	//解析to
	cJSON *dataObj = cJSON_GetObjectItem(root,"data"); //根据键获取对应的值
	printf("data:%s\n", dataObj->valuestring);
	sprintf(data, "%s", dataObj->valuestring);
	cJSON_Delete(root);//释放
	return true;
}

//线程-收发数据
void *client_thread(void *arg)
{
	int clientfd = *((int *)arg); //传套接字过来,再转int型
	//释放信号量资源
	sem_post(&sm);
	char recvbuffer[1024]={0};
	char fromid[16]; //保存发送方id
	char toid[16]; //保存接收方id
	char data[128];//要发送的数据
	while(1)
	{
		//第一次读取时候给服务器上报id(登录)->存起来和套接字绑定在一起
		//{"id":"99999", "to":"88888", data:"hello world"};
		int ret = read(clientfd, recvbuffer, 1024);
		if(ret <= 0)
		{
			printf("客户端%d掉线\n", clientfd);
			//把链表或数组中的套接字描述符要清理
			break;
		}

		//解析
		parse_data(recvbuffer, fromid, toid, data);



		//注册或者登陆要获取套接字里的id信息->从数组中查到套接字,所以对应的id
		//当第一次链接的时候id不存在要自己添加
		for(int i=0; i<MAXSIZE; i++)
		{
			if(cinfo[i].sockfd == clientfd && strlen(cinfo[i].id)==0)
			{
				strcpy(cinfo[i].id, fromid);
				
				//发送反馈信息{type:0};
				
				break;
			}
		}
		
		//通过to对应的id号找到对应用套接字->套接字才能发数据
		for(int i=0; i<MAXSIZE; i++)
		{
			
			if(cinfo[i].sockfd != 0  && strcmp(cinfo[i].id, toid)==0)
			{
				
				char tmpbuffer[1024]={0};
				sprintf(tmpbuffer, "{\"type\":\"2\",\"from\":\"%s\",\"data\":\"%s\"}", fromid, data);
				//发送数据
				write(cinfo[i].sockfd, tmpbuffer, strlen(tmpbuffer)+1);
				//发送反馈信息{type:1}
				break;
			}
		}
		printf("[clientfd=%d]%s\n",clientfd,  recvbuffer);
		//解析接收到的数据, 从链表或数组中找到对应用套接字描述符再发送数据
	}
}

客户端代码:

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
void *recv_thread(void* arg);

int main(void)
{
	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
	{
		perror("socket fail");
		return -1;
	}

	//2.连接服务器
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET ;
	addr.sin_port = htons(8989);
	addr.sin_addr.s_addr = inet_addr("192.168.24.19");
	int ret = connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));


	//创建线程接收服务器的数据
	pthread_t tid = 0;
	pthread_create(&tid, NULL, recv_thread, (void*)&sockfd);

	char id[16]={0};//存储用户id
	printf("请输入登录的ID:"); scanf("%s", id);
	//打包发送的数据
	char sendbuffer[128]={0}; //存储打包好格式的数据{id:"99999", to:"", data:""}
	sprintf(sendbuffer, "{\"id\":\"%s\", \"to\":\"\", \"data\":\"\"}",id);

	//发送数据(登录)
	write(sockfd, sendbuffer, strlen(sendbuffer)+1);


	//定义存储发送的数据
	char senddata[1024];
	//存储接收数据的id号
	char toid[16];
	while(1)
	{
		printf("请输入要发送的数据:");scanf("%s", senddata);
		printf("请输入接收ID:");scanf("%s", toid);


		//打包发送的数据
		char sendbuffer[2048]={0}; //存储打包好格式的数据{id:"99999", to:"xxxx", data:"xxx"}
		sprintf(sendbuffer, "{\"id\":\"%s\", \"to\":\"%s\", \"data\":\"%s\"}",id, toid, senddata);

		//发送打包好的数据给服务器
		write(sockfd, sendbuffer, strlen(sendbuffer)+1);
	}	
	close(sockfd);
	return 0;
}

//线程负责接收服务器发送的数据
void *recv_thread(void* arg)
{
	int sockfd = *((int*)arg);//获取套接字描述符()
	char recvbuffer[1024]; //保存接收的数据
	while(1)
	{
		int ret = read(sockfd, recvbuffer, 1024);//接收数据
		if(ret <= 0){break;}//接收出错退出

		printf("%s\n", recvbuffer);
		memset(recvbuffer, 0, 1024);//清除
	}
}

(多客户端实现了数据的转发-》收发数据)
实现结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
乱码显示原因:
该同学是在windows系统下传过来的数据,不兼容
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值