Socket網(wǎng)絡(luò)編程實現(xiàn)過程簡單總結(jié)
一、? ?服務(wù)器端
1、? ? 加載及釋放套接字庫
a)? ? ? ?使用函數(shù)WSAStartup()綁定相應(yīng)的套接字庫:
????當(dāng)一個應(yīng)用程序調(diào)用WSAStartup函數(shù)時,操作系統(tǒng)根據(jù)請求的Socket版本來搜索相應(yīng)的Socket庫,然后綁定找到的Socket庫到該應(yīng)用程序中。以后應(yīng)用程序就可以調(diào)用所請求的Socket庫中的其它Socket函數(shù)了。該函數(shù)執(zhí)行成功后返回0。
Ex:
????????wVersionRequested = MAKEWORD( 2, 1 );//指定加載2.1版本
? ? ? ? err =WSAStartup( wVersionRequested, &wsaData );
關(guān)于Socket版本:不同版本是有區(qū)別的,例如1.1版只支持TCP/IP協(xié)議,而2.0版可以支持多協(xié)議。2.0版有良好的向后兼容性,任何使用1.1版的源代碼、二進制文件、應(yīng)用程序都可以不修改地在2.0規(guī)范下使用。此外winsock 2.0支持異步 1.1不支持異步.
b)? ?使用函數(shù)WSACleanup ()綁定相應(yīng)的套接字庫:
應(yīng)用程序在完成對請求的Socket庫的使用后,要調(diào)用WSACleanup函數(shù)來解除與Socket庫的綁定并且釋放Socket庫所占用的系統(tǒng)資源。
c)? ? 以上二者需要庫Ws2_32.lib和頭文件winsock2.h
#include"winsock2.h"
#pragma comment(lib,”ws2_32.lib”)
2、? ?創(chuàng)建套接字
socket(domain=AF_INET,type=SOCK_STREAM,proto=IPPROTO_TCP)
三個參數(shù)分別是:地址系列,套接字類型,協(xié)議號
1)、Domain : Domain參數(shù)指定了通信的”域”
AF_UNIX :AF_LOCAL本地通信
AF_INET:IPv4網(wǎng)絡(luò)通信
AF_INET6:IPv6網(wǎng)絡(luò)通信
AF_PACKET:鏈路層通信
2)、Type: Type就是socket的類型,對于AF_INET協(xié)議族而言有流套接字(SOCK_STREAM)、數(shù)據(jù)包套接字(SOCK_DGRAM)、原始套接字(SOCK_RAW)。
????????SOCK_STREAM:流套接字, 提供順序,可靠,雙向,基于連接的字節(jié)流。 可以支持帶外數(shù)據(jù)傳輸機制。例
????????????如:TCP協(xié)議、FTP協(xié)議
????????SOCK_DGRAM:數(shù)據(jù)包套接字, 支持數(shù)據(jù)報(無連接,不可靠的固定最大長度的消息)例如:UDP協(xié)議
????????SOCK_RAW:原始套接字, 使用原始套接字時候調(diào)用,原始套接字也就是鏈路層協(xié)議
????????SOCK_SEQPACKET:有序分組套接字,為固定最大長度的數(shù)據(jù)報提供有序,可靠,雙向連接的數(shù)據(jù)傳輸路徑; 消費者需要利用每個輸入系統(tǒng)調(diào)用讀取整個分組
3)、Protocol:支持的協(xié)議
參數(shù)解析參考:https://blog.csdn.net/liuxingen/article/details/44995467
3、? 綁定服務(wù)器套接字地址
int bind(intsockfd, const struct sockaddr *addr,socklen_t addrlen);
服務(wù)端套接字綁定自己的IP地址與端口號,客戶端那邊可以不寫,內(nèi)核會給它分配一個臨時的端口。
參數(shù):
1)、sockfd: 服務(wù)器或者客戶端自己創(chuàng)建的socket
2)、sockaddr: 服務(wù)器或者客戶端自己的地址信息(協(xié)議族、IP、端口號)
sockaddr 與sockaddr_in:
二者的占用的內(nèi)存大小是一致的,因此可以互相轉(zhuǎn)化,從這個意義上說,他們并無區(qū)別。
sockaddr常用于bind、connect、recvfrom、sendto等函數(shù)的參數(shù),指明地址信息。是一種通用的套接字地址。而sockaddr_in 是internet環(huán)境下套接字的地址形式。所以在網(wǎng)絡(luò)編程中我們會對sockaddr_in結(jié)構(gòu)體進行操作。使用sockaddr_in來建立所需的信息,最后使用類型轉(zhuǎn)化就可以了。
3)、socklen_t: 服務(wù)器或者客戶端自己的地址信息的長度
EXP:
seraddr.sin_family = AF_INET; // 設(shè)置地址族為IPv4
seraddr.sin_port = htons(SERPORT);??? //設(shè)置地址的端口號信息
seraddr.sin_addr.s_addr = inet_addr(SERADDR);?? // 設(shè)置IP地址
ret= bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
參考:https://blog.csdn.net/zz709196484/article/details/54864770
4、? ? 將套接字設(shè)置為監(jiān)聽模式等待連接請求
intlisten(int? sockfd, int? backlog);
用法:函數(shù)應(yīng)該在調(diào)用socket和bind這兩個函數(shù)之后,accept函數(shù)之前調(diào)用。
作用:讓服務(wù)器套接字sockfd進入監(jiān)聽狀態(tài)。
參數(shù):sockfd:套接字,成功返回后進入監(jiān)聽模式,當(dāng)有新連接并accept后會再建立一個套接字保存新的連接;
? backlog:并發(fā)連接數(shù),下面詳細介紹此參數(shù):
????????1)? 當(dāng)TCP接收一個連接后(三次握手通過)會將此連接存在連接請求隊列里面,并對隊列個數(shù)+1,而backlog為此隊列允許的最大個數(shù),超過此值,則直接將新的連接刪除,即不在接收新的連接。將這些處于請求隊列里面的連接暫記為后備連接,這些都在底層自動完成,底層將連接添加到隊列后等待上層來處理(一般是調(diào)用accept函數(shù)接收連接);
????????2)? 當(dāng)上層調(diào)用accept函數(shù)接收一個連接(處于請求隊列里面的后備連接),隊列個數(shù)會-1;
????????3)? 那么這樣一個加一個減,只要底層提交的速度小于上層接收的速度(一般是這樣),很明顯backlog就不能限制連接的個數(shù),只能限制后備連接的個數(shù)。那為啥要用這個backlog呢?主要用于并發(fā)處理,當(dāng)上層沒來的及接收時,底層可以提交多個連接;
????????4)?backlog的取值范圍 ,一般為0-5。
5、? ?接受連接請求,返回一個新的對應(yīng)于此次連接的套接字
????????accept(intsocket, sockaddr *name, int *addrlen)
??????? 參數(shù)socket: 是一個已設(shè)為監(jiān)聽模式的服務(wù)器端socket的描述符。
??????? 參數(shù)sockaddr: 是一個返回值,它指向一個struct sockaddr類型的結(jié)構(gòu)體的變量,保存了發(fā)起連接的客戶端得IP地址信息和端口信息。
??????? 參數(shù)addrlen: 也是一個返回值,指向整型的變量,保存了返回的地址信息的長度。
???????accept函數(shù)返回值是一個客戶端和服務(wù)器連接的SOCKET類型的描述符,在服務(wù)器端標識著這個客戶端。
6、? ?用5返回的套接字和客戶端進行通信(send()/recv());
send(sockets, char * str, int len, int flag)
??????? 第一個參數(shù):本機創(chuàng)建的套接字
??????? 第二個參數(shù):要發(fā)送的字符串
??????? 第三個參數(shù):發(fā)送字符串長度
??????? 第四個參數(shù):會對函數(shù)行為產(chǎn)生影響,一般設(shè)置為0
??? recv(socket s, char * buf, int len,intflag)
??????? 參數(shù)socket:創(chuàng)建的可以傳輸消息過來的套接字
??????? 參數(shù)buf:接受消息的字符串緩存
??????? 參數(shù)len:允許接收字符串的緩存的最大長度
? ? ? ? 第四個參數(shù):會對函數(shù)行為產(chǎn)生影響,一般設(shè)置為0
send和recv實際上分別是write和read函數(shù)的基礎(chǔ)上擴展了第四個參數(shù):
1)、recv對應(yīng)的flags有3個選項:
??? MSG_PEEK:查看數(shù)據(jù),并不從系統(tǒng)緩沖區(qū)移走數(shù)據(jù)
??? MSG_WAITALL:等待所有數(shù)據(jù),等到所有的信息到達時才返回,使用它時,recv返回一直阻塞,直到指定的條件滿足時,或者發(fā)生錯誤
? ? ?MSG_OOB:接受或者發(fā)送帶外數(shù)據(jù)
2)、send第四個參數(shù)flags,有2個選項:
??? MSG_DONTROUTE:不查找表,它告訴ip,目的主機在本地網(wǎng)絡(luò)上,沒必要查找表。(一般用在網(wǎng)絡(luò)診斷和路由程序里面)
? ? MSG_OOB:接受或者發(fā)送帶外數(shù)據(jù)?
功能上是分別將SendBuff拷貝到發(fā)送緩沖區(qū)中和將接受緩沖區(qū)的數(shù)據(jù)拷貝到ReciveBuf中;
https://blog.csdn.net/tingyun_say/article/details/51907687
https://blog.csdn.net/rankun1/article/details/50488989
二、? 客戶端
1、加載套接字庫,創(chuàng)建套接字(WSAStartup()/socket());
2、向服務(wù)器發(fā)出連接請求(connect());
3、和服務(wù)器進行通信(send()/recv());
4、關(guān)閉套接字,關(guān)閉加載的套接字庫(closesocket()/WSACleanup());
三、? 代碼實現(xiàn)
環(huán)境:window7、vs2015
服務(wù)器端:
#include<iostream>
#include "winsock2.h"
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
????int RetVal;
????WORD SocketVersion=MAKEWORD(2, 2);
????WSADATA wsd;
????if (WSAStartup(SocketVersion, &wsd) != 0)
????{
????????cout << "綁定Socket庫失敗" << endl;
????}
????SOCKET ServerSocket;
????ServerSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
????if (ServerSocket == INVALID_SOCKET)
????{
????????cout << "創(chuàng)建服務(wù)器套接字失敗" << endl;
????????WSACleanup();
????????return -1;
}
SOCKADDR_IN ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(2345);
ServerAddr.sin_addr.S_un.S_addr = INADDR_ANY;
RetVal = bind(ServerSocket, (SOCKADDR *)&ServerAddr, sizeof(SOCKADDR_IN));
if (RetVal == SOCKET_ERROR)
{
????cout << "套接字綁定失敗" << endl;
????closesocket(ServerSocket);
????WSACleanup();
????return -1;
}
RetVal = listen(ServerSocket,2);
if (RetVal == SOCKET_ERROR)
{
????cout << "套接字監(jiān)聽失敗" << endl;
????closesocket(ServerSocket);
????WSACleanup();
????return -1;
}
SOCKET ClientSocket;
SOCKADDR_IN ClientAddr;
int ClientAddrLen = sizeof(ClientAddr);
ClientSocket = accept(ServerSocket, (SOCKADDR*)&ClientAddr, &ClientAddrLen);
if (ClientSocket == INVALID_SOCKET)
{
????cout << "接收客戶端請求失敗" << endl;
????closesocket(ServerSocket);
????WSACleanup();
????return -1;
}
char ReceiveBuff[BUFSIZ];
char SendBuff[BUFSIZ];
while (true)
{
????ZeroMemory(ReceiveBuff, BUFSIZ);
????RetVal = recv(ClientSocket, ReceiveBuff, BUFSIZ, 0);
????if (RetVal == SOCKET_ERROR)
????{
????????cout << "接收數(shù)據(jù)失敗" << endl;
????????closesocket(ServerSocket);
????????closesocket(ClientSocket);
???????WSACleanup();
????????return? -1;
????}
cout << "接收自客戶端數(shù)據(jù):" << ReceiveBuff << endl;
cout << "向客戶端發(fā)送數(shù)據(jù):";
cin >> SendBuff;
send(ClientSocket, SendBuff, strlen(SendBuff), 0);
}
closesocket(ServerSocket);
closesocket(ClientSocket);
WSACleanup();
return 0;
}
客戶端:
#include<iostream>
#include "winsock2.h"
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
????const int BUF_SIZE = 64;
????int RetVal;
????WSADATA Wsd;
????if (WSAStartup(MAKEWORD(2, 2), &Wsd) != 0)
????{
????????cout << "初始化套接字動態(tài)庫失敗" << endl;
????????return -1;
????}
????SOCKET ServerScoket;
????ServerScoket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
????if (ServerScoket == INVALID_SOCKET)
????{
????????cout << "創(chuàng)建套接字失敗" << endl;
????????WSACleanup();
????????return -1;
????}
????SOCKADDR_IN ServerAddr;
????ServerAddr.sin_family = AF_INET;
????ServerAddr.sin_port = htons(2345);
????ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
????RetVal = connect(ServerScoket, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
????if (RetVal == SOCKET_ERROR)
????{
????????cout << "鏈接服務(wù)器失敗" << endl;
????????closesocket(ServerScoket);
????????WSACleanup();
????????return -1;
????}
????char SendBuff[BUF_SIZE];
????char RECVBuff[BUF_SIZE];
????while (true)
????{
????????ZeroMemory(SendBuff, BUF_SIZE);
????????cout << "向服務(wù)器發(fā)送數(shù)據(jù)" << endl;
????????cin >> SendBuff;
????????RetVal = send(ServerScoket, SendBuff, strlen(SendBuff),0);
????????if (RetVal == SOCKET_ERROR)
????????{
????????????cout << "發(fā)送數(shù)據(jù)失敗" << endl;
????????????closesocket(ServerScoket);
????????????WSACleanup();
????????????return -1;
????????}
????ZeroMemory(RECVBuff, BUF_SIZE);
????recv(ServerScoket, RECVBuff, BUF_SIZE, 0);
????cout << endl << "從服務(wù)器接收數(shù)據(jù):" << RECVBuff << endl;
????}
????closesocket(ServerScoket);
????WSACleanup();
}