基于TCP的客户端、服务器端socket编程

本文介绍了一个基于TCP协议的客户端与服务器端socket编程实例。详细解释了socket编程的主要步骤,包括socket创建、bind绑定、listen监听、accept连接及数据读写等操作。并通过实际代码展示了如何在MACOS平台上实现这一过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基于TCP的客户端、服务器端socket编程

一、实验目的

理解tcp传输客户端服务器端通信流程

二、实验平台

MAC OS
gxx-include-dir=/usr/include/c++/4.2.1

三、实验内容

编写TCP服务器套接字程序,程序运行时服务器等待客户的连接,一旦连接成功,则显示客户的IP地址、端口号,并向客户端发送字符串。

四、实验原理

使用TCP套接字编程可以实现基于TCP/IP协议的面向连接的通信,它分为服务器端和客户端两部分,其主要实现过程如下
process

1、socket函数
在网络编程中所需要进行的第一件事情就是创建一个socket,无论是客户端还是服务器端,都需要创建一个socket,该函数返回socket文件描述符,类似于文件描述符。socket是一个结构体,被创建在内核中。

    sockfd=socket(AF_INET,SOCK_STREAM,0);   //AF_INT:ipv4, SOCK_STREAM:tcp协议

2、connect函数
客户端创建了socket后,需要和服务器端建立连接,此时使用connect函数和服务器端进行连接。

    connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))
  • 三次握手:
  • 第一次握手:客户端发送syn包(syn=x)到服务器,并进入SYN_SEND状态,等待服务器确认;
  • 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
  • 握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。

3、bind函数
把一个本地协议地址和套接口绑定,比如把本机的2222端口绑定到套接口。注意:为什么在上图中客户端不需要调用bind函数?这是因为如果没有调用bind函数绑定一个端口的话,当调用connect函数时,内核会为该套接口临时选定一个端口,因此可以不用绑定。而服务器之所以需要绑定的原因就是,所以客户端都需要知道服务器使用的哪个端口,所以需要提前绑定。

    bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))

4、listen函数
当socket创建后,它通常被默认为是主动套接口,也就是说是默认为要马上调用connect函数的,而作为服务器是需要被动接受的,所以需要调用linsten函数将主动套接口转换成被动套接口。调用linsten函数后,内核将从该套接口接收连接请求。

   /**
     * 3:调用listen函数监听(指定port监听)
     * 通知操作系统区接受来自客户端链接请求
     * 第二个参数:指定队列长度
     */

    if(listen(sockfd,10) < 0)
    {
        perror("listen error");
    }

5、accept函数
此函数返回已经握手完成的连接的套接口。注意:此处的套接口不同于服务器开始创建的监听套接口,此套接口是已经完成连接的套接口,监听套接口只是用来监听。

    accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);

6、write函数
调用IO函数(read/write)和连接的客户端进行双向通信。

    long t = time(0);
    char *s = ctime(&t);
    size_t size = strlen(s) * sizeof(char);
    //将服务器的系统时间写到客户端
    if(write(fd,s,size) != size)
    {
        perror("write error");
    }

7、close函数
数据传输完成后,需要关闭套接口

    //关闭socket
    close(fd);

linux网络编程之用一张图片说明套接口常用函数

API

五、实验流程

  • 服务器端代码
#include "iostream"
#include "netdb.h"
#include "stdio.h"
#include "stdlib.h"
#include "sys/socket.h"
#include "unistd.h"
#include "arpa/inet.h"
#include "string.h"
#include "memory.h"
#include "signal.h"
#include "time.h"

using namespace std;

int sockfd;

void sig_handler(int signo)
{
    if(signo == SIGINT)
    {
        cout<<"Server close"<<endl;
        close(sockfd);
        exit(1);
    }
}

//输出链接上来的客户端相关信息
void out_addr(struct sockaddr_in *clientaddr)
{
    //将断口从网络字节序转成主机字节序
    int port = ntohs(clientaddr->sin_port);
    char ip[16];
    memset(ip,0,sizeof(ip));
    inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
    cout<<"client:"<<ip<<"("<< port <<")connected\n"<<endl;
}

void do_service(int fd)
{
    //获取系统时间
    long t = time(0);
    char *s = ctime(&t);
    size_t size = strlen(s) * sizeof(char);
    //将服务器的系统时间写到客户端
    if(write(fd,s,size) != size)
    {
        perror("write error");
    }
}

int main(int argc,char *argv[])
{
    if(argc<2)
    {
        cout<<"usage:"<<argv[0]<<"#port"<<endl;
        exit(1);
    }

    if(signal(SIGINT,sig_handler) == SIG_ERR)
    {
        perror("signal sigint error");
        exit(1);
    }

    /**
     * 1.创建socket
     * AF_INET:ipv4
     * SOCK_STRAM:tcp协议
     */
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {
        perror("socket error");
        exit(1);
    }

    //2.调用bind函数绑定socket和地址

    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    //往地址中填入ip,port,internet类型
    serveraddr.sin_family = AF_INET;    //ipv4
    serveraddr.sin_port = htons(atoi(argv[1])); //htons主机字节序转成网络字节序

    serveraddr.sin_addr.s_addr = INADDR_ANY;

    if(bind(sockfd,(struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("bind error");
        exit(1);
    }

    /**
     * 3:调用listen函数监听(指定port监听)
     * 通知操作系统区接受来自客户端链接请求
     * 第二个参数:指定队列长度
     */

    if(listen(sockfd,10) < 0)
    {
        perror("listen error");
    }

    /**
     * 4:调用accept函数从队列中
     * 获得一个客户端的请求链接
     */

    struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);

    while(1)
    {
        int fd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);
        if(fd < 0)
        {
            perror("accept error");
            continue;
        }

        /**
         * 5:调用IO函数(read/write)和
         * 连接的客户端进行双向通信
         */
        out_addr(&clientaddr);
        do_service(fd);

        //关闭socket
        close(fd);
    }

    return 0;
}
  • 客户端代码
#include "netdb.h"
#include "sys/socket.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "memory.h"
#include "unistd.h"
#include <arpa/inet.h>
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    if(argc < 3)
    {
        cout<< "usage:"<<argv[0]<<" ip port"<<endl;
        exit(1);
    }

    //步骤1:创建socket
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {
        perror("socket error");
        exit(1);
    }

    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));

    //主机字节序转换成网络字节序
    inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);

    //步骤2:客户端调用connect函数连接到服务器
    if(connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)) < 0)
    {
        perror("connect error");
        exit(1);
    }

    //步骤3:调用IO函数(read/write)和服务器端双向通信
    char buffer[1024];
    memset(buffer,0,sizeof(buffer));
    size_t size;

    if((size=read(sockfd,buffer,sizeof(buffer)))< 0)
    {
        perror("read error");
    }

    if(write(STDOUT_FILENO,buffer,size)!=size)
    {
        perror("write error");
    }

    return 0;
}

实验结果

实验结果

思考

  • 在进行网络通信时是否需要进行字节序转换:相同字节序的平台在进行网络通信时可以不进行字节序转换,但是跨平台进行网络数据通信时必须进行字节序转换。

参考文献

<think>好的,用户现在问的是`torch.cuda.empty_cache()`是否只能清空一个GPU的缓存。我需要先回忆一下PyTorchCUDA内存管理的机制。 首先,用户之前遇到了关于Scikit-learn的分类指标警告和PyTorch的NCCL支持问题,现在转向了CUDA内存管理。可能用户正在处理多GPU训练,或者在单GPU使用中遇到了显存问题。 `torch.cuda.empty_cache()`的作用是释放当前由缓存分配器持有的未使用内存PyTorch默认情况下会缓存一部分内存以提高分配效率,这个函数可以释放那些未被使用的缓存内存。但用户的问题是,这个函数是否只影响当前的一个GPU。 根据PyTorch的文档,当使用多GPU时,每个GPU都有自己的缓存分配器。所以,如果用户有多个GPU,`torch.cuda.empty_cache()`只会释放当前设备(即通过`torch.cuda.current_device()`指定的那个)的缓存。要清空所有GPU的缓存,需要遍历每个设备,切换当前设备,然后分别调用该函数。 用户可能是在多GPU环境下运行代码,发现显存没有完全释放,或者遇到显存不足的问题。需要确认用户是否在使用多GPU,以及是否正确地切换了设备来清空每个GPU的缓存。 另外,用户可能对PyTorch内存管理机制不太熟悉,所以需要解释清楚这个函数的行为,以及在不同情况下的正确使用方法。可能还需要提醒用户,即使调用了这个函数,显存的释放也依赖于是否有未使用的缓存,实际使用的张量不会被释放。 最后,给出示例代码,说明如何为所有GPU清空缓存,并强调正确使用设备切换的重要性。还要提醒用户,显存管理需要结合其他方法,如手动删除变量、运行垃圾回收等,才能有效管理显存使用。</think>--- 在 PyTorch 中,`torch.cuda.empty_cache()` 的作用是释放当前 CUDA 设备(即当前选中的 GPU)的**未使用缓存内存**。其行为取决于 **GPU 设备的选择**和 **多 GPU 环境**的配置。以下是详细说明: --- ### **1.GPU 场景** - 如果你只有一个 GPU,或者代码中未显式指定 GPU 设备: `torch.cuda.empty_cache()` 会清空当前默认 GPU 的缓存内存。 例如: ```python import torch # 默认使用 GPU 0(仅单卡时) a = torch.randn(1000, 1000, device="cuda") # 占用显存 del a # 删除变量 torch.cuda.empty_cache() # 释放 GPU 0 的未使用缓存 ``` --- ### **2.GPU 场景** - 如果你有多个 GPU,且代码中显式切换了设备: 需要**依次选中每个 GPU 并单独调用** `empty_cache()`,才能清空所有 GPU 的缓存。 例如: ```python import torch # 清空 GPU 0 的缓存 torch.cuda.set_device(0) torch.cuda.empty_cache() # 清空 GPU 1 的缓存 torch.cuda.set_device(1) torch.cuda.empty_cache() ``` --- ### **3. 关键注意事项** 1. **缓存释放的范围**: `empty_cache()` **仅释放由 PyTorch 缓存分配器管理的未占用内存**,不会释放正在被张量占用的显存。 - 已分配的张量必须手动删除(如 `del tensor`)或超出作用域后,其显存才会被缓存分配器回收。 - 调用 `empty_cache()` 后,这些回收的内存才会真正释放回系统。 2. **多进程分布式训练**: 在分布式训练(如使用 `torch.distributed` 或 `DataParallel`)时,每个进程可能绑定到不同的 GPU。 - 每个进程需独立调用 `empty_cache()` 清理自己绑定的 GPU 缓存。 - 例如: ```python # 每个进程仅清理自己绑定的 GPU torch.cuda.empty_cache() ``` 3. **自动缓存管理**: PyTorch 默认会缓存部分显存以提高分配效率。频繁调用 `empty_cache()` 可能导致性能下降,建议仅在显存不足时手动调用。 --- ### **4. 验证显存释放** 可以使用 `torch.cuda.memory_summary()` 或以下代码查看显存状态: ```python import torch # 查看当前 GPU 的显存使用情况(单位:字节) print(torch.cuda.memory_allocated()) # 当前已分配的显存 print(torch.cuda.memory_reserved()) # 当前缓存分配器保留的显存(包括未使用的) ``` --- ### **总结** | 场景 | 行为 | |------------|----------------------------------------------------------------------| | **单 GPU** | 清空当前 GPU 的未使用缓存。 | | **多 GPU** | 需遍历所有 GPU,分别调用 `empty_cache()` 才能清空每个设备的缓存。 | --- ### **最佳实践** - 显存不足时手动调用 `empty_cache()`,但避免在循环中频繁使用。 - 结合显存监控工具(如 `nvidia-smi` 或 PyTorch 内置函数)诊断显存泄漏。 - 多 GPU 场景显式指定设备并分别清理: ```python for i in range(torch.cuda.device_count()): torch.cuda.set_device(i) torch.cuda.empty_cache() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值