socket多人聊天程序C語言版(二)
socket多人聊天程序C語言版(一)地址: //www.jb51.net/article/94938.htm
1V1實(shí)現(xiàn)了,1V多也就容易了。不過相對于1V1的程序,我經(jīng)過大改,采用鏈表來動態(tài)管理。這樣效率真的提升不少,至少CPU使用率穩(wěn)穩(wěn)的在20以下,不會飆到100了。用C語言寫這個還是挺費(fèi)時(shí)間的,因?yàn)槭裁垂δ芎瘮?shù)都要自己寫,不像C++有STL庫可以用,MFC寫就更簡單了,接下來我還會更新MFC版本的多人聊天程序。好了,廢話少說,進(jìn)入主題。
這個程序要解決的問題如下:
1.CPU使用率飆升問題 –>用鏈表動態(tài)管理
2.用戶自定義聊天,就是想跟誰聊跟誰聊 –> _Client結(jié)構(gòu)體中新增一個ChatName字段,用來表示要和誰聊天,這個字段很重要,因?yàn)閟erver轉(zhuǎn)發(fā)消息的時(shí)候就是按照這個字段來轉(zhuǎn)發(fā)的。
3.中途換人聊天,就是聊著聊著,想和別人聊,而且自己還一樣能接收到其它人發(fā)的消息 –> 這個就要小改客戶端的代碼了,可以在發(fā)送聊天消息之前插入一段代碼,用來切換聊天用戶。具體做法就是,用getch()函數(shù)讀取ESC鍵,如果用戶按了這個鍵,則表示想切換用戶,然后會輸出一行提示,請輸入chat name,就是想要和誰聊天的名字,發(fā)送這個名字過去之前要加一個標(biāo)識符,表示這個消息是切換聊天用戶消息。然后server接收到這個消息后會判斷第一個字符是不是標(biāo)識符,第二個字符不能是標(biāo)識符,則根據(jù)這個name來查找當(dāng)前在線的用戶,然后修改想切換聊天用戶的ChatName為name這個用戶。(可能有點(diǎn)繞,不懂的看代碼就清晰易懂了~)
4.下線后提醒對方 –> 還是老 ,只要send對方不通就當(dāng)對方下線了。
編寫環(huán)境:WIN10,VS2015
效果圖:
為了方便就不用虛擬機(jī)演示了,但是在虛擬機(jī)是肯定可以的,應(yīng)該說只要是局域網(wǎng),能互相ping通就可以使用這個程序。
Server code:
鏈表頭文件:
#ifndef _CLIENT_LINK_LIST_H_ #define _CLIENT_LINK_LIST_H_ #include <WinSock2.h> #include <stdio.h> //客戶端信息結(jié)構(gòu)體 typedef struct _Client { SOCKET sClient; //客戶端套接字 char buf[128]; //數(shù)據(jù)緩沖區(qū) char userName[16]; //客戶端用戶名 char IP[20]; //客戶端IP unsigned short Port; //客戶端端口 UINT_PTR flag; //標(biāo)記客戶端,用來區(qū)分不同的客戶端 char ChatName[16]; //指定要和哪個客戶端聊天 _Client* next; //指向下一個結(jié)點(diǎn) }Client, *pClient; /* * function 初始化鏈表 * return 無返回值 */ void Init(); /* * function 獲取頭節(jié)點(diǎn) * return 返回頭節(jié)點(diǎn) */ pClient GetHeadNode(); /* * function 添加一個客戶端 * param client表示一個客戶端對象 * return 無返回值 */ void AddClient(pClient client); /* * function 刪除一個客戶端 * param flag標(biāo)識一個客戶端對象 * return 返回true表示刪除成功,false表示失敗 */ bool RemoveClient(UINT_PTR flag); /* * function 根據(jù)name查找指定客戶端 * param name是指定客戶端的用戶名 * return 返回一個client表示查找成功,返回INVALID_SOCKET表示無此用戶 */ SOCKET FindClient(char* name); /* * function 根據(jù)SOCKET查找指定客戶端 * param client是指定客戶端的套接字 * return 返回一個pClient表示查找成功,返回NULL表示無此用戶 */ pClient FindClient(SOCKET client); /* * function 計(jì)算客戶端連接數(shù) * param client表示一個客戶端對象 * return 返回連接數(shù) */ int CountCon(); /* * function 清空鏈表 * return 無返回值 */ void ClearClient(); /* * function 檢查連接狀態(tài)并關(guān)閉一個連接 * return 返回值 */ void CheckConnection(); /* * function 指定發(fā)送給哪個客戶端 * param FromName,發(fā)信人 * param ToName, 收信人 * param data, 發(fā)送的消息 */ void SendData(char* FromName, char* ToName, char* data); #endif //_CLIENT_LINK_LIST_H_
鏈表cpp文件:
#include "ClientLinkList.h" pClient head = (pClient)malloc(sizeof(_Client)); //創(chuàng)建一個頭結(jié)點(diǎn) /* * function 初始化鏈表 * return 無返回值 */ void Init() { head->next = NULL; } /* * function 獲取頭節(jié)點(diǎn) * return 返回頭節(jié)點(diǎn) */ pClient GetHeadNode() { return head; } /* * function 添加一個客戶端 * param client表示一個客戶端對象 * return 無返回值 */ void AddClient(pClient client) { client->next = head->next; //比如:head->1->2,然后添加一個3進(jìn)來后是 head->next = client; //3->1->2,head->3->1->2 } /* * function 刪除一個客戶端 * param flag標(biāo)識一個客戶端對象 * return 返回true表示刪除成功,false表示失敗 */ bool RemoveClient(UINT_PTR flag) { //從頭遍歷,一個個比較 pClient pCur = head->next;//pCur指向第一個結(jié)點(diǎn) 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); //關(guān)閉套接字 free(pCur); //釋放該結(jié)點(diǎn) 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 根據(jù)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 計(jì)算客戶端連接數(shù) * param client表示一個客戶端對象 * return 返回連接數(shù) */ 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 檢查連接狀態(tài)并關(guān)閉一個連接 * 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 }; //發(fā)送下線消息給發(fā)消息的人 sprintf(error, "The %s was downline.\n", pclient->userName); send(FindClient(pclient->ChatName), error, sizeof(error), 0); closesocket(pclient->sClient); //這里簡單的判斷:若發(fā)送消息失敗,則認(rèn)為連接中斷(其原因有多種),關(guān)閉該套接字 RemoveClient(pclient->flag); break; } } } } /* * function 指定發(fā)送給哪個客戶端 * param FromName,發(fā)信人 * param ToName, 收信人 * param data, 發(fā)送的消息 */ 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); //添加發(fā)送消息的用戶名 ret = send(client, buf, sizeof(buf), 0); } else//發(fā)送錯誤消息給發(fā)消息的人 { 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)//發(fā)送下線消息給發(fā)消息的人 { 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; //服務(wù)端套接字 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; //發(fā)送數(shù)據(jù)線程 unsigned __stdcall ThreadSend(void* param) { pSend psend = (pSend)param; //轉(zhuǎn)換為Send類型 SendData(psend->FromName, psend->ToName, psend->data); //發(fā)送數(shù)據(jù) return 0; } //接受數(shù)據(jù) 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] != '#') //#表示用戶要指定另一個用戶進(jìn)行聊天 { SOCKET socket = FindClient(&pclient->buf[1]); //驗(yàn)證一下客戶是否存在 if (socket != INVALID_SOCKET) { pClient c = (pClient)malloc(sizeof(_Client)); c = FindClient(socket); //只要改變ChatName,發(fā)送消息的時(shí)候就會自動發(fā)給指定的用戶了 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)); //把發(fā)送人的用戶名和接收消息的用戶和消息賦值給結(jié)構(gòu)體,然后當(dāng)作參數(shù)傳進(jìn)發(fā)送消息進(jìn)程中 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) { //創(chuàng)建一個新的客戶端對象 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類型的數(shù)字來標(biāo)識 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) //當(dāng)至少兩個用戶都連接上服務(wù)器后才進(jìn)行消息轉(zhuǎn)發(fā) StartRecv(); Sleep(2000); } return 0; } //啟動服務(wù)器 int StartServer() { //存放套接字信息的結(jié)構(gòu) WSADATA wsaData = { 0 }; SOCKADDR_IN ServerAddr = { 0 }; //服務(wù)端地址 USHORT uPort = 18000; //服務(wù)器監(jiān)聽端口 //初始化套接字 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; } //創(chuàng)建套接字 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; } //設(shè)置服務(wù)器地址 ServerAddr.sin_family = AF_INET;//連接方式 ServerAddr.sin_port = htons(uPort);//服務(wù)器監(jiān)聽端口 ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//任何客戶端都能連接這個服務(wù)器 //綁定服務(wù)器 if (SOCKET_ERROR == bind(g_ServerSocket, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr))) { printf("bind failed with error code: %d\n", WSAGetLastError()); closesocket(g_ServerSocket); return -1; } //設(shè)置監(jiān)聽客戶端連接數(shù) 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++) //讓主線程休眠,不讓它關(guān)閉TCP連接. Sleep(10000000); //關(guān)閉套接字 ClearClient(); closesocket(g_ServerSocket); WSACleanup(); return 0; } int main() { StartServer(); //啟動服務(wù)器 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; //接受數(shù)據(jù) 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; } //發(fā)送數(shù)據(jù) 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)//為了顯示美觀,加一個無回顯的讀取字符函數(shù) continue; //getch返回值我是經(jīng)過實(shí)驗(yàn)得出如果是返回這幾個值,則getch就會自動跳過,具體我也不懂。 printf("%s: ", userName); gets_s(buf); ret = send(*(SOCKET*)param, buf, sizeof(buf), 0); if (ret == SOCKET_ERROR) return 1; } return 0; } //連接服務(wù)器 int ConnectServer() { WSADATA wsaData = { 0 };//存放套接字信息 SOCKET ClientSocket = INVALID_SOCKET;//客戶端套接字 SOCKADDR_IN ServerAddr = { 0 };//服務(wù)端地址 USHORT uPort = 18000;//服務(wù)端端口 //初始化套接字 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; } //創(chuàng)建套接字 ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ClientSocket == INVALID_SOCKET) { printf("socket failed with error code: %d\n", WSAGetLastError()); return -1; } //輸入服務(wù)器IP printf("Please input server IP:"); char IP[32] = { 0 }; gets_s(IP); //設(shè)置服務(wù)器地址 ServerAddr.sin_family = AF_INET; ServerAddr.sin_port = htons(uPort);//服務(wù)器端口 ServerAddr.sin_addr.S_un.S_addr = inet_addr(IP);//服務(wù)器地址 printf("connecting......\n"); //連接服務(wù)器 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); //啟動接收和發(fā)送消息線程 _beginthreadex(NULL, 0, ThreadSend, &ClientSocket, 0, NULL); for (int k = 0;k < 1000;k++) Sleep(10000000); closesocket(ClientSocket); WSACleanup(); return 0; } int main() { ConnectServer(); //連接服務(wù)器 return 0; }
最后,需要改進(jìn)的有以下幾點(diǎn):
1.沒有消息記錄,所以最好用文件或者數(shù)據(jù)庫的方式記錄,個人推薦數(shù)據(jù)庫。
2.沒有用戶注冊,登陸的操作,也是用文件或者數(shù)據(jù)庫來弄。程序一運(yùn)行就讀取數(shù)據(jù)庫信息就行。
3.群聊功能沒有弄,這個其實(shí)很簡單,就是服務(wù)器不管3721,把接收到的消息轉(zhuǎn)發(fā)給所有在線用戶。
4.沒有離線消息,這個就用數(shù)據(jù)庫存儲離線消息,然后用戶上線后立即發(fā)送過去就行。
最后總結(jié)一下,沒有數(shù)據(jù)庫的聊天程序果然功能簡陋~,C語言寫的程序要注意對內(nèi)存的操作。還有TCP方式的連接太費(fèi)時(shí)費(fèi)內(nèi)存(用戶量達(dá)的時(shí)候)。
C語言版聊天程序(TCP版本,接下來還有UDP版本)到這里結(jié)束~,歡迎各位提出自己的看法。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持我們。
上一篇:C++中g(shù)etline()和get()的方法淺析
欄 目:C語言
本文標(biāo)題:socket多人聊天程序C語言版(二)
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/2011.html
您可能感興趣的文章
- 01-10c語言socket多線程編程限制客戶端連接數(shù)
- 01-10C語言socket編程開發(fā)應(yīng)用示例
- 01-10三種獲取網(wǎng)頁源碼的方法(使用MFC/Socket實(shí)現(xiàn))
- 01-10tcp socket客戶端和服務(wù)端示例分享
- 01-10vc++實(shí)現(xiàn)的tcp socket客戶端和服務(wù)端示例
- 01-10udp socket客戶端和udp服務(wù)端程序示例分享
- 01-10簡單的socket編程入門示例
- 01-10Linux網(wǎng)絡(luò)編程之UDP Socket程序示例
- 01-10Linux網(wǎng)絡(luò)編程之socket文件傳輸示例
- 01-10C語言實(shí)現(xiàn)socket簡單通信實(shí)例


閱讀排行
本欄相關(guān)
- 04-02c語言函數(shù)調(diào)用后清空內(nèi)存 c語言調(diào)用
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言的正則匹配函數(shù) c語言正則表達(dá)
- 04-02c語言用函數(shù)寫分段 用c語言表示分段
- 04-02c語言中對數(shù)函數(shù)的表達(dá)式 c語言中對
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段
- 04-02C語言中怎么打出三角函數(shù) c語言中怎
- 04-02c語言調(diào)用函數(shù)求fibo C語言調(diào)用函數(shù)求