Linux下0.1版本具有聊天功能的小程序

简介

功能介绍

实现打开服务端后,运行多个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接收消息

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值