socket多人聊天順序C言語版(二)。本站提示廣大學習愛好者:(socket多人聊天順序C言語版(二))文章只能為提供參考,不一定能成為您想要的結果。以下是socket多人聊天順序C言語版(二)正文
socket多人聊天順序C言語版(一)地址:
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版本)到這裡完畢~,歡送各位提出自己的看法。
以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支持。