1. 程式人生 > >Socket通訊-accept+多執行緒

Socket通訊-accept+多執行緒

    偶然的機會,重新寫了一下windows下socket通訊的比較基礎的程式碼,
    太久沒有接觸socket以及多執行緒,查了不少部落格,但是發現大部分內容比較陳舊,
    所以決定寫一篇部落格,順便自己總結一下。

內容簡介

  • 網路通訊基本函式介紹
  • C++11多執行緒簡介
  • socket通訊tcp版本
  • socket通訊udp版本

網路通訊基本函式介紹

tcp連線模式下

客戶端流程
1.建立socket(套接字)
    socket(int af, int type, int protocol)
    第一個引數用來說明網路協議型別,tcp/ip協議只能用AF_INET
    第二個引數:socket通訊型別(tcp,udp等)
    第三個引數:與選擇的通訊型別有關
2.向伺服器發起連線
    SOCKADDR_IN 宣告套接字型別
        sin_family 協議家族
        sin_port 通訊埠
        sin_addr 網路ip

    htons 將整型型別轉換成網路位元組序
    htonl 將長整型轉換成網路位元組序

    inet_pton(int af, char * str, pvoid addrbuf) 將點分十進位制ip地址轉換成網路位元組
        第一個引數:協議家族
        第二個引數:點分十進位制ip地址,例如:"127.0.0.1"
        第三個引數:sin_addr

    inte_ntop (int af, pvoid addrbuf, char *str, size_t len) 將網路位元組序轉換成點分十進位制ip地址
        第一個引數:協議家族
        第二個引數:sin_addr
        第三個引數:儲存ip地址的字串
        第四個引數:位元組單位長度

    connect(socket s, const sockaddr *name, int namelen)
        第一個引數:建立的套接字
        第二個引數:要連結的套接字地址
        第三個引數:單位長度

3.client與server通訊
    send(socket s, char * str, int len, int flag)
        第一個引數:本機建立的套接字
        第二個引數:要傳送的字串
        第三個引數:傳送字串長度
        第四個引數:會對函式行為產生影響,一般設定為0

    recv(socket s, char * buf, int len,int flag)
        第一個引數:本機建立的套接字
        第二個引數:接受訊息的字串
        第三個引數:允許接收字串的最大長度
        第四個引數:會對函式行為產生影響,一般設定為0

4.釋放套接字
    closesocket 釋放套接字
服務端流程
1.建立套接字
2.將套接字與本地的ip和埠繫結
    bind(socket s, const sockaddr *name, int namelen)
        第一個引數:建立的套接字
        第二個引數:要繫結的資訊
        第三個引數:套接字型別單位長度
3.設定為監聽狀態
    listen(socket s, int num)
        第一個引數:要監聽的socket(套接字)
        第二個引數:等待連線佇列的最大長度
4.等待客戶請求到來;當請求到來後,接受連線請求,返回一個新的對應於此次連線的套接字
    accept(int socket, sockaddr *name, int *addrlen)
        第一個引數,是一個已設為監聽模式的socket的描述符。
        第二個引數,是一個返回值,它指向一個struct sockaddr型別的結構體的變數,儲存了發起連線的客戶端得IP地址資訊和埠資訊。
        第三個引數,也是一個返回值,指向整型的變數,儲存了返回的地址資訊的長度。
        accept函式返回值是一個客戶端和伺服器連線的SOCKET型別的描述符,在伺服器端標識著這個客戶端。
5.相互通訊
6.斷開連線

udp通訊

客戶端
1.建立套接字
2.繫結伺服器ip和port已經協議家族
3.相互通訊
    sendto(socket s, const char * buf, int len, int flags, const sockaddr *to,int tolen)
        第一個引數:建立的套接字
        第二個引數:要傳送的字串
        第三個引數:要傳送的字串長度
        第四個引數:影響函式的行為,一般為0
        第五個引數:遠端套接字資訊
        第六個引數:套接字單位長度

    recvfrom(socket s, char * buf, int len, int flags, const sockaddr *to,int tolen)
        第一個引數:建立的套接字
        第二個引數:儲存接收的字串
        第三個引數:可接受的最大字串長度
        第四個引數:影響函式的行為,一般為0
        第五個引數:遠端套接字資訊
        第六個引數:套接字單位長度
4.釋放套接字
服務端
1.建立套接字
2.繫結本地的埠,ip,協議家族資訊
3.通訊
4.釋放連線

C++11多執行緒簡介

我們在socket通訊過程中經常會有多個客戶端向服務端發起請求的問題,在tcp模式下,會發生阻塞,解決的辦法有accetp+多執行緒或者select+accept模式,這篇部落格主要對前者就行詳解,在查閱資料的過程中,我發現網上好多的部落格介紹的多執行緒機制都是多年以前的方式,使用起來特別麻煩,比如pthread這種庫,其實C++11以後,官方就已經將執行緒庫封裝了起來,我們只需要宣告一下即可,該庫thread

#include<thread>    
定義 std::thread xxx;
構造 std::thread(func, ...)
等待執行緒結束 xxx.join()   

socket通訊tcp版本

伺服器端

// ServerTcp.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"
#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <thread>

void func(SOCKET arg, SOCKADDR_IN client_addr, int pos)
{
    char recvBuf[128];
    char sendBuf[128];
    char tempBuf[256];
    int sockConn = arg;
    while (true)
    {
        // 從客戶端接收訊息
        printf("wait receive client message :\n");
        recv(sockConn, recvBuf, 128, 0);

        // 解析客戶端地址資訊
        char ipClient[16];
        inet_ntop(AF_INET, &client_addr.sin_addr, ipClient, sizeof(ipClient));
        printf("%s said: %s\n", ipClient, recvBuf);

        // 向客戶端傳送訊息
        gets_s(sendBuf);
        send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
    }
}

int main()
{
    /**********************************************************************/
    /*
    WSAStartup必須是應用程式或DLL呼叫的第一個Windows Sockets函式。
    它允許應用程式或DLL指明Windows Sockets API的版本號及獲得特定Windows Sockets實現的細節。
    應用程式或DLL只能在一次成功的WSAStartup()呼叫之後才能呼叫進一步的Windows Sockets API函式。
    */
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) { return 0; }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return 0;
    }
    /***********************************************************************/

    // 申請儲存執行緒的陣列
    std::thread threads[10];
    int thread_num = 0;

    /***********************************************************************/
    /*                        socket通訊                                   */
    // 申請套接字
    SOCKET Svr = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    SOCKADDR_IN addr;

    // 要繫結的基礎資訊
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6002);
    addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    // 進行繫結
    int len = sizeof(sockaddr);
    bind(Svr, (struct sockaddr*)&addr, len);

    // 監聽套接字
    int ret = listen(Svr, 10);
    if (ret == SOCKET_ERROR)
    {
        printf("偵聽失敗\n");
        closesocket(Svr);
    }

    // 儲存請求連線的套接字資訊
    SOCKADDR_IN addrClient;

    while (true)
    {
        // 接受連線,返回一個socket
        SOCKET sockConn = accept(Svr, (struct sockaddr*)&addrClient, &len);
        if (sockConn == INVALID_SOCKET)
        {
            //printf("無效socket\n");
            continue;
        }

        // 將通訊細節放線上程裡處理
        threads[thread_num] = std::thread(func, sockConn, addrClient, thread_num);
        thread_num++;

        if (thread_num == 5)
        {
            printf("執行緒池達到數量上限");
        }
    }

    // 等待執行緒結束
    for (int i = 0; i < thread_num; i++)
        threads[i].join();

    // 釋放套接字
    closesocket(Svr);
    WSACleanup();
    return 0;
}

客戶端

// ClientTcp.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"
#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <thread>

void new_client(int pos)
{
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) { return ; }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return ;
    }

    SOCKET Client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    SOCKADDR_IN Server;

    // 要連線的基礎資訊
    Server.sin_family = AF_INET;
    Server.sin_port = htons(6002);
    inet_pton(AF_INET, "127.0.0.1", &Server.sin_addr); //點分十進位制地址轉換成網路位元組序

    // 向服務端發起連線
    int ret = connect(Client, (struct sockaddr*)&Server, sizeof(Server));

    if (ret == SOCKET_ERROR)
    {
        printf("連線失敗\n");
        closesocket(Client);
        WSACleanup();
        return;
    }

    char recvBuf[128];
    char sendBuf[128];

    while (true)
    {
        printf("please input message:\n");
        gets_s(sendBuf);
        sprintf_s(sendBuf, "%s_%d", sendBuf, pos);
        send(Client, sendBuf, strlen(sendBuf) + 1, 0);

        recv(Client, recvBuf, 128, 0);
        char ipServer[16];
        inet_ntop(AF_INET, &Server.sin_addr, ipServer, sizeof(ipServer));
        printf("%s said: %s\n", ipServer, recvBuf);
    }

    closesocket(Client);
    WSACleanup();
}

int main()
{
    std::thread threads[10];
    int client_num = 1;
    for (int i = 0; i < client_num; i++)
        threads[i] = std::thread(new_client, i);

    for (int i = 0; i < client_num; i++)
        threads[i].join();

    return 0;
}

socket通訊udp版本

服務端

// ServerUdp.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"


#include "stdafx.h"
#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <thread>

void func(SOCKET arg, SOCKADDR_IN client_addr, int pos)
{
    char recvBuf[128];
    char sendBuf[128];
    int sockConn = arg;
    int len = sizeof(sockaddr);
    while (true)
    {
        // 從客戶端接收訊息
        printf("wait receive client message :\n");
        recvfrom(sockConn, recvBuf, 128, 0, (struct sockaddr*)&arg, &len);

        // 解析客戶端地址資訊
        char ipClient[16];
        inet_ntop(AF_INET, &client_addr.sin_addr, ipClient, sizeof(ipClient));
        printf("%s said: %s\n", ipClient, recvBuf);

        // 向客戶端傳送訊息
        gets_s(sendBuf);
        sendto(sockConn, sendBuf, strlen(sendBuf) + 1, 0, (struct sockaddr*)&arg, len);
    }
}

int main()
{
    /**********************************************************************/
    /*
    WSAStartup必須是應用程式或DLL呼叫的第一個Windows Sockets函式。
    它允許應用程式或DLL指明Windows Sockets API的版本號及獲得特定Windows Sockets實現的細節。
    應用程式或DLL只能在一次成功的WSAStartup()呼叫之後才能呼叫進一步的Windows Sockets API函式。
    */
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) { return 0; }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return 0;
    }
    /***********************************************************************/

    // 申請儲存執行緒的陣列
    std::thread threads[10];
    int thread_num = 0;

    /***********************************************************************/
    /*                        socket通訊                                   */
    // 申請套接字
    SOCKET Svr = socket(AF_INET, SOCK_DGRAM, 0);
    SOCKADDR_IN addr;

    // 要繫結的基礎資訊
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6002);
    addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    // 進行繫結
    int len = sizeof(sockaddr);
    bind(Svr, (struct sockaddr*)&addr, len);

    // 儲存請求連線的套接字資訊
    SOCKADDR_IN addrClient;

    // 將通訊細節放線上程裡處理
    threads[thread_num] = std::thread(func, Svr, addrClient, thread_num);
    thread_num++;

    // 等待執行緒結束
    for (int i = 0; i < thread_num; i++)
        threads[i].join();

    // 釋放套接字
    closesocket(Svr);
    WSACleanup();
    return 0;
}

客戶端

// ClientUdp.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"
#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <thread>

void new_client(int pos)
{
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) { return; }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return;
    }

    SOCKET Client = socket(AF_INET, SOCK_DGRAM, 0);
    SOCKADDR_IN Server;

    // 要連線的基礎資訊
    Server.sin_family = AF_INET;
    Server.sin_port = htons(6002);
    inet_pton(AF_INET, "127.0.0.1", &Server.sin_addr); //點分十進位制地址轉換成網路位元組序

    char recvBuf[128];
    char sendBuf[128];
    int len = sizeof(sockaddr);
    while (true)
    {
        printf("please input message:\n");
        gets_s(sendBuf);
        sprintf_s(sendBuf, "%s_%d", sendBuf, pos);
        sendto(Client, sendBuf, strlen(sendBuf) + 1, 0, (struct sockaddr*)&Server, len);

        recvfrom(Client, recvBuf, 128, 0, (struct sockaddr*)&Server, &len);
        char ipServer[16];
        inet_ntop(AF_INET, &Server.sin_addr, ipServer, sizeof(ipServer));
        printf("%s said: %s\n", ipServer, recvBuf);
    }

    closesocket(Client);
    WSACleanup();
}

int main()
{
    std::thread threads[10];
    int client_num = 1;
    for (int i = 0; i < client_num; i++)
        threads[i] = std::thread(new_client, i);

    for (int i = 0; i < client_num; i++)
        threads[i].join();

    return 0;
}