一、注意的问题

  • 客户端的0号文件描述要设置成非阻塞,这样才能接受其他客户端发的消息。
  • 客户端的读写操作也必须设置成非阻塞。
  • 对服务端使用setsockopt函数,允许创建多个端口号相同的套接字(解决服务器先关闭而引发TIME_WAIT状态的一系列问题)

二、源代码

comm.h

#ifndef __COMM_H__
#define __COMM_H__#define NAMESIZE 16
#define DATASIZE 1024#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/socket.h>
#include <assert.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <signal.h>typedef struct Msg
{char name[NAMESIZE];char data[DATASIZE];
}Msg;

server.c

#include "comm.h"typedef struct sockfd_node{int fd;struct sockfd_node* _prev;struct sockfd_node* _next;
}sockfd_node;sockfd_node* phead = NULL;sockfd_node* buy_node(int client_fd)
{sockfd_node* new_node = (sockfd_node*)malloc(sizeof(sockfd_node));assert(new_node != NULL);new_node->fd = client_fd;new_node->_prev = NULL;new_node->_next = NULL;return new_node;
}void push_back(sockfd_node* new_node)
{if(phead->_next == NULL){phead->_next = new_node;phead->_prev = new_node;new_node->_next = phead;new_node->_prev = phead;}else{new_node->_prev = phead->_prev;new_node->_next = phead;phead->_prev->_next = new_node;phead->_prev = new_node;}
}// 删除节点
void erase(sockfd_node* del)
{del->_prev->_next = del->_next;del->_next->_prev = del->_prev;free(del);
}// 接收到 Ctrl+C 产生的 信息后,执行此函数
void handler(int n)
{sockfd_node* cur = phead->_next;while(cur != phead){sockfd_node* next = cur->_next;free(cur);cur = next;}free(phead);printf("服务器退出\n");exit(0);
}void* thread_work(void* attr)
{sockfd_node* cur = (sockfd_node*)attr;// 储存客户端发来的消息以及客户端的姓名Msg msg;memset(&msg, 0, sizeof(msg));for(;;){// 读取客户端信息int read_size = recv(cur->fd, &msg, sizeof(msg), 0);// 读到的字节个数为0,代表客户端断开连接if(read_size == 0){printf("< < < < < %s 已下线 < < < < <\n", msg.name);erase(cur);break;}else{if(read_size < 0){perror("recv");break;}}printf("%s say : %s\n", msg.name, msg.data);// 把收到消息给所有用户发送sockfd_node* p = phead->_next;while(p != phead){if(p != cur){send(p->fd, &msg, sizeof(msg), 0);}p = p->_next;}}
}void server_work(int argc, char* argv[])
{int server_fd = socket(AF_INET, SOCK_STREAM, 0);if(server_fd < 0){perror("socket");}// 允许创建多个端口号相同的套接字// 解决:如果服务器主动断开连接会进入 TIME_WAIT 状态的问题int opt = 1;setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));struct sockaddr_in server_socket;server_socket.sin_family = AF_INET;server_socket.sin_addr.s_addr = inet_addr(argv[1]);server_socket.sin_port = htons(atoi(argv[2]));int ret = bind(server_fd, (struct sockaddr*)&server_socket, sizeof(server_socket));if(ret < 0){perror("bind()");close(server_fd);}if(listen(server_fd, 5) < 0){perror("listen");close(server_fd);}printf("bind and listen success, wait accept...\n");for(;;){struct sockaddr_in client_socket;socklen_t len = sizeof(client_socket);// 与客户端建立连接int client_fd = accept(server_fd, (struct sockaddr*)&client_socket, &len);if(client_fd < 0){continue;}char* ip = inet_ntoa(client_socket.sin_addr);int port = ntohs(client_socket.sin_port);printf("get accept, ip : %s, port : %d\n", ip, port);// 把连接的客户端的信息存放在链表里,统一管理sockfd_node* new_node = buy_node(client_fd);push_back(new_node);pthread_t tid = 0;pthread_create(&tid, NULL, thread_work, (void*)new_node);// 把线程设置成分离状态,使其结束后被操作系统自动回收(不会阻塞主线程)pthread_detach(tid);}close(server_fd);
}int main(int argc, char* argv[])
{// 把服务器设置成守护进程//daemon(0, 0);phead = buy_node(0);if(argc != 3){printf("Usage ./server [ip] [port]\n");return 1;}// 当按下 Ctrl+C 时,先销毁链表释放空间,然后服务器退出signal(SIGINT, handler);server_work(argc, argv);return 0;
}

client.c

#include "comm.h"int client_work(int argc, char* argv[])
{int server_fd = socket(AF_INET, SOCK_STREAM, 0);if(server_fd < 0){perror("socket");return 2;}struct sockaddr_in server_socket;//struct sockaddr_in client_socket;server_socket.sin_family = AF_INET;server_socket.sin_addr.s_addr = inet_addr(argv[1]);server_socket.sin_port = htons(atoi(argv[2]));int ret = connect(server_fd, (struct sockaddr*)&server_socket, sizeof(server_socket));if(ret < 0){perror("connect");return 3;}printf("%s connect success...\n", argv[3]);if(fcntl(0, F_SETFL, FNDELAY) < 0){perror("fcntl");return 4;}Msg msg;for(;;){memset(&msg, 0, sizeof(msg));strcpy(msg.name, argv[3]);int read_size = read(0, msg.data, sizeof(msg.data));if(read_size > 0){msg.data[read_size-1] = '\0';send(server_fd, &msg, sizeof(msg), MSG_DONTWAIT);}int recv_size = recv(server_fd, &msg, sizeof(msg), MSG_DONTWAIT);if(recv_size > 0){printf("\t\t%s say : %s\n", msg.name, msg.data);}}
}int main(int argc, char* argv[])
{if(argc != 4){printf("Usage ./server [ip] [port] [name]\n");return 1;}client_work(argc, argv);return 0;
}

TCP协议实现qq群聊相关推荐

  1. 00023.11 TCP协议编程:群聊(TCP通信原理,多线程、线程阻塞)

    系列文章目录 文章目录 系列文章目录 一.前言 一.需求 二.使用步骤 客户端 服务端 三.完整代码 客户端 服务器 一.前言 我们平时玩QQ或者微信的群聊,是怎么实现的呢? 是你发一个消息直接全部给 ...

  2. Java项目模拟QQ群聊和私聊(网络编程+多线程)

    [文末获取资源] 前几天学习了多线程,最近在学习网络编程,了解了UDP之后又学习了TCP,听一下大佬说,要看看你这两个东西掌握的怎么样,最好的办法就是写一个模拟QQ群聊和私聊,经过这几天的学习,以及不 ...

  3. Java网络编程,模拟QQ群聊功能

    Java网络编程,模拟QQ群聊功能 一.网络编程知识点简介: 1.C/S架构:Client客户端/Server服务器: 涉及到的应用:桌面的应用软件,QQ,王者荣耀 涉及到的技术:Socket网络编程 ...

  4. 多线程+SOCKET编程实现qq群聊的服务端和客户端

    多线程+SOCKET编程实现qq群聊的服务端和客户端 标签(空格分隔): 多线程 网络编程 线程同步 一.设计思路 1.服务端 每来一个客户端连接,服务端起一个线程维护: 将收到的消息转发给所有的客户 ...

  5. java模仿微信QQ群聊头像拼接,根据群聊内的用户头像拼接群聊头像,九宫格

    java模仿微信QQ群聊头像拼接,根据群聊内的用户头像拼接群聊头像,九宫格 效果图,这里只放了几张,1-9张图片都可以的,如果图片路径是从数据库查出来的相对路径,记得加上绝对路径否则会报找不到读取文件 ...

  6. 如何创建一个以chatgpt为基础的QQ群聊机器人的流程细节

    为了创建一个基于 ChatGPT 的 QQ 群聊机器人,您需要遵循以下步骤: 1.获取 ChatGPT 模型:您可以在 OpenAI 的 GPT-3 模型中获取 ChatGPT 模型. 2.训练模型: ...

  7. qq群聊机器人接入ChatGPT-简介和源码

    qq群聊机器人接入ChatGPT 最近 ChatGPT 很火,也注册了账号玩了玩,确实灰常强大.但是也有的小伙伴可能没办法注册账号,我就想着把qq群机器人接入ChatGPT. 过程还是比较简单顺利的. ...

  8. QQ 群聊美少女语音AI(ChatGPT版)

    QQ 群聊美少女语音AI ✨ 基于 go-cqhttp 以及 VITS-fast-fine-tuning + revChatGPT 实现 ✨ Combination of ChatGPT and VI ...

  9. php仿qq群聊,用Python写一个类似qq群聊的聊天室

    用Python写一个聊天室 功能 : 类似qq群聊 1. 进入聊天室需要输入姓名,姓名不能重复 2. 有人进入聊天室会向其他人发送通知 xxx 进入了聊天室 3. 一个人发消息,其他人会收到消息 xx ...

最新文章

  1. html,h4,h5的区别,(转)H5和H4的对比听感
  2. 十分钟学习nginx
  3. 机器学习(二)监督学习
  4. mysql查询的时候会涉及到锁_Mysql 查询 锁的问题?
  5. python计算不规则图形面积_python opencv中的不规则形状检测和测量
  6. STM32H743-梳理ADC模数转换器在CubeMX上的配置
  7. 替换后的最长重复字符
  8. 虎牙李萌:网络视听内容的工业化生产正在提速
  9. LeetCode 22. Generate Parentheses
  10. 活着是一种罪过,是上帝对你的另一种眷顾,叫做惩罚!活着痛苦!
  11. 量化分析师的python日记_量化分析师的Python日记【第1天:谁来给我讲讲Python?】...
  12. 【费用预测】基于matlab粒子群算法优化ELM神经网络预测费用【含Matlab源码 1378期】
  13. C++多线程函数_beginthread/_beginthreadex/CreateThread
  14. centos中使用goaccess分析nginx日志,goaccess分析多个nginx日志
  15. cdc有哪些rapper_CDC说唱会馆在圈内是一个什么样的存在
  16. Java初始化大乱斗
  17. excel与access结合运用_当excel不够用时,如何利用Access进行数据分析?
  18. 企业托管服务器有什么优势
  19. Oval验证框架学习
  20. 优酷通过世界杯,让所有人知道:优酷真的优,真的酷!

热门文章

  1. 基于EasyX来使用中点算法画线
  2. 一度智信:如何避免电商店铺被处罚
  3. Linux如何根据PID查找父进程PPid
  4. 行动瑜伽:何谓不执的行动
  5. Windows 7定时关机命令(很实用)
  6. Java、JSP银行柜员业务绩效考核系统
  7. oak深度相机入门教程-识别头部的姿势
  8. python实训日志_Logbook:Python 快速日志记录实践
  9. 弘辽科技:拼多多怎么去看成交关键词?拼多多怎么查看成交词?
  10. 深圳地铁和出租车上线区块链电子发票功能