• C语言网络编程


    文章目录

    套接字

    socket,套接字,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

    Linux下的套接字

    Linux中创建的文件都有一个 int 类型的编号,称为文件描述符(File Descriptor)。使用文件时,只要知道文件描述符就可以。socket 也被认为是文件的一种,和普通文件的操作没有区别

    Windows下的套接字

    WinSock(Windows Socket)编程依赖于系统提供的动态链接库(DLL),有两个版本:

    • 较早的DLL是 wsock32.dll,大小为 28KB,对应的头文件为 winsock1.h;
    • 最新的DLL是 ws2_32.dll,大小为 69KB,对应的头文件为 winsock2.h。

    加载ws2_32.dll

    #pragma comment (lib, "ws2_32.lib")
    
    • 1

    WSAStartup()

    使用DLL之前,还需要调用 WSAStartup() 函数进行初始化,以指明 WinSock 规范的版本

    int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
    
    • 1
    • wVersionRequested 为 WinSock 规范的版本号,低字节为主版本号,高字节为副版本号(修正版本号)
    • lpWSAData 为指向 WSAData 结构体的指针。

    eg:

    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    
    • 1
    • 2

    建立Socket

    Linux

    int socket(int af, int type, int protocol);
    
    • 1
    • af ,地址族(Address Family),常用AF_INET(IPv4) 和 AF_INET6(IPv6)。
    • type ,数据传输方式,常用的有 SOCK_STREAM(面向连接)和 SOCK_DGRAM(无连接)
    • protocol 表示传输协议,常用的有 IPPROTO_TCP(TCP协议) 和 IPPTOTO_UDP(UDP协议)

    1.TCP socket

    int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    
    • 1

    2.UDP socket

    int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    
    • 1

    Windows

    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);  //创建TCP套接字
    
    • 1

    bind() 函数

    将套接字与特定的IP地址和端口绑定起来

    int bind(int sock, struct sockaddr *addr, socklen_t addrlen);  //Linux
    int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);  //Windows
    
    • 1
    • 2

    示例

    //创建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    //创建sockaddr_in结构体变量
    struct sockaddr_in serv_addr;
    
    memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
    
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    
    //将套接字和IP、端口绑定
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    1、sockaddr_in 结构体

    struct sockaddr_in{
        sa_family_t     sin_family;   //地址族(Address Family),也就是地址类型
        uint16_t        sin_port;     //16位的端口号
        struct in_addr  sin_addr;     //32位IP地址
        char            sin_zero[8];  //不使用,一般用0填充
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • sin_family 和 socket() 的第一个参数的含义相同,取值也要保持一致。

    • sin_prot 为端口号。需要用 htons() 函数转换

    • sin_addrstruct in_addr 结构体类型的变量

      in_addr 结构体

      struct in_addr{
      in_addr_t  s_addr;  //32位的IP地址
      };
      
      • 1
      • 2
      • 3

      需要inet_addr()转换

      serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
      
      • 1

    2、sockaddr

    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    
    • 1

    bind() 第二个参数的类型为 sockaddr,而代码中却使用 sockaddr_in,然后再强制转换为 sockaddr,这是为什么呢?

    sockaddr 结构体的定义如下:

    struct sockaddr{
        sa_family_t  sin_family;   //地址族(Address Family),也就是地址类型
        char         sa_data[14];  //IP地址和端口号
    };
    
    • 1
    • 2
    • 3
    • 4

    下图是 sockaddr 与 sockaddr_in 的对比(括号中的数字表示所占用的字节数):

    sockaddr 和 sockaddr_in 的长度相同,都是16字节,只是将IP地址和端口号合并到一起,用一个成员 sa_data 表示。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,遗憾的是,没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值,所以使用 sockaddr_in 来代替。这两个结构体的长度相同,强制转换类型时不会丢失字节,也没有多余的字节。


    connect() 函数

    建立连接

    与bind类似

    int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);  //Linux
    int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);  //Windows
    
    • 1
    • 2

    linsten() 函数

    套接字进入被动监听状态

    int listen(int sock, int backlog);  //Linux
    int listen(SOCKET sock, int backlog);  //Windows
    
    • 1
    • 2
    • sock 需要进入监听状态的套接字
    • backlog 请求队列的最大长度

    accept() 函数

    套接字处于监听状态时,接收客户端请求,返回新的套接字

    int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);  //Linux
    SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);  //Windows
    
    • 1
    • 2
    • sock 为服务器端套接字
    • addr 为 sockaddr_in 结构体变量
    • addrlen 为参数 addr 的长度,可由 sizeof() 求得

    Linux 接受和发送数据

    write()

    写入数据

    ssize_t write(int fd, const void *buf, size_t nbytes);
    
    • 1
    • fd 为要写入的文件的描述符
    • buf 为要写入的数据的缓冲区地址
    • nbytes 为要写入的数据的字节数。

    read()

    读取数据

    ssize_t read(int fd, void *buf, size_t nbytes);
    
    • 1
    • fd 为要读取的文件的描述符
    • buf 为要接收数据的缓冲区地址
    • nbytes 为要读取的数据的字节数。

    Windos 接受和发送数据

    send()

    发送数据

    int send(SOCKET sock, const char *buf, int len, int flags);
    
    • 1
    • sock 为要发送数据的套接字
    • buf 为要发送的数据的缓冲区地址
    • len 为要发送的数据的字节数
    • flags 为发送数据时的选项,一般设置为 0 或 NULL

    recv()

    接受数据

    int recv(SOCKET sock, char *buf, int len, int flags);
    
    • 1

    同上


    示例

    Windows

    server

    #include 
    #include 
    #pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll
    int main(){
        //初始化 DLL
        WSADATA wsaData;
        WSAStartup(MAKEWORD(2, 2), &wsaData);
        //创建套接字 PF_INET:IPv4
        SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        //绑定套接字
        sockaddr_in sockAddr;
        memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
        
        sockAddr.sin_family = PF_INET;  //使用IPv4地址
        sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
        sockAddr.sin_port = htons(1234);  //端口
      	bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
        
        //进入监听状态
        listen(servSock, 20);
        //接收客户端请求
        SOCKADDR clntAddr;
        int nSize = sizeof(SOCKADDR);
        SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
        //向客户端发送数据
        char *str = "Hello World!";
        send(clntSock, str, strlen(str)+sizeof(char), NULL);
        //关闭套接字
        closesocket(clntSock);
        closesocket(servSock);
        //终止 DLL 的使用
        WSACleanup();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    client

    #include 
    #include 
    #include 
    #pragma comment(lib, "ws2_32.lib")  
    
    //加载 ws2_32.dll
    int main(){
        //初始化DLL
        WSADATA wsaData;
        WSAStartup(MAKEWORD(2, 2), &wsaData);
        //创建套接字
        SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        //向服务器发起请求
        sockaddr_in sockAddr;
        memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
        sockAddr.sin_family = PF_INET;
        sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
        sockAddr.sin_port = htons(1234);
        connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
        //接收服务器传回的数据
        char szBuffer[MAXBYTE] = {0};
        recv(sock, szBuffer, MAXBYTE, NULL);
        //输出接收到的数据
        printf("Message form server: %s
    ", szBuffer);
        //关闭套接字
        closesocket(sock);
        //终止使用 DLL
        WSACleanup();
        system("pause");
        return 0;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    Linux

    服务器端代码 server.cpp

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(){
        //创建套接字
        int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        //将套接字和IP、端口绑定
        struct sockaddr_in serv_addr;
        memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
        serv_addr.sin_family = AF_INET;  //使用IPv4地址
        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
        serv_addr.sin_port = htons(1234);  //端口
        bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
        //进入监听状态,等待用户发起请求
        listen(serv_sock, 20);
        //接收客户端请求
        struct sockaddr_in clnt_addr;
        socklen_t clnt_addr_size = sizeof(clnt_addr);
        int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
        //向客户端发送数据
        char str[] = "Hello World!";
        write(clnt_sock, str, sizeof(str));
       
        //关闭套接字
        close(clnt_sock);
        close(serv_sock);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    客户端代码 client.cpp:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(){
        //创建套接字
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        //向服务器(特定的IP和端口)发起请求
        struct sockaddr_in serv_addr;
        memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
        serv_addr.sin_family = AF_INET;  //使用IPv4地址
        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
        serv_addr.sin_port = htons(1234);  //端口
        connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
       
        //读取服务器传回的数据
        char buffer[40];
        read(sock, buffer, sizeof(buffer)-1);
       
        printf("Message form server: %s
    ", buffer);
       
        //关闭套接字
        close(sock);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    参考

    C语言网络编程_C语言中文网

    )

  • 相关阅读:
    Three.js 进阶之旅:全景漫游-初阶移动相机版
    Linux (Ubuntu)文件系统结构(入门必看)
    .NET 中的 Json 使用体验
    读图数据库实战笔记02_图数据建模
    深度强化学习第 1 章 机器学习基础
    图纸管理制度 《五》
    毕业三年
    【Java初阶】面向对象三大特性之封装
    生成式人工智能在先进无人机网络中的应用
    ICMP Redirect Attack Lab(SEED实验)
  • 原文地址:https://blog.csdn.net/qq_46416934/article/details/126654534