套接字函数概念篇

套接字是网络通信的基石,是网络通信的基本构建,最初是由加利福利亚大学 Berkeley 分校为 UNIX 开发的网络通信编程接口,本文主要介绍套接字的有关内容,了解使用套接字编写程序过程

一.概念

所谓的套接字,实际上是一个指向传输提供者的句柄。根据性质和作用的不同,套接字可以分为原始套接字、流式套接字和数据包套接字 3 种

1.TCP 的套接字的 socket 编程

基于 TCP 面向连接的 socket 编程的服务器端程序流程如下:

基于 TCP 面向连接的 socket 编程的客户端程序流程如下:

注意: 在服务器的一端,当调用了accept函数时,程序就会进行等待,直到有客户端调用connect函数发送连接请求,然后服务器接受该请求,这样服务器与客户端就建立了连接; 在服务器端要建立套接字绑定到指定的主机 IP 和端口上等待客户的请求,但是对于客户端来说,当发起连接请求并被接受后,在服务器端就保存了该客户端的 IP 地址和端口号的信息。对于服务器端来说,一旦建立连接之后,实际上它已经保存了客户端的 IP 和端口号的信息了,因此可以利用返回的套接字进行与客户端的通信

2.UDP 的套接字的 socket 编程

基于 UDP 面向无连接的 socket 编程接收端程序如下:

基于 UDP 面向无连接的 socket 编程发送端程序如下:

二.通信函数

头文件导入#include <sys/socket.h> #include <arpa/inet.h>

1.socket 函数

函数:int socket(int, int, int);

功能:创建一个套接字

解释:int socket(int af, int type, int protocol);

示例:int socket = socket(AF_INIT, SOCK_STREAM,0);创建一个对应 IPv4 的 TCP 套接字

2.bind 函数

函数:int bind(int, const struct sockaddr *, socklen_t)

功能:将套接字绑定到指定端口和地址上

解释:int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen);

sockaddr_in结构体解释.png

//示例:
struct sockaddr_in addrs;
addrs.sin_len = sizeof(addrs);
addrs.sin_family = AF_INET;
addrs.sin_port = htons(5000);
addrs.sin_addr.s_addr = htonl(INADDR_ANY);

示例:int result = bind(socket, (struct sockaddr *)&addrs,sizeof(addrs));将套接字绑定到主机地址,端口号为 5000 的端口上

3.listen 函数

函数:int listen(int, int)

功能:将套接字设置为监听模式,对于流式套接字,必须处入监听模式才能够接收客户端套接字的连接

解释:int listen(int sockfd, int backlog);

示例:listen(socket, 5);设置套接字为监听状态,为连接做准备,最大等待的连接缓冲为 5

4.accept 函数

函数:int accept(int, struct sockaddr * __restrict, socklen_t * __restrict)

功能:接受客户端的连接,在流式套接字中,只有在套接字处入监听状态,才能接受客户端的连接

解释:int accept(int sockfd, struct sockaddr *addr, int *addrlen);

示例:int socket_server = accept(socket, (struct scokaddr*)&client_addr,&addrlen); socket_server 保存接受请求后返回的新的套接字,socket_server 为绑定在地址和端口上的套接字,而 client_addr 是有关连接端地址信息结构,最后的 addrlen 是 client_addr 的大小

5.close 函数

函数:int close(int)

功能:关闭套接字

解释:int close(int sockfd);

6.connect 函数

函数:int connect(int, const struct sockaddr *, socklen_t)

功能:发送一个连接请求

解释:int connect(int sockfd,const struct sockaddr * addr_server, int addrlen)

示例:int result = connect(socket_client, (struct sockaddr *)&addr_server, sizeof(addr_server));

7.recv 函数

函数:ssize_t recv(int, void *, size_t, int)

功能:从流式套接字中接受数据,平时开发针对 TCP 套接字接收数据

解释:

ssize_t recv(
             int sockfd,    //表示流式套接字
             void *buff,    //用来存放recv函数接收到的数据的缓冲区
             size_t nbytes, //缓冲区的长度
             int flags      //表示函数的调用方式,一般填0,具体看下面表格
             )
//成功时返回接收的字节数(收到EOF是返回0),失败是返回-1

示例:ssize_t recvLen = recv(socket, receivebuf, 100, 0);其中 receivebuf 是保存接收数据,后面的 100 是实际要改善接收数据的字节数

8.send 函数

函数:ssize_t send(int, const void *, size_t, int)

功能:在流式套接字中发送数据

解释:

ssize_t send(
             int sockfd,        //表示流式套接字
             const void *buff,  //存放要发送数据的缓冲区
             size_t nbytes,     //实际要改善的数据的字节数
             int flags          //表示函数的调用方式,一般填0,具体看下面表格
             )
//返回值:成功返回发送的字节数,错误返回-1

示例:ssize_t sendLen = send(socket, sendbuf,100,0);其中 sendbuf 是保存要发送数据组

9.recvfrom 函数

函数:ssize_t recvfrom(int, void *, size_t, int, struct sockaddr * __restrict, socklen_t * __restrict)

功能:用于接收一个数据包,并保存源地址

解释:

ssize_t recvfrom(
                 int sockfd,   //表示准备接收的套接字
                 void * buff,  //指向缓冲区的指针,用来接收数据
                 size_t nbytes, //表示缓冲区的长度
                 int flags,     //表示调用方式,一般填0
                 struct sockaddr * __restrict from, /* 是一个指向地址结构体的指针,用来接收发送数据方的地址信息 */
                 socklen_t * __restrict fromLen //前面结构体长度指针
                 )
//返回值:如果正确接收返回接收到的字节数,失败返回-1.

10.sendto 函数

函数:ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t)

功能:向指定的目的地方发送数据

解释:

ssize_t sendto(
               int sockfd,        //表示准备发送数据的套接字
               const void * buff, //指向缓冲区的指针,该缓冲区包含将要发送的数据
               size_t nbytes,     //缓冲区数据长度
               int  flags,        //表示调用方式,一般填0
               const struct sockaddr *  to, //指向目标套接字地址的结构体指针
               socklen_t  tolen,  //前面结构体长度
               )
//成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。

linux 版本 flag 参数:

flags 说明 recv send
MSG_OOB 用于传输外带数据
MSG_PEEK 窥看外来消息(验证数据缓冲中是否存在接收的数据)
MSG_DONTROUTE 绕过路由表查找(数据传输过程中不参考路由表, 在本地网络中寻找目的地)
MSG_DONTWAIT 调用 I/O 函数是不阻塞, 用于使用非阻塞 I/O
MSG_WAITALL 等待所有数据(防止函数返回,直到接收全部请求的字节数)

三.辅助函数

1.htons()和 ntohs()

函数:u_short htons(u_short hostshort)

功能:将一个 16 位的无符号端整形数据由主机排列方式转换成网络排列方式,所谓的网络排列方式就是大端排列方式,MacOS 是采用小端的存储方式存储数据

使用地方:在有关主机地址和端口号结构体中struct sockaddr_in里面,属性in_port_t sin_port表示端口号,因为端口号要用网络排列方式,使用该函数转换后赋值

函数: u_short ntohs(u_short netshort)

功能: 与htons()功能相反,将 16 位无符网络排列端口转换成主机排列方式,也就是将 16 位大端排列数字转换成小端排列方式

使用地方: 得到地址结构体struct sockaddr_in,将里面的in_port_t sin_port转换成我们平时看到的小端排列的端口号

2.htonl()和 ntohl()

函数:u_long htonl(u_long hostlong)

功能:将一个 32 位无符号整形由主机排列方式转换成网络排列方式,所谓的网络排列方式就是大端排列方式

使用地方:在有关主机地址和端口号结构体中struct sockaddr_in里面,结构体属性struct in_addr sin_addr中的in_addr_t s_addr属性表示 IP 地址信息,因为 IP 地址信息要用网络排列方式,使用该函数转换后赋值

//1.申明一个字符串IP地址
NSString *ip = @"192.168.1.1";
NSArray *ip_arr = [ip componentsSeparatedByString:@"."];
//2.将IP地址转换成32位4字节的无符整形数据
uint smart_ip = ([ip_arr[0] intValue] << 24) | ([ip_arr[1] intValue] << 16) | ([ip_arr[2] intValue] << 8) | [ip_arr[3] intValue];
NSLog(@"%u",smart_ip);
     /* ① 打印出来smart_ip: 3232235777 -> 0xC0A80101
      * ② 在Xcode里面查看smart_ip内存:0x7ffeefbff5cc : 01 01 A8 C0
      *    将指针指向的地址分成内存编号如下
      *    0x7ffeefbff5cc -> 01
      *    0x7ffeefbff5cd -> 01
      *    0x7ffeefbff5ce -> A8
      *    0x7ffeefbff5cf -> C0
      *    从上面可以看出在内存中,数据是小端存储方式(数据的高位存高位),
      *    所以说要是直接传入数字对面会解析成 1.1.168.192,这样地址接反过来了,所以需要转换
      */
//3.调用函数转换数据的排列方式
uint big_ip = htonl(smart_ip);
NSLog(@"%u",big_ip);
      /* ① 打印出来smart_ip: 16885952 -> 0x0101A8C0
       * ② 在Xcode里面查看smart_ip内存:0x7ffeefbff5c8 : C0 A8 01 01
       *    将指针指向的地址分成内存编号如下
       *    0x7ffeefbff5c8 -> C0
       *    0x7ffeefbff5c9 -> A8
       *    0x7ffeefbff5ca -> 01
       *    0x7ffeefbff5cb -> 01
       *    所以传入 16885952 对面会按大端解析成 192.168.1.1
       */
//我们知道在局部变量是在函数执行时分配到栈上的,栈是由高地址位向低址位扩展的,函数执行时"先提"升栈(这里的"提升"是说栈顶指针向低址位扩展),为函数执行在栈上分配缓存的空间,smart_ip是前面的参数,所以写进栈里面在较高的内存地址位,而big_ip是后面的参数,后写进栈里面,在低址位,所以要比smart_ip内存地址低

函数:u_long ntohl(u_long netlong)

功能:与函数 htonl()功能相反,将网络排列的 32 位无符数据转换成主机排列,就是将 32 位大端排列数字转换成小段排列数据

使用地方:得到地址结构体struct sockaddr_in,将里面的in_addr_t s_addr转换成我们平时看到的小端排列 32 位 IP 地址

3.inet_addr()

函数:in_addr_t inet_addr(const char *)

功能:将存储 IP 地址的 char 字符串转换成网络排列方式的 32 位无符号整形,跟上面 htonl()函数功能一样

使用地方:转换的结果可直接用来给地址信息结构体里面的 IP 地址赋值,因为转换出来的结果是网络排列的

//声明地址信息结构体
struct sockaddr_in addr;

NSString *ip = @"192.168.1.60";
const char *cChar_ip = [ip cStringUsingEncoding:NSUTF8StringEncoding]; //转换的结果就是 "192.168.1.60"
in_addr_t addr_ip = inet_addr(cChar_ip);
addr.sin_addr.s_addr = addr_ip; //结构体里面的IP地址赋值

4.inet_aton()

函数:int inet_aton(const char *, struct in_addr *);

功能:与函数inet_addr 功能一样,将 char 字符串 IP 地址转换成网络排序的无符整形,传入struct in_addr结构体指针,直接赋给结构体

使用地方:

struct sockaddr_in addr;

NSString *ip = @"192.168.1.60";
const char *cChar_ip = [ip cStringUsingEncoding:NSUTF8StringEncoding];
int result = inet_aton(cChar_ip, &addr.sin_addr);
if (result != 0) {
    printf("地址赋值成功\n");
}else {
    printf("地址赋值无效\n");
}
//函数返回非0表示cp主机有地有效,返回0表示主机地址无效

5.inet_pton()

函数:int inet_pton(int, const char *, void *);

功能:与辅助函数htonl()inet_addr()inet_aton()的功能一样,将 char 字符串 IP 地址转换成网络排序的无符整形,直接赋给struct in_addr结构体指针里面,不一样的是可以根据地址族的不同转换 IPv6 还是 IPv4 的地址

解释:

int inet_pton(
              int af,           //地址族 AF_INET 对应IPv4,AF_INET6对应IPv6
              const char *src,  //盛装C字符串格式的IP地址信息
              void *dst         //地址结构体里面 struct in_addr IP地址结构指针
              );
//返回值:若成功则为1,若输入不是有效的IP地址表达式则为0,若出错则为-1

示例:

struct sockaddr_in addr;

NSString *ip = @"192.168.1.60";
const char *cChar_ip = [ip cStringUsingEncoding:NSUTF8StringEncoding];

int result = inet_pton(AF_INET, cChar_ip, &addr.sin_addr);
if (result == 1) {
      printf("地址赋值成功\n");
}else if (result == 0) {
      printf("输入的地址无效\n");
}else {
      printf("转换出错\n");
}

6.inet_ntoa()

函数:char *inet_ntoa(struct in_addr)

功能:正好与上面的函数inet_aton功能相反,需要传入一个关于地址信息的结构体,解析出来 C 字符串的 IP 地址

使用地方及方式:

//接着辅助函数3/4/5的例子往下走
char *cChar_ip_out= inet_ntoa(addr.sin_addr);

NSLog(@"%s",cChar_ip_out);
NSLog(@"%@",[NSString stringWithCString:cChar_ip_out encoding:NSUTF8StringEncoding]);
/* 打印结果:
   192.168.1.60
   192.168.1.60
 */

所以这样从地址结构体里面获取 IP 地址,就可以用该函数直接可以将结构体里面的struct in_addrIP 地址结构体转换成 c 字符串进行输出和打印

7.inet_ntop()

函数:const char *inet_ntop(int, const void *, char *, socklen_t);

功能:跟上面函数inet_ntoa()功能相似,于函数inet_pton()功能相反,不一样的是他可以传入地址族,传入AF_INET则解析出 IPv4 的地址,传入AF_INET6则解析出 IPv6 的地址

解释:

const char *inet_ntop(
                      int af,           //地址族 AF_INET 对应IPv4,AF_INET6对应IPv6
                      const void * src, //地址结构体里面 struct in_addr IP地址结构指针
                      char * dst,       //盛装C字符串格式的IP地址信息
                      socklen_t cnt     //C字符串的宽度
                      )
//返回值:若成功则为指向C字符串格式IP地址缓存的指针,若出错则为NULL

示例:

//接着辅助函数3、4、5的例子往下走
char cChar_ip_out[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &addr.sin_addr, cChar_ip_out, (socklen_t)sizeof((cChar_ip_out))) == NULL) {
     printf("地址转换出错\n");
}else {
     printf("地址转换成功:%s \n",cChar_ip_out);
}

8.getpeername()

函数:int getpeername(int, struct sockaddr * __restrict, socklen_t * __restrict)

功能:获取 socket 套接字对方的地址信息,返回 0 时正常,否则错误

解释:int getpeername(int sockfd, struct sockaddr * peerAddr, socklen_t * addrLen)

使用方式:

//IPv4连接,获取对方的IP地址
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);

if (getpeername(sockfd, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
    NSLog(@"获取地址出错");
 }else {

    char addrBuf[INET_ADDRSTRLEN];

    if (inet_ntop(AF_INET, &sockaddr4.sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
    {
         addrBuf[0] = '\0';//表示字符结束
     }

    NSString *ip = [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
    NSLog(@"获取的IPv4地址为:%@",ip);
}
//IPv6连接,获取对方的IP地址
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);

if (getpeername(sockfd, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
    NSLog(@"获取地址出错");
}else {

    char addrBuf[INET6_ADDRSTRLEN];

    if (inet_ntop(AF_INET6, &sockaddr6.sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
    {
           addrBuf[0] = '\0';//表示字符结束
     }

    NSString *ip = [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
    NSLog(@"获取的IPv6地址为:%@",ip);
}

8.getsockname()

函数:int getsockname(int, struct sockaddr * __restrict, socklen_t * __restrict) 功能:获取 socket 套接字的地址信息,返回 0 时正常,否则错误 解释:int getsockname(int sockfd, struct sockaddr * addr, socklen_t *addrLen)

使用方式

//IPv4连接,获取socket的IP地址
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);

if (getsockname(sockfd, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
     NSLog(@"获取地址出错");
}else
{

     char addrBuf[INET_ADDRSTRLEN];

     if (inet_ntop(AF_INET, &sockaddr4.sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
     {
        addrBuf[0] = '\0';//表示字符结束
     }
NSString *ip = [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
NSLog(@"获取socket的IPv4地址为:%@",ip);
}
//IPv6连接,获取socket的IP地址
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);

if (getsockname(sockfd, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
    NSLog(@"获取地址出错");
}else
{
    char addrBuf[INET6_ADDRSTRLEN];

    if (inet_ntop(AF_INET6, &sockaddr6.sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
    {
        addrBuf[0] = '\0';
    }
NSString *ip = [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
NSLog(@"获取socket的IPv6地址为:%@",ip);
}

四.简单的代码实现

下面是关于套接字简单的实现,只做参考,可以进行简单的通信但是不能用作正式的项目开发(因为里面有太多的逻辑没做判断和参数设置),下面代码编写是基于Xcode Command Line Tool工程

TCP 服务器和客户端代码实现

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

int creatServer(void);
int creatClient(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        int serv_result = creatServer();
        if (serv_result == 0) printf("开启服务器失败\n");

        //int clnt_result = creatClient();
        //if (clnt_result == 0) printf("开启客户端失败\n");
    }
    return 0;
}

#pragma mark ---服务器端套接字创建并开启监听
int creatServer()
{
    int serv_sockfd, clnt_sockfd;
    struct sockaddr_in serv_addr,clnt_addr;

    serv_addr.sin_len = sizeof(struct sockaddr_in);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(2000);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    serv_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (bind(serv_sockfd, (struct sockaddr*)&serv_addr, serv_addr.sin_len) < 0) return 0;
    if (listen(serv_sockfd, 1) < 0) return 0;

    socklen_t len_t = sizeof(struct sockaddr_in);
    clnt_addr.sin_len = len_t;
    clnt_sockfd = accept(serv_sockfd, (struct sockaddr*)&clnt_addr, &len_t);
    if (clnt_sockfd < 0) return 0;
    close(serv_sockfd);
    printf("有客户端连接 IP:%s Port:%d\n",inet_ntoa(clnt_addr.sin_addr),ntohs(clnt_addr.sin_port));

    char recv_buffer[512];
    while (1) {
        memset(recv_buffer, 0, 512);
        ssize_t recv_len = recv(clnt_sockfd, recv_buffer, sizeof(recv_buffer), 0);
        if (recv_len == 0) {
            printf("通信结束\n");
            break;
        }else if (recv_len < 0) {
            printf("读取数据出错\n");
            break;
        }else {
            printf("recv: %s\n",recv_buffer);
        }
    }
    printf("服务器关闭\n");
    close(clnt_sockfd);

    return 1;
}

#pragma mark ---客户端端套接字创建并开始连接
int creatClient()
{
    printf("请输入服务器IP地址:\n");
    char ip[INET_ADDRSTRLEN];
    scanf("%s",ip);

    int clnt_sockfd;
    clnt_sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in clnt_addr;
    clnt_addr.sin_len = sizeof(struct sockaddr_in);
    clnt_addr.sin_family = AF_INET;
    clnt_addr.sin_port = htons(2000);
    inet_aton(ip, &clnt_addr.sin_addr);

    if (connect(clnt_sockfd, (struct sockaddr*)&clnt_addr, clnt_addr.sin_len) < 0) return 0;
    printf("连接成功: %s 输入 'C' 退出结束\n",ip);

    char send_buffer[512];
    while (1) {
        memset(send_buffer, 0, 512);
        printf("请输入需要发送的内容:");
        gets(send_buffer);
        size_t buffer_len = strlen(send_buffer);
        if (buffer_len == 0) continue;
        if (strcmp(send_buffer, "C") == 0) break;

        ssize_t send_len = send(clnt_sockfd, send_buffer, buffer_len, 0);
        if (send_len == buffer_len) {
            printf("发送成功: %zu %zd\n",buffer_len,send_len);
        }else {
            printf("发送失败\n");
            break;
        }
    }
    printf("客户端关闭\n");
    close(clnt_sockfd);

    return 1;
}

UDP 发送端和接收端实现

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

int sendPacket(void);
int recvPacket(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        int send_result = sendPacket();
        if (send_result == 0) printf("开启发送失败\n");

        //int recv_result = recvPacket();
        //if (recv_result == 0) printf("开启接收失败\n");
    }
    return 0;
}

#pragma mark ---UDP数据发送端
int sendPacket()
{
    printf("请输入接收方IP地址:\n");
    char ip[INET_ADDRSTRLEN];
    scanf("%s",ip);

    int send_sock;
    send_sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (send_sock < 0) return 0;

    struct sockaddr_in addr;
    addr.sin_len = sizeof(struct sockaddr_in);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(2001);
    inet_aton(ip, &addr.sin_addr);

    printf("准备完毕,可以开始通信,输入字符'C'停止发送\n");
    char send_buffer[512];
    while (1) {

        memset(send_buffer, 0, 512);
        printf("请输入要发送的内容:");
        gets(send_buffer);
        size_t buffer_len = strlen(send_buffer);
        if (buffer_len == 0) continue;

        ssize_t send_len = sendto(send_sock,
                                  &send_buffer,
                                  buffer_len,
                                  0,
                                  (struct sockaddr*)&addr,
                                  addr.sin_len);

        if (send_len == buffer_len) {
            printf("UDP数据包发送成功\n");
        }else {
            printf("UDP数据包发送失败: %s\n",strerror(errno));
            break;
        }

        if (strcmp(send_buffer, "C") == 0) break;
    }

    printf("关闭UDP套接字\n");
    close(send_sock);

    return 1;
}

#pragma mark ---UDP数据接收端
int recvPacket()
{
    int recv_sock;
    recv_sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (recv_sock < 0) return 0;

    struct sockaddr_in addr,recv_addr;
    socklen_t len = sizeof(struct sockaddr_in);

    addr.sin_family = AF_INET;
    addr.sin_len = sizeof(struct sockaddr_in);
    addr.sin_port = htons(2001);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(recv_sock, (struct sockaddr*)&addr, addr.sin_len) < 0) {
        printf("%s\n",strerror(errno));
        return 0;
    }

    printf("开始接收UDP数据包,收到字符'C'停止接收\n");
    char recv_buffer[512];
    while (1) {
        memset(recv_buffer, 0, 512);

        ssize_t recv_len = recvfrom(recv_sock,
                                    recv_buffer,
                                    sizeof(recv_buffer),
                                    0,
                                    (struct sockaddr*)&recv_addr,
                                    &len);
        if (recv_len <= 0) {
            printf("接收UDP数据包失败:%s\n",strerror(errno));
            break;
        }
        printf("recv: %s\n",recv_buffer);

        if (strcmp(recv_buffer, "C") == 0) break;
    }
    printf("关闭UDP套接字\n");
    close(recv_sock);

    return 1;
}

五.尾声

接下来的文章会继续更新有关套接字的详解,这是我起初学习的一个流程,希望也对大家有帮助!