linux环境下,实现公共聊天室功能

1.题目

  • 实现公共聊天室的程序,在此环境下允许多个用户同时进行聊天;
  • 实现点到点的聊天应用,在此环境下应实现某一用户到另外一个用户的聊天功能;
  • 在图形界面的环境下进行实现(不做图形界面的要求);
  • 允许多个用户同时进行聊天(聊天室环境下);
  • 以unix或类unix的C程序完成上述功能;
  • Server端应该是充分并发的;

2.运行截图

在这里插入图片描述
在这里插入图片描述

3.总体设计

本程序分为服务端和客户端,其中公共聊天室的功能需要客户端和服务端都打开,而双人聊天室只需要打开客户端即可,并不需要服务端,通信协议都是采用TCP。
先来说说公共聊天室:客户端通过连接服务器后利用fork添加一个子进程,利用子进程用来往服务端发送信息,而父进程用来实时监听收到的信息并输出显示。而对于服务端,通过进程执行while循环用来监听所有的客户端连接请求,利用客户端信息结构体保存连接的客户端的套接字描述符以及客户端名字,当一个接收来自客户端的连接请求之后,利用pthread_create函数产生一个线程用来专属服务这个客户,并往这个线程里面传送客户端信息结构体。在这个线程里面接收来自这个客户端的信息并检查是否是普通信息还是退出聊天的请求,若是退出则相应的连接客户端数目减1,否则向所有连接的客户端进行广播这个信息。
再来说说双人聊天室:通过客户端的菜单进入双人聊天室,可以选择等待别人发起聊天还是发起聊天,若选择等待别人发起对自己的聊天则此时它就相当于服务端,当其它客户端选择发起聊天时,则两者都通过双进程实现同时发送信息,同时接收信息的功能,当一方退出聊天时候另一方也同时退出聊天。

4.详细设计

服务端:通过define定义服务端口以及服务端的最大客户端连接数目,接着是定义客户体信息结构体,包含客户端的套接字描述符以及客户端名,还定义一个服务器当前连接的客户端数目的变量client_number。然后是向所有连接服务器的进行广播信息的函数:其实就是通过循环体利用客户端信息结构体里面的各个客户端套接字描述符把消息发往各个客户端当中,实现群聊模式。
而客户端信息处理函数就是将发送的信息的客户端的名字以及服务端接收到的信息利用strcat连接起来,然后调用广播函数broadcast将消息发送给除了发送的这条信息的客户端广播该信息。在这之前利用strncmp检查发送过来的信息是否是exit,如果是则向所有的连接的客户端广播该用户已经下线,并将当前连接客户端数目减1,同时将下线的客户端的IP地址以及端口号输出在屏幕上,再利用close函数关闭该套接字描述符。
到主函数部分就是先调用socket函数创建套接字然后接着给sockaddr_in结构体各个参数设置对应的值,然后利用bind绑定相关给套接字接着进入listen状态。下面的是放在while循环里面,当客户端请求连接时候accept并输出显示连接客户端的IP地址以及端口号,调用广播函数向所有用户说明该用户已经上线,然后利用pthread_create函数为其创建单独的线程,线程的函数就是前面所说的信息处理函数并传入该客户端的信息结构体。

客户端:分别利用define定义双人聊天室服务端端口号、公共聊天室服务器端口号和公共聊天室服务器IP地址,由于各个函数之间存在相互调用,所以提前声明所有函数,包括主菜单、双人聊天室、双人聊天室之等待聊天、双人聊天室之发起聊天这几个函数,之后声明五个变量,分别是双人聊天室之发起聊天的子进程号(发送信息)、双人聊天室之发起聊天的父进程号(接收信息)、双人聊天室之等待聊天的子进程号(发送信息)、和双人聊天室之等待聊天的父进程号(接收信息)和多人聊天室的父进程号(接收信息),用于退出聊天时杀死相对应的进程。
主菜单就是利用输入相应指令进入相应的函数,分别是1-进入双人聊天、2-进入公共聊天室、3-退出程序,双人聊天室函数同样如此,分别是1-发起聊天、2-等待聊天、3-返回菜单、4-退出程序。主函数直接调用主菜单,若输入1进入双人聊天,则进入到双人聊天函数,此时选择发起聊天则进入双人聊天室之发起聊天,首先提示输入要与之聊天的IP地址,然后开始创建套接字,设置相应参数,然后利用connect函数连接对方(当然了,此时对方应该处于listen状态),提示输入你名字,然后把你的名字发送给对方,同时阻塞等待接收对方发回来的名字,接着就和对方进入聊天状态。利用fork创建进程,子进程用于发送信息,父进程用于接收信息。子进程当中输入要发送的信息之后会把自己的名字以及要发送的信息以及当前时间放在一起发送给对方,若发送的信息是exit则退出聊天并利用kill函数杀死通过父进程号杀死父进程(接收信息的),并退出子进程。对于接收信息的父进程,同样检测对方发送过来的信息,如果是exit,则父进程同样利用kill以及子进程号杀死子进程并退出。
接着说双人聊天室之等待聊天函数,同样地创建套接字,设置相关参数,利用bind绑定套接字,进入listen状态,若接收到对方发来的通信请求则利用accept接收并返回套接字描述符,接着会利用recv函数接收对方发来的名字,输入你的名字并发送给对方,接着就进入如同上面所说的利用双进程进行通信了,这里就不再赘述了。
最后说公共聊天室函数:思路基本同上面的一样,只不过这个连接的是服务器,需要在连接之前打开服务端,然后就是双进程发送、接收信息了,程序说明到此结束。

5.问题描述

1、客户端输入名字的发送给对方,在对方对话框中名字显示正常,但是在这一段
strcpy(msg, “正在和”);
strcat(msg, friend_name);
strcat(msg, “聊天…\n”);
printf("%s", msg);
当中,输出显示的名字确是有乱码的,在公共聊天输入名字的对应部分却没有出现这种情况。一开始以为是缓冲区问题便清除了缓冲区,但是并不是,后来尝试recv的时候保存实际接收的字节数,并根据此将该名字的字符串最后一个加上结束符‘\0’,问题就解决了,但是依然无法搞明白为什么会出现前面所说的情况。

2、服务端处理客户端信息函数的检查客户端发送的信息是否是要退出聊天的部分,一开始是无法执行strncmp的,通过验证出现显示信息验证之后发现,recv函数之后就直接退出了,并没有执行接下来的代码,检查之后recv函数以及相对应的客户端程序并没有问题,后来无缘无故就正常了,再也没有出现过这种情况。

3、之前有尝试过解决就是不管多人聊天还是双人聊天,退出聊天之后应该是返回主菜单才对,而不是直接退出整个程序。但是存在的问题是:例如双人聊天的两个进程,一个接收信息一个发送信息,当发送信息的部分检测到exit之后就要退出这个进程了,然后exit发送到对面的时候,对面接收该信息的进程同样也退出了,但是问题是还有发信息用户的接收信息进程以及收信息用户的发送进程依然没有退出,若在此情况直接直接调用主菜单则还是无法输入指令,并且由于发送进程和接收信息的存在,一方还是可以发送信息而另一方接收信息。
后来想着直接关闭套接字就行了吧,但是并没用,关闭一个进程的套接字之后另一个的进程的依然可以正常接收信息。也想着通过无名管道来实现当一个进程关闭时往管道发送信息告诉另一个进程,但是接收信息的管道放在哪里呢?若是放在该进程里面,则另一个进程没有退出则管道一直阻塞,会导致无法接收其他客户发送过来的信息。最后实在没办法了就想到利用另一个进程号,当这个进程退出时候就直接顺便利用kill把另一个进程也杀掉了,然后整个程序就退出了。

6.源码

6.1服务端

/*《高级网络程序设计》课程作业
    1.实现公共聊天室的程序,在此环境下允许多个用户同时进行聊天;
    2.实现点到点的聊天应用,在此环境下应实现某一用户到另外一个用户的聊天功能;
    3.在图形界面的环境下进行实现(不做图形界面的要求);
    4.允许多个用户同时进行聊天(聊天室环境下);
    5.以unix或类unix的C程序完成上述功能;
    6.Server端应该是充分并发的;
    7.源代码和报告的提交时间为2020年7月10日。
此为服务端*/

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define PORT 8080        //服务器端口号
#define MAX_CLIENT 100   //服务器最大连接数

typedef struct Client    //客户信息结构体
{
    int client_socket_fd;//客户端套接字描述符
    char client_name[20];//客户端名
}Client;
Client client[MAX_CLIENT];
int client_number = 0;   //服务器当前连接的客户端数目

//向所有连接服务器的进行广播信息
void broadcast(char *msg,Client c)
{
    int i;
    for(i=0; i < client_number; i++)
    {
	if(client[i].client_socket_fd != c.client_socket_fd)
        {
	    send(client[i].client_socket_fd, msg, strlen(msg), 0);
        }
    }
}

//客户端信息处理函数
void *pthread(void *arg)
{
    Client c = *(Client*)(arg);
    while(1)
    {
	char buf[1024] = {}, tmp[20] = {};
	strcpy(buf, c.client_name);
	strcat(buf, ": ");
        int a = strlen(buf) + 4;
        strcpy(tmp, buf);
        strcat(tmp, "exit");
	recv(c.client_socket_fd, buf + strlen(buf), 1024 - strlen(buf), 0);
        if(strncmp(tmp, buf, a) == 0)
        {
	    int i;
	    for(i = 0; i < client_number; i++)
            {
	        if(client[i].client_socket_fd == c.client_socket_fd)
                {
		    client[i] = client[client_number - 1];
		    client_number--;
                    struct sockaddr_in addr;//获取下线的客户机的IP和PORT
                    int length = sizeof(addr);
                    getpeername(c.client_socket_fd, (struct sockaddr*)&addr, &length);
                    printf("客户机成功下线 IP: %s   PORT: %d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
		    strcpy(buf, "您的好友");
		    strcat(buf, c.client_name);
		    strcat(buf, "下线了\n");
		    break;
		}	
	    }
	    broadcast(buf, c);
	    close(c.client_socket_fd);
	    return NULL;
	}
        else
	    broadcast(buf,c);
    }
}

//主函数
int main(void)
{
    int sfd = socket(AF_INET, SOCK_STREAM ,0);
    if(sfd == -1)
    {
	perror("socket error");
	return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    socklen_t addrlen = sizeof(addr);

    int ret = bind(sfd, (struct sockaddr*)(&addr), addrlen);
    if(ret == -1)
    {
	perror("bind error");
	return -1;
    }
    if(listen(sfd, MAX_CLIENT) == -1)
    {
	perror("listen error");
	return -1;
    }
    printf("正在等待客户机连接...\n");

    while(1)
    {
       	struct sockaddr_in caddr;
	socklen_t len = sizeof(caddr);
	int cfd = accept(sfd, (struct sockaddr*)(&caddr), &len);
	if(cfd == -1)
        {
	    perror("accept error");
	    return -1;
	}
        printf("客户机成功连接 IP: %s   PORT: %hu\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
	char buf[100] = {};
	recv(cfd, client[client_number].client_name, 20, 0);
	client[client_number].client_socket_fd = cfd;
	pthread_t id;
	strcpy(buf, "你的好友");
	strcat(buf, client[client_number].client_name);
	strcat(buf, "上线了\n");
	broadcast(buf, client[client_number]);
	ret = pthread_create(&id, NULL, pthread, (void*)(&client[client_number]));
	client_number++;
	if(ret != 0)
        {
	    printf("pthread_create error\n");
	    continue;
	}
    }
    return 0;
}

6.2客户端

/*《高级网络程序设计》课程作业
    1.实现公共聊天室的程序,在此环境下允许多个用户同时进行聊天;
    2.实现点到点的聊天应用,在此环境下应实现某一用户到另外一个用户的聊天功能;
    3.在图形界面的环境下进行实现(不做图形界面的要求);
    4.允许多个用户同时进行聊天(聊天室环境下);
    5.以unix或类unix的C程序完成上述功能;
    6.Server端应该是充分并发的;
    7.源代码和报告的提交时间为2020年7月10日。
此为客户端*/

#include <netdb.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT0 8888                      //双人聊天室服务端端口号
#define PORT1 8080                      //公共聊天室服务器端口号
#define IP "192.168.61.154"             //公共聊天室服务器IP地址

void Main_menu();                       //主菜单
void Double_chatting_room();            //双人聊天室
void Public_chatting_room();            //公共聊天室
void Double_chatting_room_wait();       //双人聊天室之等待聊天
void Double_chatting_room_start();      //双人聊天室之发起聊天
pid_t double_chatting_room_start_child; //双人聊天室之发起聊天的子进程号(发送信息)
pid_t double_chatting_room_start_father;//双人聊天室之发起聊天的父进程号(接收信息)
pid_t double_chatting_room_wait_child;  //双人聊天室之等待聊天的子进程号(发送信息)
pid_t double_chatting_room_wait_father; //双人聊天室之等待聊天的父进程号(接收信息)
pid_t public_chatting_room_father;      //多人聊天室的父进程号(接收信息)

//主菜单
void Main_menu()
{
    printf("1、进入双人聊天室\n2、进入公共聊天室\n3、退出程序\n你想要:");
    int i;
    scanf("%d", &i);
    if(i == 1)
        Double_chatting_room();
    else if(i == 2)
        Public_chatting_room();
    else
        exit(0);
}

//双人聊天室
void Double_chatting_room()
{
    printf("1、发起聊天\n2、等待聊天\n3、返回菜单\n4、退出程序\n你想要:");
    int i;
    scanf("%d", &i);
    if(i == 1)
        Double_chatting_room_start();
    else if(i == 2)
        Double_chatting_room_wait();
    else if(i == 3)
        Main_menu();
    else
        exit(0);
}

//双人聊天室之发起聊天
void Double_chatting_room_start()
{
    char your_name[20], friend_name[20], msg[50], server_ip[20];

    printf("请输入对方IP地址: ");
    scanf("%s", server_ip);
    int sfd;//对方套接字文件描述符
    struct sockaddr_in addr;
    if((sfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
	perror("socket error");
	exit(-1);
    }
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT0);
    addr.sin_addr.s_addr = inet_addr(server_ip);
    if(connect(sfd, (const struct sockaddr*)(&addr), sizeof(addr)) == -1)
    {
	perror("connect error");
	exit(-1);
    }
    printf("成功向对方发起聊天...\n");
    printf("请输入你的名字: ");
    scanf("%s", your_name);
    send(sfd, your_name, strlen(your_name), 0);
    int l = recv(sfd, friend_name, 20, 0);//接好友发来的名字
    strcpy(msg, "正在和");
    friend_name[l] = '\0';
    strcat(msg, friend_name);
    strcat(msg, "聊天...\n");
    printf("%s", msg);

    pid_t pid = fork();
    if(pid == -1)
    {
	perror("fork error");	
	exit(-1);
    }
    else if(pid == 0)//子进程:发送信息
    {
	while(1)
        {
            char buf[1024], tmp0[1024], tmp1[1024];
            double_chatting_room_start_child = getpid();
            strcpy(buf, your_name);
            strcat(buf, ": ");
	    scanf("%s", tmp0);
            strcpy(tmp1, tmp0);
            strcat(buf, tmp0);
            strcat(buf, "    ");
            time_t timep;//获取当前系统时间
	    time(&timep);
            strcat(buf, ctime(&timep));
	    send(sfd, buf, strlen(buf), 0);
	    if(strcmp(tmp1, "exit") == 0)
            {
                printf("你已经退出聊天...\n");
                kill(double_chatting_room_start_father, SIGKILL);//退出聊天同时杀死父进程
                break;
            }
        }
    }
    else//父进程:接收信息
    {
        while(1)
        {
            char buf[1024] = {}, tmp[1024] = {};
            double_chatting_room_start_father = getpid();
	    recv(sfd, buf, 1024, 0);
            strcpy(tmp, friend_name);
	    strcat(tmp, ": ");
            strcat(tmp, "exit");
            if(strncmp(tmp, buf, strlen(tmp)) == 0)
            {
                printf("%s已退出聊天\n", friend_name);
                close(sfd);
                kill(double_chatting_room_start_child, SIGKILL);//对方退出聊天则杀死子进程并退出父进程
                break;
            }
	    printf("\t%s", buf);
        }
    }
    Main_menu();//返回主菜单
}

//双人聊天室之等待聊天
void Double_chatting_room_wait()
{
    int sfd = socket(AF_INET, SOCK_STREAM ,0);
    if(sfd == -1)
    {
	perror("socket error");
	exit(-1);
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT0);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    socklen_t addrlen = sizeof(addr);

    int ret = bind(sfd, (struct sockaddr*)(&addr), addrlen);
    if(ret == -1)
    {
	perror("bind error");
	exit(-1);
    }
    if(listen(sfd, 1) == -1)
    {
	perror("listen error");
	exit(-1);
    }
    printf("正在等待对方发起聊天....\n");

    struct sockaddr_in caddr;
    socklen_t len = sizeof(caddr);
    int cfd = accept(sfd, (struct sockaddr*)(&caddr), &len);
    if(cfd == -1)
    {
        perror("accept error");
	exit(-1);
    }
    printf("已接受来自对方发起的聊天...\n");
    char friend_name[20], your_name[20], msg[50];
    int l = recv(cfd, friend_name, 20, 0);
    printf("请输入你的名字: ");
    scanf("%s", your_name);
    send(cfd, your_name, strlen(your_name), 0);
    strcpy(msg, "正在和");
    friend_name[l] = '\0';
    strcat(msg, friend_name);
    strcat(msg, "聊天...\n");
    printf("%s", msg);

    pid_t pid = fork();
    if(pid == -1)
    {
	perror("fork error");	
	exit(-1);
    }
    else if(pid == 0)//子进程:发送信息
    {
	while(1)
        {
            char buf[1024], tmp0[1024], tmp1[1024] = {};
            double_chatting_room_wait_child = getpid();
            strcpy(buf, your_name);
            strcat(buf, ": ");
	    scanf("%s", tmp0);
            strcpy(tmp1, tmp0);
            strcat(buf, tmp0);
            strcat(buf, "    ");
            time_t timep;//获取当前系统时间
	    time(&timep);
            strcat(buf, ctime(&timep));
	    send(cfd, buf, strlen(buf), 0);
	    if(strcmp(tmp1, "exit") == 0)
            {
                printf("你已经退出聊天...\n");
                fflush(stdin);
                kill(double_chatting_room_wait_father, SIGKILL);//退出聊天同时杀死父进程
                break;
            }
        }
    }
    else//父进程:接收信息
    {
        while(1)
        {
            char buf[1024] = {}, tmp[1024] = {};
            double_chatting_room_wait_father = getpid();
	    recv(cfd, buf, 1024, 0);
            strcpy(tmp, friend_name);
	    strcat(tmp, ": ");
            strcat(tmp, "exit");
            if(strncmp(tmp, buf, strlen(tmp)) == 0)
            {
                printf("%s已退出聊天\n", friend_name);
                close(cfd);
                kill(double_chatting_room_wait_child, SIGKILL);//对方退出聊天则杀死子进程并退出父进程
                break;
            }
	    printf("\t%s", buf);
        }
    }
}

//公共聊天室
void Public_chatting_room()
{
    char name[20];
    pid_t pid;
    int sfd;//服务器套接字描述符
    struct sockaddr_in addr;
    if((sfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
	perror("socket error");
	exit(-1);
    }
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT1);
    addr.sin_addr.s_addr = inet_addr(IP);
    if(connect(sfd, (const struct sockaddr*)(&addr), sizeof(addr)) == -1)
    {
	perror("connect error");
	exit(-1);
    }
    printf("成功连接服务器...\n");
    printf("请输入你的名字: ");
    scanf("%s", name);
    fflush(stdin);
    send(sfd, name, strlen(name), 0);

    pid = fork();
    if(pid == -1)
    {
	perror("fork error");	
	exit(-1);
    }
    else if(pid == 0)//子进程:发送信息
    {
	while(1)
        {
            char buf[1024], tmp[1024];
	    scanf("%s", buf);
            strcpy(tmp, buf);
            strcat(buf, "    ");
            time_t timep;//获取当前系统时间
	    time(&timep);
            strcat(buf, ctime(&timep));
	    send(sfd, buf, strlen(buf), 0);
	    if(strcmp(tmp, "exit") == 0)
            {
                printf("你已退出聊天...\n");
                close(sfd);
                kill(public_chatting_room_father, SIGKILL);//退出聊天则杀死父进程并退出子进程
                fflush(stdin);
                break;
            }
        }
    }
    else//父进程:接收信息
    {
        public_chatting_room_father = getpid();
        while(1)
        {
            char buf[1024] = {};
	    recv(sfd, buf, 1024, 0);
	    printf("\t%s", buf);
        }
    }
}

//主函数
int main(void)
{
    Main_menu();
    return 0;
}
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值