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

目录

一、服务运行效果

二、代码接口

1、源码下载地址:

2、主函数 tcp_test.c

3、提供基础接口

socket_api.h 

socket_api.c

4、提供客户端服务接口

tcp_client_api.h

tcp_client_api.c

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

三、编译运行


前言

 可用来实现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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

勤劳的搬运工zyh

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

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

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

打赏作者

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

抵扣说明:

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

余额充值