LinuxC语言のUDP简易聊天室 sokcet

Posted MangataTS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LinuxC语言のUDP简易聊天室 sokcet相关的知识,希望对你有一定的参考价值。

设计思路

考虑到只是一个简易版本的UDP聊天服务,所以很多不完善的地方

服务器

服务器我是开了一个父子进程,分别负责的

  • 接受客户端的消息&&发送某一个客户端的信息
  • 服务器的命令终端(只不过没有实现,只写了广播功能)

然后我封装了一个数据结构,每当客户端发一个包过来,这个包由三部分组成

  • 消息类型(login、cheat、end)
  • 发送者的姓名
  • 发送者的消息构成

然后我们分三种情况来解析这三种消息就行,具体实现就是一个简单的链表结构,详情可以看代码。

CODE

/*
 *
 * 缺点:
 * 1.以奇怪的方式下线的用户不能清理
 * 
 */


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#define false 0
#define true 1

#define N 1024

struct MSG{//消息封装
    char type;
    char name[20];
    char text[N];
};

struct Node {//用户节点信息
    struct sockaddr_in addr;
    struct Node * next;
};

struct Node * init() {//用户节点初始化
    struct Node * p = (struct Node *)malloc(sizeof(struct Node));
    memset(p,0,sizeof(struct Node));
    p->next = NULL;
    return p;
}

const char *ip = "127.0.0.1";
const int port = 8000;
struct Node *head;

void login(int sockfd,struct MSG msg,struct sockaddr_in clientaddr) {//登陆消息
    struct Node *p = head;
    strcpy(msg.text,msg.name);
    msg.text[strlen(msg.text) - 1]  = '\\0';
    strcat(msg.text," 已经上线欢迎来聊^_^~");
    //puts(msg.text);
    int cnt = 0;
    while(p->next) {//发给已经上线的用户
        if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&p->next->addr,sizeof(p->next->addr)) < 0){
            perror("online error");
        }
        p = p->next;
        cnt++;
    }
    struct Node *k = (struct Node *)malloc(sizeof(struct Node));
    k->addr = clientaddr;
    k->next = NULL;
    p->next = k;
    printf("当前总用户数:%d \\n上线用户的port: %u \\n",cnt,ntohl(clientaddr.sin_port));
}

void chat(int sockfd,struct MSG msg,struct sockaddr_in clientaddr) {//聊天消息处理函数
    struct Node *p = head;   
    char str[N]= {0};
    //puts(msg.name);
    //puts(msg.text);
    sprintf(str,"%s : %s",msg.name,msg.text);
    strcpy(msg.text,str);
    puts(msg.text);//输出到服务器终端
    while(p->next) {
        if (memcmp(&clientaddr, &p->next->addr, sizeof(clientaddr)) != 0) {
            if (sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *) &p->next->addr, sizeof(p->next->addr)) < 0) {
                perror("chat sendto error");
            }
        }
        p = p->next;
    }
}

void quit(int sockfd,struct MSG msg,struct sockaddr_in clientaddr) {//退出函数
    struct Node *p = head;
    struct Node *q = NULL;
    sprintf(msg.text,"%s用户已下线",msg.name);
    puts(msg.text);
    while(p->next) {//因为头号节点是服务器
        if(memcmp(&clientaddr,&p->next->addr,sizeof(clientaddr)) == 0) {
            q = p->next;
            p->next = q->next;
            free(q);
            q=NULL;//free后将指针赋为NULL
        }
        else {
            sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&p->next->addr,sizeof(p->next->addr));
        }
        p = p->next;
    }
}

int socketfd;

void my_end(int sign_no) {//这个是注册一个ctr+c退出时候的一个信号处理
    close(socketfd);
    exit(1);
}

int main() {
    signal(SIGINT,my_end);
    head = init();
    struct sockaddr_in serveraddr,clientaddr;
    bzero(&serveraddr,sizeof(serveraddr));
    bzero(&clientaddr,sizeof(clientaddr));

    struct MSG msg;
    socklen_t addrlen = sizeof(struct sockaddr);
    char buf[N] = "";
    if((socketfd = socket(AF_INET, SOCK_DGRAM, 0))< 0) {
        perror("socket error");
    }
    printf("socketfd = %d\\n",socketfd);

    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(port);
    printf("serveraddr.sin_port = %d\\n", htonl(port));
    if(bind(socketfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) { // bind绑定
        perror("bind error");
    }
    printf("serveraddr.sin_port = %u\\n", serveraddr.sin_port);
    printf("Accepting connections ...\\n");

    pid_t pid = fork();
    if(pid < 0) {
        perror("fork error");
    }
    if(pid == 0) {//服务器的命令终端(只不过没有实现,只写了广播功能)
        memset(&msg,0,sizeof(msg));
        strncpy(msg.name,"server",6);
        msg.type = 'C';
        while(1) {
            fgets(buf,N,stdin);
            strcpy(msg.texxt,buf);
            msg.text[strlen(msg.text)-1] = '\\0';
            puts(msg.text);
            sendto(socketfd,&msg,sizeof(msg),0,(struct sockaddr*)&serveraddr,addrlen);
        }
    }
    else {//接受客户端的消息&&发送某一个客户端的信息
        while(1) {
           // puts("father");
            if(recvfrom(socketfd,&msg,sizeof(msg),0,(struct sockaddr *)&clientaddr,&addrlen) <= 0){
                perror("father recvfrom error");
            }
            if(msg.type == 'L') {//login
                puts("YES");
                login(socketfd,msg,clientaddr);
            }
            else if(msg.type == 'C') {//chat
                chat(socketfd,msg,clientaddr);
            }
            else if(msg.type == 'Q') {//quit
                quit(socketfd,msg,clientaddr);
            }
        }
    }
    close(socketfd);//正常退出的close处理
    return 0;
}

客户端

客户端这边的话就稍微简单一点了,因为只有接受和发送这两个事件

  • 发送事件

    • 登陆发送一次
    • 退出发送一次
    • 聊天一直发送
  • 接收事件

    其实我们只用接受服务器给我们发送的消息即可

总体逻辑非常的简单,详情请看代码

CODE

//
// Created by Mangata on 2021/10/27.
//
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

#define N 1024
struct MSG{
    char type;
    char name[20];
    char text[N];
}msg;

int port = 8000;
char *ip = "127.0.0.1";

//int socketfd;
//struct sockaddr_in serveraddr;

/*void my_fun(int signal_) {
    msg.type = 'Q';
    if(sendto(socketfd,&msg,sizeof msg,0,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) {
        perror("Q - sendto error");
         exit(1);
     }
     printf("You Quit !");
     kill(getppid(),SIGKILL);
     exit(1);
}
*/
int main() {
    struct MSG msg;
    int socketfd;
    struct sockaddr_in serveraddr;
    bzero(&serveraddr,sizeof(serveraddr));
    socklen_t addrlen;
    //signal(SIGINT, my_fun);
    if((socketfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket failed");
        printf("socket failed");
        exit(1);
    }

    serveraddr.sin_family = AF_INET;
//    serveraddr.sin_addr.s_addr = inet_addr(ip);
    serveraddr.sin_port = htons(port);
    inet_pton(AF_INET,"127.0.0.1",&serveraddr.sin_addr);

    printf("输入用户名:");
    fgets(msg.name,sizeof(msg.name),stdin);
    msg.name[strlen(msg.name)-1]='\\0';
    msg.text[0]='\\0';
    msg.type= 'L';
    printf("name = %s\\t type = %c\\n",msg.name,msg.type);
    if(sendto(socketfd,&msg,sizeof(msg),0,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) {//发送登陆信号
        perror("fail to sendto");
        printf("fail to sendto");
        exit(1);
    }
    pid_t pid=fork();
    if(pid == 0) {//子进程输入数据
        while(1) {
            fgets(msg.text,N,stdin);
            msg.text[strlen(msg.text)-1]='\\0';
            printf("send msg = %s\\n",msg.text);
            if(!strncmp(msg.text,"quit",4)) {
                msg.type = 'Q';
                if(sendto(socketfd,&msg,sizeof msg,0,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) {
                    perror("Q - sendto error");
                    exit(1);
                }
                printf("You Quit !");
                kill(getppid(),SIGKILL);
                exit(1);
            }
            else {
                msg.type = 'C';
                if(sendto(socketfd,&msg,sizeof msg,0,(struct sockaddr*)&serveraddr,sizeof(serveraddr)) < 0) {
                    printf("C - sendto error");
                    exit(1);
                }
            }
        }

    }
    else {
        while(1)
        {
            recvfrom(socketfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, &addrlen);
            puts(msg.text);
        }
    }
    close(socketfd);
    return 0;
}


关于BUG

由于写的时间比较短,有一些发现的bug,但是还不清楚原因

  • 当所有的用户退出后,服务器会段错误然后宕机
  • 用户推出后会有一个用户收不到退出消息(这个原因是删除退出用户的链表的问题,只不过不想修了)
  • 服务器断开的时候,客户端不会有反馈,emmm这个其实只是我没写,逻辑还是蛮简单的,可以给用户发送一个Q类型的消息,然后让用户也自动退出即可
    其他倒是没有什么东西了,服务器和用户端的端口是设死了的,所以想要更改的直接改源码就行

效果图

服务器端

客户端口


over

以上是关于LinuxC语言のUDP简易聊天室 sokcet的主要内容,如果未能解决你的问题,请参考以下文章

java scoket (UDP通信模型)简易聊天室

UDP实现一个简易的聊天室 (Unity&&C#完成)

Python制作一个私人的简易聊天器,邀请ta来激情的聊天吧,搭建UDP网络通信模型

学习JavaSE TCP/IP协议与搭建简易聊天室

python下基于sokcet的tcp通信——入门篇

网络编程之java简易聊天室实现