简介
功能介绍
实现打开服务端后,运行多个client,多个client之间可以进行聊天。所有人处于一个房间,可以指定姓名进行私聊,也可以直接群聊
代码有很多不足之处,可能也有些许bug,会慢慢学习改进
环境
ubuntu 18.04
代码部分
先贴上代码,然后再分析(也可以先看后面的分析,再看代码)
这版本没有IO复用,用多进程实现的,缺点很明显,来一个client就创建一个进程,而且还需要解决进程间通讯的问题,相当麻烦。不过对比起单进程,又没有IO复用 ,只能处理一个client还是有点价值的
代码链接
client端代码
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
//client端
#define USERONLINE 0 //上线
#define MSGPUBLIC 1 //群聊
#define MSGSINGL 2 //私聊
/*订正版本
*发送方指定消息格式,更正为消息结构体
*
*/
/*
*用户信息结构体
*/
struct user
{
int user_id;
char userName[50];
};
//消息结构体
struct MsgInfo
{
int MessageType;
struct user sender;
struct user recipient;
char message[100];
}MsgInfo;
int main()
{
int socket_fd;
int accept_fd;
int res;
int pid;
char buffer[100]={0};
struct user user;
/*************************** socket *******************************/
socket_fd = socket(AF_INET,SOCK_STREAM,0);
if(socket <0)
{
perror("client socket error");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");//inet_addr("127.0.0.1")
addr.sin_port = htons(12345);
/************************* connect *****************************/
res = connect(socket_fd,(__CONST_SOCKADDR_ARG)&addr,sizeof(addr));
if(res < 0 )
{
perror("client connect error");
return -1;
}
//用户登录验证
memset(&user,0,sizeof(user));
printf("plese input ID(int) and Name(string)\n");
scanf("%d %s",&user.user_id,user.userName);
getc(stdin);
memset(&MsgInfo,0,sizeof(MsgInfo));
MsgInfo.MessageType = USERONLINE;
memcpy(&MsgInfo.sender,&user,sizeof(user));
res = write(socket_fd,&MsgInfo,sizeof(MsgInfo));
if(res == -1)
{
perror("write error");
return -1;
}
pid = fork();
if(pid == 0)
{
char getName[50]={0};
//write(socket_fd,"I'm online",10);
while(fgets(buffer,sizeof(buffer),stdin))
{
//判断群聊私聊
if(buffer[0] == ':')//单聊
{
MsgInfo.MessageType = MSGSINGL;
memset(getName,0,sizeof(getName));
sscanf(buffer,":%s",getName);
memset(&MsgInfo.message,0,sizeof(MsgInfo.message));
memcpy(&MsgInfo.recipient.userName,getName,sizeof(getName));
memcpy(&MsgInfo.message,buffer+strlen(getName)+1,sizeof(buffer)-strlen(getName)-1);
res = write(socket_fd,&MsgInfo,sizeof(MsgInfo));
if(res < 0 )
{
perror("client write error");
return -1;
}
}
else
{
//群聊
memset(&MsgInfo.message,0,sizeof(MsgInfo.message));
MsgInfo.MessageType = MSGPUBLIC;
memcpy(&MsgInfo.message,buffer,sizeof(buffer));
res = write(socket_fd,&MsgInfo,sizeof(MsgInfo));
if(res < 0 )
{
perror("client write error");
return -1;
}
}
}
}
else if(pid >0)
{
while(read(socket_fd,buffer,sizeof(buffer)))
{
printf("%s\n",buffer);
fflush(stdout);
memset(buffer,0,sizeof(buffer));
}
}
return 1;
}
server端代码
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
//server端
#define USERONLINE 0 //上线
#define MSGPUBLIC 1 //群聊
#define MSGSINGL 2 //私聊
/*订正版本
*发送方指定消息格式,更正为消息结构体
*
*/
//最大的用户数量
#define MAXUSER 10
//存放所有消息的指针
static char * msgBuffer;
/*
*用户信息结构体
*/
struct user
{
int user_id;
char userName[50];
};
//消息结构体
struct MsgInfo
{
int MessageType;
struct user sender;
struct user recipient;
char message[100];
}*MsgInfo;
/*
*存放在共享内存的在线用户数据
*/
struct Online
{
int accpet_fd; //存放accpet的文件描述符
struct user user; //存放用户数据信息
}*OnlineUser;
/*
*锁结构体
*/
struct mt {
int num;
pthread_mutex_t mutex;
pthread_mutexattr_t mutexattr;
}*mm;
void sigFun(int sig, siginfo_t * info , void * d)
{
//收到消息的处理函数
//上锁
pthread_mutex_lock(&mm->mutex);
char sendMessage[150] = {0};
printf("message : %s",MsgInfo->message);
sprintf(sendMessage,"%s:%s",MsgInfo->sender.userName,MsgInfo->message);
fflush(stdin);
if(MsgInfo->MessageType == MSGPUBLIC)
{
struct Online * Onlinetemp = OnlineUser;
for(int i = 0 ; i < MAXUSER;i++,Onlinetemp++)
{
if(Onlinetemp->accpet_fd>0)
{
write(Onlinetemp->accpet_fd,sendMessage,strlen(sendMessage));
}
}
}
else if(MsgInfo->MessageType == MSGSINGL)
{
struct Online * Onlinetemp = OnlineUser;
for(int i = 0 ; i < MAXUSER;i++,Onlinetemp++)
{
if(strcmp(Onlinetemp->user.userName,MsgInfo->recipient.userName)==0)
{
write(Onlinetemp->accpet_fd,sendMessage,strlen(sendMessage));
break;
}
}
}
//解锁
pthread_mutex_unlock(&mm->mutex);
}
int main()
{
int res,pid,mainPid;
int socket_fd,accept_fd;
int shmMutexid,shmMsgBufferid,shmOnlineid;
int accpet_fds[20] = {0};
//char buffer[100] = {0};
struct MsgInfo getMsg;
struct user user;
struct sigaction sigat;
sigat.sa_sigaction = sigFun;
sigat.sa_flags = SA_SIGINFO | SA_RESTART;//SA_RESTART不会中断阻塞的函数如accept
sigaction(SIGUSR1,&sigat,NULL);
//获取主进程的PID
mainPid = getpid();
//为锁开辟共享内存
shmMutexid = shmget((key_t)10010,sizeof(struct mt),IPC_CREAT | 0666);
if(shmMutexid == -1)
{
perror("shmMutexid shmget error");
return -1;
}
mm = (struct mt *)shmat(shmMutexid,NULL,SHM_RND);
if(mm == (void *)-1)
{
perror("mm shmat error");
return -1;
}
memset(mm,0,sizeof(struct mt));
//初始化锁
pthread_mutexattr_init(&mm->mutexattr); //初始化mutex属性对象
pthread_mutexattr_setpshared(&mm->mutexattr, PTHREAD_PROCESS_SHARED); //修改属性为进程间共享
pthread_mutex_init(&mm->mutex, &mm->mutexattr); //初始化一把mutex琐
//为群聊开辟共享内存
shmMsgBufferid = shmget((key_t)10011,sizeof(struct MsgInfo),IPC_CREAT | 0666);
if(shmMsgBufferid == -1)
{
perror("shmMsgBufferid shmget error");
return -1;
}
MsgInfo = (struct MsgInfo *)shmat(shmMsgBufferid,NULL,SHM_RND);
if(MsgInfo == (void *)-1)
{
perror("MsgInfo shmat error :");
return -1;
}
memset(MsgInfo,0,sizeof(struct MsgInfo));
//为存储在线用户开辟共享内存
shmOnlineid = shmget((key_t)10012,MAXUSER * sizeof(struct Online),IPC_CREAT | 0666);
if(shmOnlineid == -1)
{
perror("shmOnlineid shmget error");
return -1;
}
OnlineUser = (struct Online *)shmat(shmOnlineid,NULL,SHM_RND);
if(msgBuffer == (void *)-1)
{
perror("OnlineUser shmat error");
return -1;
}
memset(OnlineUser,0,MAXUSER * sizeof(struct Online));
/****************** socket ******************/
socket_fd = socket(AF_INET,SOCK_STREAM,0);
if(socket_fd == -1)
{
perror("socket error");
return -1;
}
//端口复用
res = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&res, sizeof(res));
/****************** bind ******************/
struct sockaddr_in addr;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(12345);
addr.sin_family =AF_INET;
res = bind(socket_fd,(__CONST_SOCKADDR_ARG)&addr,sizeof(addr));
if(res == -1)
{
perror("bind error");
return -1;
}
/****************** listen ******************/
res = listen(socket_fd,30);
if(res == -1)
{
perror("bind error");
return -1;
}
while(1)
{
/****************** accept ******************/
accept_fd = accept(socket_fd,NULL,NULL);
if(accept_fd == -1)
{
perror("accept error");
return -1;
}
pid = fork();
if(pid == 0)
{
//子进程处理socket
while (1)
{
memset(&getMsg,0,sizeof(getMsg));
res = read(accept_fd,&getMsg,sizeof(getMsg));
if(res == -1)
{
perror("read error");
return -1;
}
else if(res == 0)
{
//子进程退出时需要删除在线用户
struct Online * Onlinetemp = OnlineUser;
for(int i = 0 ; i < MAXUSER;i++,Onlinetemp++)
{
//有用户存在
if(Onlinetemp->accpet_fd == accept_fd)
{
memset(Onlinetemp,0,sizeof(struct Online));
}
}
shmdt(mm);
shmdt(msgBuffer);
close(accept_fd);
close(socket_fd);
_exit(1);
}
else if(res == 1)
{
continue;
}
if(getMsg.MessageType == USERONLINE)
{
//上线包
printf("user id :%d ,user name :%s \n",getMsg.sender.user_id,getMsg.sender.userName);
struct Online * Onlinetemp = OnlineUser;
for(int i = 0 ; i < MAXUSER;i++,Onlinetemp++)
{
//用来临时存放单条用户在线数据
char temponlineInfo[100] = {0};
//有用户存在
if(Onlinetemp->accpet_fd > 0)
{
sprintf(temponlineInfo,"Online User info : ID : %d ,UserName : %s ",Onlinetemp->user.user_id,Onlinetemp->user.userName);
write(accept_fd,temponlineInfo,sizeof(temponlineInfo));
}
else
{
//存入在线用户名单
Onlinetemp->accpet_fd = accept_fd;
memcpy(&Onlinetemp->user,&getMsg.sender,sizeof(getMsg.sender));
break;
}
}
}
//上锁
pthread_mutex_lock(&mm->mutex);
memcpy(MsgInfo,&getMsg,sizeof(getMsg));
//解锁
pthread_mutex_unlock(&mm->mutex);
//发送信号
sigval val;
val.sival_int = 1;
sigqueue(getppid(),SIGUSR1,val);
}
}
}
//如果主进程退出
if(mainPid == getpid())
{
pthread_mutexattr_destroy(&mm->mutexattr); //销毁mutex属性对象
pthread_mutex_destroy(&mm->mutex); //销毁mutex
}
shmdt(mm);
shmdt(msgBuffer);
close(accept_fd);
close(socket_fd);
return 1;
}
代码分析
server思路分析
首先要了解socket的用法。在server端主要就是四个步骤:
1.socket
2.bind
3.listen
4.accept
accept之后返回的fd就能够直接用read,write等函数直接读写了;
接着就是进程间通讯
共享内存和信号
一共开辟了三块共享内存
1.存放消息内容
2.存放在线用户的信息
3.存放锁信息
程序开始先进行申请内存,安装信号,等等,socket的执行到第三个步骤结束。
然后进行while循环,每连接一个client就触发一次accept,然后新建一个进程。
子进程就处理这一个client的文件描述符的所有事情。然后要是读到消息了,就发送一个信号,
让信号处理函数来处理这一个消息。
client代码分析
client部分就相对简单
socket只有两步:
1.socket
2.connect
一开始socket连接完了之后,就创建两个进程,一个读取键盘输入,发送消息。
另一个则读取socket接收消息