主要參考:
網絡編程和套接字
網絡編程
是編寫程序使兩臺 聯網的 計算機相互交換數據, 主要依靠操作系統提供的“套接字”部件.套接字
是用於網絡數據傳輸的軟件設備, 如果將網絡通信類比爲電話機通信系統,那套接字就是電話機, 渠道就是互聯網, 和電話撥打或接聽一樣,套接字也可以發送或接收.
消息接收過程
- 先有一臺電話機: 創建套接字, 即 實例化
int socket(int domain, int type, int protocol);
- 電話機要有電話號碼的問題: 給套接字 綁定 地址信息(IP地址和端口號)
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
- 設置電話機爲可接聽狀態: 設置套接字爲 可接聽 狀態
int listen(int sockfd, int backlog);
- 接聽電話: 套接字 接受 消息
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
消息發送過程
- 調用socket函數創建套接字
- 調用connect函數向服務端發送 連接請求
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
UDP協議
簡介
UDP(User Datagram Protocol,用戶數據報協議)是傳輸層的協議,是在IP的數據報服務之上增加了最基本的服務:複用和分用以及差錯檢測。
- 傳輸層協議
- 無連接
- 不可靠傳輸
- 面向數據報
優勢:
-
UDP無連接,時間上不存在建立連接需要的時延。空間上,TCP需要在端系統中維護連接狀態,需要一定的開銷。此連接裝入包括接收和發送緩存,擁塞控制參數和序號與確認號的參數。UCP不維護連接狀態,也不跟蹤這些參數,開銷小。空間和時間上都具有優勢。
-
分組首部開銷小,TCP首部20字節,UDP首部8字節。
-
UDP沒有擁塞控制,應用層能夠更好的控制要發送的數據和發送時間,網絡中的擁塞控制也不會影響主機的發送速率。某些實時應用要求以穩定的速度發送,能容忍一些數據的丟失,但是不能允許有較大的時延(比如實時視頻,直播等)
-
UDP提供盡最大努力的交付,不保證可靠交付。所有維護傳輸可靠性的工作需要用戶在應用層來完成。沒有TCP的確認機制、重傳機制。如果因爲網絡原因沒有傳送到對端,UDP也不會給應用層返回錯誤信息
-
UDP是面向報文的,對應用層交下來的報文,添加首部後直接鄉下交付爲IP層,既不合並,也不拆分,保留這些報文的邊界。對IP層交上來UDP用戶數據報,在去除首部後就原封不動地交付給上層應用進程,報文不可分割,是UDP數據報處理的最小單位。
-
UDP常用一次性傳輸比較少量數據的網絡應用,如DNS,SNMP等,因爲對於這些應用,若是採用TCP,爲連接的創建,維護和拆除帶來不小的開銷。UDP也常用於多媒體應用(如IP電話,實時視頻會議,流媒體等)數據的可靠傳輸對他們而言並不重要,TCP的擁塞控制會使他們有較大的延遲,也是不可容忍的
代碼實現
- Server 端:
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>
#include <winsock2.h>
int main(int argc, char* argv[])
{
WSADATA wsaData;
WORD sockVersion = MAKEWORD(2, 2);
if (WSAStartup(sockVersion, &wsaData) != 0)
{
return 0;
}
SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (serSocket == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(serSocket, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("bind error !");
closesocket(serSocket);
return 0;
}
const int MAX_LENGTH = 255;
char sendData[MAX_LENGTH];
char recvData[MAX_LENGTH];
sockaddr_in remoteAddr;
int nAddrLen = sizeof(remoteAddr);
while (true)
{
int ret = recvfrom(serSocket, recvData, MAX_LENGTH, 0, (sockaddr*)&remoteAddr, &nAddrLen); // 返回讀入的字節數
if (ret > 0)
{
recvData[ret] = 0x00;
printf("來自 %s: %s\n", inet_ntoa(remoteAddr.sin_addr), recvData);
strcpy_s(sendData, "已讀.\n");
sendto(serSocket, sendData, strlen(sendData), 0, (sockaddr *)&remoteAddr, nAddrLen);
}
}
closesocket(serSocket);
WSACleanup();
return 0;
}
- Client 端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>
#include <winsock2.h>
int main(int argc, char* argv[])
{
WORD socketVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(socketVersion, &wsaData) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int len = sizeof(sin);
const int MAX_LENGTH = 255;
char sendData[MAX_LENGTH] = "在嗎?";
char recvData[MAX_LENGTH];
// 開啓會話
sendto(sclient, sendData, strlen(sendData), 0, (sockaddr *)&sin, len);
printf("send: 在嗎?\n");
while (true)
{
printf("\r ");
printf("\r接收數據...");
Sleep(1000);
int ret = recvfrom(sclient, recvData, MAX_LENGTH, 0, (sockaddr *)&sin, &len);
if (ret > 0)
{
recvData[ret] = 0x00;
printf("\r ");
printf("\rrecv: %s\n", recvData);
printf("send: ");
gets_s(sendData, MAX_LENGTH);
printf("發送數據...\t");
sendto(sclient, sendData, strlen(sendData), 0, (sockaddr *)&sin, len);
Sleep(1000);
printf("done.");
Sleep(1000); // 模擬網絡延遲
}
}
closesocket(sclient);
WSACleanup();
return 0;
}
TCP協議
建議參考博文: TCP詳解
簡介
TCP協議全稱: 傳輸控制協議, 顧名思義, 就是要對數據的傳輸進行一定的控制.
- 傳輸層協議
- 有連接
- 可靠傳輸
- 面向字節流
正常情況下, tcp需要經過三次握手建立連接, 四次揮手斷開連接.
第一次: 客戶端 - - > 服務器 __ 此時服務器知道了客戶端要建立連接了
第二次: 客戶端 < - - 服務器 __ 此時客戶端知道服務器收到連接請求了
第三次: 客戶端 - - > 服務器 __ 此時服務器知道客戶端收到了自己的迴應
到這裏, 就可以認爲客戶端與服務器已經建立了連接.
四次揮手
代碼實現
- Server 端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA wsaData;
int port = 5099;
char buf[] = "Server: hello, I am a server.....";
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Failed to load Winsock");
return -1;
}
//創建用於監聽的套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(port); //1024以上的端口號
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
if (retVal == SOCKET_ERROR) {
printf("Failed bind:%d\n", WSAGetLastError());
return -1;
}
if (listen(sockSrv, 10) == SOCKET_ERROR) {
printf("Listen failed:%d", WSAGetLastError());
return -1;
}
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
//等待客戶請求到來
SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrClient, &len);
if (sockConn == SOCKET_ERROR) {
printf("Accept failed:%d", WSAGetLastError());
//break;
}
printf("Accept client IP:[%s]\n", inet_ntoa(addrClient.sin_addr));
//發送數據
int iSend = send(sockConn, buf, sizeof(buf), 0);
if (iSend == SOCKET_ERROR) {
printf("send failed");
// break;
}
char recvBuf[100];
memset(recvBuf, 0, sizeof(recvBuf));
// //接收數據
recv(sockConn, recvBuf, sizeof(recvBuf), 0);
printf("%s\n", recvBuf);
closesocket(sockConn);
closesocket(sockSrv);
WSACleanup();
system("pause");
return 0;
}
- Client 端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
//加載套接字
WSADATA wsaData;
char buff[1024];
memset(buff, 0, sizeof(buff));
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Failed to load Winsock");
return -1;
}
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(5099);
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//創建套接字
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKET_ERROR == sockClient) {
printf("Socket() error:%d", WSAGetLastError());
return -1;
}
//向服務器發出連接請求
if (connect(sockClient, (struct sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET) {
printf("Connect failed:%d", WSAGetLastError());
return -1;
}
else
{
//接收數據
recv(sockClient, buff, sizeof(buff), 0);
printf("%s\n", buff);
}
//發送數據
auto *buffSend = "hello, this is a Client....";
send(sockClient, buffSend, strlen(buffSend) + 1, 0);
printf("%d", strlen(buffSend) + 1);
//關閉套接字
closesocket(sockClient);
WSACleanup();
system("pause");
return 0;
}