主要参考:



网络编程和套接字

  • 网络编程是编写程序使两台 联网的 计算机相互交换数据, 主要依靠操作系统提供的“套接字”部件.
  • 套接字是用于网络数据传输的软件设备, 如果将网络通信类比为电话机通信系统,那套接字就是电话机, 渠道就是互联网, 和电话拨打或接听一样,套接字也可以发送或接收.

消息接收过程

  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;
}