C++ SOCKET多線程實(shí)現(xiàn)聊天小程序
本文實(shí)例為大家分享了C++ SOCKET多線程實(shí)現(xiàn)聊天小程序的具體代碼,供大家參考,具體內(nèi)容如下
TCP/IP協(xié)議與SOCKET
什么是網(wǎng)絡(luò)協(xié)議?
計(jì)算機(jī)網(wǎng)絡(luò)中,各個(gè)實(shí)體之間的數(shù)據(jù)交換必須遵守事先約定好的規(guī)則,這些規(guī)則就稱為協(xié)議。
網(wǎng)絡(luò)協(xié)議的組成要素有:
1.語(yǔ)法,數(shù)據(jù)與控制信息的結(jié)構(gòu)或格式
2.語(yǔ)義:需要發(fā)出何種控制信息,完成哪些動(dòng)作以及做出何種響應(yīng)
3.時(shí)序:事件實(shí)現(xiàn)順序的詳細(xì)說(shuō)明
在一個(gè)網(wǎng)絡(luò)協(xié)議中,通信的實(shí)體的相同層次的結(jié)構(gòu)必須執(zhí)行相同的協(xié)議,這是協(xié)議的對(duì)等性原則。
TCP/IP體系結(jié)構(gòu)與SOCKET

關(guān)于TCP/IP體系結(jié)構(gòu)的詳細(xì)內(nèi)容本文不做論述,如果你沒(méi)有這方面的知識(shí)想要快速理解這個(gè)東西,可以把網(wǎng)絡(luò)通信類比成兩個(gè)人之間寫信。你的信件就是通信過(guò)程中要傳遞的消息或者數(shù)據(jù),而網(wǎng)絡(luò)協(xié)議對(duì)你的“信件”進(jìn)行了包裝,比如給你貼了郵票、包了信封、投進(jìn)了郵箱,然后你的“信件”就能通過(guò)郵局送到收信人那里。
SOCKET(套接字)是TCP/IP網(wǎng)絡(luò)操作系統(tǒng)為網(wǎng)絡(luò)程序開(kāi)發(fā)提供的典型網(wǎng)絡(luò)編程界面,進(jìn)程通過(guò)SOCKET發(fā)送消息和接收消息。你可以把SOCKET看作一道“門”,發(fā)送消息的進(jìn)程從“門”把消息推出去;消息被推出之后利用下層的通信設(shè)施傳遞到接收進(jìn)程所在的“門”;然后接收進(jìn)程再?gòu)摹伴T”把消息拉進(jìn)去。套接字SOCKET又分為數(shù)據(jù)報(bào)套接字和流式套接字,分別使用UDP協(xié)議和TCP協(xié)議。
SOCKET編程
我們嘗試編寫一個(gè)單播聊天室,這個(gè)聊天室可以讓多個(gè)客戶端與服務(wù)器端進(jìn)行連接,而單播的意思是各個(gè)客戶端只能與服務(wù)端進(jìn)行單獨(dú)通信,不同客戶端之間無(wú)法通信。為了實(shí)現(xiàn)這個(gè)目標(biāo)我們還需要用到多線程。整體實(shí)現(xiàn)思路如下圖:

話不多說(shuō),上代碼。
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//最多能接受同時(shí)在線的客戶端數(shù)量,可以隨意修改
#define MaxBufSize 1024
int num =0;//客戶端數(shù)量計(jì)數(shù)器
#define _CRT_SECURE_NO_WARINGS
//服務(wù)線程
DWORD WINAPI SeverThread(LPVOID lpParameter)
{
//新建一個(gè)SOCKET用于通信
SOCKET *ClientSocket = (SOCKET*)lpParameter;
int receByt = 0;
char RecvBuf[MaxBufSize];
char SendBuf[MaxBufSize];
char exitBuf[5];
//開(kāi)始接收
while (1)
{
receByt = recv(*ClientSocket, RecvBuf, sizeof(RecvBuf), 0);
if (receByt > 0)
{
//當(dāng)客戶端發(fā)來(lái)的消息是“exit”,就關(guān)閉連接
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
{
//下面說(shuō)到的客戶端關(guān)閉連接是指客戶端掉線了
if (WSAGetLastError() == 10054)//檢測(cè)到客戶端關(guān)閉連接
{
cout << "client " << *ClientSocket << " exit!" << endl;
closesocket(*ClientSocket);
num--;
return 0;
}
else//接收失敗顯示錯(cuò)誤信息
{
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)//檢測(cè)到客戶端主動(dòng)關(guān)閉連接
{
cout << "client " << *ClientSocket << " exit!" << endl;
closesocket(*ClientSocket);
num--;
return 0;
}
else//發(fā)送失敗顯示錯(cuò)誤信息
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;//本機(jī)ip
ListenAddr.sin_port = htons(PORT);
//綁定監(jiān)聽(tīng)端口
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;
}
//開(kāi)始監(jiān)聽(tīng)
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)
{
//循環(huán)接收客戶端連接請(qǐng)求并創(chuàng)建服務(wù)線程
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;
}
在這個(gè)服務(wù)器端,每有一個(gè)新的客戶端請(qǐng)求建立連接,服務(wù)器都會(huì)新開(kāi)一個(gè)線程為一個(gè)客戶端提供服務(wù),并在這個(gè)線程中新建立一個(gè)SOCKET用于與客戶端進(jìn)行通信,同時(shí)服務(wù)器也應(yīng)該能夠在不同階段(接收或發(fā)送)檢測(cè)客戶端是否已經(jīng)斷開(kāi)連接,以便及時(shí)釋放資源。
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];//數(shù)據(jù)輸入緩沖區(qū)
char SendBuff[MaxBufSize];//發(fā)送數(shù)據(jù)緩沖區(qū)
char RecvBuff[MaxBufSize];//接收數(shù)據(jù)緩沖區(qū)
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;
}
在本例中,客戶端與服務(wù)器建立連接后,必須由客戶端先發(fā)送消息才能開(kāi)啟對(duì)話。支持中英文聊天,一次最多發(fā)送1024個(gè)字節(jié)的數(shù)據(jù)。你要建立多個(gè)客戶端的話只需要再新建幾個(gè)工程然后把Client的代碼復(fù)制進(jìn)去運(yùn)行即可。或者直接多復(fù)制幾個(gè)編譯生成的exe程序。
總結(jié)
本例中,當(dāng)同時(shí)有多個(gè)客戶端建立了連接時(shí),他們可以先發(fā)送消息,服務(wù)器不一定要立即回復(fù),而當(dāng)服務(wù)器接收了來(lái)自多個(gè)客戶端的消息然后再進(jìn)行回復(fù)時(shí),回復(fù)的順序是按照接收順序來(lái)的,誰(shuí)的消息先送到就先回復(fù)誰(shuí),我們?cè)诜?wù)端無(wú)法指定我接下來(lái)的這條消息發(fā)給誰(shuí)。這是因?yàn)槲也](méi)有使用線程池,因此不同線程之間無(wú)法識(shí)別也無(wú)法建立連接,而操作系統(tǒng)默認(rèn)當(dāng)多個(gè)線程都在等待回復(fù)時(shí)(此時(shí)這個(gè)線程處于掛起狀態(tài)),如果沒(méi)有特殊規(guī)定且資源夠用的話 就要遵循先來(lái)后到的順序。想要徹底明白這一部分需要一些操作系統(tǒng)的相關(guān)知識(shí)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)小學(xué)生隨機(jī)出題測(cè)試計(jì)分
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)小學(xué)生隨機(jī)出題測(cè)試計(jì)分,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03
QT中QStringListModel類的應(yīng)用介紹
QStringListModel是最簡(jiǎn)單的模型類,具備向視圖提供字符串?dāng)?shù)據(jù)的能力,本文主要介紹了QT中QStringListModel類的應(yīng)用介紹,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
c++多線程之死鎖的發(fā)生的情況解析(包含兩個(gè)歸納,6個(gè)示例)
這篇文章主要介紹了c++多線程之死鎖的發(fā)生的情況解析(包含兩個(gè)歸納,6個(gè)示例),需要的朋友可以參考下2018-01-01
C++左值與右值,右值引用,移動(dòng)語(yǔ)義與完美轉(zhuǎn)發(fā)詳解
這篇文章主要為大家詳細(xì)介紹了Python實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03
C++實(shí)現(xiàn)并優(yōu)化異常系統(tǒng)
異常處理是C++的一項(xiàng)語(yǔ)言機(jī)制,用于在程序中處理異常事件,下面這篇文章主要給大家介紹了關(guān)于C++中異常的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)創(chuàng)建及遍歷十字鏈表
這篇文章主要介紹了C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)十字鏈表的創(chuàng)建及遍歷,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2021-10-10
C++實(shí)現(xiàn)LeetCode(241.添加括號(hào)的不同方式)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(241.添加括號(hào)的不同方式),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07

