socket编程应用案例详细分析

本文详细分析了一个基于UDP的套接字服务器和客户端的实现,包括服务器端的socket创建、地址设置、bind函数、sendto和recvfrom函数的使用,以及客户端的socket创建、发送和接收数据。此外,还介绍了线程创建,特别是发送和接收线程的实现,以及环形缓冲队列用于数据存储和传输,利用互斥锁和条件变量实现线程同步。
摘要由CSDN通过智能技术生成

socket编程应用案例详细分析



套接字server端

#define SERVER_PORT 5678
#define PRINT_BUF_SIZE   (16*1024)

static int g_iSocketServer;
static struct sockaddr_in g_tSocketServerAddr;
static struct sockaddr_in g_tSocketClientAddr;
static int g_iHaveConnected = 0;
static char *g_pcNetPrintBuf;
static int g_iReadPos  = 0;
static int g_iWritePos = 0;

static pthread_t g_tSendTreadID;
static pthread_t g_tRecvTreadID;

static pthread_mutex_t g_tNetDbgSendMutex  = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  g_tNetDbgSendConVar = PTHREAD_COND_INITIALIZER;


/**********************************************************************
 * 函数名称: NetDbgInit
 * 功能描述: "网络输出调试通道"的初始化函数
 *            1. 设置端口信息
 *            2. 创建2个子线程, 一个用来接收控制命令, 比如打开/关闭某个打印通道, 设置打印级别
 *               (本程序有两个打印通道: 标准输出,网络打印)
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 0 - 成功, 其他值 - 失败
 ***********************************************************************/
static int NetDbgInit(void)
{
    /* socket初始化 */
    int iRet;
    
    g_iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == g_iSocketServer)
    {
        printf("socket error!\n");
        return -1;
    }

    g_tSocketServerAddr.sin_family      = AF_INET;
    g_tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
     g_tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
    memset(g_tSocketServerAddr.sin_zero, 0, 8);
    
    iRet = bind(g_iSocketServer, (const struct sockaddr *)&g_tSocketServerAddr, sizeof(struct sockaddr));
    if (-1 == iRet)
    {
        printf("bind error!\n");
        return -1;
    }

    g_pcNetPrintBuf = malloc(PRINT_BUF_SIZE);
    if (NULL == g_pcNetPrintBuf)
    {
        close(g_iSocketServer);
        return -1;
    }


    /* 创建netprint发送线程: 它用来发送打印信息给客户端 */
    pthread_create(&g_tSendTreadID, NULL, NetDbgSendTreadFunction, NULL);            
    
    /* 创建netprint接收线否: 用来接收控制信息,比如修改打印级别,打开/关闭打印 */
    pthread_create(&g_tRecvTreadID, NULL, NetDbgRecvTreadFunction, NULL);            

    return 0;    
}

socket创建套接字

socket 函数创建一个服务器端的套接字(socket),使用 IPv4 和 UDP 协议。

 g_iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);

设置服务器地址

设置服务器地址,包括地址族、端口号和 IP 地址。

  g_tSocketServerAddr.sin_family      = AF_INET;
    g_tSocketServerAddr.sin_port        = htons(SERVER_PORT);
    g_tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
    memset(g_tSocketServerAddr.sin_zero, 0, 8);

bind 绑定函数

bind 绑定函数将套接字与服务器地址绑定。

iRet = bind(g_iSocketServer, (const struct sockaddr *)&g_tSocketServerAddr, sizeof(struct sockaddr));

sendto函数发送

用sendto函数发送打印信息给客户端

iAddrLen = sizeof(struct sockaddr);
sendto(g_iSocketServer, strTmpBuf, i, 0,
                              (const struct sockaddr *)&g_tSocketClientAddr, iAddrLen);

recvfrom读取

recvfrom读取客户端发来的数据

iRecvLen = recvfrom(g_iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);

套接字client端

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

/* socket
 * connect
 * send/recv
 */

#define SERVER_PORT 5678


/*
 * ./netprint_client <server_ip> dbglevel=<0-9>
 * ./netprint_client <server_ip> stdout=0|1
 * ./netprint_client <server_ip> netprint=0|1
 * ./netprint_client <server_ip> show // setclient,并且接收打印信息
 */

int main(int argc, char **argv)
{
    int iSocketClient;
    struct sockaddr_in tSocketServerAddr;
    
    int iRet;
    unsigned char ucRecvBuf[1000];
    int iSendLen;
    int iRecvLen;
    int iAddrLen;

    if (argc != 3)
    {
        printf("Usage:\n");
        printf("%s <server_ip> dbglevel=<0-9>\n", argv[0]);
        printf("%s <server_ip> stdout=0|1\n", argv[0]);
        printf("%s <server_ip> netprint=0|1\n", argv[0]);
        printf("%s <server_ip> show\n", argv[0]);
        return -1;
    }

    iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);

    tSocketServerAddr.sin_family      = AF_INET;
    tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
     //tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
     if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
     {
        printf("invalid server_ip\n");
        return -1;
    }
    memset(tSocketServerAddr.sin_zero, 0, 8);

    if (strcmp(argv[2], "show") == 0)
    {
        /* 发送数据 */
        iAddrLen = sizeof(struct sockaddr);
        iSendLen = sendto(iSocketClient, "setclient", 9, 0,
                              (const struct sockaddr *)&tSocketServerAddr, iAddrLen);

        while (1)
        {
            /* 循环: 从网络读数据, 打印出来 */
            iAddrLen = sizeof(struct sockaddr);
            iRecvLen = recvfrom(iSocketClient, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketServerAddr, &iAddrLen);
            if (iRecvLen > 0)
            {
                ucRecvBuf[iRecvLen] = '\0';
                printf("%s\n", ucRecvBuf);
            }
        }
    }
    else
    {
        /* 发送数据 */
        iAddrLen = sizeof(struct sockaddr);
        iSendLen = sendto(iSocketClient, argv[2], strlen(argv[2]), 0,
                              (const struct sockaddr *)&tSocketServerAddr, iAddrLen);
    }

    return 0;
}

这段代码是一个简单的UDP客户端程序,用于向服务器发送数据并接收服务器返回的数据。
代码中使用了以下系统库函数和数据结构:

  • socket:用于创建一个套接字,即网络通信的端点。
  • struct sockaddr_in:IPv4地址结构体,用于存储服务器的地址信息。
  • inet_aton:将点分十进制的IP地址转换为二进制形式。
  • sendto:向服务器发送数据。
  • recvfrom:从服务器接收数据。
    程序的主要逻辑如下:
  1. 首先创建一个UDP套接字,通过调用 socket 函数。
  2. 初始化服务器的地址信息,包括服务器的IP地址和端口号,存储在 struct sockaddr_in 结构体中。
  3. 根据命令行参数选择不同的操作:
    • 如果命令行参数为 “show”,则发送 “setclient” 到服务器,并进入一个循环,不断接收并打印服务器返回的数据。
    • 如果命令行参数不是 “show”,则直接将参数作为数据发送到服务器。
      使用 sendto 函数将数据发送到服务器。
    • 如果需要接收服务器返回的数据,则使用 recvfrom 函数从服务器接收数据,并打印出来。
      这段代码实现了一个简单的UDP客户端,用于与服务器进行通信,并根据命令行参数发送不同的数据或接收服务器返回的数据。

socket创建套接字

socket 函数创建一个服务器端的套接字(socket),使用 IPv4 和 UDP 协议。

iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);

设置服务器地址

设置服务器地址,包括地址族、端口号和 IP 地址。

tSocketServerAddr.sin_family      = AF_INET;
tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
        printf("invalid server_ip\n");
        return -1;
    }
    memset(tSocketServerAddr.sin_zero, 0, 8);

sendto函数发送

用sendto函数发送打印信息给客户端

iAddrLen = sizeof(struct sockaddr);
sendto(g_iSocketServer, strTmpBuf, i, 0,(const struct sockaddr *)&g_tSocketClientAddr, iAddrLen);

recvfrom读取

recvfrom读取客户端发来的数据

/* 循环: 从网络读数据, 打印出来 */
iAddrLen = sizeof(struct sockaddr);
iRecvLen = recvfrom(iSocketClient, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketServerAddr, &iAddrLen);

线程创建pthread_create

#include <pthread.h>
 int  pthread_create(pthread_t *thread,  const pthread_attr_tattr,  
                             void * ( routine)(void *),  void *arg)

函数参数
thread:创建的线程
attr:指定线程的属性,NULL表示使用缺省属性
routine:线程执行的函数
arg: 传递给线程执行的函数的参数
函数返回值
成功:0
出错:-1

创建网络打印发送线程

pthread_create(&g_tSendTreadID, NULL, NetDbgSendTreadFunction, NULL);

创建网络打印接收线程

 pthread_create(&g_tRecvTreadID, NULL, NetDbgRecvTreadFunction, NULL);

环形缓冲队列与发送接收实现

/* 要通过网络打印的信息存在环型缓冲区里,
 * 当有客户端连接上本程序时, 发送线程才从环型缓冲区中取出数据发送出去
 */
 
/**********************************************************************
 * 函数名称: isFull
 * 功能描述: 判断环型缓冲区是否满
 * 输入参数: 无
 * 输出参数: 无
 
 ***********************************************************************/
static int isFull(void)
{
    return (((g_iWritePos + 1) % PRINT_BUF_SIZE) == g_iReadPos);
}

/**********************************************************************
 * 函数名称: isEmpty
 * 功能描述: 判断环型缓冲区是否空
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 0 - 里面有数据, 1 - 无数据
 ***********************************************************************/
static int isEmpty(void)
{
    return (g_iWritePos == g_iReadPos);
}


/**********************************************************************
 * 函数名称: PutData
 * 功能描述: 往环型缓冲区中放入数据
 * 输入参数: cVal - 数据
 * 输出参数: 无
 * 返 回 值: 0 - 成功, 其他值 - 失败(缓冲区满)
 ***********************************************************************/
static int PutData(char cVal)
{
    if (isFull())
        return -1;
    else
    {
        g_pcNetPrintBuf[g_iWritePos] = cVal;
        g_iWritePos = (g_iWritePos + 1) % PRINT_BUF_SIZE;
        return 0;
    }    
}

/**********************************************************************
 * 函数名称: GetData
 * 功能描述: 从环型缓冲区中获得数据
 * 输入参数: 无
 * 输出参数: pcVal - 用来存放所获得的数据
 * 返 回 值: 0 - 成功, 其他值 - 失败(无数据)
 ***********************************************************************/
static int GetData(char *pcVal)
{
    if (isEmpty())
        return -1;
    else
    {
        *pcVal = g_pcNetPrintBuf[g_iReadPos];
        g_iReadPos = (g_iReadPos + 1) % PRINT_BUF_SIZE;
        return 0;
    }
}


/**********************************************************************
 * 函数名称: NetDbgSendTreadFunction
 * 功能描述: "网络输出调试通道"的发送线程函数
 *            此线程平时休眠, 有数据要发送时被唤醒, 然后通过网络把数据发送出去
 * 输入参数: pVoid - 未用
 * 输出参数: 无
 * 返 回 值: NULL - 线程退出
 ***********************************************************************/
static void *NetDbgSendTreadFunction(void *pVoid)
{
    char strTmpBuf[512];
    char cVal;
    int i;
    int iAddrLen;
    //int iSendLen;
    
    while (1)
    {
        /* 平时休眠, 其他线程调用DBG_PRINTF函数时, 将会调用到g_tNetDbgOpr.DebugPrint, 它会唤醒本线程 */
        pthread_mutex_lock(&g_tNetDbgSendMutex);
        pthread_cond_wait(&g_tNetDbgSendConVar, &g_tNetDbgSendMutex);    
        pthread_mutex_unlock(&g_tNetDbgSendMutex);

        while (g_iHaveConnected && !isEmpty())
        {
            i = 0;

            /* 把环形缓冲区的数据取出来, 最多取512字节 */
            while ((i < 512) && (0 == GetData(&cVal)))
            {
                strTmpBuf[i] = cVal;
                i++;
            }
            
            /* 执行到这里, 表示被唤醒 */
            /* 用sendto函数发送打印信息给客户端 */
            iAddrLen = sizeof(struct sockaddr);
            sendto(g_iSocketServer, strTmpBuf, i, 0,
                                  (const struct sockaddr *)&g_tSocketClientAddr, iAddrLen);

        }

    }
    return NULL;
}

环形缓冲队列

isFull 函数:判断环型缓冲区是否满。

static int isFull(void)
{
    return (((g_iWritePos + 1) % PRINT_BUF_SIZE) == g_iReadPos);
}

通过计算写指针 g_iWritePos 的下一个位置,并将其与读指针 g_iReadPos 进行比较,如果相等,则表示环型缓冲区已满。
isEmpty 函数:判断环型缓冲区是否为空。

static int isEmpty(void)
{
    return (g_iWritePos == g_iReadPos);
}

判断写指针和读指针是否相等,如果相等,则表示环型缓冲区为空。
PutData 函数:往环型缓冲区中放入数据。

static int PutData(char cVal)
{
    if (isFull())
        return -1;
    else
    {
        g_pcNetPrintBuf[g_iWritePos] = cVal;
        g_iWritePos = (g_iWritePos + 1) % PRINT_BUF_SIZE;
        return 0;
    }
}

首先,调用 isFull 函数判断环型缓冲区是否已满。
如果缓冲区已满,则返回-1表示失败。
如果缓冲区未满,则将数据 cVal 放入缓冲区,并更新写指针 g_iWritePos 的位置。
返回0表示成功。
GetData 函数:从环型缓冲区中获取数据。

static int GetData(char *pcVal)
{
    if (isEmpty())
        return -1;
    else
    {
        *pcVal = g_pcNetPrintBuf[g_iReadPos];
        g_iReadPos = (g_iReadPos + 1) % PRINT_BUF_SIZE;
        return 0;
    }
}

首先,调用 isEmpty 函数判断环型缓冲区是否为空。
如果缓冲区为空,则返回-1表示失败。
如果缓冲区非空,则从缓冲区中获取数据,将其存储到 pcVal 所指向的位置,并更新读指针 g_iReadPos 的位置。
返回0表示成功。

接收/发送线程函数

NetDbgSendTreadFunction 函数:发送线程函数,负责从环型缓冲区取出数据并发送给客户端。

static void *NetDbgSendTreadFunction(void *pVoid)
{
    char strTmpBuf[512];
    char cVal;
    int i;
    int iAddrLen;

    while (1)
    {
        // 平时休眠,等待被唤醒
        pthread_mutex_lock(&g_tNetDbgSendMutex);
        pthread_cond_wait(&g_tNetDbgSendConVar, &g_tNetDbgSendMutex);
        pthread_mutex_unlock(&g_tNetDbgSendMutex);

        // 当有连接且缓冲区非空时,取出数据并发送给客户端
        while (g_iHaveConnected && !isEmpty())
        {
            i = 0;

            // 从环形缓冲区取出数据,最多取512字节
            while ((i < 512) && (0 == GetData(&cVal)))
            {
                strTmpBuf[i] = cVal;
                i++;
            }

            // 使用 sendto 函数将打印信息发送给客户端
            iAddrLen = sizeof(struct sockaddr);
            sendto(g_iSocketServer, strTmpBuf, i, 0,
                   (const struct sockaddr *)&g_tSocketClientAddr, iAddrLen);
        }


    }
    return NULL;
}

线程首先进入休眠状态,等待被唤醒。其他线程通过调用 pthread_cond_signal 函数来唤醒该线程。
当被唤醒后,线程会检查是否存在连接且环型缓冲区非空。
如果满足条件,线程将从环型缓冲区中取出数据,并使用 sendto 函数将数据发送给客户端。
该过程会循环执行,直到没有连接或缓冲区为空。
NetDbgRecvTreadFunction函数:接收线程函数,接收客户端发来的数据,并根据接收到的数据进行相应的处理。

static void *NetDbgRecvTreadFunction(void *pVoid)
{
    socklen_t iAddrLen;
    int iRecvLen;
    char ucRecvBuf[1000];
    struct sockaddr_in tSocketClientAddr;

    while (1)
    {
        // 读取客户端发来的数据
        iAddrLen = sizeof(struct sockaddr);
        iRecvLen = recvfrom(g_iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);

        if (iRecvLen > 0)
        {
            ucRecvBuf[iRecvLen] = '\0';

            // 打印接收到的消息
            DBG_PRINTF("netprint.c get msg: %s\n", ucRecvBuf);

            // 解析数据并进行相应处理
            if (strcmp(ucRecvBuf, "setclient") == 0)
            {
                // 设置接收打印信息的客户端
                g_tSocketClientAddr = tSocketClientAddr;
                g_iHaveConnected = 1;
            }
            else if (strncmp(ucRecvBuf, "dbglevel=", 9) == 0)
            {
                // 修改打印级别
                SetDbgLevel(ucRecvBuf);
            }
            else
            {
                // 开/关打印通道
                SetDbgChanel(ucRecvBuf);
            }
        }
    }
    return NULL;
}

线程循环执行,不断接收客户端发来的数据。
使用 recvfrom 函数接收数据,并将其存储在 ucRecvBuf 缓冲区中。
如果接收到的数据长度大于0,说明接收到了有效的数据。
根据接收到的数据进行相应的处理:
如果接收到的数据是 “setclient”,则设置接收打印信息的客户端,并标记有连接。
如果接收到的数据以 “dbglevel=” 开头,则根据数据内容修改打印级别。
否则,根据数据内容开关打印通道。
这段代码实现了一个简单的网络输出调试通道。通过环型缓冲区存储需要发送的数据,在有客户端连接时,发送线程会从缓冲区中取出数据并发送给客户端。接收线程负责接收客户端发来的数据,并根据数据内容进行相应的处理,如设置接收打印信息的客户端、修改打印级别和开关打印通道等。

互斥锁与条件变量

static pthread_mutex_t g_tNetDbgSendMutex  = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  g_tNetDbgSendConVar = PTHREAD_COND_INITIALIZER;

两个参数是用于线程同步和互斥操作的。它们的作用如下:
g_tNetDbgSendMutex:这是一个互斥锁(mutex),用于实现线程间的互斥操作。互斥锁用于保护临界区,确保同时只有一个线程可以访问共享资源,避免出现竞争条件(race condition)。
PTHREAD_MUTEX_INITIALIZER 是一个宏,用于初始化互斥锁。它会将互斥锁设置为默认值,相当于调用 pthread_mutex_init 函数进行初始化。
g_tNetDbgSendMutex 是一个静态变量,被声明为 pthread_mutex_t 类型,用于在发送线程和其他线程之间实现互斥访问。
g_tNetDbgSendConVar:这是一个条件变量(condition variable),用于实现线程间的同步操作。条件变量用于在线程之间传递和等待特定条件的信号。
PTHREAD_COND_INITIALIZER 是一个宏,用于初始化条件变量。它会将条件变量设置为默认值,相当于调用 pthread_cond_init 函数进行初始化。
g_tNetDbgSendConVar 是一个静态变量,被声明为 pthread_cond_t 类型,用于在发送线程和其他线程之间实现等待和唤醒的操作。
在代码中的使用方式如下:
pthread_mutex_lock 和 pthread_mutex_unlock 函数用于获取和释放互斥锁,确保在访问共享资源之前和之后的临界区域被正确保护。
pthread_cond_wait 函数用于使线程等待条件变量满足特定条件。发送线程会在条件变量上等待,直到其他线程调用 pthread_cond_signal 函数来唤醒它。
pthread_cond_signal 函数用于唤醒等待在条件变量上的线程。其他线程可以通过调用该函数来通知发送线程有数据要发送,以便它从休眠状态中被唤醒并执行相应的操作。
通过使用互斥锁和条件变量,可以实现线程之间的同步和互斥操作,确保数据的正确处理和共享资源的安全访问。

static void *NetDbgSendTreadFunction(void *pVoid)
/* 平时休眠, 其他线程调用DBG_PRINTF函数时, 将会调用到g_tNetDbgOpr.DebugPrint, 它会唤醒本线程 */
pthread_mutex_lock(&g_tNetDbgSendMutex);
pthread_cond_wait(&g_tNetDbgSendConVar, &g_tNetDbgSendMutex);    
pthread_mutex_unlock(&g_tNetDbgSendMutex);

static int NetDbgPrint(char *strData)
    /* 如果已经有客户端连接了, 就把数据通过网络发送给客户端 */
    /* 唤醒netprint的发送线程 */
    pthread_mutex_lock(&g_tNetDbgSendMutex);
    pthread_cond_signal(&g_tNetDbgSendConVar);
    pthread_mutex_unlock(&g_tNetDbgSendMutex);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苦梨甜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值