Socket通訊-accept+多執行緒
阿新 • • 發佈:2019-01-09
偶然的機會,重新寫了一下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;
}