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);


目录
相关文章
|
7月前
|
安全 网络协议 Java
Thread类的用法 && 线程安全 && 多线程代码案例 && 文件操作和 IO && 网络原理初识 &&UDP socket
Thread类的用法 && 线程安全 && 多线程代码案例 && 文件操作和 IO && 网络原理初识 &&UDP socket
40 0
|
监控 网络协议 Perl
[原创]结合案例深入解析orphan socket产生与消亡(一)
本文看点:结合服务器运行案例和TCP代码分析orphan socket产生与消亡以及对系统的影响。精彩的部分在(二)细节分析章节。 ##问题背景 tengine服务器发生过多次orphan socket数量很多的情况,例如有一次使用ss -s命令查看: ``` $ss -s T
8047 0
|
7月前
|
前端开发 JavaScript Java
SpringBoot整合Socket实战案例,实现单点、群发,1对1,1对多
本篇内容: 后端 + 前端简单HTML页面 功能场景点: 群发,所有人都能收到 局部群发,部分人群都能收到 单点推送, 指定某个人的页面
|
8月前
|
弹性计算 负载均衡 监控
记一次socket read导致业务线程阻塞的案例分析
记一次socket read导致业务线程阻塞的案例分析
249 3
|
前端开发 JavaScript Java
Springboot 整合 Socket 实战案例 ,实现 单点发送、广播群发,1对1,1对多
Springboot 整合 Socket 实战案例 ,实现 单点发送、广播群发,1对1,1对多
1183 0
Springboot 整合 Socket 实战案例 ,实现 单点发送、广播群发,1对1,1对多
java.nio.* 篇(1) FileChannel AsynchronousFileChannel ServerSocket Socket 使用案例
java.nio.* 篇(1) FileChannel AsynchronousFileChannel ServerSocket Socket 使用案例
|
JavaScript Windows
JS案例:Socket聊天室(两种方式)
JS案例:Socket聊天室(两种方式)
58 0
|
设计模式 网络协议 小程序
Socket 案例
Socket 案例
84 4
Socket 案例
|
存储 分布式计算 网络协议
Sparkstreaming 案例 — socket 回顾 | 学习笔记
快速学习 Sparkstreaming 案例 — socket 回顾
135 0
Sparkstreaming 案例 — socket 回顾 | 学习笔记
|
网络协议 Java API
java网络编程(2)socket通信案例(TCP和UDP)
java生下来一开始就是为了计算机之间的通信,因此这篇文章也将开始介绍一下java使用socket进行计算机之间的通信,在上一篇文章中已经对网络通信方面的基础知识进行了总结,这篇文章将通过代码案例来解释说明。
226 0
java网络编程(2)socket通信案例(TCP和UDP)