C++:實現socket通訊(TCP/IP)例項
首先宣告,博主之前從來沒有寫過通訊方面的東西,這次之所以寫這個是因為專案需要,因此本文主要介紹一個使用C++語言及Socket來實現TCP/IP通訊的例項,希望可以幫助入門者。
一、什麼是TCP/IP?
TCP提供基於IP環境下的資料可靠性傳輸,事先需要進行三次握手來確保資料傳輸的可靠性。詳細的博主不再贅述,感興趣的朋友可以去search一下。
二、什麼是socket?
socket顧名思義就是套接字的意思,用於描述地址和埠,是一個通訊鏈的控制代碼。應用程式通過socket向網路發出請求或者回應。
socket程式設計有三種,流式套接字(SOCK_STREAM),資料報套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),前兩者較常用。基於TCP的socket程式設計是流式套接字。
三、client/server即C/S模式:
TCP/IP通訊中,主要是進行C/S互動。廢話不多說,下面看看具體互動內容:
服務端:建立socket,申明自身的port和IP,並繫結到socket,使用listen監聽,然後不斷用accept去檢視是否有連線。如果有,捕獲socket,並通過recv獲取訊息的內容,通訊完成後呼叫closeSocket關閉這個對應accept到的socket。如果不需要等待任何客戶端連線,那麼用closeSocket直接關閉自身的socket。
客戶端:建立socket,通過埠號和地址確定目標伺服器,使用Connect連線到伺服器,send傳送訊息,等待處理,通訊完成後呼叫closeSocket關閉socket。
四、程式設計步驟
1、server端
(1)載入套接字型檔,建立套接字(WSAStartup()/socket());
#include<winsock.h> #pragma comment(lib,"ws2_32.lib") void initialization(); int main() { //建立套接字 s_server = socket(AF_INET, SOCK_STREAM, 0); } void initialization() { //初始化套接字型檔 WORD w_req = MAKEWORD(2, 2);//版本號 WSADATA wsadata; int err; err = WSAStartup(w_req, &wsadata); if (err != 0) { cout << "初始化套接字型檔失敗!" << endl; } else { cout << "初始化套接字型檔成功!" << endl; } //檢測版本號 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) { cout << "套接字型檔版本號不符!" << endl; WSACleanup(); } else { cout << "套接字型檔版本正確!" << endl; } //填充服務端地址資訊 }
(2)繫結套接字到一個IP地址和一個埠上(bind());
server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(5010);
(3)將套接字設定為監聽模式等待連線請求(listen());
//設定套接字為監聽狀態
if (listen(s_server, SOMAXCONN) < 0)
{
cout << "設定監聽狀態失敗!" << endl;
WSACleanup();
}
else
{
cout << "設定監聽狀態成功!" << endl;
}
cout << "服務端正在監聽連線,請稍候...." << endl;
(4)請求到來後,接受連線請求,返回一個新的對應於此次連線的套接字(accept());
//接受連線請求
len = sizeof(SOCKADDR);
s_accept = accept(s_server, (SOCKADDR *)&accept_addr, &len);
if (s_accept == SOCKET_ERROR)
{
cout << "連線失敗!" << endl;
WSACleanup();
return 0;
}
cout << "連線建立,準備接受資料" << endl;
(5)用返回的套接字和客戶端進行通訊(send()/recv());
//接收資料
while (1)
{
recv_len = recv(s_accept, recv_buf, 100, 0);
if (recv_len < 0)
{
cout << "接受失敗!" << endl;
break;
}
else
{
cout << "客戶端資訊:" << recv_buf << endl;
}
cout << "請輸入回覆資訊:";
cin >> send_buf;
send_len = send(s_accept, send_buf, 100, 0);
if (send_len < 0)
{
cout << "傳送失敗!" << endl;
break;
}
}
(6)返回,等待另一個連線請求;
(7)關閉套接字,關閉載入的套接字型檔(closesocket()/WSACleanup());
//關閉套接字
closesocket(s_server);
closesocket(s_accept);
//釋放DLL資源
WSACleanup();
return 0;
2、Client端
(1)載入套接字型檔,建立套接字(WSAStartup()/socket);
#include<winsock.h>
#pragma comment(lib,"ws2_32.lib")
void initialization();
int main()
{
//建立套接字
s_server = socket(AF_INET, SOCK_STREAM, 0);
}
void initialization() {
//初始化套接字型檔
WORD w_req = MAKEWORD(2, 2);//版本號
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata);
if (err != 0) {
cout << "初始化套接字型檔失敗!" << endl;
}
else {
cout << "初始化套接字型檔成功!" << endl;
}
//檢測版本號
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {
cout << "套接字型檔版本號不符!" << endl;
WSACleanup();
}
else {
cout << "套接字型檔版本正確!" << endl;
}
//填充服務端地址資訊
}
(2)向伺服器發出連線請求(connect());
if (connect(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
cout << "伺服器連線失敗!" << endl;
WSACleanup();
}
else {
cout << "伺服器連線成功!" << endl;
}
(3)和伺服器進行通訊(send()/recv());
//傳送,接收資料
while (1) {
cout << "請輸入傳送資訊:";
cin >> send_buf;
send_len = send(s_server, send_buf, 100, 0);
if (send_len < 0) {
cout << "傳送失敗!" << endl;
break;
}
recv_len = recv(s_server, recv_buf, 100, 0);
if (recv_len < 0) {
cout << "接受失敗!" << endl;
break;
}
else {
cout << "服務端資訊:" << recv_buf << endl;
}
}
(4)關閉套接字,關閉載入的套接字型檔(closesocket()/WSACleanup())
//關閉套接字
closesocket(s_server);
//釋放DLL資源
WSACleanup();
五、Windows下基於VS2017實現的socket簡單例項(TCP/IP)
(1)server端程式碼
#include "pch.h"
#include<iostream>
#include<winsock.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
void initialization();
int main() {
//定義長度變數
int send_len = 0;
int recv_len = 0;
int len = 0;
//定義傳送緩衝區和接受緩衝區
char send_buf[100];
char recv_buf[100];
//定義服務端套接字,接受請求套接字
SOCKET s_server;
SOCKET s_accept;
//服務端地址客戶端地址
SOCKADDR_IN server_addr;
SOCKADDR_IN accept_addr;
initialization();
//填充服務端資訊
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(5010);
//建立套接字
s_server = socket(AF_INET, SOCK_STREAM, 0);
if (bind(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
cout << "套接字繫結失敗!" << endl;
WSACleanup();
}
else {
cout << "套接字繫結成功!" << endl;
}
//設定套接字為監聽狀態
if (listen(s_server, SOMAXCONN) < 0) {
cout << "設定監聽狀態失敗!" << endl;
WSACleanup();
}
else {
cout << "設定監聽狀態成功!" << endl;
}
cout << "服務端正在監聽連線,請稍候...." << endl;
//接受連線請求
len = sizeof(SOCKADDR);
s_accept = accept(s_server, (SOCKADDR *)&accept_addr, &len);
if (s_accept == SOCKET_ERROR) {
cout << "連線失敗!" << endl;
WSACleanup();
return 0;
}
cout << "連線建立,準備接受資料" << endl;
//接收資料
while (1) {
recv_len = recv(s_accept, recv_buf, 100, 0);
if (recv_len < 0) {
cout << "接受失敗!" << endl;
break;
}
else {
cout << "客戶端資訊:" << recv_buf << endl;
}
cout << "請輸入回覆資訊:";
cin >> send_buf;
send_len = send(s_accept, send_buf, 100, 0);
if (send_len < 0) {
cout << "傳送失敗!" << endl;
break;
}
}
//關閉套接字
closesocket(s_server);
closesocket(s_accept);
//釋放DLL資源
WSACleanup();
return 0;
}
void initialization() {
//初始化套接字型檔
WORD w_req = MAKEWORD(2, 2);//版本號
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata);
if (err != 0) {
cout << "初始化套接字型檔失敗!" << endl;
}
else {
cout << "初始化套接字型檔成功!" << endl;
}
//檢測版本號
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {
cout << "套接字型檔版本號不符!" << endl;
WSACleanup();
}
else {
cout << "套接字型檔版本正確!" << endl;
}
//填充服務端地址資訊
}
(2)client端:
#include "pch.h"
#include<iostream>
#include<winsock.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
void initialization();
int main() {
//定義長度變數
int send_len = 0;
int recv_len = 0;
//定義傳送緩衝區和接受緩衝區
char send_buf[100];
char recv_buf[100];
//定義服務端套接字,接受請求套接字
SOCKET s_server;
//服務端地址客戶端地址
SOCKADDR_IN server_addr;
initialization();
//填充服務端資訊
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(1234);
//建立套接字
s_server = socket(AF_INET, SOCK_STREAM, 0);
if (connect(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
cout << "伺服器連線失敗!" << endl;
WSACleanup();
}
else {
cout << "伺服器連線成功!" << endl;
}
//傳送,接收資料
while (1) {
cout << "請輸入傳送資訊:";
cin >> send_buf;
send_len = send(s_server, send_buf, 100, 0);
if (send_len < 0) {
cout << "傳送失敗!" << endl;
break;
}
recv_len = recv(s_server, recv_buf, 100, 0);
if (recv_len < 0) {
cout << "接受失敗!" << endl;
break;
}
else {
cout << "服務端資訊:" << recv_buf << endl;
}
}
//關閉套接字
closesocket(s_server);
//釋放DLL資源
WSACleanup();
return 0;
}
void initialization() {
//初始化套接字型檔
WORD w_req = MAKEWORD(2, 2);//版本號
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata);
if (err != 0) {
cout << "初始化套接字型檔失敗!" << endl;
}
else {
cout << "初始化套接字型檔成功!" << endl;
}
//檢測版本號
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {
cout << "套接字型檔版本號不符!" << endl;
WSACleanup();
}
else {
cout << "套接字型檔版本正確!" << endl;
}
//填充服務端地址資訊
}
注:對於入門級別學習的同學一些使用指導,想要讓這倆程式跑起來,如果只有一臺電腦,那麼只需要在一臺電腦上VS中建立兩個不同的控制檯應用程式,然後把server和client程式碼分別copy到這倆新建專案的主程式中,直接執行即可。
六、執行結果顯示
(1)server端
(2)client端