socket多人聊天法式C說話版(二)。本站提示廣大學習愛好者:(socket多人聊天法式C說話版(二))文章只能為提供參考,不一定能成為您想要的結果。以下是socket多人聊天法式C說話版(二)正文
socket多人聊天法式C說話版(一)地址: http://www.jb51.net/article/94938.htm
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]; //客戶端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 <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)); //記載客戶端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 <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;
}
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版本)到這裡停止~,迎接列位提出本身的意見。
以上就是本文的全體內容,願望對年夜家的進修有所贊助,也願望年夜家多多支撐。