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版本)到這裡停止~,迎接列位提出本身的意見。
以上就是本文的全體內容,願望對年夜家的進修有所贊助,也願望年夜家多多支撐。