当前位置: 首页 > article >正文

网络编程(udp tcp)

组播通讯:

  1. 发送端实现步骤:
  2. 创建 UDP 类型的套接字
  3. 设置组播地址和组播端口
  4. 向组播地址和组播端口发送数据
  5. 可以接收回复的数据
  6. 关闭套接字
    2.接收端实现步骤:
    1.创建UDP类型的套接字
    2.绑定任意IP,组播端口到套接字上
    3.加入组播组(设置套接字 IPPROTO_IP层 IP_ADD_MEMBERSHIP属性)
struct ip_mreqn mreq;
mreq.imr_multiaddr = xxxx
mreq.imr_address = xxxxx;
mreq.imr_ifindex = if_nametoindex("ens33");
//设置套接字选项
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, 
sizeof(mreq));

4.接收组播数据
5.可以向发送端回复网络数据
6.离开组播组

setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, 
sizeof(mreq));

7.关闭套接字

UDP 广播通讯

广播通讯只能由UDP来完成。广播通讯要借助广播地址。
广播地址:
1. 直接广播地址 (主机ID数据位全为1) 例: 192.168.11.255
2. 本地广播地址 (网络ID和主机ID数据位全为1) 例: 255.255.255.255
广播通讯的流程:
1. 发送端

  1. 创建UDP类型套接字
    2) 设置套接字的广播属性
    setsockopt
    头文件: #include <sys/socket.h>
    函数原型: int setsockopt(int sockfd,int level, int optname ,
    const void *optval,socklen_t optlen);
    函数功能: 接收网络数据
    函数参数: sockfd: 套接字描述符
    level: 指定控制套接字的层次.可以取三种值:
    1) SOL_SOCKET: 通用(应用层)套接
    字选项.
    2) IPPROTO_IP: IP选项(网络层).
    3) IPPROTO_TCP: TCP选项(传输层).
    optname: 待设置的属性名
    optval: 指向属性值的指针
    optlen: 属性值的长度:

函数返回值:成功返回 0
失败返回-1. 错误码存储在errno
例子:
int enable = 1;

setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&enable,sizeof(int));
3) 向广播地址/端口进行数据发送。
4) 接收网络回复数据;
5) 关闭套接字
2. 接收端
1) 创建UDP类型套接字
2) 绑定任意IP/广播端口到套接字上
3) 接收广播数据;
4) 向发送端回复网络数据
5) 关闭套接字

UDP组播通讯

   组播通讯只能由UDP来完成。组播通讯要借助组播地址。

组播地址:D类IP主要用于组播通讯:

        永久组播地址:  224.0.0.1 ~ 224.0.0.255

公用组播地址: 224.0.1.0 ~ 224.0.1.255

函数原型: int listen(int sockl,int backlog);
函数功能: 接收网络数据
函数参数: sockl: 监听套接字描述符
backlog: 可排队连接的最大连接数
函数返回值:成功返回0
失败返回-1. 错误码存储在errno
4. 接受客户端连接,并生成通讯套接字
accept
头文件: #include <sys/socket.h>
函数原型: int accept(int sockl,struct sockaddr* addr,int*
socklen);
函数功能: 接收客户端;连接
函数参数: sockl: 监听套接字描述符
addr: 指向地址结构体指针,用来获取客户端地址信息
socklen: 获取客户端地址信息 结构体长度
函数返回值:成功返回新的套接字。用于与客户端数据收发
失败返回-1. 错误码存储在errno
5. 与客户端进行数据的收发通讯
send
头文件: #include <sys/socket.h>
函数原型: int sendto(int sockfd,void* buf,int len ,int flags);
函数功能: 发送网络数据
函数参数: sockfd: 套接字描述符
buf: 内存缓冲区地址,用于存储待发送的网络数据
len: 待发送的数据长度
flags: 操作标志,一般写为0

函数返回值:成功返回实际发送的数据长度(字节为单位)
失败返回-1. 错误码存储在errno

   recv
    头文件:    #include <sys/socket.h>

函数原型: int recv(int sockfd,void* buf,int len ,int flags);
函数功能: 接收网络数据
函数参数: sockfd: 套接字描述符
buf: 内存缓冲区地址,用于存储接收到的网络数据
len: 内存缓冲区长度
flags: 操作标志,一般写为0
from: 指向地址结构体的指针,用于接收通讯对方的地址信息:
fromlen: 用于接收对方地址结构体的长度

综合案例

  1. 代码功能概述
    该代码实现了一个简单的广播发送端程序,通过 UDP 协议向指定网络中的广播地址发送用户输入的数据,直到用户输入 “quit” 为止。

  2. 代码结构分析
    头文件和预处理
    #include “header.h”:包含自定义头文件(未提供具体内容),可能定义了 SockAddr、SockAddrIn 等类型。
    缺少标准库头文件:
    需要手动添加以下头文件:

#include <stdio.h> // 用于输入输出(puts、printf)
#include <stdlib.h> // 用于字符串转数字(atoi)
#include <string.h> // 用于字符串操作(strcmp、strlen、memset)
#include <unistd.h> // 用于系统调用(close)
#include <arpa/inet.h> // 用于网络地址转换(inet_addr、htons)
#include <sys/socket.h> // 用于套接字操作(socket、setsockopt、sendto)

主函数
命令行参数检查:

如果参数数量少于 3,输出提示并退出。
第 1 个参数(argv[1]):广播地址(如 192.168.12.255 或 255.255.255.255)。
第 2 个参数(argv[2]):广播端口号。
打印命令行参数:

使用 printf 输出广播地址和端口号。
创建套接字:

使用 socket(AF_INET, SOCK_DGRAM, 0) 创建一个 UDP 套接字。
如果创建失败,输出错误信息并退出。
设置广播模式:

使用 setsockopt 开启套接字的广播模式。
参数说明:
SOL_SOCKET:操作级别(套接字级别)。
SO_BROADCAST:启用广播模式。
&is_enable:设置为 1,表示启用广播。
如果设置失败,输出错误信息并关闭套接字后退出。
配置广播地址:

使用 SockAddrIn 结构体配置广播地址信息。
sin_family:设置为 AF_INET,表示 IPv4 协议。
sin_port:使用 htons 将端口号转换为网络字节序。
sin_addr.s_addr:使用 inet_addr 将广播地址字符串转换为二进制格式。
发送广播数据:

进入一个无限循环,等待用户输入。
使用 fgets 从标准输入读取用户输入的数据。
使用 strlen 删除输入数据中的换行符。
使用 sendto 将数据发送到广播地址。
如果用户输入 “quit”,跳出循环。
使用 memset 清空发送缓冲区。
关闭套接字:

在退出循环后,使用 close 关闭套接字。
3. 代码问题与改进建议
问题
缺少头文件:
代码中未包含必要的头文件,可能导致编译错误。
缓冲区溢出风险:
msg[strlen(msg) - 1] = 0; 如果 msg 为空字符串(如直接回车),会导致数组越界。
广播地址处理不灵活:
代码只支持从命令行参数传入广播地址,未支持默认广播地址(如 255.255.255.255)。
SockAddrIn 定义不明:
代码中使用了 SockAddrIn,但未提供其定义,可能是对 struct sockaddr_in 的别名。
sendto 参数问题:
sendto 的第三个参数 sizeof(msg) 可能导致发送多余的空字节,应使用 strlen(msg)。
改进建议
添加必要的头文件:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

修复缓冲区溢出问题:

if (msg[strlen(msg) - 1] == '\n')
    msg[strlen(msg) - 1] = '\0';

支持默认广播地址:

if (strcmp(argv[1], "default") == 0)
    send_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
else
    send_addr.sin_addr.s_addr = inet_addr(argv[1]);

明确 SockAddrIn 定义:

typedef struct sockaddr_in SockAddrIn;
#include "header.h"
/*
广播的发送端:
    1.调用setsockopt开启广播模式
    2.设置广播的特殊IP地址: 192.168.12.255   或者 255.255.255.255
    3.广播的发送端只做发送

*/

int main(int argc, char const *argv[])
{
    // 通过main函数的参数传递ip和端口
    if (argc < 3)
    {
        puts("命令行缺少参数!");
        return -1;
    }

    // 打印命令行参数
    printf("%s--%s\n", argv[1], argv[2]);

    // 创建发送端的套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("socket failed");
        return -1;
    }

    // 1.调用setsockopt开启广播模式
    int is_enable = 1;
    int r = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &is_enable, sizeof(is_enable));
    if (-1 == r)
    {
        perror("setsockopt failed");
        close(sockfd);
        return -1;
    }

    // 2.设置广播的特殊IP地址: 192.168.12.255   或者 255.255.255.255
    // 定义地址结构体变量
    SockAddrIn send_addr;
    // ip4协议
    send_addr.sin_family = AF_INET;
    send_addr.sin_port = htons(atoi(argv[2]));
     // 192.168.12.255   或者 255.255.255.255
    send_addr.sin_addr.s_addr = inet_addr(argv[1]);
    // send_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);  //255.255.255.255

    // 3.广播的发送端只做发送
    // 定义要发送的数据
    char msg[64] = {0};
    while (1)
    {
        puts("请输入要发送的内容:");
        fgets(msg, sizeof(msg), stdin);
        // 删除最后的换行符
        msg[strlen(msg) - 1] = 0; //  '\0'
        // 调用sendto函数发送数据
        sendto(sockfd, msg, sizeof(msg), 0, (SockAddr *)&send_addr, sizeof(send_addr));
        // 当用户输入quit是退出发送端
        if (strcmp("quit", msg) == 0)
        {
            break;
        }
        // 清理数据
        memset(msg, 0, sizeof(msg)); 
    }

    // 关闭套接字
    close(sockfd);

    return 0;
}

#include "header.h"
/*
广播接收端:
    1.广播接收端是被动接收,只做接收,不用其他设置
*/
int main(int argc, char const *argv[])
{
    // 通过命令行传递ip和端口
    if (argc < 2)
    {
        puts("命令行缺少参数");
        return -1;
    }

    // 测试打印参数
    printf("%s\n", argv[1]);
    // 创建接收端的套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("socket failed");
        return -1;
    }
    // 定义地址结构体
    SockAddrIn recv_addr = {0};
    // ip4协议
    recv_addr.sin_family = AF_INET;
    // 端口
    recv_addr.sin_port = htons(atoi(argv[1]));
    // ip地址 INADDR_ANY : 0.0.0.0
    recv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip地址是:0.0.0.0 表示任意一个ip地址 
    //可以通过netstat -tunl 命令查看网络ip地址与端口的占用状态 
    // 绑定地址
    int r = bind(sockfd, (SockAddr *)&recv_addr, sizeof(recv_addr));
    if (-1 == r)
    {
        perror("bind failed");
        close(sockfd);
        return -1;
    }
    // 循环接收数据
    char buf[64] = {0};
    // 定义广播发送端的地址结构体变量
    SockAddrIn broadcast_send_addr = {0};
    ssize_t size = -1;
    while (1)
    {
        socklen_t len = sizeof(broadcast_send_addr);
        size = recvfrom(sockfd, buf, sizeof(buf), 0, (SockAddr *)&broadcast_send_addr, &len);
        // 打印接收到的数据
        char *ip = inet_ntoa(broadcast_send_addr.sin_addr);
        printf("收到%s发送过来的%ld字节是数据是:%s\n", ip, size, buf);
        // 清空缓冲区
        memset(buf, 0, sizeof(buf));
        //清空本次地址信息
        memset(&broadcast_send_addr, 0, sizeof(broadcast_send_addr));
    }
    // 关闭套接字
    close(sockfd);

    return 0;
}

  1. 代码功能概述
    该代码实现了一个组播接收端程序,通过加入组播组(如 239.0.0.2)接收组播数据,并实时打印发送者的 IP 地址和接收到的数据。

  2. 代码结构分析
    头文件和预处理
    #include “header.h”:包含自定义头文件(未提供具体内容),可能定义了 SockAddr、SockAddrIn 等类型。
    缺少标准库头文件:
    需要手动添加以下头文件:

#include <stdio.h> // 用于输入输出(puts、printf)
#include <stdlib.h> // 用于字符串转数字(atoi)
#include <string.h> // 用于字符串操作(memset)
#include <unistd.h> // 用于系统调用(close)
#include <arpa/inet.h> // 用于网络地址转换(inet_pton、inet_ntoa、htons)
#include <sys/socket.h> // 用于套接字操作(socket、bind、recvfrom)
#include <net/if.h> // 用于网卡索引操作(if_nametoindex)

主函数
命令行参数检查:

如果参数数量少于 3,输出提示并退出。
第 1 个参数(argv[1]):本机 IP 地址(组播组成员的 IP)。
第 2 个参数(argv[2]):组播端口号。
打印命令行参数:

使用 printf 输出本机 IP 地址和端口号。
创建套接字:

使用 socket(AF_INET, SOCK_DGRAM, 0) 创建一个 UDP 套接字。
如果创建失败,输出错误信息并退出。
绑定套接字:

使用 SockAddrIn 结构体配置绑定地址信息。
sin_family:设置为 AF_INET,表示 IPv4 协议。
sin_port:使用 htons 将端口号转换为网络字节序。
sin_addr.s_addr:设置为 INADDR_ANY,表示接收任意 IP 地址发送的数据。
使用 bind 将地址绑定到套接字。如果绑定失败,输出错误信息并关闭套接字后退出。
加入组播组:

使用 struct ip_mreqn 结构体配置组播组信息。
imr_multiaddr:设置组播组的 IP 地址(239.0.0.2)。
imr_address:设置组播组成员的 IP 地址(从命令行参数获取)。
imr_ifindex:使用 if_nametoindex 获取网卡索引(如 ens33)。
使用 setsockopt 将套接字加入组播组。
接收组播数据:

进入一个无限循环,使用 recvfrom 接收组播数据。
使用 inet_ntoa 解析发送者的 IP 地址。
打印发送者的 IP 地址和接收到的数据。
使用 memset 清空接收缓冲区。
退出逻辑:

程序无退出条件(除非手动终止),因此不会执行到移除组播组和关闭套接字的代码。
3. 代码问题与改进建议
问题
缺少头文件:
代码中未包含必要的头文件,可能导致编译错误。
无限循环:
程序无限循环,无法正常退出,移除组播组和关闭套接字的代码无法执行。
mreqn.imr_ifindex 未处理错误:
if_nametoindex 可能返回 0(表示网卡不存在),但未处理这种情况。
缓冲区溢出风险:
recvfrom 的缓冲区大小为 64 字节,未处理数据超过缓冲区的情况。
SockAddrIn 定义不明:
代码中使用了 SockAddrIn,但未提供其定义,可能是对 struct sockaddr_in 的别名。
改进建议
添加必要的头文件:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <net/if.h>

添加退出条件:
例如,收到特定消息(如 “quit”)时退出循环。
处理 if_nametoindex 错误:

mreqn.imr_ifindex = if_nametoindex("ens33");
if (mreqn.imr_ifindex == 0) {
    perror("if_nametoindex failed");
    close(sockfd);
    return -1;
}

优化缓冲区处理:
使用动态缓冲区或设置最大接收长度。
明确 SockAddrIn 定义:

typedef struct sockaddr_in SockAddrIn;
#include "header.h"
/*
组播的接收端:
     1.绑定任意发送端的ip到接收端的套接字上
     2.添加组播组(设置套接字的选项)         IP_ADD_MEMBERSHIP           struct ip_mreqn mreqn;
        2.1:设置组播组的ip                 mreqn.imr_multiaddr
        2.2: 设置组播组成员的ip             mreqn.imr_address
        2.3: 通过网卡名称获取索引并设置     mreqn.imr_ifindex
     3.移除组播组 IP_ADD_MEMBERSHIP
*/
int main(int argc, char const *argv[])
{
    // 通过命令行传递参数
    if (argc < 3)
    {
        puts("命令行缺少参数");
        return -1;
    }
    printf("%s---%s\n", argv[1], argv[2]);
    // 创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("socket failed");
        return -1;
    }

    // 1.绑定任意发送端的ip到接收端的套接字上
    SockAddrIn multiAddr;
    multiAddr.sin_family = AF_INET;
    multiAddr.sin_port = htons(atoi(argv[2]));
    // 设置接收任意ip地址发送的组播信息
    multiAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0  通配ip,代表所有ip地址
    // 绑定地址到当前套接字上
    int r = bind(sockfd, (SockAddr *)&multiAddr, sizeof(multiAddr));
    if (-1 == r)
    {
        perror("bind failed");
        close(sockfd);
        return -1;
    }

    // 2.添加组播组(设置套接字的选项)
    // 定义组播相关的结构体
    struct ip_mreqn mreqn;
    // 设置组播组的ip地址: 239.0.0.0~239.255.255.255
    inet_pton(AF_INET, "239.0.0.2", &mreqn.imr_multiaddr);
    // mreqn.imr_multiaddr.s_addr = inet_addr("239.0.0.2");
    // 设置组播组成员的ip地址
    inet_pton(AF_INET, argv[1], &mreqn.imr_address);
    // 设置网卡的索引
    //  mreqn.imr_ifindex = 2;
    mreqn.imr_ifindex = if_nametoindex("ens33");
    printf("index: %d\n", if_nametoindex("ens33"));
    // 设置套接字选项
    setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn));

    // 接收数据
    char buf[64] = {0};
    ssize_t size = -1;
    // 定义地址结构体,用于接收发送数据的地址信息
    SockAddrIn sendAddr;
    while (1)
    {
        socklen_t len = sizeof(sendAddr);
        // 接收数据
        size = recvfrom(sockfd, buf, sizeof(buf), 0, (SockAddr *)&sendAddr, &len);
        // 解析ip地址
        char *ip = inet_ntoa(sendAddr.sin_addr);
        // 打印接收到的数据
        printf("收到%s发送过来长度为%ld的信息是: %s\n", ip, size, buf);
        // 清除缓冲数据
        memset(buf, 0, sizeof(buf));
    }
    // 3.移除组播组
    //  将当前组播中的成员移除
    setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn));

    // 关闭套接字
    close(sockfd);

    return 0;
}

#include "header.h"
/*
组播的发送端:
    1.设置组播发送端的地址和端口
    2.发送数据
*/
int main(int argc, char const *argv[])
{
    // 通过命令传递参数
    if (argc < 3)
    {
        puts("命令行缺少参数");
        return -1;
    }
    printf("%s---%s\n", argv[1], argv[2]);

    // 创建套接字socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("socket failed");
        return -1;
    }

    // 1.设置组播发送端的地址和端口
    SockAddrIn multiAddr;
    multiAddr.sin_family = AF_INET;
    multiAddr.sin_port = htons(atoi(argv[2]));

    // multiAddr.sin_addr.s_addr = inet_addr(argv[1]);
    // multiAddr.sin_addr.s_addr = htonl(INADDR_BROADCAST);  //255.255.255.255
    // 设置ip地址: 可以将字符串按照协议格式转换成32位整数并设置给结构体成员
    inet_pton(AF_INET, argv[1], &(multiAddr.sin_addr));

    // 2.发送数据
    // 定义字符串
    char msg[64] = {0};
    while (1)
    {
        puts("请输入要发送的内容:");
        fgets(msg, sizeof(msg), stdin);
        // 去除换行符
        msg[strlen(msg) - 1] = 0;
        sendto(sockfd, msg, strlen(msg), 0, (SockAddr *)&multiAddr, sizeof(multiAddr));
        // 当用户输入quit时退出
        if (strcmp("quit", msg) == 0)
        {
            break;
        }
        // 清除数据缓冲区
        memset(msg, 0, sizeof(msg));
    }
    // 关闭套接字
    close(sockfd);

    return 0;
}

  1. 代码功能概述
    该代码实现了一个简单的 TCP 服务端程序,监听指定 IP 地址和端口,等待客户端连接。客户端连接后,接收客户端发送的数据并回显给客户端。

  2. 代码结构分析
    头文件和预处理
    #include “header.h”:包含自定义头文件(未提供具体内容),可能定义了 SockAddr、SockAddrIn 等类型。
    缺少标准库头文件:
    需要手动添加以下头文件:

#include <stdio.h> // 用于输入输出(puts、printf)
#include <stdlib.h> // 用于字符串转数字(atoi)
#include <string.h> // 用于字符串操作(strlen)
#include <unistd.h> // 用于系统调用(close)
#include <arpa/inet.h> // 用于网络地址转换(inet_addr、inet_ntoa、htons、ntohs)
#include <sys/socket.h> // 用于套接字操作(socket、bind、listen、accept、recv、send)

主函数
命令行参数检查:

如果参数数量少于 3,输出提示并退出。
第 1 个参数(argv[1]):服务端 IP 地址。
第 2 个参数(argv[2]):服务端端口号。
打印命令行参数:

使用 printf 输出服务端 IP 地址和端口号。
创建套接字:

使用 socket(AF_INET, SOCK_STREAM, 0) 创建一个 TCP 套接字。
如果创建失败,输出错误信息并退出。
绑定地址:

使用 SockAddrIn 结构体配置绑定地址信息。
sin_family:设置为 AF_INET,表示 IPv4 协议。
sin_port:使用 htons 将端口号转换为网络字节序。
sin_addr.s_addr:使用 inet_addr 将 IP 地址字符串转换为二进制格式。
使用 bind 将地址绑定到套接字。如果绑定失败,输出错误信息并关闭套接字后退出。
监听客户端:

使用 listen 将套接字设置为监听状态,最大连接数为 2。
如果监听失败,输出错误信息并关闭套接字后退出。
接收客户端连接:

使用 accept 接收客户端的连接。
如果接收失败,输出错误信息并关闭套接字后退出。
成功连接后,打印提示信息。
接收客户端数据:

使用 recv 接收客户端发送的数据。
使用 inet_ntoa 和 ntohs 解析客户端的 IP 地址和端口号。
打印接收到的数据和客户端地址。
发送响应给客户端:

使用 send 将接收到的数据原样返回给客户端。
关闭套接字:

使用 close 关闭客户端和服务端套接字。

/*
TCP网络通讯的服务端
*/
#include "header.h"

int main(int argc, char const *argv[])
{
    if (argc < 3)
    {
        puts("命令行缺少参数");
        return -1;
    }
    printf("%s---%s\n", argv[1], argv[2]);

    // 1.创建socket
    int s_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == s_sockfd)
    {
        perror("socket failed");
        return -1;
    }
    // 2.绑定自己的地址
    SockAddrIn serverAddr;
    // ip4协议
    serverAddr.sin_family = AF_INET;
    // 端口  --- arv[2]
    serverAddr.sin_port = htons(atoi(argv[2]));
    // ip  -- argv[1]
    serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
    // 调用bind函数
    int r = bind(s_sockfd, (SockAddr *)&serverAddr, sizeof(serverAddr));
    if (-1 == r)
    {
        perror("bind failed");
        close(s_sockfd);
        return -1;
    }

    // 3.监听客户端
    int r2 = listen(s_sockfd, 2);
    if (-1 == r)
    {
        perror("listen failed");
        close(s_sockfd);
        return -1;
    }

    // 4.接收客户端的连接
    SockAddrIn clientAddr;
    socklen_t len = sizeof(clientAddr);
    // accept有阻塞效果,当和客户端完成三次握手建立连接后,该函数结束阻塞状态,并返回客户端的套接字描述符
    int c_sockfd = accept(s_sockfd, (SockAddr *)&clientAddr, &len);
    if (-1 == c_sockfd)
    {
        perror("accept failed");
        close(s_sockfd);
        return -1;
    }
    puts("客户端已经和服务端完成了三次握手并连接上了");

    // 5.接收客户端发送过来的数据
    char buf[64] = {0};
    // recv 有阻塞效果,当客户端没有调用send发送数据时,该函数下方的代码不执行
    ssize_t size = recv(c_sockfd, buf, sizeof(buf), 0);
    puts("等待客户端发送数据");
    // 打印接收到的数据和客户端的地址
    char *ip = inet_ntoa(clientAddr.sin_addr);
    unsigned short port = ntohs(clientAddr.sin_port);
    printf("[%s:%d]发送过来了%ld个字节的数据是 %s\n", ip, port, size, buf);

    // 6.给客户端做出响应:服务端发送数据给客户端
    send(c_sockfd, buf, strlen(buf), 0);

    // 7.关闭套接字
    close(c_sockfd);
    close(s_sockfd);

    return 0;
}

  1. 代码功能概述
    该代码实现了一个简单的 TCP 客户端程序,连接到指定的服务端 IP 地址和端口,发送用户输入的数据,并接收服务端的响应。

  2. 代码结构分析
    头文件和预处理
    #include “header.h”:包含自定义头文件(未提供具体内容),可能定义了 SockAddr、SockAddrIn 等类型。
    缺少标准库头文件:
    需要手动添加以下头文件:

#include <stdio.h> // 用于输入输出(puts、printf、fgets)
#include <stdlib.h> // 用于字符串转数字(atoi)
#include <string.h> // 用于字符串操作(strlen)
#include <unistd.h> // 用于系统调用(close)
#include <arpa/inet.h> // 用于网络地址转换(inet_addr、htons)
#include <sys/socket.h> // 用于套接字操作(socket、connect、send、recv)

主函数
命令行参数检查:

如果参数数量少于 3,输出提示并退出。
第 1 个参数(argv[1]):服务端 IP 地址。
第 2 个参数(argv[2]):服务端端口号。
打印命令行参数:

使用 printf 输出服务端 IP 地址和端口号。
创建套接字:

使用 socket(AF_INET, SOCK_STREAM, 0) 创建一个 TCP 套接字。
如果创建失败,输出错误信息并退出。
连接服务端:

使用 SockAddrIn 结构体配置服务端地址信息。
sin_family:设置为 AF_INET,表示 IPv4 协议。
sin_port:使用 htons 将端口号转换为网络字节序。
sin_addr.s_addr:使用 inet_addr 将 IP 地址字符串转换为二进制格式。
使用 connect 连接到服务端。如果连接失败,输出错误信息并关闭套接字后退出。
发送消息:

使用 fgets 从标准输入读取用户输入的数据。
使用 strlen 删除输入数据中的换行符。
使用 send 将数据发送到服务端。
接收服务端响应:

使用 recv 接收服务端发送的数据。
打印服务端的 IP 地址、端口号和响应数据。
关闭套接字:

使用 close 关闭客户端套接字。
3. 代码问题与改进建议
问题
缺少头文件:
代码中未包含必要的头文件,可能导致编译错误。
未处理发送数据错误:
send 可能返回 -1,但未处理这种情况。
未处理接收数据错误:
recv 可能返回 -1(表示错误)或 0(表示服务端断开连接),但未处理这种情况。
缓冲区溢出风险:
msg[strlen(msg) - 1] = 0; 如果 msg 为空字符串(如直接回车),会导致数组越界。
SockAddrIn 定义不明:
代码中使用了 SockAddrIn,但未提供其定义,可能是对 struct sockaddr_in 的别名。

/*
TCP网络通讯的客户端:

*/
#include "header.h"

int main(int argc, char const *argv[])
{
    if (argc < 3)
    {
        puts("命令行缺少参数");
        return -1;
    }
    printf("%s---%s\n", argv[1], argv[2]);
    // 1.创建套接字
    int c_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == c_sockfd)
    {
        perror("socket failed");
        return -1;
    }

    // 2.连接服务器
    SockAddrIn sockAddr;
    // ipv4
    sockAddr.sin_family = AF_INET;
    // 端口
    sockAddr.sin_port = htons(atoi(argv[2]));
    // ip地址
    sockAddr.sin_addr.s_addr = inet_addr(argv[1]);
    int r = connect(c_sockfd, (SockAddr *)&sockAddr, sizeof(sockAddr));
    if (-1 == r)
    {
        perror("connet failed");
        close(c_sockfd);
        return -1;
    }
    // 3.发送消息到服务器
    char msg[64] = {0};
    puts("请输入要发送的信息:");
    fgets(msg, sizeof(msg), stdin);
    // 去除换行符
    msg[strlen(msg) - 1] = 0;
    // 发送数据
    send(c_sockfd, msg, strlen(msg), 0);

    // 4.接收服务端的信息
    char data[64] = {0};
    recv(c_sockfd, data, sizeof(data), 0);
    printf("[%s:%d]响应回来的信息是 %s\n", argv[1], atoi(argv[2]), data);

    // 5.关闭套接字
    close(c_sockfd);
    return 0;
}


http://www.kler.cn/a/543409.html

相关文章:

  • (一)获取数据和读取数据
  • 在 ARM64 架构系统离线安装 Oracle Java 8 全流程指南
  • Unity URP后处理在Game窗口不显示
  • vue 主子表加校验问题
  • jupyter notebook中3种读图片的方法_与_图片翻转(上下翻转,左右翻转,上下左右翻转)
  • 本地部署DeepSeek R1 + 界面可视化open-webui
  • 前沿技术新趋势:值得关注的创新发展
  • 校园网绕过认证上网很简单
  • 并发编程---多线程不安全示例以及解决,多线程创建方式
  • AI大语言模型
  • JAVA面试之容器
  • Linux进程创建与终止
  • Amazon Keyspaces (for Apache Cassandra)
  • 未来趋势系列 篇一(加更四):DeepSeek题材解析和股票梳理
  • CF Round 997 记录 题解 (div. 2 A - E)
  • SpringBoot中的Javaconfig
  • KRR(知识表示与推理,Knowledge Representation and Reasoning)
  • 冒泡排序
  • 多租户数据源隔离
  • kindle.cn 无法接收邮件
  • pnpm的使用
  • 【工业安全】-CVE-2022-35555- Tenda W6路由器 命令注入漏洞
  • Java入门进阶
  • 日语学习-日语知识点小记-构建基础-JLPT-N4&N5阶段(4):~てもいいです & ~てはいきません征求许可
  • 在Mac M1上面安装Miniconda
  • 名词解释:npm,cnpm,yarn,vite,vue,electron