基于linux的网络聊天室系统设计(C语言)

该系统采用多线程方式实现不同主机之间的通信。

可实现多个用户间的通信以及可支持私聊模式。(输入用户名即可进入私聊模式,输入ALL返回群聊模式)

某一客户端退出并不影响其他客户的使用,所有用户全部退出,服务器关闭端口,结束进程。

服务器主线程程序框图

 服务器接收连接线程程序框图(不完全,代码后续更新了,没改框图)

  客户端主线程程序框图)

客户端消息接发线程程序框图

  实验结果

服务器端代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <unistd.h>
#define PORT 8080  //1023~65535
#define MAX 100
#define IP "172.17.0.6"
#define USER 20
//设置总的用户连接数
pthread_mutex_t mm;//发送数据的时候加锁,防止阻塞
typedef struct
{
    char buf[100];
}userbuf;//读写缓存
typedef struct sockaddr_in SIN; 
SIN cli_addr[USER];//
int cli_fd[USER];//客户端套接字
int tag=0;//群聊or私聊判断标志位
int count=0;//退出聊天用户数
int user=0;//实际用户数
int sockfd;
typedef struct sockaddr SA;
char siliao[20]="[PRIVATE]";//私聊信息附加显示
userbuf username[USER];//存储用户民信息
userbuf username_plus[USER];//给用户名增加冒号显示
userbuf recvbuf[USER];//服务器端接收缓存
userbuf sendbuf[USER];//服务器端发送缓
int length[USER];
int rank(char a[])
{
    int i;
    for(i=0;i<user;i++)
        if(strncmp(a,username[i].buf,length[i])==0)
            return i;
    return 88;
}

void *talk(void *arg)//每个客户端分配一个线程,实现消息的接收和转发
{
    int num=tag++;//获取线程编号
    int m;
    int flag=88;
    char name[100]={0};
    memset(username[num].buf,0,100);
    length[num]=recv(cli_fd[num],username[num].buf,MAX,0);//接收用户名
    strcpy(username_plus[num].buf,username[num].buf);//
    strcat(username_plus[num].buf,":");
    strcpy(name,username_plus[num].buf);
    while(1)
    {
        memset(recvbuf[num].buf,0,100);//
        recv(cli_fd[num],recvbuf[num].buf,MAX,0);//接收客户端发送的信息
        if(strncmp(recvbuf[num].buf,"quit",4)==0)//如果输入的是quit则表示该客户端结束聊天
        {
            count++;
            close(cli_fd[num]);
            pthread_exit(NULL);//线程结束
        }
        if(strlen(recvbuf[num].buf)==0)
            break;
        int flag=rank(recvbuf[num].buf);
        if(flag==88)
        {
            strcpy(username[num].buf,name);
            strcpy(sendbuf[num].buf,strcat(username[num].buf,recvbuf[num].buf));//用户信息和发送信息组装
            system("date");
            write(1,sendbuf[num].buf,100);
            pthread_mutex_lock(&mm);//发送消息
            for(m=0;m<num;m++)//接收用户消息发给其他用户
            {
                send(cli_fd[m],sendbuf[num].buf,strlen(sendbuf[num].buf),MSG_NOSIGNAL);//其中MSG_NOSIGNAL是为了解决有客户退出后仍然可以发消息的问题
            }
            for(m=num+1;m<user;m++)
            {
                send(cli_fd[m],sendbuf[num].buf,strlen(sendbuf[num].buf),MSG_NOSIGNAL);//发送接收到的消息给其他用户端
            }
            memset(sendbuf[num].buf,0,100);//清空接收的缓存  
            pthread_mutex_unlock(&mm);
        } else{
            while(1){
                memset(recvbuf[num].buf,0,100);//
                recv(cli_fd[num],recvbuf[num].buf,MAX,0);//接收客户端发送的信息
                if(strncmp(recvbuf[num].buf,"quit",4)==0)//如果输入的是quit则表示该客户端结束聊天
                {
                    count++;
                    close(cli_fd[num]);
                    pthread_exit(NULL);//线程结束
                }
                if(strncmp(recvbuf[num].buf,"ALL",3)==0)
                    break;
                strcpy(username[num].buf,name);
                strcat(username[num].buf,siliao);
                strcpy(sendbuf[num].buf,strcat(username[num].buf,recvbuf[num].buf));//用户信息和发送信息组装
                pthread_mutex_lock(&mm);
                send(cli_fd[flag],sendbuf[num].buf,strlen(sendbuf[num].buf),0);
                pthread_mutex_unlock(&mm);
            }       
        }
    }
    close(cli_fd[num]);
    pthread_exit(NULL);
}

void *user_connect(void *arg)//专门使用一个线程来处理连接,动态处理用户的连接
{
    pthread_t tid[USER];//为每个客户端分配一个线程
    sockfd=socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1)
    {
        perror("socket create failure\r\n");
        return 0;
    }
    //2、创建结构,初始化数据 struct sockaddr      使用 struct sockaddr_in
    SIN ser_addr;//服务器端
    ser_addr.sin_family = AF_INET;//选择ipv4协议族
    ser_addr.sin_port=htons(PORT);  //端口号要转换端绪,从小端绪转换从大端绪
    ser_addr.sin_addr.s_addr=inet_addr(IP);//十进制的字符ip转换成网端数据格式

    //3、绑定端口号和IP地址  bind()
    if(bind(sockfd,(SA *)&ser_addr,sizeof(ser_addr)) == -1)
    {
        perror("bind failure\r\n");
        return 0;
    }

    //4、创建监听 listen() 数量
    if(listen(sockfd, USER) == -1)
    {
        perror("listen failure\r\n");
        return 0;
    }
    printf("waiting connect\r\n");
    int i;
    for(i=0;i<USER;i++)
        memset(&cli_addr[i],0,sizeof(cli_addr[i]));
    while(1)
    {
        int len=sizeof(cli_addr[user]);
        cli_fd[user]=accept(sockfd,(SA *)&cli_addr[user],&len);//接受客户端的连接
        if(cli_fd[user] == -1)
        {
            perror("wait failure\r\n");
            return 0;
        }
        else{
            printf("THE %d USER CONNECT SUCCESS!\n",user+1);
            pthread_create(&tid[user],NULL,talk,NULL);
        }
        user++;
    }
}
int main(int argc,char *argv[])
{
    //1、创建套接字文件,返回套接字文件描述符 socket()
    pthread_mutex_init(&mm, NULL);
    pthread_t CON;
    pthread_create(&CON,NULL,user_connect,NULL);//分配连接的线程
    sleep(30);//30s等待连接
    while(count!=user);
    printf("-------**********************************-------\n");
    printf("-------WELCOME JOIN DADONG TALK ROOM AGAIN-------\n");
    //7、关闭文件描述符 close(
    close(sockfd);
    return 0;
}

客户端代码:

#include <stdio.h>
#include <string.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <netinet/ip.h>
#define IP "172.17.0.6"
#define PORT 8080
#define MAX 100
int final=0;
// 创建socket套接字文件,并连接
// 接受数据  client 客户端
typedef struct sockaddr_in SIN;
typedef struct sockaddr SA;
SIN ser_addr;
int sockfd;
void *message(void *arg)
{
    printf("thread creat success!\n");
    char buf[100];
    int signal;
    while(1)
    {
        if(final==1)
            break;
        memset(buf,0,100);
        signal=recv(sockfd,buf,MAX,0);
        if(signal!=0){
            system("date");
            write(1,buf,strlen(buf));
            memset(buf,0,100);}
    }
    pthread_exit(NULL);
}
int main(int argc,char *argv[])
{
    //1、创建套接字文件,返回套接字文件描述符 socket()
    sockfd=socket(AF_INET, SOCK_STREAM, 0);
    pthread_t tid;
    if(sockfd == -1)
    {
        perror("socket create failure\r\n");
        return 0;     }

    //2、创建结构,初始化数据 struct sockaddr      使用 struct sockaddr_in
    SIN ser_addr;
    ser_addr.sin_family = AF_INET;//选择ipv4协议族  
    ser_addr.sin_port=htons(PORT);  //端口号要转换端绪,从小端绪转换从大端绪
    ser_addr.sin_addr.s_addr=inet_addr(IP);//十进制的字符ip转换成网端数据格式 服务端IP
    int len=sizeof(ser_addr);
    if(connect(sockfd,(SA *)&ser_addr,len)==-1)
    {
        perror("connect failure\r\n");
        return 0;
    }else
    {
        printf("WELCOME TO DADONG TALK ROOM!\r\n");   
        printf("Please sign you name:");
        char temp[20]={0};
        memset(temp,0,100);
        scanf("%s",temp);
        send(sockfd,temp,strlen(temp),0);//发送信息
        pthread_create(&tid,NULL,message,NULL);
    }
    char wbuf[100]={0};
    while(1)
    {
        memset(wbuf,0,100);
        read(0,wbuf,100);
        if(strncmp(wbuf,"quit",4)==0)
        {
            send(sockfd,wbuf,strlen(wbuf),0);
            final=1;
            close(sockfd);
            return 0;
        }
        send(sockfd,wbuf,strlen(wbuf),MSG_NOSIGNAL);
        memset(wbuf,0,100);
        usleep(20);
    }
    close(sockfd);
    return 0;
}        

猜你喜欢

转载自blog.csdn.net/yoonaanna/article/details/123434301