目录
前言
可用来实现tcp、udp客户端或服务器(服务器链表式存储客户端信息)
socket基础接口、
tcp客户端、服务器接口(tcp并发服务器、epoll多路复用服务器)
udp客户端、服务器服务接口
一、服务运行效果
二、代码接口
1、源码下载地址:
点击下载:
实现tcp、udp客户端或服务器(服务器链表式存储客户端信息)
源码说明:
#一、接口说明
可用来实现tcp、udp客户端或服务器(服务器链表式存储客户端信息)
socket基础接口、
tcp客户端、服务器接口
udp客户端、服务器服务接口
#二、服务器模型说明
##1、循环服务器
tcp循环服务器不太常用
udp循环服务器(常用)
···
创建socket
绑定自己的地址
while(1)
{
通信
}
关闭socket
```
##2、并发服务器
tcp并发服务器:
···
创建socket,bind,监听
while(1)
{
accept接受连接
创建子进程/子线程,实现通信,通信结束关闭已连接Socket
}
关闭等连接socket
···
##3、多路复用服务器
tcp多路复用服务器
···
创建socket,bind,监听
创建表,初始化表
while(1)
{
select/epoll_wait
遍历表,执行IO操作
}
关闭等连接socket
···
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"
#include <sys/socket.h>
/****************************服务端*************************************/
static void client_onLogin(tcp_server_t *server, client_data_t client)
{
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(tcp_server_t *server, client_data_t client, void *buf, int len)
{
printf("收到客户端消息(socket_fd=%d ip=%s port=%d):len=%d, buf=%s\n", client.socket_fd, client.ip, client.port, len, (char *)buf);
#if 0
char send_msg[128] = "hello";
printf("返回消息给客户端:%s\n", send_msg);
send(client.socket_fd, send_msg, strlen(send_msg), 0);//发送数据
printf("\n\n");
#endif
}
static void client_onExit(tcp_server_t *server, client_data_t client)
{
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");
}
void server_loop(void)
{
tcp_server_t server1 = {
.type = 0,
.ip = "0.0.0.0",
.bind_port = 6666,
.max_listen_backlog = 32,
.max_client_online_num = 128,
.rcv_buf_size = 0,
.client_onLogin = client_onLogin,
.client_onMessage = client_onMessage,
.client_onExit = client_onExit
};
//启动服务端
start_tcp_server(&server1);
while(1) {
sleep(10);
#if 1
//给每个客户端发送消息
client_node_t* current = server1.client_list.head;
while (current != NULL) {
client_data_t client = current->data;
send(client.socket_fd, "hello", strlen("hello"), 0);//发送数据
current = current->next;
}
#endif
}
//关闭服务端
close_tcp_server(&server1);
}
/****************************客户端*************************************/
void on_message(int fd, void *buf, int 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)
{
tcp_client_t client1 = {
.remote_ip = (char *)"10.10.48.11",
.remote_port = 6666,
.bind_port = 0,
.rcv_buf_size = 0,
.on_connect = NULL,
.on_disconnect = NULL,
.on_message = on_message
};
start_tcp_client(&client1);
char str[64] = {0};
int i = 0;
while(1) {
sleep(5);
if (client1.connect_flag) {
i++;
sprintf(str, "hello %d", i);
printf("发送消息到服务端:%s\n", str);
send(client1.socket_fd, str, strlen(str), 0);
//socket_sendn(client1.socket_fd, str, strlen(str));
}
}
close_tcp_client(&client1);
}
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、提供基础接口
socket_api.h
/*************************************************
func: 封装socket 基础接口
author: zyh
date: 2024.4.16
**************************************************/
#ifndef _SOCKET_API_H_
#define _SOCKET_API_H_
#ifdef __cplusplus
extern "C" {
#endif
/**
一般是udp用到
广播方式发给局域网内所有主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。
组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。既可以发给多个主机,
又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)
**/
//允许地址的快速重用
int set_socket_reuseAddr(int sockfd);
//设置是否广播,一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性
int set_socket_broadcast(int sockfd, int flag);
// 设置是否接收发自自己的组播组的数据包(通常用于测试)
int set_socket_multicastLoop(int sockfd, int flag);
//加入多播组(可运行多次加入不同的地址)
int set_socket_ipAddMembership(int sockfd, const char *ip);
//离开多播组
int set_socket_ipDropMembership(int sockfd, const char *ip);
//修改socket接收缓冲区大小
int set_socket_recvBuf(int sockfd, int nRecvBuf);
//修改socket发送缓冲区大小
int set_socket_sendBuf(int sockfd, int nSendBuf);
//用于设置发送数据的超时时间。通过这个选项,你可以指定在发送数据时等待的最长时间。如果在指定的时间内无法完成发送操作,系统将会返回一个错误。
int set_socket_sndTime(int sockfd, struct timeval tv);
//用于设置接收数据的超时时间。通过这个选项,可以指定在接收数据时等待的最长时间。如果在指定的时间内未接收到数据,系统将会返回一个错误。
int set_socket_rcvTime(int sockfd, struct timeval tv);
//客户端连接服务器,返回:成功:0, 失败:-1
int socket_connect(int sockfd, char *ip, int port);
//客户端或服务端绑定本地地址,返回:成功:0, 失败:-1
int socket_bind(int sockfd, const char *local_ip, int local_port);
//将文件描述符默认的阻塞行为修改为非阻塞
int set_fd_nonblock(int fd);
// 将文件描述符设置为阻塞模式
int set_fd_block(int fd);
//select 监听文件描述符是否可读,返回0可读,返回-1 监听超时(不可读)或失败
int select_fd(int fd, int timeout_ms);
/**
非阻塞模式下循环读取直到缓冲区没有数据可读为止
非阻塞模式下如果没有数据会返回,不会阻塞着读,因此需要循环读取直到缓冲区没有数据可读为止
返回:-1 出错,0 连接关闭,成功返回实际接收到字节数
**/
ssize_t socket_recv_again(int fd, void *buf, size_t count);
//确保写入n个字节,返回:-1 出错,0 连接关闭,成功返回实际字节数
ssize_t socket_sendn(int fd, const void *buf, size_t n);
/**
函数功能:socket非堵塞接收消息(select实现,监听最大文件描述符数量为1024, 如果存在大量客户端连接的情况下,文件描述符过多,则不适用)
入参:fd:串口文件描述符
buf:接收数据的缓冲区
len:缓冲区的大小
timeout_ms:超时时间,毫秒
出参:无
返回:成功返回实际接收的字节数,0已断开连接, -1失败,-2超时无数据可读
**/
int socket_rcv_nonblock(int socket_fd, void *buf, int size, int timeout_ms);
//sendto发送数据,一般是udp通信用到(如果是广播,那么广播的BROADCAST_IP 为"255.255.255.255")
int socket_sendto(int sockfd, void *buf, size_t len, int flags, char *ip, int port);
#ifdef __cplusplus
}
#endif
#endif
socket_api.c
/*************************************************
func: 封装socket 基础接口
author: zyh
date: 2024.4
**************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>//提供IP地址转换函数
#include <sys/types.h>//数据类型定义文件
#include <sys/socket.h>//提供socket函数及数结构
#include <netinet/in.h>//定义数据结构体sockaddr_in
#include <netinet/ip.h>
#include <pthread.h>
#include "socket_api.h"
//允许地址的快速重用
int set_socket_reuseAddr(int sockfd)
{
int reuse = 1;//允许地址的快速重用
if (0 > setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))) {
perror("set sockopt");
return -1;
}
return 0;
}
//设置是否广播,一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性
int set_socket_broadcast(int sockfd, int flag)
{
//int flag = 1;
if (0 > setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(int))) {
perror("setsockopt SO_BROADCAST");
return -1;
}
return 0;
}
// 设置是否接收发自自己的组播组的数据包(通常用于测试)
int set_socket_multicastLoop(int sockfd, int flag)
{
if (0 > setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &flag, sizeof(int))) {
perror("setsockopt IP_MULTICAST_LOOP");
return -1;
}
return 0;
}
//加入多播组
int set_socket_ipAddMembership(int sockfd, const char *ip)
{
//组播
struct ip_mreqn mreq = {0};
mreq.imr_multiaddr.s_addr = inet_addr(ip);// 多播组地址
//mreq.imr_interface.s_addr = htonl(INADDR_ANY); // 本地所有接口
//加入多播组
if (0 > setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))) {
perror("setsockopt IP_ADD_MEMBERSHIP");
return -1;
}
return 0;
}
//离开多播组
int set_socket_ipDropMembership(int sockfd, const char *ip)
{
struct ip_mreqn mreq;
mreq.imr_multiaddr.s_addr = inet_addr(ip);// 多播组地址
//mreq.imr_interface.s_addr = htonl(INADDR_ANY); // 本地网络接口
if (0 > setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq))) {
perror("setsockopt IP_DROP_MEMBERSHIP");
return -1;
}
return 0;
}
//修改socket接收缓冲区大小
int set_socket_recvBuf(int sockfd, int nRecvBuf)
{
#if 0//读缓冲区大小
unsigned long snd_size = 0;/*发送缓冲区大小*/
unsigned long rcv_size = 0;/*选项值长度*/
socklen_t optlen;/*选项值长度*/
optlen = sizeof(snd_size);//获得原始发送缓冲区大小
getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &snd_size, &optlen);
optlen = sizeof(rcv_size);
getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcv_size, &optlen);
printf("发送缓冲区原始大小为:%ld \n", snd_size);
printf("接收缓冲区原始大小为:%ld \n", rcv_size);
#endif
// 接收缓冲区
//int nRecvBuf = 48*1024;//设置为48K
if(0 > setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (const char*)&nRecvBuf, sizeof(int))) {
perror("setsockopt SO_RCVBUF");
return -1;
}
return 0;
}
//修改socket发送缓冲区大小
int set_socket_sendBuf(int sockfd, int nSendBuf)
{
#if 0//读缓冲区大小
unsigned long snd_size = 0;/*发送缓冲区大小*/
unsigned long rcv_size = 0;/*选项值长度*/
socklen_t optlen;/*选项值长度*/
optlen = sizeof(snd_size);//获得原始发送缓冲区大小
getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &snd_size, &optlen);
optlen = sizeof(rcv_size);
getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcv_size, &optlen);
printf("发送缓冲区原始大小为:%ld \n", snd_size);
printf("接收缓冲区原始大小为:%ld \n", rcv_size);
#endif
//发送缓冲区
//int nSendBuf = 48*1024;//设置为48K
if(0 > setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char*)&nSendBuf, sizeof(int))) {
perror("setsockopt SO_SNDBUF");
return -1;
}
return 0;
}
//用于设置发送数据的超时时间。通过这个选项,你可以指定在发送数据时等待的最长时间。如果在指定的时间内无法完成发送操作,系统将会返回一个错误。
int set_socket_sndTime(int sockfd, struct timeval tv)
{
if (0 > setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv))) {
perror("setsockopt SO_SNDTIMEO");
return -1;
}
return 0;
}
//用于设置接收数据的超时时间。通过这个选项,可以指定在接收数据时等待的最长时间。如果在指定的时间内未接收到数据,系统将会返回一个错误。
int set_socket_rcvTime(int sockfd, struct timeval tv)
{
if (0 > setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) {
perror("setsockopt SO_RCVTIMEO");
return -1;
}
return 0;
}
//客户端连接服务器,返回:成功:0, 失败:-1
int socket_connect(int sockfd, char *ip, int port)
{
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
if (0 >= inet_pton(AF_INET, ip, &serv_addr.sin_addr)) {
perror("inet_pton error");
return -1;
}
if (0 > connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
perror("connect");
return -1;
}
return 0;
}
//客户端或服务端绑定本地地址,返回:成功:0, 失败:-1
int socket_bind(int sockfd, const char *local_ip, int local_port)
{
/*绑定地址和端口*/
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(local_port);
if (strstr(local_ip, "127.0.0.1")) {//只能在本机上测试,无法接受来自其他机器的连接请求
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
} else {//可以接受任何机器的连接请求,即绑定到此机器的所有网卡上
addr.sin_addr.s_addr = INADDR_ANY;
}
if (0 > bind(sockfd, (struct sockaddr *)&addr, sizeof(addr))) {
perror("bind");
return -1;
}
return 0;
}
//将文件描述符默认的阻塞行为修改为非阻塞
int set_fd_nonblock(int fd)
{
int flag = fcntl(fd, F_GETFL);
if (0 > flag) {
return -1;
}
if (0 > fcntl(fd, F_SETFL, flag | O_NONBLOCK)) {
return -1;
}
return 0;
}
// 将文件描述符设置为阻塞模式
int set_fd_block(int fd)
{
int flag = fcntl(fd, F_GETFL);
if (0 > flag) {
return -1;
}
if (0 > fcntl(fd, F_SETFL, flag & (~O_NONBLOCK))) {
return -1;
}
return 0;
}
//select 监听文件描述符是否可读,返回0可读,返回-1 监听超时(不可读)或失败
int select_fd(int fd, int timeout_ms)
{
struct timeval time_out;
time_out.tv_sec = timeout_ms/1000;
time_out.tv_usec = (timeout_ms - time_out.tv_sec*1000)*1000;
fd_set readfd;
FD_ZERO(&readfd);
FD_SET(fd, &readfd);
if (0 >= select(fd + 1, &readfd, NULL, NULL, &time_out)) {//超时
return -1;
}
if (0 == FD_ISSET(fd, &readfd)) {
return -1;
}
return 0;
}
/*
非阻塞模式下循环读取直到缓冲区没有数据可读为止
非阻塞模式下如果没有数据会返回,不会阻塞着读,因此需要循环读取直到缓冲区没有数据可读为止
返回:-1 出错,0 连接关闭,成功返回实际接收到字节数
*/
ssize_t socket_recv_again(int fd, void *buf, size_t count)
{
ssize_t n = 0;//累计字节数
size_t nleft = 0;//剩余的字节数
ssize_t nread = 0;//当前接收的字节数
char *ptr = (char *)buf;
nleft = count;
while (0 < nleft) {//由于不能保证一次读操作能够返回字节数是多少,所以需要进行循环来接收
//printf("0000000\n");
nread = recv(fd, ptr + n, nleft, 0);// <0:出错,=0:连接关闭,>0接收到数据大小
if (0 > nread) {
if (errno == EAGAIN) {//EAGAIN:表示当前没有数据可读取了
//printf("111111\n");
break;
} else if (errno == EINTR) {// 被信号中断,重试
continue;
} else { // 其他错误,退出读取
return -1;
}
} else if (0 == nread) {//对方关闭了
return 0;
}
n += nread;
nleft -= nread;
}
return n;
}
//确保写入n个字节,返回:-1 出错,0 连接关闭,成功返回实际字节数
ssize_t socket_sendn(int fd, const void *buf, size_t n)
{
size_t nleft = 0; // 剩余未写的字节数
ssize_t nwritten = 0; // 单次写入的字节数
char *ptr = (char *)buf;
nleft = n;
while (nleft > 0) {
nwritten = send(fd, ptr, nleft, 0);
if (0 > nwritten) {
if (errno == EINTR) { // 写入操作被信号中断,继续写入
continue;
} else { //其他错误,退出
return -1;
}
} else if (0 == nwritten) {//对方关闭了
return 0;
}
nleft -= nwritten;
ptr += nwritten;
}
//return (n - nleft); /* return >= 0 */
return n;
}
/**
函数功能:socket非堵塞接收消息(select实现,监听最大文件描述符数量为1024, 如果存在大量客户端连接的情况下,文件描述符过多,则不适用)
入参:fd:串口文件描述符
buf:接收数据的缓冲区
len:缓冲区的大小
timeout_ms:超时时间,毫秒
出参:无
返回:成功返回实际接收的字节数,0已断开连接, -1失败,-2超时无数据可读
**/
int socket_rcv_nonblock(int socket_fd, void *buf, int size, int timeout_ms)
{
if (0 > select_fd(socket_fd, timeout_ms)) {//超时或不可读写
return -2;
}
int len = 0;
#if 1
/*
当 recv 函数返回 -1 时,可以检查 errno 来确定错误的原因
EAGAIN:没有数据可读
EINTR:接收操作被中断
上面采用的select监听,因此不用判断EAGAIN的情况
*/
len = recv(socket_fd, buf, size, 0);
#else
len = socket_recv_again(socket_fd, buf, size);
#endif
if (0 == len) {//关闭了连接
return 0;
} else if (0 > len) {
return -1;
}
return len;
}
//sendto发送数据,一般是udp通信用到(如果是广播,那么广播的BROADCAST_IP 为"255.255.255.255")
int socket_sendto(int sockfd, void *buf, size_t len, int flags, const char *ip, int port)
{
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);//广播的BROADCAST_IP 为"255.255.255.255"
int addr_len = sizeof(addr);
return sendto(sockfd, buf, len, flags, (struct sockaddr*)&addr, addr_len);
}
4、提供客户端服务接口
tcp_client_api.h
/*************************************************
func: 封装tcp客户端接口,实现tcp通信
author: zyh
date: 2024.4.16
**************************************************/
#ifndef _TCP_CLIENT_API_H_
#define _TCP_CLIENT_API_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <pthread.h>
// 定义socket客户端服务信息结构体
typedef struct {
/*/要连接的远程服务器信息*/
char *remote_ip;//要连接的远程服务器地址
int remote_port;//端口
/*本客户端配置*/
int bind_port;//绑定的本地端口,如果为0则不绑定
int reconnect_time_s;//中断重连时间(秒),如果为0则内部默认1秒
int socket_fd;//客户端句柄
int rcv_buf_size;//设置接收数据缓冲区大小,如果为0,内部默认1024
/*状态变量*/
//int rcv_flag;//1接收到数据,0未接收到
//int rcv_len;// 大于0为接收到的数据
//unsigned char *rcv_buf;//rcv_buf[rcv_buf_size]
void *arg;//留给外部的自定义传参
void (*on_connect)(int socket_fd, void *arg);//当客户端连接上服务器时的回调函数
void (*on_disconnect)(int socket_fd, void *arg);//当客户端与服务器断开连接时的回调函数
void (*on_message)(int socket_fd, void *buf, int len, void *arg);//当收到消息时的回调函数
//内部运行状态变量---------
bool connect_flag;//通信连接的状态,0未连接,1连接正常
pthread_t thread_id;
int run_flag;//本服务启动标志:1启动,0未启动或已退出
} tcp_client_t;
//启动一个tcp客户端(这是一个线程服务)
int start_tcp_client(tcp_client_t *client);
int close_tcp_client(tcp_client_t *client);
int restart_tcp_client(tcp_client_t *client);
#if 0 //test
tcp_client_t client1 = {
.remote_ip = (char *)"10.10.48.11",
.remote_port = 6666,
.bind_port = 0,
.rcv_buf_size = 0,
.on_connect = NULL,
.on_disconnect = NULL,
.on_message = NULL
};
#endif
#ifdef __cplusplus
}
#endif
#endif
tcp_client_api.c
/*************************************************
func: 封装tcp客户端接口,实现tcp通信
author: zyh
date: 2024.4
**************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>//提供IP地址转换函数
#include <sys/types.h>//数据类型定义文件
#include <sys/socket.h>//提供socket函数及数结构
//#include <netinet/in.h>//定义数据结构体sockaddr_in
//#include <netinet/ip.h>
#include <pthread.h>
#include "socket_api.h"
#include "tcp_client_api.h"
//客户端初始化
int tcp_client_init(int bind_port)
{
int socket_fd = 0;
socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (0 > socket_fd) {
perror("creat socket");
return -1;
}
if (0 > set_socket_reuseAddr(socket_fd)) {
printf("set_socket_reuseAddr failed\n");
close(socket_fd);
return -1;
}
if (0 < bind_port) {//客户端是否绑定本地端口
if (0 > socket_bind(socket_fd, "0.0.0.0", bind_port)) {
printf("socket_bind failed\n");
close(socket_fd);
return -1;
}
}
return socket_fd;
}
static void *tcp_client_loop(void *ptr)
{
int ret = 0;
tcp_client_t *client = (tcp_client_t *)ptr;
client->socket_fd = -1;
printf("start tcp client: -------------\n");
if (0 < client->bind_port) {
printf("bind_local_port:%d\n", client->bind_port);
}
printf("remote_ip:%s\n", client->remote_ip);
printf("remote_port:%d\n\n", client->remote_port);
int rcv_buf_size = (0 < client->rcv_buf_size) ? client->rcv_buf_size : 1024;
//int rcv_flag = 0;
int rcv_len = 0;
unsigned char *rcv_buf = (unsigned char *)calloc(1, client->rcv_buf_size);
if (!rcv_buf) {
printf("calloc rcv_buf failed\n");
goto done;
}
client->connect_flag = 0;
while (client->run_flag) {
if (0 == client->connect_flag) {//未连接
if (0 < client->socket_fd) {
close(client->socket_fd);
client->socket_fd = -1;
}
client->socket_fd = tcp_client_init(client->bind_port);
if (0 > client->socket_fd) {
sleep(1);
printf("tcp_client_init failed\n");
continue;
}
//请求连接
printf("tcp client connect remote server (ip:%s port:%d)\n", client->remote_ip, client->remote_port);
if (0 > socket_connect(client->socket_fd, client->remote_ip, client->remote_port)) {
printf("tcp client connect failed\n");
if (0 < client->reconnect_time_s) {
sleep(client->reconnect_time_s);
} else {
sleep(1);
}
continue;
}
//连接成功后再设置socket为非堵塞
if (0 > set_fd_nonblock(client->socket_fd)) {
printf("set_fd_nonblock failed\n");
continue;
}
client->connect_flag = 1;//连接成功
printf("tcp client connect server succeeded\n");
if (client->on_connect) {
client->on_connect(client->socket_fd, client->arg);
}
} else {//连接成功,就进入内部监听消息
ret = socket_rcv_nonblock(client->socket_fd, rcv_buf, rcv_buf_size - 1, 100);//超时100ms
if (0 < ret) {// 处理接收到的数据
//rcv_flag = 1;
rcv_len = ret;
rcv_buf[ret] = '\0';
if (client->on_message) {
client->on_message(client->socket_fd, rcv_buf, rcv_len, client->arg);
}
} else if (0 == ret) {// 已经关闭了连接
printf("tcp client disconnect\n");
client->connect_flag = 0;
if (client->on_disconnect) {
client->on_disconnect(client->socket_fd, client->arg);
}
} else if (-1 == ret) {
printf("socket_rcv_nonblock failed\n");
}
}
}
done:
//关闭释放资源
client->connect_flag = 0;
if (0 < client->socket_fd) {
close(client->socket_fd);
client->socket_fd = -1;
}
if (rcv_buf) {
free(rcv_buf);
rcv_buf = NULL;
}
printf("exit tcp client\n");
return NULL;
}
//启动一个tcp客户端(这是一个线程服务)
int start_tcp_client(tcp_client_t *client)
{
int ret = 0;
ret = pthread_create(&client->thread_id, NULL, tcp_client_loop, client);
if (ret < 0) {
printf("pthread_create tcp_client_loop is failed\n");
return -1;
}
client->run_flag = 1;
return 0;
}
int close_tcp_client(tcp_client_t *client)
{
if (client->run_flag) {
client->run_flag = 0;
pthread_join(client->thread_id, NULL);
}
return 0;
}
int restart_tcp_client(tcp_client_t *client)
{
close_tcp_client(client);
return start_tcp_client(client);
}
5、提供服务端服务接口(链表式存储客户端信息)
tcp服务端服务接口:(链表式存储客户端信息)
如有需要请下载源码
三、编译运行
Makefile编译:
make clean
make
运行tcp客户端:./tcp_test 1
运行tcp服务端:./tcp_test 2