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系统下传过来的数据,不兼容