1. TCP 套接字編程流程
1.1 概念
流式套接字編程針對TCP協(xié)議通信,即是面向對象的通信,分為服務端和客戶端兩部分。
1.2 服務端編程流程:
1)加載套接字庫(使用函數(shù)WSAStartup()),創(chuàng)建套接字(使用socket())
2)綁定套接字到一個IP地址和一個端口上(使用函數(shù)bind())
3)將套接字設置為監(jiān)聽模式等待連接請求(使用函數(shù)listen()),監(jiān)聽套接字即完成
4)請求到來后,接收連接請求,返回一個新的對應于此次連接的套接字(accept())
5)使用新的套接字和客戶端進行通信,發(fā)送和接收數(shù)據(jù)(send()或recv()),通信結束就關閉這個新創(chuàng)建的套接字(closesocket())
6)若要退出服務器程序,應先關閉監(jiān)聽套接字(使用函數(shù)closesocket()),再釋放加載的套接字庫(使用函數(shù)WSACleaup())
1.3 客戶端編程步驟:
1)加載套接字(使用函數(shù)WSAStartup),創(chuàng)建套接字(使用函數(shù)socket)
2)先服務器發(fā)送請求(使用函數(shù)connect)
3)和服務端進行通信,及發(fā)送或接收數(shù)據(jù)(使用函數(shù)send或recv)
4)若要關閉客戶端,先關閉套接字(使用函數(shù)closesocket),再釋放加載的套接字庫(使用函數(shù)WSACleanup)
2. 協(xié)議簇和地址簇
2.1 協(xié)議族
不同協(xié)議的集合,用來標識不同的協(xié)議
3. socket套接字
一個套接字代表通信的一端,socket套接字包含了IP地址和端口信息,IP地址能從網(wǎng)絡中識別主機,端口能識別主機上的進程。
4.TCP套接字相關函數(shù)
2.0版本的window API函數(shù)的聲明在window2.h中,在Ws2_32.dll中實現(xiàn)。
1.WSAStartup(WORD wVersionRequseted,LPWASDATA) |
用于初始化Windsock庫 |
WORD參數(shù)用于指定Winsock規(guī)范的版本 lpWSAData參數(shù)返回請求的socket版本信息 |
2. socket(int af,int type,int protocol); |
用于創(chuàng)建套接字 |
af參數(shù)用于指定所使用的協(xié)議簇 type參數(shù)用于指定套接字類型 protocol參數(shù)指定應用程序所使用的通信協(xié)議,例如TCP、UPD協(xié)議 |
3. bind(SOCKET s,const struct sockaddr name, int namelen) |
將本地地址信息關聯(lián)到一個套接字身上 |
s參數(shù)標識一個帶綁定的套接字描述符 name參數(shù)為指向sockaddr的指針,結構體中包含了IP地址和端口號 namelen確定緩沖區(qū)長度 |
4.listen(socket s,int backlog) |
用于服務端的流套接字,讓套接字處于監(jiān)聽狀態(tài) |
s參數(shù)表示一個流套接字的描述符 backlog參數(shù)表示連接請求隊列客戶連接的最大數(shù)量 |
5. accept(SOCKET s,struct sockaddr *addr,int *addelen)或 WSAAccept() |
取出客戶端請求隊列中最前面的請求,并創(chuàng)建一個新的套接字保持與客戶套接字進行連接 |
s參數(shù)表示為處于監(jiān)聽狀態(tài)的流套接字描述符 addr參數(shù)返回新創(chuàng)建的套接字地址結構 addrlen參數(shù)指向結構sockaddr的長度99 |
6. connnet(SOCKET s,const struct sockaddr *name,int namelen) 或WSAConnect() |
客戶端使用connect函數(shù)與服務端的監(jiān)聽套接字建立連接,連接成功則返回0 |
s參數(shù)表示還未建立連接的套接字描述符 name參數(shù)表示對方套接字的地址 namelen參數(shù)表示name所指向的緩存區(qū)大小 |
7. send(SOCKET s,const char *buf,int len, int flags)或WSASend() |
用于在建立連接的socket上發(fā)送數(shù)據(jù),客戶端和服務端都可以使用 |
s參數(shù)為發(fā)送端套接字的描述符 buf存放應用程序要發(fā)送數(shù)據(jù)的緩存區(qū) len參數(shù)為緩存區(qū)的大小 flags參數(shù)一般設置為0 |
8. recv(SOCKET s,char * buf,int len,int flags) 或WSARecv() |
從連接的或未連接的套接字中接收數(shù)據(jù) |
s參數(shù)為已連接或綁定(針對無連接)的套接字的描述符 buf為緩存區(qū) len為緩存區(qū)的大小 flags一般為0 |
9. closesocket(SOCKET s) |
用于關閉套接字 |
s參數(shù)為要關閉的套接字,關閉成功者返回0 |
10. inet_addr(const char * cp) |
用于將點分字符串的ip地址轉化為無符號長整型 |
cp參數(shù)指向一個ip地址的字符串 |
11. inet_ntoa(struct in_addr in) |
用于將in_addr結構體中的ip地址轉化為點分ip地址 |
in參數(shù)是in_addr結構類型的ip地址 |
12. htonl(u_long hostlong) |
將一個u_long類型的主機字節(jié)序轉化為網(wǎng)絡字節(jié)序(大端) |
hostlong參數(shù)表示要轉化為網(wǎng)絡字節(jié)序的數(shù)據(jù) |
13. htons(u_short hostshort) |
將一個u_short類型的主機字節(jié)序轉化為網(wǎng)絡字節(jié)序(大端) |
hostshort參數(shù)表示要轉化為網(wǎng)絡字節(jié)序的數(shù)據(jù) |
14. WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int, int wMsg,long lEvent) |
將某個套接字的網(wǎng)絡事件關聯(lián)到窗口上,以便從窗口上接收網(wǎng)絡事件的信息通知 |
s參數(shù)為套接字描述符 hWnd為網(wǎng)絡事件發(fā)生時,用于接收信息的窗口句柄 wMsg為網(wǎng)絡事件發(fā)生時所接收的信息 lEvent參數(shù)應用程序感興趣的比特組合碼 |
15. WSACleanup() |
解除與Winsock庫綁定并釋放Winsock庫所占用的系統(tǒng)資源 |
5. TCP套接字編程
阻塞套接字模式適用場景:能夠立即發(fā)送和接收數(shù)據(jù)且處理的套接字數(shù)量較少。缺點:在大量建立好的套接字線程之間進行通信時比較困難,擴展性差
6.數(shù)據(jù)發(fā)送和數(shù)據(jù)接收的緩沖區(qū)
第一個緩沖區(qū)(應用程序緩沖區(qū))
第二個緩沖區(qū)(TCP套接字緩沖區(qū)):處于內核協(xié)議棧中,也稱為內核緩沖區(qū)。發(fā)送的數(shù)據(jù)要從應用程序緩沖區(qū)復制到協(xié)議棧中的套接字緩沖區(qū),后將套接字緩沖區(qū)中的數(shù)據(jù)發(fā)送打網(wǎng)絡上。
TPC數(shù)據(jù)傳輸?shù)奶攸c:
1)TCP是流協(xié)議,接收者收到的數(shù)據(jù)是一個個字節(jié)流,沒有消息邊界
2)真正發(fā)送多少數(shù)據(jù)由內核根據(jù)當前網(wǎng)絡狀態(tài)決定
3)真正發(fā)送數(shù)據(jù)的事件點也由內核協(xié)議棧根據(jù)當前網(wǎng)絡狀態(tài)決定
4)接收端在調用接收函數(shù)時并不知道recv函數(shù)會返回多少數(shù)據(jù)
數(shù)據(jù)發(fā)送的6種情形:發(fā)送數(shù)據(jù)A和數(shù)據(jù)B
1)網(wǎng)絡情況良好,A和B沒有受到發(fā)送窗口、擁塞窗口和TCP最大傳輸單元影響
2)發(fā)送數(shù)據(jù)A時,網(wǎng)絡狀況不好,數(shù)據(jù)A發(fā)送延遲,協(xié)議棧將數(shù)據(jù)A和數(shù)據(jù)B合并為一個數(shù)據(jù)端再發(fā)送,并且合并后的數(shù)據(jù)長度沒有超過窗口大小和最大傳輸單元
3)發(fā)送數(shù)據(jù)A時產(chǎn)生延遲,協(xié)議棧將數(shù)據(jù)A和數(shù)據(jù)B合并,超過TCP傳輸最大單元,數(shù)據(jù)A較小,切割發(fā)生在數(shù)據(jù)B身上
4)發(fā)送數(shù)據(jù)A時產(chǎn)生延遲,協(xié)議棧將數(shù)據(jù)A和數(shù)據(jù)B合并,超過TCP傳輸最大單元,數(shù)據(jù)A較大,切割發(fā)生在數(shù)據(jù)A身上
5)接收窗口較小,內核協(xié)議棧將緩沖區(qū)中的數(shù)據(jù)按照接收方向進行依次切分
6)發(fā)送過程中出現(xiàn)錯誤,數(shù)據(jù)發(fā)送失敗
7. I/O控制命令
套接字的I/O控制用來設置套接字的工作模式(阻塞模式或者非阻塞模式)
1)iocltsocket()函數(shù)和WSAIoct 1中來發(fā)送I/O 控制命令
int icotlsocket(SOCKET s,long cmd, u_long *argp) // winsocket版的函數(shù)
參數(shù)說明
參數(shù)s為設置I/O模式的套接字描述符
參數(shù)cmd表示發(fā)給套接字的I/O控制命令,取值如下:
1)FIONBIO:表示設置或清除阻塞模式的命令,當argp=0時,套接字為阻塞模式,argp為非0時,套接字為非阻塞模式
WSAAsynSelect()函數(shù)會自動將套接字設置為非阻塞模式,此時并不能通過icotlsocket()函數(shù)將套接字設置為阻塞模式
2)FIONREAD:用于確定套接字s自動讀入數(shù)據(jù)量的命令。若s是流式套接字,則argp得到函數(shù)recv調用一次可讀入的數(shù)據(jù)量;
若s是數(shù)據(jù)報套接字,則argp返回套接字排隊的第一個數(shù)據(jù)報的大小。
3)FIOASYNC:表示設置或清除異步I/O的命令
2)WSAIoctl是Winsock 2中的I/O控制命令的函數(shù),功能更為強大。函數(shù)執(zhí)行成功返回0,否則返回SOCKET—ERROR,可用WSAGetLastError獲取錯誤碼
int WSAIoctl(SOCKET s,DWORD dwIoControlCode,LPVIOID IpvInBuffer,DWORD cbInBuffer, LPVOID lpvOutBuffer,DWORD cdOutBuffer, LPDWORD lpcbByteReturned,···)
參數(shù)說明
s 套接字描述符
dwIoControCode 存放用于操作的控制碼
lpInBuffer 指向輸入緩沖區(qū)地址
cbInBuffer 指向輸入緩沖區(qū)大小
lpInBuffer 輸出緩沖區(qū)地址
cbInBuffer 輸出緩沖區(qū)大小
IpcbBytesReturned 指向存放實際輸出數(shù)據(jù)的字節(jié)大小的變量地址
IpOverlapped 指向WSAOVERLAPPED結構體的地址
IpCompletionRoutine 指向一個例程函數(shù),該函數(shù)會在操作結束后調用
8. 獲取套接字選項
概念:套接字不僅可以通過I/O 控制命令來設置套接字,還可以通過設置套接字的選項來進一步對套接字進行控制,比如:
1)設置套接字的接收或發(fā)送緩沖區(qū)大小
2)指定是否允許套接字綁定到一個已經(jīng)使用過的地址
3)判斷套接字是否支持廣播
4)控制帶外數(shù)據(jù)的處理、獲取和設置超時參數(shù)
選項級別:
SOL_SOCKET 該級別的選項與套接字的具體協(xié)議無關,只作用于套接字本身
SOL_LPLMP 作用于IrDA協(xié)議
IPPROTO_IP 作用于IPv4協(xié)議
IPPROTO_IPV6 作用于IPv6協(xié)議
IPPROTO_RM 作用于可靠的多播傳輸
IPPROTO_TCP 使用于流式套接字
IPPROTO_UDP 適用于數(shù)據(jù)報套接字
獲取套接字選項:
int getsockopt(SOCKET s, int level, int optname, char* optval, int* optlen);
參數(shù)說明
s 套接字描述符
參數(shù)level 表示選項的級別
optname 表示要獲取的選項名稱
optval 指向存放接收到的選項內容的緩沖區(qū)
9. 設置套接字選項
Winsock提供了setsockopt來設置套接字選項
int setsockopt(SOCKET s,int level, int optname, const char* optval, int optlen)
參數(shù)說明
s 套接字描述符
level 選項的級別
optname 獲取選項的名稱
optval 指向存放要設置的選項值的緩沖區(qū)
opelen 緩沖區(qū)的大小
?10.代碼
? 服務端代碼:
#include<iostream>
#include<WinSock2.h>
#include<WS2tcpip.h>
#include<string>
#pragma comment(lib,"ws2_32.lib")
#pragma warning(disable:4996)
#define _WINSOCK_DEPRECATED_NO_WARNINGS // 為適用inet_ntoa時不出現(xiàn)警告
using namespace std;
int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2); // 制作Winsock庫的版本號
err = WSAStartup(wVersionRequested, &wsaData); // 初始化winsock庫
if (err != 0) return 0;
// 判斷返回的版本號時候正確
if(LOBYTE(wsaData.wVersion)!=2 || HIBYTE(wsaData.wVersion)!=2){
WSACleanup();
return 0;
}
// 創(chuàng)建套接字,用于監(jiān)聽客戶端的連接
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 適用主機任何可用的ip
// 適用ipv4的協(xié)議簇
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(8000); // 服務端的端口
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 綁定
listen(sockSrv, 5); // 開啟監(jiān)聽狀態(tài)
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while (1) {
cout << "等待客戶端" << endl;
// 從連接隊列中取出最靠前的一個客戶端請求,如果隊列為空則阻塞
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
char sendBuf[1001];
sprintf_s(sendBuf, "歡迎登錄服務端(%s)", inet_ntoa(addrClient.sin_addr)); // 組成字符串
send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
char recvBuf[100];
recv(sockConn, recvBuf, 100, 0); // 接收客戶端信息
cout << "接收到客戶端信息:" << recvBuf << endl; // 打印客戶端信息
puts("是否監(jiān)聽:y/n");
char ch[2];
cin >> ch;
if (tolower(ch[0]) == 'n') {
break;
}
closesocket(sockSrv);
WSACleanup();
}
system("pause");
return 0;
}
客戶端代碼:
#include "pch.h"
#include <iostream>
#include<WinSock2.h>
#include<WS2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
#pragma warning(disable:4996)
#define _WINSOCK_DEPRECATED_NO_WARNINGS // 為適用inet_ntoa時不出現(xiàn)警告
int main()
{
std::cout << "Hello World!\n";
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2); // 初始化Winsock庫
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
return 0;
}
// 判斷返回的版本號是否正確
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
WSACleanup();
return 0;
}
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建一個套接字
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 服務器的ip
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(8000); // 服務端的端口
err = connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
if (SOCKET_ERROR == err) {
std::cout << "服務器連接錯誤,請檢查服務器是否啟動!" << std::endl;
return 0;
}
char recvBuf[100];
recv(sockClient, recvBuf, 100, 0); // 接收來自服務端的信息
std::cout << "收到來自服務端的信息:" << recvBuf << std::endl;
send(sockClient, "你好,我是客戶端發(fā)送的信息", strlen("你好,我是客戶端發(fā)送的信息") + 1,0);
closesocket(sockClient);
WSACleanup(); // 釋放套接字
system("pause");
return 0;
}
?文章來源地址http://www.zghlxwxcb.cn/news/detail-776652.html文章來源:http://www.zghlxwxcb.cn/news/detail-776652.html
?
到了這里,關于C++網(wǎng)絡編程 TCP套接字基礎知識,利用TCP套接字實現(xiàn)客戶端-服務端通信的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!