tcp客户端、服务端(C/C++)(链表式存储客户端信息)

目录

一、服务运行效果

二、代码接口

1、源码下载地址:

2、主函数 tcp_test.c

3、提供客户端服务接口

tcp_client_api.h

4、提供服务端服务接口(链表式存储客户端信息)

tcp_server_api.h

三、编译运行


前言

 可用来实现tcp、udp客户端或服务器(服务器链表式存储客户端信息)

socket基础接口、
tcp客户端、服务器接口(tcp并发服务器、epoll多路复用服务器)
udp客户端、服务器服务接口


一、服务运行效果

二、代码接口

1、源码下载地址:

点击下载:

实现tcp、udp客户端或服务器(服务器链表式存储客户端信息)

源码说明:

#一、接口说明
可用来实现tcp、udp客户端或服务器(服务器链表式存储客户端信息)

socket基础接口、
tcp客户端、服务器接口
udp客户端、服务器服务接口

#二、服务器模型说明
##1、循环服务器
tcp循环服务器(不常用),udp循环服务器(常用)
···
	创建socket, 绑定bind, 监听listen,设置socket为非堵塞
	while(1)
	{
		通信
	}
	关闭已连接的socket( > 0)
```	

##2、并发服务器
tcp并发服务器:
···
	创建socket, 绑定bind, 监听listen,设置socket为非堵塞
	while(1)
	{
		select + accept 接受客户端连接
		为每个客户端创建子进程/子线程,实现通信,通信结束,关闭已连接Socket
	}
	关闭已连接的socket( > 0)
···		

##3、多路复用服务器
tcp多路复用服务器
···
	创建socket, 绑定bind, 监听listen,设置socket为非堵塞
	while(1)
	{
		epoll_wait 遍历处理客户端所有事件,执行IO操作(连接、通信、关闭连接) + accept 接受客户端连接
	}
	关闭已连接的socket( > 0)
···

epoll工作原理:
		1. 通过epoll_create创建epoll句柄//实际上是在内核创建一棵存放描述符的红黑树,并且返回树的编号
		2. 通过epoll_ctl将想监听的描述符放在树上,作为树的节点
		3. 通过epoll_wait等待文件可以进行IO操作
		4. 内核在遍历红黑树,如果有文件可以进行IO操作时,会通过epoll_wait将文件描述符返回
		5. 遍历epoll_wait返回的数组,对应每个的文件进行IO操作
		6. 重复3~5
	特点: 
		相对select来说,少了表的拷贝,轮询的节点都是有必要的,所以效率高。
		select只能监听1024个,epoll可以监听的描述符更多。


#三、编译测试
Makefile编译,输入:
···
make clean
make
···

运行tcp客户端:./tcp_test 1
运行tcp服务端:./tcp_test 2

2、主函数 tcp_test.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "tcp_client_api.h"
#include "tcp_server_api.h"


/****************************服务端*************************************/
static void client_onLogin(tcpServer_t *server, client_data_t client, void *arg)
{
	printf("有新客户端登录(socket_fd=%d, ip=%s, port=%d)\n", client.socket_fd, client.ip, client.port);
	printf("在线客户端总数:%d\n", server->client_list.count);
	print_client_list(&server->client_list);
	printf("\n\n");
}

static void client_onExit(tcpServer_t *server, client_data_t client, void *arg)
{
	printf("有客户端退出(socket_fd=%d, ip=%s, port=%d)\n", client.socket_fd, client.ip, client.port);
	printf("剩余客户端总数:%d\n", server->client_list.count);
	print_client_list(&server->client_list);
	printf("\n\n");
}

static void client_onMessage(tcpServer_t *server, int32_t clientSocketFd, void *buf, uint32_t len, void *arg)
{
	printf("收到客户端消息(socket_fd=%d):len=%d, buf=%s\n", clientSocketFd, len, (char *)buf);
#if 0
	char send_msg[128] = "1111";
	printf("返回消息给客户端:%s\n", send_msg);
	tcpServerSendClientByFd(server, clientSocketFd, (void *)send_msg, strlen(send_msg));//发送数据
	printf("\n\n");
#endif    
}


void server_loop(void)
{
    tcpServer_t server = {0};
    tcpServerInit(&server, 0, "0.0.0.0", 6666, 0, 0, 0);
    tcpServerSetCb(&server, client_onLogin, client_onExit, client_onMessage, NULL); 
    tcpServerStart(&server);
    while (1) {
        sleep(1);    
        tcpServerSendClients(&server, (void *)"hello client", strlen("hello client"));
    }
    tcpServerStop(&server);
}


/****************************客户端*************************************/
void onMessage(int32_t fd, void *buf, uint32_t len, void *arg)
{
	unsigned char *ptr = (unsigned char *)buf;
    //for (int i = 0; i < len; i++) {
     //   printf("%02x ", ptr[i]);
    //}
    //printf("\n");
    printf("收到服务端消息(len=%d):%s\n", len, ptr);
}

void client_loop(void)
{
    tcpClient_t client = {0};
    tcpClientInit(&client, "10.10.151.211", 6666, 0, 0, 0);
    //tcpClientSetCb(&client, NULL, NULL, onMessage, NULL); 
    
    tcpClientStart(&client);
    while (1) {
        if (tcpClientIsTConnect(&client)) {
            tcpClientSend(&client, (void *)"hello", strlen("hello"));
        }
        sleep(1);
    }
    tcpClientStop(&client);
}


int main(int argc, char *argv[])
{
    printf("choose: 1 -- tcp client; 2 -- tcp server\n");
    if (1 >= argc) {
        return 0;
    }
    
    int mode = atoi(argv[1]);
    
    if (1 == mode) {
        client_loop();
    } else {
        server_loop();
    }
	
	return 0;
}

3、提供客户端服务接口

tcp_client_api.h

/*************************************************
@brief: 封装tcp客户端接口,实现tcp通信
@author: zyh
@date: 2024.4.16
**************************************************/
#ifndef _TCP_CLIENT_API_H_
#define _TCP_CLIENT_API_H_

#include <pthread.h>
#include <stdint.h>
#include <stdbool.h>

#ifdef __cplusplus
extern "C" {
#endif

// 定义socket客户端服务信息结构体
typedef struct {
    /*/要连接的远程服务器信息*/
	char remoteIp[32];//要连接的远程服务器地址
	uint32_t remotePort;//远程服务器端口
    
    /*本客户端配置*/
	uint32_t bindPort;//绑定的本地端口,如果为0,则不绑定
    uint32_t reconnectTimeS;//中断重连时间(秒),如果为0,内部默认1秒
    int32_t socketFd;//客户端句柄
    
    uint32_t rcvBufSize;//设置接收数据缓冲区大小,如果为0,内部默认1024
    /*状态变量*/
    //bool rcvFlag;//1接收到数据,0未接收到
    //uint32_t rcvLen;// 大于0为接收到的数据
    //uint8_t *rcvBuf;//rcv_buf[rcv_buf_size]

	void *arg;//留给外部的自定义传参
	void (*onConnect)(int32_t socketFd, void *arg);//当客户端连接上服务器时的回调函数
	void (*onDisconnect)(int32_t socketFd, void *arg);//当客户端与服务器断开连接时的回调函数
	void (*onMessage)(int32_t socketFd, void *buf, uint32_t len, void *arg);//当收到消息时的回调函数
	
	//内部运行状态变量---------	
	bool connectFlag;//通信连接的状态,0未连接,1连接正常
	pthread_t threadId;
	bool runFlag;//本服务启动标志:1启动,0未启动或已退出
} tcpClient_t;

//初始化客户端
int32_t tcpClientInit(tcpClient_t *client, const char* remoteIp, uint32_t remotePort, 
                                    uint32_t bindLocalPort, uint32_t reconnectTimeS, uint32_t rcvBufSize);

//设置客户端回调函数,如果参数为NULL,则表示不设置
void tcpClientSetCb(tcpClient_t *client, 
                        void (*onConnect)(int32_t socketFd, void *arg),
                        void (*onDisconnect)(int32_t socketFd, void *arg),
                        void (*onMessage)(int32_t socketFd, void *buf, uint32_t len, void *arg),
                        void *arg);

int32_t tcpClientStart(tcpClient_t *client);//启动tcp客户端(这是一个线程服务,内部自动重连)
int32_t tcpClientStop(tcpClient_t *client);//关闭客户端服务
int32_t tcpClientRestart(tcpClient_t *client);
bool tcpClientIsTConnect(tcpClient_t *client);
int32_t tcpClientSend(tcpClient_t *client, const void *buf, uint32_t n);

#if 0 //test
tcpClient_t client = {0};
tcpClientInit(&client, "10.10.151.211", 6666, 0, 0, 0);
//tcpClientSetCb(&client, NULL, NULL, onMessage, NULL); 

tcpClientStart(&client);
while (1) {
    if (tcpClientIsTConnect(&client)) {
        tcpClientSend(&client, (void *)"hello", strlen("hello"));
    }
    sleep(1);
}
tcpClientStop(&client);
#endif 

#ifdef __cplusplus
}
#endif

#endif

4、提供服务端服务接口(链表式存储客户端信息)

tcp_server_api.h

/*************************************************
@brief: 封装tcp服务端接口
@author: zyh
@date: 2024.4.24
**************************************************/
#ifndef _TCP_SERVER_API_H_
#define _TCP_SERVER_API_H_

#include <pthread.h>
#include <stdint.h>   

#ifdef __cplusplus
extern "C" {
#endif

// 定义客户端信息结构体
typedef struct {
	int32_t socket_fd;
	char ip[24];
	uint32_t port;
} client_data_t;

// 定义链表节点结构体
typedef struct client_node {
    client_data_t data;
    struct client_node* next;
} client_node_t;

// 定义链表结构体
typedef struct {
    client_node_t* head;
    client_node_t* tail;
    uint32_t count; // 节点计数
    pthread_mutex_t lock; // 互斥锁
} client_link_list_t;

//获取某个客户端信息,失败返回NULL
client_data_t* get_client(client_link_list_t* list, int32_t socket_fd);

//遍历打印链表中客户端信息
void print_client_list(client_link_list_t* list);

typedef struct tcpServer tcpServer_t;
// 定义服务端信息结构体(内部自动处理上下线的客户端信息)
struct tcpServer {
    /************配置服务端参数***********/
    /*
    服务器类型:
        0 为并发服务器(为每个客户端创建独立子线程);
        1为epoll多路复用服务器;
    */
    uint8_t type;
	char ip[24];//服务器地址:"127.0.0.1"或"0.0.0.0"
    uint32_t bind_port;//绑定的本地端口
	uint32_t max_listen_backlog;//socket监听客户端连接套接口排队的最大连接个数,如果为0,内部默认128
	uint32_t max_client_online_num;//在线客户端的最大数量,如果为0,内部默认128
    uint32_t rcv_buf_size;//设置接收数据缓冲区大小,如果为0,内部默认1024

	void *arg;//留给外部的自定义传参
	void (*client_onLogin)(tcpServer_t *server, client_data_t client, void *arg);//客户端登录的回调
	void (*client_onExit)(tcpServer_t *server, client_data_t client, void *arg);//客户端断开连接的回调
	void (*client_onMessage)(tcpServer_t *server, int32_t clientSocketFd, void *buf, uint32_t len, void *arg);//接收处理客户端消息的回调
	
	/*在线客户端信息(如需主动发送消息给客户端,则可以通过链表客户端消息里面的通信句柄发送)*/
	client_link_list_t client_list;//链表方式动态存储在线客户端信息,节省内存空间
	
	//内部运行状态变量---------	
	pthread_t thread_id;
	bool run_flag;//本服务启动标志:1启动,0未启动或已退出
};

//初始化设置基础参数
int32_t tcpServerInit(tcpServer_t *server, uint8_t serverType, const char* hostIp, uint32_t bindPort, 
                                    uint32_t maxListenNum, uint32_t maxClientOnlineNum, uint32_t rcvBufSize);

//设置回调函数,如果参数为NULL,则表示不设置
void tcpServerSetCb(tcpServer_t *server, 
                            void (*clientOnLogin)(tcpServer_t *server, client_data_t client, void *arg),
                            void (*clientOnExit)(tcpServer_t *server, client_data_t client, void *arg),
                            void (*clientOnMessage)(tcpServer_t *server, int32_t clientSocketFd, void *buf, uint32_t len, void *arg),
                            void *arg);


//启动服务端服务
int32_t tcpServerStart(tcpServer_t *server);
int32_t tcpServerStop(tcpServer_t *server);
int32_t tcpServerRestart(tcpServer_t *server);

//给所有客户端发送消息(成功返回已发送的客户端数量)
int32_t tcpServerSendClients(tcpServer_t *server, const void *buf, uint32_t n);

//通过IP和端口给某个客户端发送消息(端口等于0,表示不判断端口)(0 > 失败, 0 = 关闭了连接, 0 < 已发送数据长度)
int32_t tcpServerSendClientByIp(tcpServer_t *server, const char *clientIp, uint32_t clientPort, const void *buf, uint32_t n);

//通过fd给某个客户端发送消息(0 > 失败, 0 = 关闭了连接, 0 < 已发送数据长度)
int32_t tcpServerSendClientByFd(tcpServer_t *server, int32_t clientSocketFd, const void *buf, uint32_t n);

#if 0 //test
tcpServer_t server = {0};
tcpServerInit(&server, 0, "0.0.0.0", 6666, 0, 0, 0);
tcpServerSetCb(&server, client_onLogin, client_onExit, client_onMessage, NULL); 
tcpServerStart(&server);
while (1) {
    sleep(1);    
    tcpServerSendClients(&server, (void *)"hello client", strlen("hello client"));
}
tcpServerStop(&server);

#endif 

#ifdef __cplusplus
}
#endif

#endif

三、编译运行

Makefile编译:
make clean
make
 
运行tcp客户端:./tcp_test 1
运行tcp服务端:./tcp_test 2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

勤劳的搬运工zyh

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

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

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

打赏作者

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

抵扣说明:

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

余额充值