套接字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:从服务器接收数据。
程序的主要逻辑如下:
- 首先创建一个UDP套接字,通过调用 socket 函数。
- 初始化服务器的地址信息,包括服务器的IP地址和端口号,存储在 struct sockaddr_in 结构体中。
- 根据命令行参数选择不同的操作:
- 如果命令行参数为 “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);