目录
前言
可用来实现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