Linux socket chat room

Table of contents

1. Operation effect

1. Compile the client and server codes separately

2. Run

3. Use effect

 Two, the code

chat.h

server code 

client code


1. Operation effect

1. Compile the client and server codes separately

gcc client.c -o C -lpthread

gcc server.c -o S -lpthread

2. Run

Run the server first, 8888 is the port number

./S 8888

 Run the client again, create two clients here, and the port number should be the same as that of the server

./C 127.0.0.1 8888

         It can be seen that after the window on the lower left is opened, it will enter the registration interface; and the server will also prompt that there is a client's ip connected in, and at this time, use the window on the right to run the client

  After entering the two homepages, the server will have different ports corresponding to different clients

3. Use effect

register

         As can be seen from the above picture, after a registration is made on the lower left, it will prompt that the registration is successful, and there will be a record on the server, and the login will be performed below

         After logging in at the bottom left of the picture above, it will prompt that the login is successful, and the server will also have a record, and the window at the bottom left can enter the function home page just by pressing Enter again. Next, use the lower left window to register a user 2

         As can be seen from the figure above, after user 2 successfully registers and logs in, the user at the bottom left of the function home page will also be prompted that the user at the bottom right is online, and the server will also record it. The users on the lower left below carry out "public chat", as long as they are online users will receive the information

         In the picture above, the user sits down and enters the number 3. After selecting the function, the user will be prompted to enter. After entering, press Enter, and the user will see a message (blue font) on the lower right, as follows 

         In the picture above, you can see the blue font of "public chat information" appears on the lower right user, and the user on the right below uses "private chat" with the user on the left

         After entering 4 to select the function, it will ask who to send the message to, then enter the user's name, enter the message to send, and press Enter to send it, as follows

        You can see that the user who sat down has received the private chat information, so that everyone will not be able to see it like the public chat. Enter 5 to see who is online. Next, log out the user at the bottom right

In the picture above, you can see that only the name of your own user 1 is online, and the second user is used to query below.

 As can be seen from the figure above, after user 2 goes online, two users are found to be online

 Two, the code

chat.h

#ifndef __CHAT_H
#define __CHAT_H

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

#define SERVER_PORT 8888
#define SIZE 32
/*最大的用户数量*/
#define MAX_USER_NUM 64
/*在线用户*/
struct ONLINE{
    int fd;                     /*描述符,-1:该用户下线   >0:该用户已经登录,对应的套接字*/
    int flage;                  /*注册标志,-1 该条目没有用户信息  1:该条目有用户注册信息*/
    char name[SIZE];            /*姓名*/
    char password[SIZE];        /*密码*/
};
/*C/S通信的结构体*/
struct protocol{
    int cmd;                    /*命令*/
    int state;                  /*存储命令返回信息*/
    char name[SIZE];            /*用户姓名*/
    char data[MAX_USER_NUM];    /*数据*/
};
/*cmd(命令类型)*/
#define BROADCAST       0X00000001          /*广播数据*/
#define PRIVATE         0X00000002          /*私聊*/
#define REGISTE         0X00000004          /*注册*/
#define LOGIN           0X00000008          /*登录*/
#define ONLINEUSER      0X00000010          /*显示在线用户*/
#define LOGOUT          0X00000020          /*退出*/

/*return code(服务器处理结果返回值)*/
#define OP_OK               0X80000000      /*操作成功*/
#define ONLINEUSER_OK       0X80000001      /*显示在线用户,未结束*/
#define ONLINEUSER_OVER     0X88888888      /*显示在线用户,已经发送完毕*/
#define NAME_EXIST          0X80000003      /*注册信息,用户名已经存在*/
#define NAME_PWD_NMATCH     0X80000004      /*登录时,输入的用户名密码不对 */
#define USER_LOGED          0X80000005      /*登录时,提示该用户已经在线*/
#define USER_NOT_REGIST     0X80000006      /*登录时,提示用户没有注册*/

#endif


server code 

#include "chat.h"
#include <stdio.h>
#include <unistd.h>

struct ONLINE online[MAX_USER_NUM];
/*将用户注册信息存储到在线用户列表中*/
int add_user(int socket_fd, struct protocol *msg) {
  int i, index = -1;
  char buf[128] = {0};
  /*历在线用户列表数组,从第一个开始找没有被占用的位置,将用户的注册信息存储到该位置*/
  for (i = 0; i < MAX_USER_NUM; i++) { /*判断当前位置是否已经被占用*/
    if (online[i].flage == -1) {       /* 标记该位置为已占用*/
      online[i].flage = 1;
      strcpy(online[i].name, msg->name);
      strcpy(online[i].password, msg->data);
      printf("regist %s to %d \n", msg->name, i);
      index = i;
      return index; /* 返回存储用户信息的位置*/
    }
  }
  return index;
}
/*从在线列表中删除
 *在聊天室程序中将在线用户列表中指定位置的客户端信息删除,
 *并向所有在线客户端发送某用户下线通知
 */
void del_user_online(int index) {
  int i;
  char buf[128] = {0};
  /*通过比较传入的 index 变量与 0 的大小,来判断传入参数是否合法
   *如果 index 小于 0,代表传入参数无效,直接返回
   */
  if (index < 0)
    return;
  /*删除在线用户列表中相应位置的客户端信息
   *将在线用户列表中指定位置的客户端信息的 fd 字段设置为 -1,表示该客户端已下线
   */
  online[index].fd = -1;
  /*通知所有客户端,某个用户下线了
   *遍历在线用户列表中的每个客户端,fd 不为 -1
   *的客户端,向其发送某用户下线的通知消息buf
   */
  sprintf(buf, "%s offline\n", online[index].name);
  for (i = 0; i < MAX_USER_NUM; i++) {
    if (online[i].fd == -1) {
      continue;
    }
    write(online[i].fd, buf, strlen(buf));
  }
  return;
}
/*向所有在线用户广播消息*/
void broadcast(int index, struct protocol *msg) {
  int i;
  char buf[128] = {0};
  sprintf(buf, "\033[0;34m\t%s say:%s\33[0m\n", online[index].name, msg->data);
  for (i = 0; i < MAX_USER_NUM; i++) {
    if ((online[i].fd == -1) || (i == index)) /*跳过不在线的和自己*/
    {
      continue;
    }
    write(online[i].fd, buf, strlen(buf));
  }
}
/*用于判断准备登录的用户是否已经登录,并返回其在列表中的索引*/
int find_dest_user_online(int socket_fd, int *index, struct protocol *msg) {
  int i;
  /*遍历在线用户列表数组*/
  for (i = 0; i < MAX_USER_NUM; i++) {
    if (online[i].flage == -1) {
      continue;
    }
    /*如果用户名和密码都匹配*/
    if ((strcmp(msg->name, online[i].name) == 0) &&
        (strcmp(msg->data, online[i].password) == 0)) 
    {/*不在线的先建立服务器连接*/
      if (online[i].fd == -1) {
        online[i].fd = socket_fd;
        *index = i;
        return OP_OK;
      } else {/*在线的打印已经登陆在线*/
        printf("%s had login\n", online[i].name);
        return USER_LOGED;/*用户已经登陆*/
      }
    }
  }/*遍历完所有在线用户仍然没有找到匹配的用户*/
  return NAME_PWD_NMATCH;
}
/*查找在线用户列表 online 中指定用户名*/
int find_dest_user(char *name) {
  int i;
  for (i = 0; i < MAX_USER_NUM; i++) {
    if (online[i].flage == -1) {
      continue;
    }
    if (strcmp(name, online[i].name) == 0) {
      return i;
    }
  }
  return -1;
}
/*私聊功能*/
void private(int index, struct protocol *msg) {
  int dest_index;
  char buf[128];
  /*查找目标用户在在线用户列表 online 中的索引*/
  dest_index = find_dest_user(msg->name);
  if (dest_index == -1) {
    /*向发送私聊请求的用户发送一条提示消息,告诉其该用户不在线*/
    sprintf(buf, "there is no  user :%s \n", msg->name);
    write(online[index].fd, buf, strlen(buf));
    return;
  } else {
    sprintf(buf, "\033[0;34m\t%s say to %s:%s\33[0m\n", online[index].name,
                                                online[dest_index].name,
                                                msg->data);
    write(online[dest_index].fd, buf, strlen(buf));
    return;
  }
}
/*列出在线用户*/
void list_online_user(int index) {
  int i;
  struct protocol msg;
  for (i = 0; i < MAX_USER_NUM; i++) {
    /*如果该套接字已经关闭,则继续下一个循环*/
    if (online[i].fd == -1) {
      continue;
    }
    memset(&msg, 0, sizeof(msg));
    msg.cmd = ONLINEUSER;
    msg.state = ONLINEUSER_OK;
    strcpy(msg.name, online[i].name);
    printf("list online[%d].name =%s \n",i, msg.name);
     /*向客户端发送在线用户结构体的数据*/
    write(online[index].fd, &msg, sizeof(msg));
    sleep(1);
  }
  /*表示在线用户列表已经全部发送完毕*/
  msg.cmd = ONLINEUSER;
  msg.state = ONLINEUSER_OVER;
  /*用于通知客户端当前任务已完成,并结束本次传输*/
  write(online[index].fd, &msg, sizeof(msg));
}
/*注册*/
void registe(int socket_fd, int *index, struct protocol *msg) {
  int dest_index;
  char buf[128];
  struct protocol msg_back;
  msg_back.cmd = REGISTE;
  /*查找该用户名是否已经被其他用户注册*/
  dest_index = find_dest_user(msg->name);
  if (dest_index == -1) {
    *index = add_user(socket_fd, msg);
    online[*index].flage = 1;
    msg_back.state =  OP_OK;
    printf("user %s regist success!\n", msg->name);
    write(socket_fd, &msg_back, sizeof(msg_back));
    return;
  } else {/*用户名已存在*/
    msg_back.state = NAME_EXIST;
    printf("user %s exist!\n", msg->name);
    write(socket_fd, &msg_back, sizeof(msg_back));
    return;
  }
}
/*登录*/
void login(int socket_fd, int *index, struct protocol *msg) {
  int i, ret;
  char buf[128];
  struct protocol msg_back;
  msg_back.cmd = LOGIN;
  /*查找该用户名是否已经在线*/
  ret = find_dest_user_online(socket_fd, index, msg);
  if (ret != OP_OK) {/*不等于表示该用户名不存在或者该用户未登录*/
    msg_back.state = ret;
    strcpy(buf, " no this user\n");
    printf("user %s login fail!\n", msg->name);
    write(socket_fd, &msg_back, sizeof(msg_back));
    return;
  } else {/*登录成功*/
    msg_back.state = OP_OK;
    strcpy(msg_back.data, "login success\n");
    printf("user %s login success!index =%d \n", msg->name, *index);
    write(online[*index].fd, &msg_back, sizeof(msg_back));
  }
  // 通知所有客户端,某个用户上线了
  sprintf(buf, "%s online\n", online[*index].name);
  for (i = 0; i < MAX_USER_NUM; i++) {
    if (online[i].fd != -1) {
      write(online[i].fd, buf, strlen(buf));
    }
  }
}
void *func(void *arg) {
  int socket_fd = *((int *)arg);
  char buf[64];
  int len;
  int index = -1;
  struct protocol msg;
  free(arg);
  /*进入聊天*/
  while (1) {
    /*read()函数和recv()函数都可以用于从套接字中读取数据,都会阻塞
     *小于等于0说明未能成功读取任何数据或连接已关闭,因此用户被视为离线
     */
    if ((len = read(socket_fd, &msg, sizeof(msg))) <= 0) {
      printf("%s offline\n", online[index].name);
      del_user_online(index);
      close(socket_fd);
      return;
    }

    switch (msg.cmd) {
    case REGISTE:
      registe(socket_fd, &index, &msg);
      break;
    case LOGIN:
      login(socket_fd, &index, &msg);
      break;
    case BROADCAST:
      broadcast(index, &msg);
      break;
    case PRIVATE:
      private(index, &msg);
      break;
    case ONLINEUSER:
      list_online_user(index);
      break;
    default:
      break;
    }
  }
}
int main(int argc, char *argv[]) {
  int ls_fd, new_fd;
  int addr_len, clientaddr_len;
  struct sockaddr_in my_addr;
  struct sockaddr_in client_addr;
  char buf[64] = {0};
  pthread_t pid;
  int *arg, i, port_number, ret;
  /*判断输入命令行参数数量是否正确*/
  if (argc != 2) {
    fprintf(stderr, "Usage: %s [port_number]>5000\a\n", argv[0]);
    exit(-1);
  }
  /*将第二个参数转换为整数类型并赋值给变量,然后判断输入的端口参数是否小于0*/
  if ((port_number = atoi(argv[1])) < 5000) {
    fprintf(stderr, "Usage: %s [port_number]>5000\a\n", argv[0]);
    exit(-1);
  }
  /*原型:int socket(int domain, int type, int protocol);
   *创建一个网络通信端点(打开一个网络通信),成功则返回一个网络文件描述符;调用失败,则会返回-1
   *domain:AF_INET,协议族名字,代表IPv4互联网协议;如果用IPV6在后面加个6即可
   *type:SOCK_STREAM,这是用于 TCP 协议
   *protocol:0,表示为给定的通信域和套接字类型选择默认协议
   */
  if ((ls_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    perror("socket() fail\n");
    return;
  }
  /*将 server_addr 结构体变量所占用的内存区域全部清零。*/
  memset(&my_addr, 0, sizeof(struct sockaddr_in));
  /*指定套接字服务器的地址信息*/
  /*表示使用 IPv4 地址*/
  my_addr.sin_family = AF_INET;
  /*端口号设置并将主机字节顺序转换为网络字节顺序*/
  my_addr.sin_port = htons(port_number);
  /*使用了宏 INADDR_ANY
   * 来指定监听所有可用的网络接口,从而使得服务器能够接受到所有网络接口上的连接请求*/
  my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  addr_len = sizeof(struct sockaddr_in);
  /*int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
   *将参数 sockfd 指定的套接字与一个地址 addr 进行绑定,成功返回
   *0,失败情况下返回-1 sockfd:网络文件描述符 addr:服务器结构体地址
   *addrlen:addr参数所指向的结构体对应的字节长度
   */
  if (bind(ls_fd, (struct sockaddr *)(&my_addr), addr_len) == -1) {
    perror("bind() fail\n");
    exit(-1);
  }
  /*int listen(int sockfd, int backlog);
   *让服务器进程进入监听状态,监听sockfd描述符并创建一个等待连接队列,若执行成功则返回0,否则返回-1
   *sockfd:要设置为监听状态的套接字描述符
   *backlog:5,指定允许的连接数为
   *5。如果有更多的客户端连接请求到达,它们将被服务器拒绝或忽略。
   */
  if (listen(ls_fd, 5) == -1) {
    fprintf(stderr, "listen error:%s\n\a", strerror(errno));
    return;
  }
  clientaddr_len = sizeof(struct sockaddr_in);
  /*初始化online结构体*/
  for (i = 0; i < 64; i++) {
    online[i].fd = -1;
    online[i].flage = -1;
  }
  while (1) {
    /*int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
     *调用成功返回一个新的
     *socket_fd描述符,用于与客户端通信的。如果发生错误或者连接被中断,则函数返回
     *-1 函数成功返回时,addr 所指的缓冲区将被填充上客户端的地址信息,而 addrlen
     *则会被更新为实际的地址信息长度
     *调用时没有客户端请求连接(等待连接队列中也没有等待连接的请求),
     *accept()会进入阻塞状态,直到客户程序建立连接
     */
    if ((new_fd = accept(ls_fd, (struct sockaddr *)(&client_addr),
                         &clientaddr_len)) == -1) {
      fprintf(stderr, "Accept error:%s\n\a", strerror(errno));
      return;
    }
    /*char *inet_ntoa(struct in_addr in);
     *将32位IP地址转换为点分十进制形式的字符串表示
     */
    printf("Client-ip: %s\tport: %d\t\n", inet_ntoa(client_addr.sin_addr),
           client_addr.sin_port);
    /*用堆空间单独存放new_fd,防止高并发状态覆盖原栈空间的new_fd*/
    arg = malloc(sizeof(int));
    *arg = new_fd;
    /*pthread_create(pthread_t *thread,
                        const pthread_attr_t *attr,
                        void *(*start_routine) (void *),
                        void *arg);
     *创建一个新的线程,返回 0
     表示成功,否则返回一个非零的错误码,表示创建线程失败 *thread:线程标识符pid
     *attr:指定线程的属性,如果传递了 NULL,则表示使用默认属性
     *start_routine:是一个函数指针,用于指定线程启动时要执行的函数
     *arg:传递给该函数的参数
     */
    if ((ret = pthread_create(&pid, NULL, func, (void *)arg)) != 0) {
      perror("pthread_create err");
      return;
    }
  }
  close(new_fd);
  close(ls_fd);
  exit(0);
}

client code

#include "chat.h"
#include <bits/types/timer_t.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

int socket_fd, addrlen;
struct sockaddr_in server_addr;

pthread_t pid;

int login_f = -1;

void *func(void *arg) {
  int len;
  char buf[128] = {0};
  struct protocol *msg;
  while (1) {
    if (login_f != 1) {
      continue;
    }
    memset(buf, 0, sizeof(buf));
    if ((len = read(socket_fd, buf, sizeof(buf))) <= 0) {
      close(socket_fd);
      return;
    }
    msg = (struct protocol *)buf;
    /*显示在线用户*/
    if ((msg->state == ONLINEUSER_OK) && (msg->cmd == ONLINEUSER)) {
      printf("%s\t", msg->name);
      continue;
    }
    if ((msg->state == ONLINEUSER_OVER) && (msg->cmd == ONLINEUSER)) {
      printf("\n");
      continue;
    }
    buf[len] = '\0';
    printf("%s\n", buf);
  }
}
/*广播信息*/
void broadcast(int fd) {
  struct protocol msg;
  msg.cmd = BROADCAST;
  printf("say:\n#");
  scanf("%s", msg.data);
  write(fd, &msg, sizeof(msg));
}
/*私聊信息*/
void private(int fd) {
  struct protocol msg, msgback;
  msg.cmd = PRIVATE;
  printf("input name you want to talk:\n\t#");
  scanf("%s", msg.name);
  printf("\n#say:\n");
  scanf("%s", msg.data);
  write(fd, &msg, sizeof(msg));
}
/*显示在线人数*/
void list_online_user(int socket_fd) {
  struct protocol msg;
  msg.cmd = ONLINEUSER;
  write(socket_fd, &msg, sizeof(msg));
  while (1) {
    read(socket_fd, &msg, sizeof(msg));
    if (msg.state != ONLINEUSER_OK) {
      getchar();
      getchar();
      break;
    } else
      printf("%s\r\n", msg.name);
  }
}
/*注册*/
int registe(int fd) {
  struct protocol msg, msgback;
  msg.cmd = REGISTE;
  printf("input your name\n");
  scanf("%s", msg.name);
  printf("input your passwd\n");
  scanf("%s", msg.data);
  write(socket_fd, &msg, sizeof(msg));
  read(socket_fd, &msgback, sizeof(msgback));
  if (msgback.state != OP_OK) {
    printf("\033[0;31m\tName had exist,try again!\n");
    getchar();
    getchar();
    return -1;
  } else {
    printf("\033[0;31m\tRegist success!\n");
    getchar();
    getchar();
    return 0;
  }
}
/*登陆*/
int login(int fd) {
  struct protocol msg, msgback;
  msg.cmd = LOGIN;

  printf("input your name\n");
  scanf("%s", msg.name);
  printf("input your passwd\n");
  scanf("%s", msg.data);
  write(socket_fd, &msg, sizeof(msg));
  read(socket_fd, &msgback, sizeof(msgback));
  if (msgback.state != OP_OK) {
    printf("\033[0;31m\tName had exist,maybe the password is wrong,try "
           "again!\33[0m\n");
    getchar();
    getchar();
    login_f = -1;
    return NAME_PWD_NMATCH;
  } else {
    printf("\033[0;31m\tLogin success!\33[0m\n");
    getchar();
    getchar();
    login_f = 1;
    return OP_OK;
  }
}
/*退出*/
int logout(int fd) {
  close(fd);
  login_f = -1;
}
int main(int argc, char *argv[]) {
  int sel, ret;
  int port_number;
  int min_sel, max_sel;
  struct protocol msg;
  /* 检测参数个数*/
  if (argc != 3) {
    fprintf(stderr, "Usage:%s hostname portnumber\a\n", argv[0]);
    exit(-1);
  }
  /*argv2 存放的是端口号 ,读取该端口,转换成整型变量*/
  if ((port_number = atoi(argv[2])) < 5000) {
    fprintf(stderr, "Usage:%s hostname [portnumber]>5000\a\n", argv[0]);
    exit(-1);
  }
  /*创建一个 套接字*/
  if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    fprintf(stderr, "Socket Error:%s\a\n", strerror(errno));
    return;
  }
  /*填充结构体,ip和port必须是服务器的*/
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  /*转换为网络字节序*/
  server_addr.sin_port = htons(port_number);
  /*点分十进制表示的字符串形式转换成二进制 Ipv4 或 Ipv6 地址*/
  server_addr.sin_addr.s_addr = inet_addr(argv[1]);
  addrlen = sizeof(struct sockaddr_in);
  if (connect(socket_fd, (struct sockaddr *)(&server_addr), addrlen) == -1) {
    fprintf(stderr, "Connect Error:%s\a\n", strerror(errno));
    return;
  }
  /*创建线程*/
  if ((ret = pthread_create(&pid, NULL, func, NULL)) != 0) {
    perror("pthread_create err");
    return;
  }
  while (1) {
    system("clear");
    if (login_f == -1) {
      printf(
          "\033[0;33m+---------------------------------------------+\033[0m\n");
      printf("\33[0;31m\t 1、 注册 \n\33[0m");
      printf("\33[0;31m\t 2、 登录 \33[0m\n");
    } else if (login_f == 1) {
      printf(
          "\033[0;33m+---------------------------------------------+\033[0m\n");
      printf("\33[0;31m\t 3、 公聊\33[0m\n");
      printf("\33[0;31m\t 4、 私聊\33[0m\n");
      printf("\33[0;31m\t 5、 在线用户\33[0m\n");
    }
    printf("\33[0;31m\t 0、 退出\033[0m\n");
    printf(
        "\033[0;33m+---------------------------------------------+\033[0m\n");
    fflush(stdin);
    scanf("%d", &sel);
    if (sel == 0) {
      break;
    }
    if (login_f == 1) {
      min_sel = 3;
      max_sel = 5;
    } else if (login_f == -1) {
      min_sel = 1;
      max_sel = 2;
    }
    if (sel < min_sel || sel > max_sel) {
      printf("输入的数字不在范围内,请重新输入\n");
      continue;
    }
    switch (sel) {
    case 1:
      registe(socket_fd);
      break;
    case 2:
      login(socket_fd);
      break;
    case 3:
      broadcast(socket_fd);
      break;
    case 4:
      private
      (socket_fd);
      break;
    case 5:
      list_online_user(socket_fd);
      break;
    case 0:
      logout(socket_fd);
      break;
    default:
      break;
    }
    if (sel == 0) {
      exit(0);
    }
  }
}

This project learns from the realization of a Linux socket-based chat room-multithreaded server model-1 from 0_One mouthful of Linux blog-CSDN blog

Implementing a Linux socket-based chat room from 0 - a very obscure error in multi-threaded servers Blog-CSDN Blog
 Realize Linux socket-based chat room from 0-realize the public chat and private chat function of the chat room-4_One mouthful of Linux blog-CSDN blog
 

Yikou Linux blogger is powerful, experienced, and has a wide range of knowledge, and his articles are rich in content. You can pay attention to them.

Guess you like

Origin blog.csdn.net/weixin_46829095/article/details/129972039