首頁 > 軟體

C++ SOCKET多執行緒實現聊天小程式

2021-06-30 19:00:03

本文範例為大家分享了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] == '')
   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程式。

總結

本例中,當同時有多個使用者端建立了連線時,他們可以先傳送訊息,伺服器不一定要立即回覆,而當伺服器接收了來自多個使用者端的訊息然後再進行回覆時,回覆的順序是按照接收順序來的,誰的訊息先送到就先回復誰,我們在伺服器端無法指定我接下來的這條訊息發給誰。這是因為我並沒有使用執行緒池,因此不同執行緒之間無法識別也無法建立連線,而作業系統預設當多個執行緒都在等待回覆時(此時這個執行緒處於掛起狀態),如果沒有特殊規定且資源夠用的話 就要遵循先來後到的順序。想要徹底明白這一部分需要一些作業系統的相關知識。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com