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

#include

//客户端信息结构体

typedef struct _Client

{

SOCKET sClient; //客户端套接字

char buf[128]; //数据缓冲区

char userName[16]; //客户端用户名

char IP[20]; //客户端IP

unsigned 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->3

if (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 1

pClient 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);

else

sprintf(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

#include

#include

#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)); //记录客户端IP

pclient->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

#include

#include

#include

#include

#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;

}

else

Sleep(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;

}

//输入服务器IP

printf("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版本)到这里结束~,欢迎各位提出自己的看法。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

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

  1. python输入一个正整数n求下列算式的值_C语言编写程序:输入一个正整数x和一个正整数n,求下列算式的值。,C语言 编写一个程序,输入一个正整数,求出它是几位数。...

    导航:网站首页 > C语言编写程序:输入一个正整数x和一个正整数n,求下列算式的值.,C语言 编写一个程序,输入一个正整数,求出它是几位数. C语言编写程序:输入一个正整数x和一个正整数n,求下 ...

  2. 用C语言编写一个Linux下的简单shell程序

    这是一个简单的C程序,展示了如何进行系统调用执行logout cd ls pwd pid rm mkdir mv cp等命令,这是一个简单的命令解释程序shell,其源代码如下: #include & ...

  3. C语言编写一个四位数的和,c语言编写一段程序,输入一个四位数,输出各位数字的和...

    用C语言编写程序,输入一个正整数n(1 #include"stdio.h"intmain(){\x09inti,j,n;\x09inta[12];\x09intmin,mx;\x0 ...

  4. C语言编程编制职工档案管理程序,C语言 编写一个职工档案程序.doc

    C语言 编写一个职工档案程序 一.实验项目: 实验6 课程设计 (时间安排:6课时) 二.实验内容: 编写一个职工档案程序,设计实现如下功能: 建立一个职工数据结构,结构包含姓名.序号.性别和年龄信息 ...

  5. c语言计算二次函数顶点坐标,C语言编写一个求一元二次方程的实根的程序。 编辑一个小程序去做一元二次方程的求解(b^24ac)...

    导航:网站首页 > C语言编写一个求一元二次方程的实根的程序. 编辑一个小程序去做一元二次方程的求解(b^2>4ac) C语言编写一个求一元二次方程的实根的程序. 编辑一个小程序去做一元二 ...

  6. 编写程序C语言 用递归法求n,用C语言编写一个递归程序用来计算:1*2+2*3+3*4+.+(n-1)*n...

    用C语言编写一个递归程序用来计算:1*2+2*3+3*4+.+(n-1)*n以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧 ...

  7. 在Linux环境下用C语言编写一个乘法程序mult,从命令行接收两个数字,然后输出其乘积;再用C语言编写一个exec1程序,在程序中使用execvp调用mult程序计算5与10的乘积。

    在Linux环境下用C语言编写一个乘法程序mult,从命令行接收两个数字,然后输出其乘积:再用C语言编写一个exec1程序,在程序中使用execvp调用mult程序计算5与10的乘积. 1.mult. ...

  8. 使用Java语言编写一个五子棋UI界面并实现网络对战功能(非局域网)

    使用Java语言编写一个五子棋UI界面并实现网络对战功能(非局域网) 一,前期准备 1,Java IDE(Eclipse)与JDK的安装与配置 jdk-15.0.1-免配置路径版 提取码:earu 免 ...

  9. 用C语言编写一个电话簿管理系统

    用C语言编写一个电话簿管理系统 仅供参考 #include<stdio.h> #include<stdlib.h> #include<string.h> #incl ...

最新文章

  1. SM37job状态意义
  2. 最短工期 (25 分)【拓扑排序模板】
  3. 7-14 电话聊天狂人 (25 分)map做法 + 详解 + 思路分析
  4. 移动端的开发-视口-适配
  5. 贵州丹寨:庆苗年 迎新春
  6. Hive分桶(bucket)
  7. Android之eclipse简单NDK入门
  8. 云上系统迁移系列(一):概览
  9. DockLayout布局
  10. 『Delphi』File not found的解决办法
  11. epoll 使用实例
  12. QT5.14.2 官方例子 - 学习系列
  13. c#通过网络链接打印PDF
  14. 计算机系大二学年鉴定表,计算机大二学生自我鉴定
  15. 天使投资人讨论区块链投资:区块链虚火还是真火?
  16. gnome 如何自定义样式_在Gnome 3中自定义字体
  17. 9N90-ASEMI大功率场效应管9A 900V
  18. 关于Proximal Methods,近端梯度下降的理解
  19. Vultr新用户充值送50刀
  20. 2020丘成桐科学奖计算机名单,2016东润丘成桐科学奖(数学)国内各赛区获奖名单...

热门文章

  1. Win10电脑关闭实时保护功能方法教学
  2. Android 监听系统语言变化
  3. 3D与Blender:开源的3D计算机图形软件集
  4. springboot疫情防控学生自助申报系统毕业设计源码260839
  5. 大神总结 | 强化学习线路图
  6. tensorflow平台极简方式_TensorFlow极简入门教程
  7. python数据清洗去空值_Python 数据清洗--处理Nan
  8. 自定义webjars
  9. 20220323补卡-AC算法
  10. 基于Pytorch实现LSTM(多层LSTM,双向LSTM)进行文本分类