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:从服务器接收数据。
程序的主要逻辑如下:
- 首先创建一个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);