基于Linux系统的网络聊天室实现

前言

Linux是一个开源系统,也是很多pc系统的“鼻祖”。早期的Linux并没有像现在这样功能强大,也没有这么复杂,随着Linux开发人员几十年如一日的优化,如今的Linux系统几乎涵盖了生活中的各个方面,作为一名软件开发人员,了解Linux系统是非常有必要的。当然,关于Linux系统的知识太多太多,在这里就不献丑了,有兴趣的小伙伴可以私下了解。

网络编程

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。关于TCP/IP协议族的内容在这里就不聊太多了,有兴趣的自行查看。那么今天的项目需要了解的有:TCP/UDP的区别、IPV4/IPV6的区别、什么是IP/域名/端口号、网络字节序、服务器及客户端的区别、网络编程的API、Linux多线程等(哈哈,相信看到这里的人这些都懂),具体内容不再详细说明。

前边的准备工作后差不多就可以写一个网络通信的项目了,既然是通信那肯定是需要多方的,在这个项目里,实现的是多个客户端和服务器的通信(类似vx群聊)。

客户端:

采用多线程思想,主函数初始化客户端套接字,与服务器建立连接,接收消息并显示;消息处理函数接受用户消息,并发送到服务器。

注:ip(点分十进制格式)可通过主函数传参(argv[1])填入,端口号需要与服务器端保持一致,通过argv[2]传入。

#include "main.h"

void snd(void);

#define BUFFSIZE 128

int sockfd;
int main(int argc,char *argv[])
{
    pthread_t thread;       
    struct sockaddr_in serv_addr;  
    char buf[BUFFSIZE];
    // 初始化服务端地址结构
    bzero(&serv_addr, sizeof(struct sockaddr_in));  // bzero 清零
    serv_addr.sin_family = AF_INET;        
    serv_addr.sin_port = htons(argv[2]);       
    inet_pton(AF_INET,argv[1], &serv_addr.sin_addr);   
    // 创建客户端套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    if(sockfd < 0)
    {
        perror("fail to socket");
        exit(-1);
    }
    // 与服务器建立连接
    printf("connecting... \n");
    if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) // connect
    {
        perror("fail to connect");
        exit(-2);
    }
    // 创建发送消息的线程,调用发送消息的函数snd
    pthread_create(&thread, NULL, (void *)(&snd), NULL);    
    // 接收消息
    while(1)
    {
        int len;
        if((len=read(sockfd, buf, BUFFSIZE)) > 0)      
        {
            buf[len] = '\0';        // 添加结束符,避免显示缓冲区中残留的内容
            printf("\n%s", buf);
            fflush(stdout);         // fflush 冲洗标准输出,确保内容及时显示
        }
    }
    return 0;
}
  
  // 发送消息的函数
void snd(void)
{
    char name[32], buf[BUFFSIZE];
    fgets(name, 32, stdin); // fgets会读取输入字符串后的换行符
    write(sockfd, name, strlen(name));      // write 写入通信套接字
    while(1)
    {
        fgets(buf, BUFFSIZE, stdin);
        write(sockfd, buf, strlen(buf));
        if(strcmp(buf, "bye\n") == 0)   
            exit(0);
    }
}

服务器端:

采用多线程思想,主函数负责服务器端的初始化,接受连接;消息处理函数负责客户端的消息处理及转发;quit为退出监测函数,当输入quit时,关闭服务器。

注:由于采用多线程的处理方式,在gcc编译时需要添加 -lpthread(本工程中已经在makefile中添加,正常编译即可)

#include "main.h"

#define PORT 5555
#define MAXMEM 10 
#define BUFFSIZE 128
 
void quit(void);
void rcv_snd(int p);

int listenfd, connfd[MAXMEM];
int main(void)
{
    struct sockaddr_in serv_addr, cli_addr;
    int i;
    time_t ticks;
    pthread_t thread;
    char buff[BUFFSIZE];

    printf("running...\n(Prompt: enter command ""quit"" to exit server)\n");
    bzero(&serv_addr, sizeof(struct sockaddr_in));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0)
    {
        perror("fail to socket");
        exit(-1);
    }

    if(bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
        perror("fail to bind");
        exit(-2);
    }

    listen(listenfd, MAXMEM);
    pthread_create(&thread, NULL, (void *)(quit), NULL);

    // 将套接字描述符数组初始化为-1,表示空闲
    for(i=0; i<MAXMEM; i++)
        connfd[i] = -1;

    while(1)
    {
        int len;
        for(i=0; i<MAXMEM; i++)
        {
            if(connfd[i] == -1)
                break;
        }
        // accept 从listen接受的连接队列中取得一个连接
        connfd[i] = accept(listenfd, (struct sockaddr *)&cli_addr, &len);
        if(connfd[i] < 0)
        {
            perror("fail to accept");
        }
        ticks = time(NULL);
        //sprintf(buff, "%.24s\r\n", ctime(&ticks));
        printf("%.24s\n\tconnect from: %s, port %d\n",\
        ctime(&ticks),\
        inet_ntop(AF_INET,\
        &(cli_addr.sin_addr),\
        buff, BUFFSIZE),
        ntohs(cli_addr.sin_port)); 

        /*针对当前套接字创建一个线程,对当前套接字的消息进行处理*/
        pthread_create(malloc(sizeof(pthread_t)), NULL, (void *)(&rcv_snd), (void *)i);
    }
    return 0;
}

void quit(void)
{
    char msg[10];
    while(1)
    {
        scanf("%s", msg);     
        if(strcmp(msg, "quit") == 0)
        {
            printf("Byebye... \n");
            close(listenfd);
            exit(0);
        }
    }
}

void rcv_snd(int n)
{
    int len, i;
    char name[32], mytime[32], buf[BUFFSIZE];
    time_t ticks;
    int ret;

    // 获取此线程对应的套接字用户的名字
    write(connfd[n], "your name: ", strlen("your name: "));
    len = read(connfd[n], name, 32);
    if(len > 0)
        name[len-1] = '\0';     // 去除换行符
    strcpy(buf, name);
    strcat(buf, "\tjoin in\n\0");
    // 把当前用户的加入 告知所有用户
    for(i=0; i<MAXMEM; i++)
    {
        if(connfd[i] != -1)
            write(connfd[i], buf, strlen(buf));
    }

    while(1)
    {
        char temp[BUFFSIZE];
        if((len=read(connfd[n], temp, BUFFSIZE)) > 0)
        {
            temp[len-1] = '\0';
            // 当用户输入bye时,当前用户退出
            if(strcmp(temp, "bye") == 0)
            {
                close(connfd[n]);
                connfd[n] = -1;
                pthread_exit(&ret);
                printf("用户%s已退出\n",name);
            }
            ticks = time(NULL);
            sprintf(mytime, "%.24s\r\n", ctime(&ticks));
            //write(connfd[n], mytime, strlen(mytime));
            strcpy(buf, name);
            strcat(buf, "\t");
            strcat(buf, mytime);
            strcat(buf, "\r\t");
            strcat(buf, temp);
            strcat(buf, "\n");

            for(i=0; i<MAXMEM; i++)
            {
                if(connfd[i] != -1)
                    write(connfd[i], buf, strlen(buf));
            }
        }
    }

}

具体工程请参考这里

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈大本事er

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值