socket编程与TCP协议

如果你想和远方的朋友通电话,但是,没有办法直接把自己的声音放在电线上变成电流信号,你需要使用电话机拿起听筒拨号,而这个电话就是Socket,它让你简单方便地完成电流通话,从我们编程的角度来看,我们直接使用TCP传输信息,需要考虑的东西太多了,而socket替我们封装实现了TCP,我们只需要使用socket的API,即可间接完成TCP通信。

文章目录

  • 一、什么是socket ?
  • 二、网络通信三要素
  • 三、Socket和TCP的关系
  • 四、如何进行Socket编程(流程)
  • 五、TCP服务端的实现
  • 六、Socket函数
    • 1.socket()函数原型
    • socket()函数参数举例:
    • 常见错误:
    • 2.setsockopt()函数
        • 1. 函数原型与参数
        • 2. 常用选项与功能
        • 3. 典型应用场景
      • UDP协议选项总结
        • 1. SOL_SOCKET层选项(通用选项)
        • 2. IPPROTO_UDP层选项(UDP协议特有)
        • 3. 典型应用场景
    • 3.关于sockaddr_in结构体
    • 4.bind()函数
    • 5.listen()监听函数
    • 6.accept()函数
    • 7.recv()函数
    • 8.send()函数
    • 9.close()函数
  • 七、TCP客户端的实现


一、什么是socket ?

Socket是网络通信的“接口”,也就是程序与网路之间的中介。它为开发者提供了一套通用的函数或API,允许程序通过网络发送和接收数据。具体地说,socket抽象了网络底层的细节。比如数据包的传输、协议选择、错误处理等,开发者只需要使用socket提供的函数来建立连接和传输数据,而不需要关心网络底层的实现

  1. socket的特点
    在这里插入图片描述

二、网络通信三要素

socket又称网络套接字
在这里插入图片描述

三、Socket和TCP的关系

Socket可以被视为对网络协议的封装或使用协议的工具。它提供了一组API,让开发者能够在应用层与网络协议(如TCP或UDP)进行交互。通过Socket,开发者可以方便地创建链接、发送和接收数据,而不需要直接处理底层协议的细节。因此,Socket实在应用程序和网络之间的一个抽象层。
在这里插入图片描述
简单的来说就是socket是一个网络编程函数,这个函数根据传入参数不同,就可以支持对应的协议通讯。。

四、如何进行Socket编程(流程)

在这里插入图片描述

五、TCP服务端的实现

基于Linux系统的服务端实现(请在Linux系统下进行部署)

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>using namespace std;int main()
{int server_fd,new_socket; //声明服务器socket和新的客户端socketstruct sockaddr_in address; //声明用于存储地址信息的结构体int opt = 1; //用于设置socket选项socklen_t addrlen = sizeof(address); //地址结构体的大小const int PORT = 8888; //服务器监听的端口号//第一步//创建服务器socketserver_fd=socket(AF_INET,SOCK_STREAM,0);  //创建TCP流式socketif(server_fd==0)                          //创建失败则退出{cerr<<"Failed to create socket"<<endl;return 1;  //退出程序}//设置socket选项,允许重用地址(可选)setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//设置服务其地址信息address.sin_family=AF_INET;  //IPv4地址族address.sin_addr.s_addr=INADDR_ANY;   //允许接收来自任何IP地址(此处指的是本地服务)address.sin_port=htons(PORT);  //端口号//第二步   绑定//将socket绑定到指定的地址和端口if(bind(server_fd,(struct sockaddr*)&address,sizeof(address))<0){cerr<<"Failed to bind"<<endl;  //输出绑定失败的原因return 1;  //退出程序}//第三步   监听//开始监听传入的连接请求,队列长度为3if(listen(server_fd,3)<0){cerr<<"Failed to listen"<<endl;return 1;  //退出程序}cout<<"Server is listening on port "<<PORT<<endl;//无限循环,接受客户端的连接while(true){//第四步:  接受客户端的连接请求//接受客户端的连接请求new_socket=accept(server_fd,(struct sockaddr*)&address,(socklen_t*)addrlen);if(new_socket<0){//出错则跳过cerr<<"Failed to accept"<<endl;//输出连接失败的原因continue;;  //跳过出错的连接}//接受客户端发送的消息char buffer[1024];//创建缓冲区//第五步:  读取信息ssize_t bytes_read=recv(new_socket,buffer,sizeof(buffer),0);//接收消息if(bytes_read>0)     //处理接收到的消息{//输出接收到的消息cout<<"Received message: "<<buffer<<endl;//第六步:  发送信息const char* message="Hello from server";   //响应消息send(new_socket,message,strlen(message),0);//发送消息}//关闭连接close(new_socket);}//关闭服务器socketclose(server_fd);return 0;
}

六、Socket函数

socket()函数是网络编程中创建套接字的基础函数,它用于创建一个套接字并返回文件描述符,该描述符可以用于后续的网络通信操作。

1.socket()函数原型

int socket(int domain,int type,int protocol);

在这里插入图片描述
···返回值
成功时:返回套接字的文件描述符(一个整数,表示套接字),后续通过这个文件描述符来操作该套接字。
失败时:返回-1,并设置error来指示具体错误原因。

socket()函数参数举例:

在这里插入图片描述

常见错误:

在这里插入图片描述

2.setsockopt()函数

在TCP网络编程中,setsockopt()是一个常用的函数,用于配置Socket的一些行为或参数

  1. setsockopt()起什么作用?
setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,&&opt,sizeof(opt);
  • settsockopt():这个函数的作用是设置一个套接字的选项。其函数原型如下:
int setsockopt(int sockfd,int level,int optname,const void*optval,socklen_t optlen);
  • server_fd:这是之前通过socket()创建的服务器套接字的文件描述符。
  • SOL_SOCKET:表示要操作的级别是套接字本身(而不是某个特定协议的选项)。- SOL_SOCKET:是用于通用套接字选项的常量。
  • SO_REUSEADDR :这个选项允许重用本地地址。在关闭套接字后,TCP会等待一段时间(TIME_WAIT状态)才能重新使用相同的端口和地址。如果你设置了SO_REUSEADDR,即使上一个连接还没有完全释放(处于TIME_WAIT状态),你也可以立即绑定相同的地址和端口,避免绑定失败。
  • &opt:opt是一个整型变量,通常设置为1,表示启用SO_REUSEADDR选项。&opt是指向该变量指针,用于传递给setsockopt()。
  • sizeof(opt):表示opt变量的大小,通常是4个字节(整数)。
1. 函数原型与参数
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);  
  • sockfd:套接字描述符。
  • level:选项的协议层,常用值包括:
    • SOL_SOCKET(套接字通用选项)
    • IPPROTO_TCP(TCP协议选项)
    • IPPROTO_IP(IPv4协议选项)。
  • optname:具体选项名称(如 SO_REUSEADDR)。
  • optval:指向选项值的指针(类型因选项而异)。
  • optlen:选项值的长度。

2. 常用选项与功能

(1)套接字通用选项(SOL_SOCKET层)

选项名功能说明数据类型
SO_REUSEADDR允许重用本地地址和端口(避免 TIME_WAIT 状态阻塞)int
SO_KEEPALIVE启用 TCP 保活机制(检测连接存活)int
SO_RCVBUF/SO_SNDBUF设置接收/发送缓冲区大小(需注意内核可能自动调整)int
SO_RCVTIMEO/SO_SNDTIMEO设置接收/发送超时时间(需 struct timeval 结构体)struct timeval
SO_LINGER延迟关闭连接(控制 close() 行为,如优雅关闭)struct linger
SO_BROADCAST允许 UDP 发送广播数据int
SO_DONTROUTE禁用路由查找(仅通过本地接口发送)int

(2)TCP 协议选项(IPPROTO_TCP层)

  • TCP_NODELAY:禁用 Nagle 算法(减少小数据包延迟)。

3. 典型应用场景
  1. 地址复用

    int reuse = 1;  
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));  
    

    需在 bind() 前调用。

  2. 超时控制

    struct timeval tv = { .tv_sec = 5, .tv_usec = 0 };  // 5秒超时  
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));  
    
  3. 优雅关闭连接

    struct linger lin = { .l_onoff = 1, .l_linger = 30 };  // 延迟30秒关闭  
    setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &lin, sizeof(lin));  
    
  4. 优化性能

    int buf_size = 64 * 1024;  // 64KB缓冲区  
    setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(int));  
    

UDP协议选项总结

UDP协议在网络编程中通过setsockopt()函数设置选项,主要涉及SOL_SOCKET(通用套接字层)和IPPROTO_UDP(UDP协议层)两类选项。以下是关键选项及其应用场景:


1. SOL_SOCKET层选项(通用选项)
选项名功能说明数据类型示例代码
SO_BROADCAST允许UDP发送广播数据包(需目标地址为广播地址,如255.255.255.255)int83 _ 31
int broadcast = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(int));

| SO_REUSEADDR | 允许多个套接字绑定到同一端口,适用于UDP多播或快速重启服务 | int |

int reuse = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));

| SO_RCVBUF/SO_SNDBUF | 设置接收/发送缓冲区大小(需注意内核可能自动调整) | int |

int buf_size = 65536;
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(int));

| SO_DONTROUTE | 禁止路由查找,数据包直接通过本地接口发送(适用于本地网络调试) | int |

int dontroute = 1;
setsockopt(sockfd, SOL_SOCKET, SO_DONTROUTE, &dontroute, sizeof(int));

2. IPPROTO_UDP层选项(UDP协议特有)
选项名功能说明数据类型示例代码
UDP_NOCHECKSUM禁用UDP校验和计算(发送时校验和设为0),适用于低可靠性需求场景int(布尔值)24
int nochecksum = 1;
setsockopt(sockfd, IPPROTO_UDP, UDP_NOCHECKSUM, &nochecksum, sizeof(int));

| UDP_CHECKSUM_COVERAGE | 强制启用UDP校验和计算(默认通常启用) | int(布尔值) |

int checksum = 1;
setsockopt(sockfd, IPPROTO_UDP, UDP_CHECKSUM_COVERAGE, &checksum, sizeof(int));

| UDP_RECV_MAX_COALESCED_SIZE | 设置接收时合并数据包的最大字节数,优化接收效率(需系统支持) | DWORD |

DWORD max_size = 4096;
setsockopt(sockfd, IPPROTO_UDP, UDP_RECV_MAX_COALESCED_SIZE, &max_size, sizeof(DWORD));

3. 典型应用场景
  1. 广播通信
    • 启用SO_BROADCAST后,UDP可向局域网广播地址发送数据包,适用于设备发现或群发消息。
  2. 多播(组播)支持
    • 结合IP_ADD_MEMBERSHIP选项,加入多播组接收数据(需IP层设置)31。
  3. 高性能传输优化
    • 增大缓冲区(SO_RCVBUF)减少丢包,或禁用校验和(UDP_NOCHECKSUM)降低CPU开销。
  4. 本地调试与测试
    • 使用SO_DONTROUTE避免数据包经路由转发,直接通过指定接口发送。

3.关于sockaddr_in结构体

sockaddr_in是用于处理IPv4地址的结构体,它用于存储网络套接字(Socket)的地址信息,通常在使用bind()、connect()、accept()等网络相关函数时会用到。
sockaddr_in是sockaddr结构体的一个专用版本,它更方便地处理IPv4地址,而sockaddr是通用的套接字地址结构体。由于网络函数通常要求参数类型是sockaddr,我们会将sockaddr_in类型强制转换为sockaddr来使用

为什么不直接用sockaddr这个结构体呢?
因为sockaddr是通用的,但是这个 不易用

  • sockaddr_in的结构体定义
    在头文件<netinet/in.h>中,sockaddr_in结构体的定义如下
struct sockaddr_in{sa_family_t    sin_family;   //协议族(Address family),即协议类型in_port_t      sin_port;     //16位的端口号,网络字节序(大端序)struct in_addr sin_addr;     //32位的IPv4地址char           sin_zero[8];  //填充字段,保持与sockaddr结构体的大小一致
}
  • 1.sin_family
    该字段指定了地址族,必须设置为AF_INET,表示使用IPv4协议族(Internet Protocol version 4)
address.sin_family=AF_INET;
  • 2.sin_port
    该字段存储16位的端口号,表示与服务器或客户端进行通信的端口号。它必须使用网络字节序(大端序)来存储,因此在代码中经常会使用htons()函数来将主机字节序转换为网络字节序
address.sin_port=htons(8080);//将端口号8080转换为网络字节序
  • 3.sin_addr
    这是存储IPv4地址的结构体,使用的是struct in_addr类型。它实际上是一个32位的整数,通常通过inet_addr()函数来将点分十进制格式的IP地址转换为这个字段可以接收的格式
address.sin_addr.s_addr=inet_addr(127.0.0.1);//将点分十进制的IP地址转换为网络字节序
  • 4.sin_zero

  • 该字段是一个长度为8字节的填充字段,其作用是使sockaddr_in的大小与sockaddr结构体保持一致,这个字段通常不被使用,直接填充为0即可。

  • 5.sockaddr_in和sockaddr的关系
    sockaddr_in是用于IPv4的转用结构体,而网络函数(如bing()、connect())需要传递的参数类型是通用的sockaddr结构体,为了兼容,通常我们会将sockaddr_in强制转换为sockaddr类型

bind(server_fd,(struct sockaddr**address,szeof(address));

在上面的服务器代码中,address是sockaddr_in类型的变量,但在bind()函数中我们强制转换为sockaddr*来使用。

4.bind()函数

bind()函数的主要作用是将套接字与特定的本地地址(IP地址)和端口号绑定起来,使得该套接字可以通过指定的地址和端口接收数据。bind()函数通常用于服务器的套接字编程,以便为套接字分配一个固定的本地地址和端口。

  1. 函数原型
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
  • sockfd:套接字描述符,由socket()函数返回。表示需要绑定的套接字。
  • addr:指向sockaddr结构体的指针,包含要绑定的IP地址和端口信息。
  • addrlen:addr的大小,同工厂为sizeof(struct sockaddr_in)。
  1. 参数详解
  • sockfd:套接字描述符,由socket()函数返回。表示需要绑定的套接字。bind()函数使用这个套接字和本地地址进行绑定操作。
  • addr:指向sockaddr结构体的指针,包含要绑定的IP地址和端口信息。大多数情况下,传递的是sockaddr_in结构体(IPv4)或者sockaddr_in6结构体(IPv6)的地址,并通过强制类型转换为sockaddr结构体。
  • sockaddr_in结构体:
struct sockaddr_in{sa_family_t    sin_family;   //协议族(Address family),即协议类型in_port_t      sin_port;     //16位的端口号,网络字节序(大端序)struct in_addr sin_addr;     //32位的IPv4地址char           sin_zero[8];  //填充字段,保持与sockaddr结构体的大小一致
}
  • addrlen:该参数指定地址的长度,通常为sizeof(struct sockaddr_in)或者(sizeof(struct sockaddr_in6)),具体取决于使用的是IPv4还是IPv6地址。
  1. 返回值
  • 成功时返回0
  • 失败时返回-1,并设置error以指示错误类型。常见的错误类型包括:
    • EADDRINUSE:指定的地址以及被使用,通常是端口号冲突。
    • EADDRNOTAVAIL:指定的IP地址不可用。
    • EBADF:无效的文件描述符,通常是传递的sockfd非法。
    • EINVAL:套接字已经绑定,不能重复绑定。
    • ENOTSOCK:sockfd不是一个套接字。
  1. bind的作用
    bind()函数通常用于服务器套接字编程,起作用是将套接字绑定到一个本地地址和端口上。具体来说,bind()会将该套接字关联到以下三项信息:
    • (1)本地IP地址:表示该套接字从哪个本地网络接口接收数据。常见的绑定选项:INADDR_ANY:绑定到本地机器的所有网络接口,表示任何网络接口上发往这个端口的数据都将被接收。具体的IP地址:可以绑定一个特定的IP地址,如:“192.168.1.1”。
    • (2)本地端口:表示该套接字将监听的端口号。服务器通常会绑定到一个特定的端口来等待客户连接。
    • (3)协议族:治党使用的协议族(IPV4或IPv6).。通过设置sin_family为AF_INET或AF_INET6,来分别表示使用IPv4或IPv6.
  1. 为什么使用bind()?
  • 服务器段:
    在服务器端,bind()函数必须调用,它将套接字绑定到一个特定的端口和IP地址,使得客户端能够通过该地址和端口连接到服务器,如果不调用bind(),操作系统可能会自动分配一个临时的本地端口,这对于服务器来说是不可接受的,因为服务器需要一个固定的端口号以供客户端连接。

  • 客户端
    在客户端编程中,bind()通常不是必需的,客户端套接字一般依赖于系统自动分配的本地端口,以便于服务器通信。如果有特殊需求(比如希望客户端使用特定的本地IP地址和端口),也可以调用bind()绑定。

5.listen()监听函数

listen()函数在服务器端网络编程中用于将一个套接字设置为监听状态,使其能够接收来接客户端的连接请求。它是服务器端TCP套接字生命周期中一个关键步骤,用于处理被动套接字(即等待客户端连接的套接字)。

  1. 函数原型
int listen(int sockfd,int backlog);//
  1. 参数详解
    • sockfd:套接字文件描述符
    • backlog:(连接队列的最大长度)
  • backlog参数指定内核为套接字维护的已完成连接队列和未完成连接队列的总长度。这个值表示最大可以同时等待处理的客户端连接请求数量
  • 已完成连接队列:保存已经完成TCP三次握手的连接。
  • 未完成连接队列:保存正在等待完成三次握手的连接。
  • 当队列满了之后,如果有新的连接请求到达,它们会被拒绝,客户端将会收到ECONNREFUSED错误。
  • 值可以根据服务器的预期负载进行设置。例如,高并发服务器可能设置较大的backlog值。
    • 返回值:成功时返回0;失败返回-1,并设置errno来指示错误。
  1. listen()函数的功能和工作流程
  • (1)将套接字从主动状态转换为被动状态
    • 调用listen()(之前,服务器端的套接字处于主动状态,它只能用于发起连接请求(比如客户端连接其他服务器)
    • 调用listen()后,套接字被转换为被动套接字,即它不会发起连接,而是等待客户端连接请求。
  • (2)维护连接请求队列:
    • 内核为套接字维护两个队列:未完成队列和已完成队列
    • 未完成队列:存储那些已发起连接但尚未完成三次握手的客户端请求。
    • 已完成队列:存储那些已经完成三次握手等待accept()的客户端连接。
  • backlog参数决定了这两个队列的总长度上线,如果队列已满,新的连接请求将会被拒绝。

backlog并不是严格定义允许的最大连接数,而是一个指导值,操作系统可能会根据实际情况调整该值。如果backlog设置的过小,服务器可能会丢失一些连接请求,特别是在高并发场景下。不同操作系统对backlog的处理方式略有不同,但一般建议根据应用场景 设置合适的值。

  1. 常见错误以及解决方案
  • EBADF:sockfd参数不是有效的文件描述符。通常是由于传递了未创建的套接字描述符
  • ENOTSOCK:文件描述符不是一个套接字,可能误用了其他类型的文件描述符
  • EADDRINUSE:绑定的地址已被另一个套接字使用,可以使用setsockopt()设置SO_REUSEADDR选项来允许地址重用。
  • EINVAL:sockfd套接字未通过bind()函数绑定到一个本地地址或未设置为TCP类型套接字(SOCK_STREAM)
  • EOPNOTSUPP:使用了不支持listen()操作的套接字类型,比如UDP套接字。
  1. listen()与TCP三次握手的关系
  • 当服务器调用listen()(后,它开始等待客户端的连接请求。客户端发起连接请求时,会进行TCP的三次握手过程:a.客户端发送SYN包给服务器,表示请求建立连接。b.服务器返沪SYN-ACK包,表示同意建立连接。c.客户端返回ACK包,连接建立完成。
  • 在三次握手完成之前,连接请求会被放入未完成队列;三次握手完成后,连接请求会被移到已完成队列。
  • 服务器可以通过accept()函数获取已完成连接队列中的客户端连接。

6.accept()函数

accept()函数是服务器端套接字编程中的关键函数,用于从连接队列中取出等待的客户端连接,并为其创建一个新的套接字。通过accept(),服务器可以与客户端进行后续通信。

  1. 函数原型
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
  • sockfd:服务器监听套接字的文件描述符,表示等待连接的套接字
  • addr:指向sockaddr结构体的指针,用于存储客户端的地址信息
  • addrlen:指向socklen_t类型变量的指针,传递给accept()用于表示addr的长度,并在函数返回时保存客户端地址的长度。
    2.返回值
    (1)成功时,accept()返回一个新的套接字描述符(new_sockfd),该套接字专用于与该客户端的通信。
    (2)失败时,accept(()返回-1.并设置errno以指示错误类型,如:
  • EAGAIN或EWOULDBLOCK:套接字为非阻塞模式,但没有连接请求处于等待状态。
  • EBADF:无效的文件描述符
  • ECONNABORTED:客户端的连接请求被中止
  • EINTR:函数调用被中断
  • EINVAL:监听套接字无效,可能尚未调用listen()
  • ENOTSOCK:sockfd不是一个套接字

·················

  1. 阻塞与非阻塞模式
  • 阻塞模式:在默认情况下,accept()是阻塞的,如果没有客户端连接,accept()会一直等待,直到有客户端连接进来,;如果由客户端连接进来,accept()立即返回,返回值是与该客户端通信的新的套接字描述符。
  • 非阻塞模式:如果将套接字设置为非阻塞模式,accpt()调用立即返回,如果没有等待连接,accept()返回-1,并设置errno为EAGAIN或EWOULDBLOCK。可以通过使用fcntl()函数将套接字设置为非阻塞模式。
fcntl(sockfd,F_SETFL,O_NONBLOCK);
  1. accept()阻塞的处理过程
    当服务器调用accept()后,如果当前没有客户端连接请求,accept()会进入阻塞状态,此时服务器会处于等待状态,直到有新的客户端尝试连接。在阻塞期间,服务器的主线程无法执行其他操作,必须等待accept()返回新的连接。
    ·当有客户端连接时:
    • accept()解除阻塞,取出连接队列中的第一个连接请求。返回用于与该客户端通信的新的套接字描述符。
      如果连接队列中没有等待的连接请求,accept()会继续阻塞,直到有新的连接到来。

accept()函数本身不自带无限循环,它只负责从服务器的连接队列中取出一个客户端连接并返回该连接的套接字。因此,accept()每次只处理一个客户端连接,当该连接被接受之后,服务器需要再次调用accept()来等待新的客户端连接。
因此,在实际的服务器编程中,为了能够不断接受来自不同客户端的连接,通常会在accept()的外部包裹一个while(true)或类似的循环。这是因为服务器需要不断地监听新的连接,而不仅仅是处理一次连接请求。

7.recv()函数

  1. 函数定义
ssize_t recv(int sockfd,void *buf,size_t len,int flags);

返回值:表示实际接收收到的数据字节数,失败时返回-1,并设置errno指示错误。
···
2. 参数详解

  • sockfd:套接字描述符,指出你想要接收数据的套接字

  • buf:类型是:void *;其作用:这是一个缓冲区指针,指向用于存储收到的数据的内存区域。该内存区域由调用者分配,recv()函数将接收到的数据复制到这个缓冲区中。

  • len:表示可以接受的最大字节数,即缓冲区buf的大小,recv()将最多接收len字节的数据,存储在buf中

  • flags:这个参数是提供额外的控制选项,影响recv()的行为,常见的选项包括:

    • 0:默认模式,没有特殊标志
    • MSG_PEEK:从缓冲区中窥视数据,不移除它,这意味着数据仍然保留在套接字接收队列中,下次调用recv()时仍然可以读取到相同的数据。
    • MSG_WAITALL:等待接收完整的len字节,直到所有数据被接收或连接关闭
    • MSG_OOB:接收带外数据,通常用于TCP的紧急数据
  1. 返回值
  • '> 0表示成功接收的数据字节数
  • 0:表示连接已被对方关闭(对于TCP连接)

-1:表示出现了错误,并设置errno指示错误原因

特别注意
1.阻塞行为:默认情况下,recv()是阻塞的。也就是说,当没有数据可以接受时,recv()会阻塞,直到有数据可用或连接关闭,如果你希望让recv()在没有数据时立即返回而不是阻塞,可以将套接字设置为非阻塞模式,或者使用MSG_DONTWAIT标志
2.连接关闭:当recv()返回0时,表示对方关闭了连接。在TCP连接中,这是一个常见的方式来检测客户端是否断开
3.数据长度问题:recv(并不能保证每次接收的数据量等于你请求的长度len。数据可能会分多次传送,尤其是在网络不稳定的情况下。因此,如果你需要接收固定大小的数据,你需要检查recv()的返回值,并在必要时多次调用recv()以获取完整数据。

8.send()函数

send()函数用于通过已连接的套接字发送数据,通常在TCP网络编程中使用,它与recv()时对应的操作,recv()负责接收数据,而send()负责发送数据。

  1. 函数定义
sszie_t send(int sockfd,const void* buf,size_t len,int falgs);

返回值:表示成功发送的字节数,如果出现错误则返回-1,并设置errno来指示错误。

  1. 参数详解
  • sockfd:套接字描述符

  • buf:指向要发送的数据的缓冲区,即存放要通过网络传输的数据。发送的数据会从该缓冲区中读取。

  • len:指定要发送的字节数,也就是缓冲区中数据的长度。send()函数会尝试发送len个字节的数据,但不保证一次就嫩那个发送全部数据。

  • flags:控制发送行为的标志,常用的标志包括:

    • 0:默认标志,没有特殊选项。
    • MSG_DONTWAIT:非阻塞发送,立即返回而不等待缓冲区可用。如果缓冲区已满,函数会返回-1,并设置errno为EAGAIN或EWOULDBLOCK
    • MSG_OOB:发送带外数据(紧急数据)。在TCP中,这种数据通常用于传输控制信息。
    • MSG_NOSIGNAL:阻止在连接被关闭时发送SIGPIPE信号,防止进程异常终止。
  1. 返回值
  • ·>0:表示成功发送的字节数,可能小于len,这意味着并未将全部数据一次性发送完
  • 0:在send()中返回0并不常见,通常不会用于指示成功的传输结束
  • -1:表示出现了错误,errno会设置为具体的错误码。
  1. 发送数据长度问题
    在网络编程中,特别是对于send()函数,不能保证依次调用send()会将所有数据发送完。特别是在传输大块数据时,send()可能只发送了一部分数据,然后返回实际发送的字节数。这时需要通过循环调用send()来确保所有数据都已发送。
    以下是一个例子
ssize_t total=0;
ssize_t bytes_sent;
const char *data="A long message";
size_t data_len=strlen(data);
//循环发送直到所有数据发送完毕
while(total<data_len)
{butes_sent=sned(sock,data+total,data_llen-total,0);if(bytes_sent==-1){std::cerr<<"Send Error"<<std::endl;break;}total+=butes_sent;
}
  1. 特别注意
  • 阻塞行为:默认情况下,send()是阻塞的,也就是说,如果系统的发送缓冲区已满,send()会等待直到有足够空间可以发送数据。在这种情况下,程序会暂停执行,直到数据成功发送或出现错误。
  • 非阻塞模式:如果套接字被设置为非阻塞模式,或者send()函数使用了MSG_DONTWAIT标志,那么当发送缓冲区时,send()会立即返回,可能返回-1,并将errno设置为EAGAIN或EWOULDBLOCK
  • 数据丢失:send()函数负责将数据交给操作系统内核,实际的数据传输过程由内核管理,如果网络连接出现问题,数据可能无法送达目的地,但send()并不会返回传输失败,除非连接已经断开(例如,ECONNRESET或EPIPE错误)。
  • send()用于将数据从应用层发送到网络上,通过套接字通信
  • 函数的flags参数允许你控制数据发送的行为,例如发送带外数据(紧急数据)、非阻塞发送等。
  • send()是阻塞的,除非使用了非阻塞模式或相应的flags
  • 数据发送时,send()不保证一次发送全部数据,可能需要多次调用才能传输完整数据。

9.close()函数

close()函数用于关闭套接字,当一个应用程序不再需要与远程注意进行通信时,调用此函数可用释放相关的资源。套接字的关闭过程涉及到网络连接的中止和资源的清理。在网络编程中,特别是设计TCP协议时,close()的行为比较复杂,因为它需要处理连接的安全中止,确保所有数据都成功传输。

  1. 函数定义
int close(int sockfd);

返回值:成功返回0,失败返回-1,并设置errno指示错误。

  1. close()的作用
    *释放资源:当调用close()时,系统会回收该套接字占用的资源,包括网络缓冲区、套接字描述符等,否则会导致资源泄露,最终可能耗尽系统资源。
    通知对端:对于基于TCP的连接,close()函数会通知对端,本地应用程序已经关闭连接,不会再发送数据。TCP会通过四次挥手(Four-way handshake)来确保连接的正常终止

  2. 关于TCP协议中的close()
    在TCP连接中,close()不仅仅是简单的关闭文件描述符,它还会触发一些列与TCP协议相关的步骤:

  • 发送FIN:当应用程序调用close()关闭TCP连接时,操作系统会向对端发送一个FIN(finish)报文,表示本地不会再发送数据。
  • 等待对端确认:对端接收到FIN报文后,会回复一个ACK确认包,表示它已经知道本地关闭了连接。
  • 等待对端的FIN:对端接收到FIN报文后,会回复一个ACK确认包,表示它已经知道本地关闭了连接。
  • 接收ACK:本地再回复一个ACK ,表示已经收到对端的关闭请求,至此连接正式断开。
    这种过程称为TCP的四次挥手,在调用close()后,连接不会立即断开而是需要等待对端的确认。这个过程会让套接字进入TIME_WAIT状态,通常持续2分钟,以确保网络上的延迟不会影响连接的安全关闭。
  1. close()的行为
  • 关闭发送/接收通道:close()会同时关闭TCP连接的发送和接收通道。这意味着本地程序无法再通过该套接字发送或接收任何数据
  • 数据未发送完:如果再调用close()时,发送缓冲区中仍有数据未穿输完,操作系统会试图讲过这些数据继续发送,但如果需要立即中断数据传输,可使用shutdown()函数,它提供了更精细的控制。
  • 阻塞与非阻塞:调用close()后,程序不会等待远程主机的响应,而是立即返回,并把关闭操作符交给操作系统处理。这意味着close()是非阻塞操作。
  1. close()与shutdown()的区别
  • close()关闭整个套接字,包括发送和接收的功能,并释放资源。
  • shutdown()提供了更精细的控制,可以选择仅关闭发送或接收的通道,而不关闭整个套接字。
  • shutdown()的函数签名如下:
int shutdown(int sockfd,int how);

how参数:控制关闭行为,取值如下:

    • SHUT_RD:关闭读通道,程序不能再从套接字接收数据
    • SHUT_WR:关闭写通道,程序不能在向套接字发送数据
    • SHUT_RDWR:同时关闭读写通道,相当于close()
  1. 注意事项
  • 多次close():同一个套接字描述符只能关闭一次,调用close()后,该套接字描述符变得无效,如果再次调用close()会返回-1,并设置errno为EBADF。
  • TIME_WAIT状态:在TCP连接中,调用close()后,套接字可能会进入TIME_WAIT状态,防止旧数据影响新连接,通常该状态会持续2分钟。
  • 文件描述符复用:在调用close()后,文件描述符可能被操作系统重新分配给其他文件或套接字,因此不要再对已经关闭的文件描述符进行任何操作。

七、TCP客户端的实现

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
#include <arpa/inet.h>using namespace std;int main()
{int sock;struct sockaddr_in server;//创建socketsock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){cerr<<"Could not create socket"<<endl;return 1;}//设置服务器地址server.sin_family=AF_INET;server.sin_port=htons(8888);server.sin_addr.s_addr=inet_addr("127.0.0.1");//连接服务器if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0){cerr<<"Could not connect to server"<<endl;return 1;}//发送数据const char* message="Hello from client";send(sock,message,strlen(message),0);//接收数据char buf[1024];recv(sock,buf,sizeof(buf),0);cout<<"Server response: "<<buf<<endl;//关闭socketclose(sock);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/32807.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

css基本功

为什么 ::first-letter 是伪元素&#xff1f; ::first-letter 的作用是选择并样式化元素的第一个字母&#xff0c;它创建了一个虚拟的元素来包裹这个字母&#xff0c;因此属于伪元素。 grid布局 案例一 <!DOCTYPE html> <html lang"zh-CN"><head&…

环境配置 | 5分钟极简Git入门:从零上手版本控制

你是否刚接触Git&#xff1f;别担心&#xff01;这篇指南将用最简单的步骤带你掌握Git核心操作&#xff0c;快速开启版本控制之旅&#xff01;✨ 1.git在win10上的下载安装 1.1.下载git 打开官方网站 Git - Downloadshttps://git-scm.com/downloads ​ ​​ 1.2.git安装 …

软件工程概述、软件过程模型、逆向工程(高软45)

系列文章目录 软件工程概述、软件过程模型、逆向工程。 文章目录 系列文章目录前言一、软件工程概述二、能力成熟度模型1.能力成熟度模型CMM2.能力成熟度模型集成CMMI 三、软件过程模型1.瀑布模型SDLC2.原型化模型3.螺旋模型4.增量模型5.喷泉模型6.敏捷模型7.统一过程模型RUP 四…

接口自动化入门 —— Jmeter实现在接口工具中关联接口处理方案

1. JMeter 接口关联处理的核心概念 接口关联是指在多个接口请求之间共享数据&#xff0c;例如将一个接口的返回值作为另一个接口的输入参数。常见的场景包括&#xff1a; 使用登录接口返回的 Token 作为后续接口的认证信息。 将一个接口返回的 ID 作为另一个接口的请求参数。…

websocket学习手册及python实现简单的聊天室

概述 WebSocket 是一种网络通信协议&#xff0c;允许在单个 TCP 连接上进行全双工通信。它最核心的优势就在于实现了持久连接&#xff0c;实现了实时的数据传输。HTTP 协议有一个很大的缺点&#xff0c;通信只能由客户端发起&#xff0c;服务器返回响应后连接就会关闭&#xf…

小白学习:提示工程(什么是prompt)

课程链接 https://www.bilibili.com/video/BV1PX9iYQEry/?spm_id_from333.337.search-card.all.click 一 什么是提示工程 【提示工程】也叫【指令工程】 prompt就是给大模型发的指令&#xff0c;如“给我讲个笑话” 懂得提示工程原理会带来什么优势 懂得原理 为什么有的指…

ROS实践(五)机器人自动导航(robot_navigation)

目录 一、知识点 1. 定位 2. 路径规划 (1)全局路径规划 (2)局部路径规划 3. 避障 二、常用工具和传感器 三、相关功能包 1. move_base(决策规划) 2. amcl(定位) 3. costmap_2d(代价地图) 4. global_planner(全局规划器) 5. local_planner(局部规划器…

分治算法区

分治 一.分治二.经典应用案例三.快速排序&#xff08;1&#xff09;颜色分类&#xff08;2&#xff09;排序数组&#xff08;3&#xff09;数组中第K个最大的元素 四.归并排序1.排序数组2.交易逆序对总数3.计算右侧小于当前元素的个数4.翻转对 一.分治 分治算法是一种通过将复…

设计模式C++

针对一些经典的常见的场景, 给定了一些对应的解决方案&#xff0c;这个就叫设计模式。 设计模式的作用&#xff1a;使代码的可重用性高&#xff0c;可读性强&#xff0c;灵活性好&#xff0c;可维护性强。 设计原则&#xff1a; 单一职责原则&#xff1a;一个类只做一方面的…

零成本搭建Calibre个人数字图书馆支持EPUB MOBI格式远程直读

文章目录 前言1.网络书库软件下载安装2.网络书库服务器设置3.内网穿透工具设置4.公网使用kindle访问内网私人书库 前言 嘿&#xff0c;各位书虫们&#xff01;今天要给大家安利一个超级炫酷的技能——如何在本地Windows电脑上搭建自己的私人云端书库。亚马逊服务停了&#xff…

Qt 数据库操作(Sqlite)

数据库简介 关于数据库的基础知识这里就不做介绍了&#xff0c;相关博客可以查看&#xff1a; SQL基础知识 数据库学霸笔记 上面博客都写的比较详细&#xff0c;本文主要介绍如何使用Qt进行数据库相关操作&#xff0c;数据库分为关系型数据库和非关系型数据&#xff0c;关系…

hackme靶机详细攻略

扫描ip arp-scan -l 访问网站后&#xff0c;点击sign up now注册 注册后登录 点击search显示全部数据 尝试sql注入 确认闭合方式 OSINTand 12# 确定列数 OSINT order by 3# 显示正常 OSINT order by 4# 显示异常 确认回显位置 -1 union select 1,2,3# 确认数据库名 -…

Tweak Power:全方位电脑系统优化的高效工具

在日常使用电脑时&#xff0c;系统性能的下降、垃圾文件的堆积以及硬盘的老化等问题常常困扰着用户。为了提升电脑性能、优化系统运行&#xff0c;许多人会选择系统优化工具。然而&#xff0c;国内一些系统优化软件常常因为广告过多或功能冗杂而让人望而却步。此时&#xff0c;…

element-plus中Autocomplete自动补全输入框组件的使用

目录 1.基本使用 ①从官网赋值如下代码 ②查看运行效果 ③代码解读 2.调用后端接口&#xff0c;动态获取建议数据 结语 1.基本使用 ①从官网赋值如下代码 <template> <div><!-- 自动补全输入框 --><el-autocompletev-model"state":fetc…

SSM基础专项复习6——Spring框架AOP(3)

系列文章 1、SSM基础专项复习1——SSM项目整合-CSDN博客 2、SSM基础专项复习2——Spring 框架&#xff08;1&#xff09;-CSDN博客 3、SSM基础专项复习3——Spring框架&#xff08;2&#xff09;-CSDN博客 4、SSM基础专项复习4——Maven项目管理工具&#xff08;1&#xff…

MATLAB基于ResNet18的交通标志识别系统

1. 数据准备 数据集&#xff1a;该数据集包含了大量标注好的交通标志图片&#xff0c;每类标志都有不同的样本。数据预处理&#xff1a;图像需要进行一些基本的预处理&#xff0c;如调整大小、归一化等&#xff0c;以适应ResNet18的输入要求。 2. 网络设计 使用MATLAB自带的…

【2步解决】phpstudy开机自启(自动启动phpstudy、mysql、nignx或apache、自动打开网址)

重启执行最终效果图&#xff1a; 一、场景 线下部署&#xff0c;需要开启自动动&#xff0c;并打开网址http://localhost/。 二、操作步骤 ①、新建start.txt&#xff0c;并修改为start.bat&#xff0c;使用记事本编辑&#xff0c;粘贴上方代码如下&#xff1a; echo off:…

C++20 `<bit>` 中的整数 2 的幂运算和 `std::bit_cast`:由浅入深的探索

文章目录 引言 1\. 整数 2 的幂运算1.1 检测是否为 2 的幂&#xff1a;std::has_single_bit1.2 计算不小于 x 的最小 2 的幂&#xff1a;std::bit_ceil1.3 计算不大于 x 的最大 2 的幂&#xff1a;std::bit_floor 2\. std::bit_cast2.1 基本用法2.2 实用场景&#xff1a;字节序…

阿里巴巴发布 R1-Omni:首个基于 RLVR 的全模态大语言模型,用于情感识别

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Ubuntu24.04 LTS 版本 Linux 系统在线和离线安装 Docker 和 Docker compose

一、更换软件源并更新系统 在 Ubuntu 24.04 LTS 中&#xff0c;系统引入了全新的软件源配置格式。现在的源配置文件内容更加结构化且清晰&#xff0c;主要包含了软件类型 (Types)、源地址 (URIs)、版本代号 (Suites) 以及组件 (Components) 等信息。 # cat /etc/apt/sources.li…