C++ SOCKET多線程實現聊天小程序
本文實例為大傢分享瞭C++ SOCKET多線程實現聊天小程序的具體代碼,供大傢參考,具體內容如下
TCP/IP協議與SOCKET
什麼是網絡協議?
計算機網絡中,各個實體之間的數據交換必須遵守事先約定好的規則,這些規則就稱為協議。
網絡協議的組成要素有:
1.語法,數據與控制信息的結構或格式
2.語義:需要發出何種控制信息,完成哪些動作以及做出何種響應
3.時序:事件實現順序的詳細說明
在一個網絡協議中,通信的實體的相同層次的結構必須執行相同的協議,這是協議的對等性原則。
TCP/IP體系結構與SOCKET
關於TCP/IP體系結構的詳細內容本文不做論述,如果你沒有這方面的知識想要快速理解這個東西,可以把網絡通信類比成兩個人之間寫信。你的信件就是通信過程中要傳遞的消息或者數據,而網絡協議對你的“信件”進行瞭包裝,比如給你貼瞭郵票、包瞭信封、投進瞭郵箱,然後你的“信件”就能通過郵局送到收信人那裡。
SOCKET(套接字)是TCP/IP網絡操作系統為網絡程序開發提供的典型網絡編程界面,進程通過SOCKET發送消息和接收消息。你可以把SOCKET看作一道“門”,發送消息的進程從“門”把消息推出去;消息被推出之後利用下層的通信設施傳遞到接收進程所在的“門”;然後接收進程再從“門”把消息拉進去。套接字SOCKET又分為數據報套接字和流式套接字,分別使用UDP協議和TCP協議。
SOCKET編程
我們嘗試編寫一個單播聊天室,這個聊天室可以讓多個客戶端與服務器端進行連接,而單播的意思是各個客戶端隻能與服務端進行單獨通信,不同客戶端之間無法通信。為瞭實現這個目標我們還需要用到多線程。整體實現思路如下圖:
話不多說,上代碼。
Server端
#include "stdafx.h" #include<WinSock2.h> #include<string.h> #include<iostream> #pragma comment (lib, "ws2_32.lib") using namespace std; const int PORT = 8000; #define IP "127.0.0.1" #define MaxClient 10//最多能接受同時在線的客戶端數量,可以隨意修改 #define MaxBufSize 1024 int num =0;//客戶端數量計數器 #define _CRT_SECURE_NO_WARINGS //服務線程 DWORD WINAPI SeverThread(LPVOID lpParameter) { //新建一個SOCKET用於通信 SOCKET *ClientSocket = (SOCKET*)lpParameter; int receByt = 0; char RecvBuf[MaxBufSize]; char SendBuf[MaxBufSize]; char exitBuf[5]; //開始接收 while (1) { receByt = recv(*ClientSocket, RecvBuf, sizeof(RecvBuf), 0); if (receByt > 0) { //當客戶端發來的消息是“exit”,就關閉連接 if (strlen(RecvBuf)==4) { for (int i = 0; i < 5; i++) { exitBuf[i] = RecvBuf[i]; } int flag = strcmp(exitBuf, "exit"); if (flag==0)//接收到exit消息 { cout << "client " << *ClientSocket << " exit!" << endl; num--; send(*ClientSocket, "Your server has been closed", sizeof(SendBuf), 0); closesocket(*ClientSocket); return 0; } } cout << "receive message :" << RecvBuf << " from client:" << *ClientSocket << endl; } else { //下面說到的客戶端關閉連接是指客戶端掉線瞭 if (WSAGetLastError() == 10054)//檢測到客戶端關閉連接 { cout << "client " << *ClientSocket << " exit!" << endl; closesocket(*ClientSocket); num--; return 0; } else//接收失敗顯示錯誤信息 { cout << "failed to receive,Error:" << WSAGetLastError() << endl; break; } } memset(RecvBuf, 0, 1024); cout << "input your message to client:" << endl; scanf_s("%s",SendBuf,MaxBufSize); int k = 0; k = send(*ClientSocket, SendBuf, sizeof(SendBuf), 0); if (k < 0) { if (WSAGetLastError()==10054)//檢測到客戶端主動關閉連接 { cout << "client " << *ClientSocket << " exit!" << endl; closesocket(*ClientSocket); num--; return 0; } else//發送失敗顯示錯誤信息 cout << "failed to send, Error:" << WSAGetLastError()<<endl; } memset(SendBuf, 0, 1024); } if (*ClientSocket != INVALID_SOCKET) { closesocket(*ClientSocket); } return 0; } int _tmain(int argc, _TCHAR* argv[]) { WSAData wsd; WSAStartup(MAKEWORD(2, 2), &wsd); SOCKET ListenSocket = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN ListenAddr; ListenAddr.sin_family = AF_INET; ListenAddr.sin_addr.S_un.S_addr = INADDR_ANY;//本機ip ListenAddr.sin_port = htons(PORT); //綁定監聽端口 int n; n = bind(ListenSocket, (LPSOCKADDR)&ListenAddr, sizeof(ListenAddr)); if (n == SOCKET_ERROR) { cout << "failed to bind!" << endl; return -1; } else { cout << "bind success to:" << PORT << endl; } //開始監聽 int l = listen(ListenSocket, MaxClient); if (l == 0) { cout << "server ready, wait to requirement..." << endl; } else { cout << "Error:" << GetLastError() << "listen return" << l << endl; } while (1) { //循環接收客戶端連接請求並創建服務線程 if(num < MaxClient) { SOCKET *ClientSocket=new SOCKET; HANDLE hThread; int SockAddrlen = sizeof(sockaddr); *ClientSocket = accept(ListenSocket, 0, 0); cout << "client " << *ClientSocket << " has connect to server" << endl; num++; hThread = CreateThread(NULL, NULL, &SeverThread, (LPVOID)ClientSocket, 0, NULL); CloseHandle(hThread); } else { cout << "Max Client!Please wait for accept..." << endl; } } closesocket(ListenSocket); WSACleanup(); return 0; }
在這個服務器端,每有一個新的客戶端請求建立連接,服務器都會新開一個線程為一個客戶端提供服務,並在這個線程中新建立一個SOCKET用於與客戶端進行通信,同時服務器也應該能夠在不同階段(接收或發送)檢測客戶端是否已經斷開連接,以便及時釋放資源。
Client端
#include "stdafx.h" #include<iostream> #include<cstdio> #include<string> #include<Winsock2.h> #pragma comment(lib,"ws2_32.lib") using namespace std; const int PORT = 8000; #define MaxBufSize 1024 #define _CRT_SECURE_NO_WARINGS int _tmain(int argc, _TCHAR* argv[]) { WSADATA wsd; WSAStartup(MAKEWORD(2, 2), &wsd); SOCKET SocketClient = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN ClientAddr; ClientAddr.sin_family = AF_INET; ClientAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); ClientAddr.sin_port = htons(PORT); int n = 0; n = connect(SocketClient, (struct sockaddr*)&ClientAddr, sizeof(ClientAddr)); if (n == SOCKET_ERROR) { cout << "failed to connect" << endl; return -1; } cout << "success to connect to Server" << endl; char info[1024];//數據輸入緩沖區 char SendBuff[MaxBufSize];//發送數據緩沖區 char RecvBuff[MaxBufSize];//接收數據緩沖區 while (1) { cout << "input your message:" << endl; scanf_s("%s",&info,MaxBufSize); if (info[0] == '\0') break; strcpy(SendBuff, info); memset(info, 0, sizeof(info)); int k = 0; k = send(SocketClient, SendBuff, sizeof(SendBuff), 0); memset(SendBuff, 0, sizeof(SendBuff)); if (k < 0) { cout << WSAGetLastError() << endl; cout << "failed to send" << endl; } int n = 0; n = recv(SocketClient, RecvBuff, sizeof(RecvBuff), 0); if (n>0) { cout << "receive message from Server:" << RecvBuff << endl; memset(RecvBuff, 0, sizeof(RecvBuff)); } } closesocket(SocketClient); WSACleanup(); return 0; }
在本例中,客戶端與服務器建立連接後,必須由客戶端先發送消息才能開啟對話。支持中英文聊天,一次最多發送1024個字節的數據。你要建立多個客戶端的話隻需要再新建幾個工程然後把Client的代碼復制進去運行即可。或者直接多復制幾個編譯生成的exe程序。
總結
本例中,當同時有多個客戶端建立瞭連接時,他們可以先發送消息,服務器不一定要立即回復,而當服務器接收瞭來自多個客戶端的消息然後再進行回復時,回復的順序是按照接收順序來的,誰的消息先送到就先回復誰,我們在服務端無法指定我接下來的這條消息發給誰。這是因為我並沒有使用線程池,因此不同線程之間無法識別也無法建立連接,而操作系統默認當多個線程都在等待回復時(此時這個線程處於掛起狀態),如果沒有特殊規定且資源夠用的話 就要遵循先來後到的順序。想要徹底明白這一部分需要一些操作系統的相關知識。
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 詳談C++ socket網絡編程實例
- C++實現聊天小程序
- C++ Socket實現TCP與UDP網絡編程
- C++基於socket UDP網絡編程實現簡單聊天室功能
- C++基於socket多線程實現網絡聊天室