==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍


socket多人聊天程序C语言版(一)地址:
http://blog.csdn.net/qq_18297675/article/details/52819975

1V1实现了,1V多也就容易了。不过相对于1V1的程序,我经过大改,采用链表来动态管理。这样效率真的提升不少,至少CPU使用率稳稳的在20以下,不会飙到100了。用C语言写这个还是挺费时间的,因为什么功能函数都要自己写,不像C++有STL库可以用,MFC写就更简单了,接下来我还会更新MFC版本的多人聊天程序。好了,废话少说,进入主题。

这个程序要解决的问题如下:
1.CPU使用率飙升问题 -->用链表动态管理

2.用户自定义聊天,就是想跟谁聊跟谁聊 --> _Client结构体中新增一个ChatName字段,用来表示要和谁聊天,这个字段很重要,因为server转发消息的时候就是按照这个字段来转发的。

3.中途换人聊天,就是聊着聊着,想和别人聊,而且自己还一样能接收到其它人发的消息 --> 这个就要小改客户端的代码了,可以在发送聊天消息之前插入一段代码,用来切换聊天用户。具体做法就是,用getch()函数读取ESC键,如果用户按了这个键,则表示想切换用户,然后会输出一行提示,请输入chat name,就是想要和谁聊天的名字,发送这个名字过去之前要加一个标识符,表示这个消息是切换聊天用户消息。然后server接收到这个消息后会判断第一个字符是不是标识符,第二个字符不能是标识符,则根据这个name来查找当前在线的用户,然后修改想切换聊天用户的ChatName为name这个用户。(可能有点绕,不懂的看代码就清晰易懂了~)

4.下线后提醒对方 --> 还是老套路,只要send对方不通就当对方下线了。

编写环境:WIN10,VS2015
效果图:
为了方便就不用虚拟机演示了,但是在虚拟机是肯定可以的,应该说只要是局域网,能互相ping通就可以使用这个程序。

Server code:

链表头文件:

#ifndef _CLIENT_LINK_LIST_H_
#define _CLIENT_LINK_LIST_H_#include <WinSock2.h>#include <stdio.h>//客户端信息结构体
typedef struct _Client
{SOCKET sClient;            //客户端套接字char buf[128];          //数据缓冲区char userName[16];       //客户端用户名char IP[20];            //客户端IPunsigned short Port; //客户端端口UINT_PTR flag;           //标记客户端,用来区分不同的客户端char ChatName[16];     //指定要和哪个客户端聊天_Client* next;         //指向下一个结点
}Client, *pClient;/*
* function  初始化链表
* return    无返回值
*/
void Init();/*
* function  获取头节点
* return    返回头节点
*/
pClient GetHeadNode();/*
* function  添加一个客户端
* param     client表示一个客户端对象
* return    无返回值
*/
void AddClient(pClient client);/*
* function  删除一个客户端
* param     flag标识一个客户端对象
* return    返回true表示删除成功,false表示失败
*/
bool RemoveClient(UINT_PTR flag);/*
* function  根据name查找指定客户端
* param     name是指定客户端的用户名
* return    返回一个client表示查找成功,返回INVALID_SOCKET表示无此用户
*/
SOCKET FindClient(char* name);/*
* function  根据SOCKET查找指定客户端
* param     client是指定客户端的套接字
* return    返回一个pClient表示查找成功,返回NULL表示无此用户
*/
pClient FindClient(SOCKET client);/*
* function  计算客户端连接数
* param     client表示一个客户端对象
* return    返回连接数
*/
int CountCon();/*
* function  清空链表
* return    无返回值
*/
void ClearClient();/*
* function  检查连接状态并关闭一个连接
* return 返回值
*/
void CheckConnection();/*
* function  指定发送给哪个客户端
* param     FromName,发信人
* param     ToName,   收信人
* param     data,     发送的消息
*/
void SendData(char* FromName, char* ToName, char* data);#endif //_CLIENT_LINK_LIST_H_

链表cpp文件:

#include "ClientLinkList.h"pClient head = (pClient)malloc(sizeof(_Client)); //创建一个头结点/*
* function  初始化链表
* return    无返回值
*/
void Init()
{head->next = NULL;
}/*
* function  获取头节点
* return    返回头节点
*/
pClient GetHeadNode()
{return head;
}/*
* function  添加一个客户端
* param     client表示一个客户端对象
* return    无返回值
*/
void AddClient(pClient client)
{client->next = head->next;  //比如:head->1->2,然后添加一个3进来后是head->next = client;        //3->1->2,head->3->1->2
}/*
* function  删除一个客户端
* param     flag标识一个客户端对象
* return    返回true表示删除成功,false表示失败
*/
bool RemoveClient(UINT_PTR flag)
{//从头遍历,一个个比较pClient pCur = head->next;//pCur指向第一个结点pClient pPre = head;      //pPre指向head while (pCur){// head->1->2->3->4,要删除2,则直接让1->3if (pCur->flag == flag){pPre->next = pCur->next;closesocket(pCur->sClient);  //关闭套接字free(pCur);   //释放该结点return true;}pPre = pCur;pCur = pCur->next;}return false;
}/*
* function  查找指定客户端
* param     name是指定客户端的用户名
* return    返回socket表示查找成功,返回INVALID_SOCKET表示无此用户
*/
SOCKET FindClient(char* name)
{//从头遍历,一个个比较pClient pCur = head;while (pCur = pCur->next){if (strcmp(pCur->userName, name) == 0)return pCur->sClient;}return INVALID_SOCKET;
}/*
* function  根据SOCKET查找指定客户端
* param     client是指定客户端的套接字
* return    返回一个pClient表示查找成功,返回NULL表示无此用户
*/
pClient FindClient(SOCKET client)
{//从头遍历,一个个比较pClient pCur = head;while (pCur = pCur->next){if (pCur->sClient == client)return pCur;}return NULL;
}/*
* function  计算客户端连接数
* param     client表示一个客户端对象
* return    返回连接数
*/
int CountCon()
{int iCount = 0;pClient pCur = head;while (pCur = pCur->next)iCount++;return iCount;
}/*
* function  清空链表
* return    无返回值
*/
void ClearClient()
{pClient pCur = head->next;pClient pPre = head;while (pCur){//head->1->2->3->4,先删除1,head->2,然后free 1pClient p = pCur;pPre->next = p->next;free(p);pCur = pPre->next;}
}/*
* function 检查连接状态并关闭一个连接
* return 返回值
*/
void CheckConnection()
{pClient pclient = GetHeadNode();while (pclient = pclient->next){if (send(pclient->sClient, "", sizeof(""), 0) == SOCKET_ERROR){if (pclient->sClient != 0){printf("Disconnect from IP: %s,UserName: %s\n", pclient->IP, pclient->userName);char error[128] = { 0 };   //发送下线消息给发消息的人sprintf(error, "The %s was downline.\n", pclient->userName);send(FindClient(pclient->ChatName), error, sizeof(error), 0);closesocket(pclient->sClient);   //这里简单的判断:若发送消息失败,则认为连接中断(其原因有多种),关闭该套接字RemoveClient(pclient->flag);break;}}}
}/*
* function  指定发送给哪个客户端
* param     FromName,发信人
* param     ToName,   收信人
* param     data,     发送的消息
*/
void SendData(char* FromName, char* ToName, char* data)
{SOCKET client = FindClient(ToName);   //查找是否有此用户char error[128] = { 0 };int ret = 0;if (client != INVALID_SOCKET && strlen(data) != 0){char buf[128] = { 0 };sprintf(buf, "%s: %s", FromName, data);   //添加发送消息的用户名ret = send(client, buf, sizeof(buf), 0);}else//发送错误消息给发消息的人{if(client == INVALID_SOCKET)sprintf(error, "The %s was downline.\n", ToName);elsesprintf(error, "Send to %s message not allow empty, Please try again!\n", ToName);send(FindClient(FromName), error, sizeof(error), 0);}if (ret == SOCKET_ERROR)//发送下线消息给发消息的人{sprintf(error, "The %s was downline.\n", ToName);send(FindClient(FromName), error, sizeof(error), 0);}}

server cpp:

/*#include <WinSock2.h>
#include <process.h>
#include <stdlib.h>
#include "ClientLinkList.h"
#pragma comment(lib,"ws2_32.lib")SOCKET g_ServerSocket = INVALID_SOCKET;      //服务端套接字
SOCKADDR_IN g_ClientAddr = { 0 };           //客户端地址
int g_iClientAddrLen = sizeof(g_ClientAddr);typedef struct _Send
{char FromName[16];char ToName[16];char data[128];
}Send,*pSend;//发送数据线程
unsigned __stdcall ThreadSend(void* param)
{pSend psend = (pSend)param;  //转换为Send类型SendData(psend->FromName, psend->ToName, psend->data); //发送数据return 0;
}//接受数据
unsigned __stdcall ThreadRecv(void* param)
{int ret = 0;while (1){pClient pclient = (pClient)param;if (!pclient)return 1;ret = recv(pclient->sClient, pclient->buf, sizeof(pclient->buf), 0);if (ret == SOCKET_ERROR)return 1;if (pclient->buf[0] == '#' && pclient->buf[1] != '#') //#表示用户要指定另一个用户进行聊天{SOCKET socket = FindClient(&pclient->buf[1]);    //验证一下客户是否存在if (socket != INVALID_SOCKET){pClient c = (pClient)malloc(sizeof(_Client));c = FindClient(socket);                        //只要改变ChatName,发送消息的时候就会自动发给指定的用户了memset(pclient->ChatName, 0, sizeof(pclient->ChatName));   memcpy(pclient->ChatName , c->userName,sizeof(pclient->ChatName));}else  send(pclient->sClient, "The user have not online or not exits.",64,0);continue;}pSend psend = (pSend)malloc(sizeof(_Send));//把发送人的用户名和接收消息的用户和消息赋值给结构体,然后当作参数传进发送消息进程中memcpy(psend->FromName, pclient->userName, sizeof(psend->FromName));memcpy(psend->ToName, pclient->ChatName, sizeof(psend->ToName));memcpy(psend->data, pclient->buf, sizeof(psend->data));_beginthreadex(NULL, 0, ThreadSend, psend, 0, NULL);Sleep(200);}return 0;
}//开启接收消息线程
void StartRecv()
{pClient pclient = GetHeadNode();while (pclient = pclient->next)_beginthreadex(NULL, 0, ThreadRecv, pclient, 0, NULL);
}//管理连接
unsigned __stdcall ThreadManager(void* param)
{while (1){CheckConnection();  //检查连接状况Sleep(2000);     //2s检查一次}return 0;
}//接受请求
unsigned __stdcall ThreadAccept(void* param)
{_beginthreadex(NULL, 0, ThreadManager, NULL, 0, NULL);Init(); //初始化一定不要再while里面做,否则head会一直为NULL!!!while (1){//创建一个新的客户端对象pClient pclient = (pClient)malloc(sizeof(_Client));//如果有客户端申请连接就接受连接if ((pclient->sClient = accept(g_ServerSocket, (SOCKADDR*)&g_ClientAddr, &g_iClientAddrLen)) == INVALID_SOCKET){printf("accept failed with error code: %d\n", WSAGetLastError());closesocket(g_ServerSocket);WSACleanup();return -1;}recv(pclient->sClient, pclient->userName, sizeof(pclient->userName), 0); //接收用户名和指定聊天对象的用户名recv(pclient->sClient, pclient->ChatName, sizeof(pclient->ChatName), 0);memcpy(pclient->IP, inet_ntoa(g_ClientAddr.sin_addr), sizeof(pclient->IP)); //记录客户端IPpclient->flag = pclient->sClient; //不同的socke有不同UINT_PTR类型的数字来标识pclient->Port = htons(g_ClientAddr.sin_port);AddClient(pclient);  //把新的客户端加入链表中printf("Successfuuly got a connection from IP:%s ,Port: %d,UerName: %s , ChatName: %s\n",pclient->IP, pclient->Port, pclient->userName,pclient->ChatName);if (CountCon() >= 2)                    //当至少两个用户都连接上服务器后才进行消息转发                                                          StartRecv();Sleep(2000);}return 0;
}//启动服务器
int StartServer()
{//存放套接字信息的结构WSADATA wsaData = { 0 };SOCKADDR_IN ServerAddr = { 0 };              //服务端地址USHORT uPort = 18000;                       //服务器监听端口//初始化套接字if (WSAStartup(MAKEWORD(2, 2), &wsaData)){printf("WSAStartup failed with error code: %d\n", WSAGetLastError());return -1;}//判断版本if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){printf("wVersion was not 2.2\n");return -1;}//创建套接字g_ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (g_ServerSocket == INVALID_SOCKET){printf("socket failed with error code: %d\n", WSAGetLastError());return -1;}//设置服务器地址ServerAddr.sin_family = AF_INET;//连接方式ServerAddr.sin_port = htons(uPort);//服务器监听端口ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//任何客户端都能连接这个服务器//绑定服务器if (SOCKET_ERROR == bind(g_ServerSocket, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr))){printf("bind failed with error code: %d\n", WSAGetLastError());closesocket(g_ServerSocket);return -1;}//设置监听客户端连接数if (SOCKET_ERROR == listen(g_ServerSocket, 20000)){printf("listen failed with error code: %d\n", WSAGetLastError());closesocket(g_ServerSocket);WSACleanup();return -1;}_beginthreadex(NULL, 0, ThreadAccept, NULL, 0, 0);for (int k = 0;k < 100;k++) //让主线程休眠,不让它关闭TCP连接.Sleep(10000000);//关闭套接字ClearClient();closesocket(g_ServerSocket);WSACleanup();return 0;
}int main()
{StartServer(); //启动服务器return 0;
}

Client code:

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#pragma comment(lib,"ws2_32.lib")
#define RECV_OVER 1
#define RECV_YET 0
char userName[16] = { 0 };
char chatName[16] = { 0 };
int iStatus = RECV_YET;
//接受数据
unsigned __stdcall ThreadRecv(void* param)
{char buf[128] = { 0 };while (1){int ret = recv(*(SOCKET*)param, buf, sizeof(buf), 0);if (ret == SOCKET_ERROR){Sleep(500);continue;}if (strlen(buf) != 0){printf("%s\n", buf);iStatus = RECV_OVER;}elseSleep(100);   }return 0;
}//发送数据
unsigned __stdcall ThreadSend(void* param)
{char buf[128] = { 0 };int ret = 0;while (1){int c = getch();if (c == 27)   //ESC ASCII是27{memset(buf, 0, sizeof(buf));printf("Please input the chat name:");gets_s(buf);char b[17] = { 0 };sprintf(b, "#%s", buf);ret = send(*(SOCKET*)param,b , sizeof(b), 0);if (ret == SOCKET_ERROR)return 1;continue;}if(c == 72 || c == 0 || c == 68)//为了显示美观,加一个无回显的读取字符函数continue;                   //getch返回值我是经过实验得出如果是返回这几个值,则getch就会自动跳过,具体我也不懂。printf("%s: ", userName);gets_s(buf);ret = send(*(SOCKET*)param, buf, sizeof(buf), 0);if (ret == SOCKET_ERROR)return 1;}return 0;
}//连接服务器
int ConnectServer()
{WSADATA wsaData = { 0 };//存放套接字信息SOCKET ClientSocket = INVALID_SOCKET;//客户端套接字SOCKADDR_IN ServerAddr = { 0 };//服务端地址USHORT uPort = 18000;//服务端端口//初始化套接字if (WSAStartup(MAKEWORD(2, 2), &wsaData)){printf("WSAStartup failed with error code: %d\n", WSAGetLastError());return -1;}//判断套接字版本if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){printf("wVersion was not 2.2\n");return -1;}//创建套接字ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (ClientSocket == INVALID_SOCKET){printf("socket failed with error code: %d\n", WSAGetLastError());return -1;}//输入服务器IPprintf("Please input server IP:");char IP[32] = { 0 };gets_s(IP);//设置服务器地址ServerAddr.sin_family = AF_INET;ServerAddr.sin_port = htons(uPort);//服务器端口ServerAddr.sin_addr.S_un.S_addr = inet_addr(IP);//服务器地址printf("connecting......\n");//连接服务器if (SOCKET_ERROR == connect(ClientSocket, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr))){printf("connect failed with error code: %d\n", WSAGetLastError());closesocket(ClientSocket);WSACleanup();return -1;}printf("Connecting server successfully IP:%s Port:%d\n",IP, htons(ServerAddr.sin_port));printf("Please input your UserName: ");gets_s(userName);send(ClientSocket, userName, sizeof(userName), 0);printf("Please input the ChatName: ");gets_s(chatName);send(ClientSocket, chatName, sizeof(chatName), 0);printf("\n\n");_beginthreadex(NULL, 0, ThreadRecv, &ClientSocket, 0, NULL); //启动接收和发送消息线程_beginthreadex(NULL, 0, ThreadSend, &ClientSocket, 0, NULL);for (int k = 0;k < 1000;k++)Sleep(10000000);closesocket(ClientSocket);WSACleanup();return 0;
}int main()
{ConnectServer(); //连接服务器return 0;
}

最后,需要改进的有以下几点:
1.没有消息记录,所以最好用文件或者数据库的方式记录,个人推荐数据库。

2.没有用户注册,登陆的操作,也是用文件或者数据库来弄。程序一运行就读取数据库信息就行。

3.群聊功能没有弄,这个其实很简单,就是服务器不管3721,把接收到的消息转发给所有在线用户。

4.没有离线消息,这个就用数据库存储离线消息,然后用户上线后立即发送过去就行。

最后总结一下,没有数据库的聊天程序果然功能简陋~,C语言写的程序要注意对内存的操作。还有TCP方式的连接太费时费内存(用户量达的时候)。

C语言版聊天程序(TCP版本,接下来还有UDP版本)到这里结束~,欢迎各位提出自己的看法。

socket多人聊天程序C语言版(二)相关推荐

  1. 用c语言编写一个1V1聊天程序,socket多人聊天程序C语言版(二)

    1V1实现了,1V多也就容易了.不过相对于1V1的程序,我经过大改,采用链表来动态管理.这样效率真的提升不少,至少CPU使用率稳稳的在20以下,不会飙到100了.用C语言写这个还是挺费时间的,因为什么 ...

  2. socket多人聊天程序C语言版(一)

    ==> 学习汇总(持续更新) ==> 从零搭建后端基础设施系列(一)-- 背景介绍 socket编程client和server直接通信是很简单的,就是一个发送一个接收就完了,但这却是基础. ...

  3. socket多人聊天室c语言,一分钟实现网页多人聊天室【Socket.IO】

    socket.io是个基于node.js的快平台实时通讯框架.只用不到10行代码,就可以搭建一个简单的多人实时聊天室. 先来看看运行后的效果: socket.io多人聊天室 只要简单几步,就可以实现. ...

  4. 视频教程-实战Go语言:多人聊天室-Go语言

    实战Go语言:多人聊天室 多年互联网从业经验: 有丰富的的企业网站.手游.APP开发经验: 曾担任上海益盟软件技术股份有限公司项目经理及产品经理: 参与项目有益盟私募工厂.睿妙影音家庭物联网设备.手游 ...

  5. Socket多人聊天MFC版

    ==> 学习汇总(持续更新) ==> 从零搭建后端基础设施系列(一)-- 背景介绍 源码下载地址: https://github.com/coderxj/Socket-Group-Chat ...

  6. 利用TCP和UDP协议,实现基于Socket的小聊天程序(初级版)

    TCP TCP (Transmission Control Protocol)属于传输层协议.其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送.可靠性.有效流控.全双工操作和多路复用 ...

  7. Linux下socket多人聊天室

    目录 前言 一.聊天室的实验内容 二.逐个功能的简单分析 三.系统功能模块分解图 1.服务端功能模块图 2.客户端功能模块图 3.守护进程功能模块图 四.功能模块流程图 1.服务端流程图 2.客户端流 ...

  8. Socket网络编程--聊天程序(3)

    上一小节,已经讲到可以每个人多说话,而且还没有限制,简单的来说,我们已经完成了聊天的功能了,那么接下来我们要实现什么功能呢?一个聊天程序至少应该支持一对多的通讯吧,接下来就实现多个客户端往服务器发送数 ...

  9. Node.js + Web Socket 打造即时聊天程序嗨聊(上)

    前端一直是一块充满惊喜的土地,不仅是那些富有创造性的页面,还有那些惊赞的效果及不断推出的新技术.像node.js这样的后端开拓者直接将前端人员的能力扩大到了后端.瞬间就有了一统天下的感觉,来往穿梭于前 ...

  10. [前端] Node.js + Web Socket 打造即时聊天程序嗨聊

    前端一直是一块充满惊喜的土地,不仅是那些富有创造性的页面,还有那些惊赞的效果及不断推出的新技术.像node.js这样的后端开拓者直接将前端人员的能力扩大到了后端.瞬间就有了一统天下的感觉,来往穿梭于前 ...

最新文章

  1. Citrix Receiver For Linux 预览版
  2. 基于变分自动编码器(Variational Autoencoders)进行推荐系统的实施、Keras实现并可视化训练和验证误差、最后给出topK准确率和召回率
  3. mysql二进制包下的support-files文件夹
  4. Python(三)对装饰器的理解
  5. IntelliJ IDEA内存优化最佳实践(转)
  6. JAVA与SAP数据交互的方式总结
  7. 豆瓣网络爬虫-java网络爬虫[验证码模拟登陆]详细介绍
  8. Linux中切换用户(su命令)
  9. xtrabackup-增量备份
  10. 2019 牛客多校第三场 H Magic Line
  11. c语言作业制作仓库管理系统,c语言课设仓库管理系统.doc
  12. python中if else语句用法_Python条件语句详解:if、else、switch都有了
  13. web eTerm是什么
  14. 微软拼音输入法调整状态栏水平/垂直选项失效解决办法
  15. 关于微信的几点更新与操作
  16. 比较常见的几种代理ip类型
  17. Python------2022-1-11作业
  18. antd select.option选项加入额外属性
  19. 【Motif Discovery with Missing Data】
  20. 在线职业教育APP开发,技能快速掌握的管家

热门文章

  1. 针式PKM V5.78
  2. 纯干货:手把手教你用Python做数据可视化(附代码)
  3. 200多个引流推广渠道及技巧,全网引流布局
  4. 《机器学习》周志华note2
  5. vue 数字上下滚动抽奖
  6. 圈圈USB开发板 IDE40
  7. 安全计算:AVG免费版提供免费病毒防护
  8. C语言入门经典(第四版).pdf
  9. 《CCNA学习指南:数据中心(640-911)》——2.5 考试要点
  10. FREETEXTBOX