網絡通信: TCP與UDP協議

主要參考:



網絡編程和套接字

  • 網絡編程是編寫程序使兩臺 聯網的 計算機相互交換數據, 主要依靠操作系統提供的“套接字”部件.
  • 套接字是用於網絡數據傳輸的軟件設備, 如果將網絡通信類比爲電話機通信系統,那套接字就是電話機, 渠道就是互聯網, 和電話撥打或接聽一樣,套接字也可以發送或接收.

消息接收過程

  1. 先有一臺電話機: 創建套接字, 即 實例化 int socket(int domain, int type, int protocol);
  2. 電話機要有電話號碼的問題: 給套接字 綁定 地址信息(IP地址和端口號) int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
  3. 設置電話機爲可接聽狀態: 設置套接字爲 可接聽 狀態 int listen(int sockfd, int backlog);
  4. 接聽電話: 套接字 接受 消息 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

消息發送過程

  1. 調用socket函數創建套接字
  2. 調用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;
}
發佈了38 篇原創文章 · 獲贊 17 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章