Skip to content
On this page

C语言UDP聊天室


标签:代码片段C/unix  
c
#ifndef __HEAD_H__
#define __HEAD_H__
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <unistd.h>

#define ERR_MSG(msg)                                                           \
  do {                                                                         \
    fprintf(stderr, "line:%d\n", __LINE__);                                    \
    perror(msg);                                                               \
  } while (0)

#define SERVER_IP "192.168.0.170"
#define SERVER_PORT 5678
#endif
c
#include "head.h"

typedef struct node {
  struct sockaddr_in addr;
  struct node *next;
} *Node;

Node fdlist = NULL;

/**
 * @brief 比较两个地址信息结构体是否为同一个
 */
int addrcmp(struct sockaddr_in addr1, struct sockaddr_in addr2) {
  return addr1.sin_addr.s_addr == addr2.sin_addr.s_addr &&
         addr1.sin_port == addr2.sin_port;
}

/**
 *@brief 添加一个地址信息结构体(头插)
 */
int addAddr(struct sockaddr_in addr) {
  Node node = (Node)malloc(sizeof(Node));
  node->addr = addr;
  node->next = fdlist;
  fdlist = node;
  return 0;
}

/**
 *@brief 删除一个地址信息结构体(查找 + 删除)
 */
void delAddr(struct sockaddr_in addr) {
  Node prev = NULL;
  Node cur = fdlist;
  while (cur != NULL && !addrcmp(cur->addr, addr)) {
    prev = cur;
    cur = cur->next;
  }
  if (cur == NULL)
    return;
  if (prev == NULL)
    fdlist = cur->next;
  else
    prev->next = cur->next;
  free(cur);
}

typedef struct CallbackData {
  int sfd;    /* 服务器的 socket */
  char *msg;  /* 发送的消息 */
  int msglen; /* 消息长度,本程序都是 512 */
} CallbackData;

/**
 *@brief 遍历链表
 */
int eachAddr(int (*fun)(struct sockaddr_in addr, CallbackData data),
             CallbackData data) {
  Node node = fdlist;
  while (node) {
    if (fun(node->addr, data) < 0) {
      ERR_MSG("callback in eachfd()");
      return -1;
    }
    node = node->next;
  }
  return 0;
}

/**
 * @brief 封装一下 sendto 让它可以作为回调传入链表遍历
 */
int mysendto(struct sockaddr_in addr, CallbackData data) {
  // 分开使用打包的参数
  int sfd = data.sfd;
  int msglen = data.msglen;
  char *msg = data.msg;
  // 使用addr进行相关操作
  // mysend(sfd, msg, msglen, addr);
  if (sendto(sfd, msg, msglen, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
    ERR_MSG("sendto()");
    exit(-1);
  }
  return 0;
}

/**
 * @brief 发送广播消息
 */
void boardcast(int sfd, char *msg, int msglen);

/**
 * @brief 服务器终端输入,发送给所有连接客户端
 */
void *on_child(void *arg);

/**
 * @brief 用户登录
 */
void on_login(int sfd, char *name, struct sockaddr_in cin);

/**
 * @brief 用户发消息
 */
void on_chat(int sfd, char *name, char *msg, struct sockaddr_in cin);

/**
 * @brief 用户退出
 */
void on_quit(int sfd, char *name, struct sockaddr_in cin);

int main() {
  int sfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (sfd < 0) {
    ERR_MSG("socket()");
    return -1;
  }

  struct sockaddr_in sin;
  sin.sin_family = AF_INET;
  sin.sin_port = htons(SERVER_PORT);
  sin.sin_addr.s_addr = inet_addr(SERVER_IP);

  if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
    ERR_MSG("bind()");
    return -1;
  }

  /* 子线程处理服务器输入数据 */
  pthread_t tid;
  if (pthread_create(&tid, NULL, on_child, (void *)&sfd) != 0) {
    fprintf(stderr, "line: %d pthread_create failed\n", __LINE__);
    return -1;
  }
  pthread_detach(tid);

  /* 主线程接收客户端传入数据 */
  struct sockaddr_in cin;
  cin.sin_family = AF_INET;
  socklen_t addrlen = sizeof(cin);
  int res = -1;
  char buf[149] = {0};

  while (1) {
    bzero(buf, sizeof(buf));
    res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr *)&cin, &addrlen);
    if (res < 0) {
      ERR_MSG("recvfrom()");
      return -1;
    }

    char type = buf[0]; /* 'L', 'C', 'Q' */
    char *name = buf + 2;
    char *msg = name + strlen(name) + 1;

    if (type == 'L')
      on_login(sfd, name, cin);
    if (type == 'C')
      on_chat(sfd, name, msg, cin);
    if (type == 'Q')
      on_quit(sfd, name, cin);
  }

  close(sfd);
  return 0;
}

void on_login(int sfd, char *name, struct sockaddr_in cin) {
  /* 添加到链表 */
  addAddr(cin);

  char buf[512] = {0};
  sprintf(buf, "用户[%s]登入聊天室", name);
  boardcast(sfd, buf, 512);
}

void on_quit(int sfd, char *name, struct sockaddr_in cin) {
  delAddr(cin);

  char buf[512] = {0};
  sprintf(buf, "用户[%s]退出聊天室", name);
  boardcast(sfd, buf, 512);
}

void on_chat(int sfd, char *name, char *msg, struct sockaddr_in cin) {
  char buf[512] = {0};
  sprintf(buf, "用户[%s]说:%s", name, msg);
  boardcast(sfd, buf, 512);
}

void *on_child(void *arg) {
  int sfd = *(int *)arg;
  char buf[512] = {0};
  while (1) {
    bzero(buf, sizeof(buf));
    scanf(" %s", buf);
    boardcast(sfd, buf, 512);
  }
}

void boardcast(int sfd, char *msg, int msglen) {
  CallbackData data = {.msg = msg, .sfd = sfd, .msglen = msglen};
  eachAddr(mysendto, data);
}
c
#include "head.h"

void *on_child(void *arg) {
  int sfd = *(int *)arg;
  char buf[512] = {0};
  while (1) {
    bzero(buf, sizeof(buf));
    if (recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL) < 0) {
      ERR_MSG("recvfrom");
      break;
    }
    printf("[服务器消息]%s\n", buf);
  }
  pthread_exit(NULL);
}

int main() {
  int isOnline = 0;
  int sfd = socket(AF_INET, SOCK_DGRAM, 0);

  struct sockaddr_in sin;
  sin.sin_family = AF_INET;
  sin.sin_port = htons(SERVER_PORT);
  sin.sin_addr.s_addr = inet_addr(SERVER_IP);

  /* 子线程接收服务器端数据 */
  pthread_t tid;
  if (pthread_create(&tid, NULL, on_child, (void *)&sfd) != 0) {
    fprintf(stderr, "line: %d pthread_create failed\n", __LINE__);
    return -1;
  }
  pthread_detach(tid);

  char buf[149] = {0};
  char msg[128] = {0};
  char name[20] = {0};
  char type;
  while (1) {
    bzero(buf, sizeof(buf));
    bzero(msg, sizeof(msg));
    if (!isOnline) {
      type = 'L';
      printf("请输入您的用户名: ");
      scanf(" %s", name);
      isOnline = 1;
    } else {
      scanf(" %s", msg);
      type = strncmp(msg, "quit", 4) ? 'C' : 'Q';
    }
    sprintf(buf, "%c%c%s%c%s", type, 0, name, 0, msg);
    if (sendto(sfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, sizeof(sin)) <
        0) {
      ERR_MSG("sendto");
      return -1;
    }
    if (type == 'Q')
      break;
  }

  close(sfd);
  return 0;
}

Last updated: