socket编程应用案例详细分析

简介: 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);


目录
相关文章
|
安全 网络协议 Java
Thread类的用法 && 线程安全 && 多线程代码案例 && 文件操作和 IO && 网络原理初识 &&UDP socket
Thread类的用法 && 线程安全 && 多线程代码案例 && 文件操作和 IO && 网络原理初识 &&UDP socket
70 0
|
2月前
|
JSON 前端开发 JavaScript
socket.io即时通信前端配合Node案例
本文介绍了如何使用socket.io库在Node.js环境下实现一个简单的即时通信前端配合案例,包括了服务端和客户端的代码实现,以及如何通过socket.io进行事件的发送和监听来实现实时通信。
38 2
|
3月前
|
网络协议 Java
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
这篇文章全面讲解了基于Socket的TCP网络编程,包括Socket基本概念、TCP编程步骤、客户端和服务端的通信过程,并通过具体代码示例展示了客户端与服务端之间的数据通信。同时,还提供了多个案例分析,如客户端发送信息给服务端、客户端发送文件给服务端以及服务端保存文件并返回确认信息给客户端的场景。
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
|
5月前
|
监控 网络协议 安全
Socket网络编程中的常见应用场景与实例分析
Socket网络编程中的常见应用场景与实例分析
|
5月前
|
Java 测试技术 开发者
Java Socket编程实战案例:打造实时通信应用
【6月更文挑战第21天】Java Socket编程用于构建实时通信应用,如简易聊天系统。阻塞式Socket在读写时会阻塞线程,适合入门级应用。非阻塞式Socket(NIO)更高效,适用于高并发场景,允许线程在无数据时立即返回。通过对比两者,可理解实时通信技术的选择关键。示例代码展示了服务器端和客户端的实现。学习Socket编程能为应对未来挑战打下基础。
52 0
|
6月前
|
网络协议 Java 网络安全
【计算机网络】—— Socket通信编程与传输协议分析
【计算机网络】—— Socket通信编程与传输协议分析
|
前端开发 JavaScript Java
SpringBoot整合Socket实战案例,实现单点、群发,1对1,1对多
本篇内容: 后端 + 前端简单HTML页面 功能场景点: 群发,所有人都能收到 局部群发,部分人群都能收到 单点推送, 指定某个人的页面
|
弹性计算 负载均衡 监控
记一次socket read导致业务线程阻塞的案例分析
记一次socket read导致业务线程阻塞的案例分析
431 3
|
前端开发 JavaScript Java
Springboot 整合 Socket 实战案例 ,实现 单点发送、广播群发,1对1,1对多
Springboot 整合 Socket 实战案例 ,实现 单点发送、广播群发,1对1,1对多
1504 0
Springboot 整合 Socket 实战案例 ,实现 单点发送、广播群发,1对1,1对多
|
设计模式 网络协议 小程序
Socket 案例
Socket 案例
108 4
Socket 案例