首页 > 其他分享 >C语言通过socket实现TCP客户端

C语言通过socket实现TCP客户端

时间:2024-06-06 20:12:20浏览次数:23  
标签:socketfd socket buffer timebuf tcp C语言 TCP 服务器

socket概念

​ 从wiki上了解,socket这个词追溯到 1971 年 RFC 147 的发布。

​ 目前我的理解:常用于指操作系统提供的 API,该 API 允许使用 TCP、UDP 进行连接,但不仅限于 TCP、UDP 协议。

实现目的

利用系统提供函数接口,通过C语言实现对TCP 服务器(IP地址)的连接,以及收发数据。

实现过程

1、socket(2) 创建套接字

2、connect(2) 连接服务器。服务器已打开,否则会直接返回错误。

3、send(2) 向服务器发送数据。连接成功后,即可与服务器通信。

4、recv(2) 接收服务器发送过来的数据。

5、close(2) 关闭套接字。

实现代码

/****************************************************************************
 *
 * file name: mytcp_client.c
 * author   : [email protected]
 * date     : 2024-06-05
 * function : TCP协议的客户端操作。
 * note     :
 *              测试编译指令: gcc ./src/mytcp_client.c ./src/mytime.c -o ./bin/mytcp_client -I ./include
 *              通过命令行输入服务器ip和端口,示例:./bin/mytcp_client IP PORT
 *
 * CopyRight (c)   2024   [email protected]   Right Reseverd
 *
 ****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdbool.h>
#include <pthread.h>
#include <netinet/in.h>
#include "mytime.h" //时间头文件

#define DEBUG // 开启调试模式

/******************************* 全局变量 START *******************************/
char timebuf[128]; // 时间输出缓冲区
/******************************* 全局变量 END *******************************/

/****************************************************************************
 *
 * function name     : tcp_v4_hton
 * function          : 将ipv4服务器的信息从本地字节序转换为网络字节序,并存储在destinfo指针下。
 * parameter         :
 *                      @destinfo: 存储转换后的信息指针。
 *                      @address: 需要转换的点分十进制IPv4地址,例如 "192.168.5.1"
 *                      @port: 需要转换的端口,例如:60000
 *
 * return value      : None
 * note              : None
 *
 * author            : [email protected]
 * date              : 2024-06-06
 * version           : V1.0
 * revision history  : None
 *
 ****************************************************************************/
void tcp_v4_hton(struct sockaddr_in *destinfo, const char *address, const int port)
{

    destinfo->sin_family = AF_INET;                 // 协议,AF_INET代表IPV4协议
    destinfo->sin_port = htons(port);               // 服务器端口,必须将目标端口转为网络字节序(大端)
    destinfo->sin_addr.s_addr = inet_addr(address); // 服务器ip,必须将目标ip转为网络字节序(大端)
}

/****************************************************************************
 *
 * function name     : tcp_v4_connect
 * function          : 连接IPv4 TCP服务器
 * parameter         :
 *                      @socketfd: socket指针。
 *                      @destinfo: 储存 IPv4 TCP服务器信息的指针
 *
 * return value      : 成功返回true,失败返回false
 * note              : None
 *
 * author            : [email protected]
 * date              : 2024-06-06
 * version           : V1.0
 * revision history  : None
 *
 ****************************************************************************/
bool tcp_v4_connect(int *socketfd, struct sockaddr_in *destinfo)
{
    // 创建ipv4 TCP 通信端点
    *socketfd = socket(destinfo->sin_family, SOCK_STREAM, 0);
    if (-1 == *socketfd)
    {
        fprintf(stderr,
                "[%s] [%s] 创建ipv4 TCP 通信端点,Error code: %d, Error message: %s\n",
                __FILE__,
                __func__,
                errno,
                strerror(errno));
        return false;
    }

    // 请求连接 tcp服务器
    if (-1 == connect(*socketfd, (const struct sockaddr *)destinfo, sizeof(struct sockaddr_in)))
    {
        fprintf(stderr,
                "[%s] [%s] 连接ipv4 TCP 通信端点失败,Error code: %d, Error message: %s\n",
                __FILE__,
                __func__,
                errno,
                strerror(errno));
        return false;
    }

// 成功连接
#ifdef DEBUG
    time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");
    printf("[DEBUG][%s]成功连接服务器 \n", timebuf);
#endif
    return true;
}

/****************************************************************************
 *
 * function name     : tcp_v4_send
 * function          : 向IPv4 TCP服务器发送一条数据
 * parameter         :
 *                      @socketfd: 已连接服务器的socket句柄
 *                      @buf: 存储待发送数据的缓冲区指针。
 *                      @bufsize: 数据的大小,单位字节。
 *
 * return value      : 成功返回true,失败返回false
 * note              :
 *                      必须先使用tcp_v4_connect()连接服务器后再使用。
 *
 * author            : [email protected]
 * date              : 2024-06-06
 * version           : V1.0
 * revision history  : None
 *
 ****************************************************************************/
bool tcp_v4_send(const int socketfd, const char *buf, const int bufsize)
{
    if (bufsize != send(socketfd, buf, bufsize, 0))
    {
#ifdef DEBUG
        time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");
        printf("[DEBUG][%s] 发送 [%s] 失败!!!\n", timebuf, buf);
#endif
        return false;
    }

#ifdef DEBUG
    time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");
    printf("[DEBUG][%s] 成功发送 [%s] \n", timebuf, buf);
#endif
    return true;
}

/****************************************************************************
 *
 * function name     : tcp_client_recv
 * function          : 线程任务,连接TCP IPv4服务器后,接收服务器发来的信息并输出。
 * parameter         :
 *                      @arg: 已连接服务器的socket句柄指针
 *
 * return value      : None
 * note              :
 *                      必须先使用tcp_v4_connect()连接服务器后再使用。
 *
 * author            : [email protected]
 * date              : 2024-06-06
 * version           : V1.0
 * revision history  : None
 *
 ****************************************************************************/
void *tcp_client_recv(void *arg)
{
    int socketfd = *((int *)arg); // 转换通过参数传入socket套接字句柄
    char buffer[512] = {0};       // 接收数据缓冲区

    // 循环阻塞等待服务器发送的数据
    while (1)
    {
        if (0 == (recv(socketfd, buffer, sizeof(buffer), 0)))
        {
            // 服务器终止连接,结束程序
            time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");
            printf("[%s] 服务器已经终止连接\n", timebuf);
            close(socketfd);
            exit(EXIT_SUCCESS);
        }
        time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");
        printf("[%s]收到服务器发来的信息:[%s]\n", timebuf, buffer);
        bzero(buffer, sizeof(buffer)); // 清空缓存
    }
}

int main(int argc, char const *argv[])
{
    // 通过终端传入服务器的信息
    if (3 != argc)
    {
        printf("参数无效,请输入 [./xxx IP PORT] 执行\n");
        return -1;
    }

    int socketfd;                // 创建套接字
    char buffer[512] = {0};      // 发送数据缓冲区
    struct sockaddr_in destinfo; // 定义IPv4 地址和端口的结构体 变量保存服务端信息

    tcp_v4_hton(&destinfo, argv[1], atoi(argv[2])); // 转换为网络字节序

    // 连接服务器
    if (!(tcp_v4_connect(&socketfd, &destinfo)))
        return -1;

    // 创建线程接收服务器发送的信息
    pthread_t tcp_recv_task;
    if (0 != (pthread_create(&tcp_recv_task, NULL, tcp_client_recv, &socketfd)))
    {
        printf("********** 创建接收服务器发送的信息线程失败! **********\n");
    }
#ifdef DEBUG
    else
    {
        time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");
        printf("[DEBUG][%s] 成功创建接收服务器发送的信息线程 \n", timebuf);
    }
#endif

    // 需要发送的信息
    while (1)
    {
        printf("请输入发送的数据:\n");
        fgets(buffer, sizeof(buffer), stdin); // 标准输入获取数据
        buffer[strcspn(buffer, "\n")] = '\0'; // 替换换行符
        if (0 == strlen(buffer))
        {
            printf("********** 请输入有效信息 **********\n");
        }
        else if (!(tcp_v4_send(socketfd, buffer, strlen(buffer))))
        {
            time_format(timebuf, sizeof(timebuf), "%Y年%m月%d日 %H:%M:%S");
            printf("[%s] 服务器已经终止连接\n", timebuf);
            close(socketfd);
            break;
        }
        else
        {
            bzero(buffer, sizeof(buffer)); // 清空缓存
        }
    }

    return 0;
}

测试结果

C语言TCP客户端测试

参考信息

衍生问题

​ Berkeley 套接字是用于创建和使用套接字的行业标准应用程序编程接口 (API)。它最初被用作 Unix 操作系统的 API,后来被 TCP/IP 采用。

标签:socketfd,socket,buffer,timebuf,tcp,C语言,TCP,服务器
From: https://www.cnblogs.com/crazy3min/p/18235928

相关文章

  • C语言关于预处理的基础介绍
    一:预定义符号    在c语言中有一些预定义的符号,这些符号已经定义好了,用户不需要再次定义只需要使用即可。 __FILE__:显示当前进行编译的源文件__LINE__:显示当前代码行号__DATE__:显示当前文件被编译的日期__TMIE__:显示当前文件被编译的时间__STDC__:查看当前编译......
  • C语言:详解gcc驱动程序完成编译、汇编、链接的过程
    相关阅读C语言https://blog.csdn.net/weixin_45791458/category_12423166.html?spm=1001.2014.3001.5482    gcc是一个命令,严格意义上说,它只是一个驱动程序,而不是一个编译器。gcc负责调用GNU工具链中的预处理器、编译器、汇编器、链接器等工具,通过传递不同的选项给g......
  • 【C语言】指针(4)
    一、回顾 在这之前,我们学习了很多关于指针的内容,我们先在这里简单的回顾一下。1、一级指针int*p;  --整形指针-指向整形的指针 char*p;...void*p;......2、二级指针int**p; char**p;...3、数组指针--指向数组的指针int(*p)[];intmain(){......
  • TCP的校验和与编号
    TCP的校验和与编号TCP校验和特点:端到端校验:校验和覆盖TCP首部和TCP数据,确保从发送端到接收端的数据完整性。检错能力:TCP校验和具有较强的检错能力,可以检测出在传输过程中发生的位错误。伪首部:在计算TCP校验和时,会加上一个12字节的伪首部,包含源IP地址、目的IP地址、保留字节(......
  • C语言杂谈:函数栈帧,函数调用时到底发生了什么
            我们都知道在调用函数时,要为函数在栈上开辟空间,函数后续内容都会在栈帧空间中保存,如非静态局部变量,返回值等。这段空间就叫栈帧。    当函数调用,就会开辟栈帧空间,函数返回时,栈帧空间就会被释放。这里的释放并非清空,而是让其无效化,可以后续的使用。1,......
  • 通过端口复用直接进行正向tcp代理--win会被识别为病毒
    学习项目,win会被识别为病毒,关闭病毒和威胁防护,参考https://blog.csdn.net/u_say2what/article/details/134669122main.gopackagemainimport( "context" "fmt" _"golang.org/x/sys/unix" "golang.org/x/sys/windows" "io" "......
  • 001__C语言程序入门
    一、第一个程序:helloworld配置部署好vsCode之后,就可以直接在上面写代码了,新建一个新的C程序文件,向屏幕输出一串字符“HelloWorld!”下面,从整体上来分析一下这个最简单的C语言程序,将这个最简程序的各个部分剖析清楚,明白我们写下的每一个字符的具体含义。二、C语言的基本结......
  • JAVAEE值网络编程(2)_TCP流套接字及通信模型、TCP网络编程及代码实例
    前言 在上一节内容中,我们介绍了什么是套接字,以及使用UDP数据报套接字网络编程,最后我们还介绍了Java数据报套接字通信模型以及相关代码实例。在这一节我们将会介绍TCP流套接字编程。一、流套接字及通信模型1.1TCP套接字 TCP,即TransmissionControlProtocol(传输控制......
  • websocket
     2024-06-0611:26:53.333DEBUG[http-nio-8080-exec-6]org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator62-[TxId:,SpanId:]TransporterrorinStandardWebSocketSession[id=e4702cd6-4150-c22c-b049-8da635daa236,uri=wss://zkh-g......
  • C语言—字符函数和字符串函数
    1.字符分类函数C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。这些函数的使用都需要包含一个头文件ctype.h。例:将一句话中的小写字母改成大写字母。2.字符转换函数头文件:ctype.hC语言提供了2个字符转换函数:int tolower(intc);//将......