C语言 网络编程(十)TCP通信创建流程---客户端

简介: 在TCP通信中,客户端需通过一系列步骤与服务器建立连接并进行数据传输。首先使用 `socket()` 函数创建一个流式套接字,然后通过 `connect()` 函数连接服务器。连接成功后,可以使用 `send()` 和 `recv()` 函数进行数据发送和接收。最后展示了一个完整的客户端示例代码,实现了与服务器的通信过程。

TCP通信创建流程

1. 客户端创建TCP连接

在整个流程中, 主要涉及以下⼏个接⼝
            socket() : 创建套接字, 使⽤的套接字类型为流式套接字
            connect() : 连接服务器
            send() : 数据发送
            recv() : 数据接收

创建套接字

首先,我们需要创建套接字,套接字是通信的基础。我们可以通过 socket() 函数来创建套接字。

int socket(int domain, int type, int protocol);
参数:
    @domain
            地址族
            AF_UNIX, AF_LOCAL  本地通信,数据不仅过网卡
            AF_INET         IPV4 ineter⽹通信  
            AF_INET6        IPV6 ineter⽹通信
            AF_PACKET       网卡上的数据包通信
            ....


    @ type
            使⽤协议类型
                SOCK_STREAM 流式套接字(TCP)
                SOCK_DGRAM 报⽂套接字(UDP)
                SOCK_RAW原始套接字: (IP,ICMP)
                ......

    @protocol
            协议编号
            0 : 让系统⾃动识别
            IPPROTO_TCP : TCP协议
            IPPROTO_UDP : UDP协议


返回值:
        成功返回得到的⽂件描述符。当前可使用的最小描述符
        失败返回 -1

连接服务器

创建套接字之后,我们需要连接服务器。连接服务器需要调用 connect() 函数。

发起对套接字的连接 (基于⾯向连接的协议)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
    @sockfd
            套接字描述符

    @addr   连接的套接字的地址结构对象的地址 (⼀般为服务器)
            服务器地址
                struct sockaddr {
   
                    unsigned short sa_family; // 地址族 对应socket()中的domain
                    char sa_data[14]; // 地址数据 ip地址端口信息
                };

                struct sockaddr_in {
    
                    short int sin_family; // 地址族 AF_INET
                    unsigned short int sin_port; // 端口号
                    struct in_addr sin_addr;// IP地址
                    unsigned char sin_zero[8]; // 填充字节 为了对齐sockaddr
                };

                struct in_addr {
   
                    uint32_t       s_addr; // IP地址
                };

    @addrlen
            地址长度


返回值:
        成功返回0
        失败返回-1 并设置 errno

数据发送

连接服务器之后,我们就可以向服务器发送数据。发送数据需要调用 send() 函数。

基于套接字(建⽴连接)发送数据
int send(int sockfd, const void *buf, size_t len, int flags);
参数:
    @sockfd
            套接字描述符

    @buf    发送的数据

    @len    发送数据的长度

    @flags  发送标志
函数返回值:
    成功返回发送的字节数
    失败返回-1,并设置errno

数据接收

服务器向客户端发送数据之后,客户端就可以接收数据。接收数据需要调用 recv() 函数。

接收套接字的数据 (基于⾯向连接的协议)
int recv(int sockfd, void *buf, size_t len, int flags);
参数:
    @sockfd
            套接字描述符

    @buf    接收的数据

    @len    接收数据的长度

    @flags  接收标志
函数返回值:
    成功返回接收的字节数
    失败返回-1,并设置errno

完整流程

//todo tcp客户端,循环发送数据,接收回传数据
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#define N 128
//初始化socket
int  init_socket(char *ip,char *port){
   

    int init_socket_fd= socket(AF_INET,SOCK_STREAM,0);
    if (init_socket_fd==-1){
   
        printf("init_socket err");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    socklen_t len=sizeof(server_addr);
    bzero(&server_addr,len);
    server_addr.sin_family=AF_INET;
    inet_aton(ip,&server_addr.sin_addr);
    server_addr.sin_port= htons(atoi(port));

    //连接
    int ret= connect(init_socket_fd,(struct sockaddr*)&server_addr,len);
    if (ret==-1){
   
        printf("connect error,连接失败\n");
        exit(EXIT_FAILURE);
    }


return init_socket_fd;
}

//客户端接收数据
int Client_Receive_data(int socket_fd){
   
    char receive_msg[N];
    bzero(receive_msg,N);
    int recv_len= recv(socket_fd, receive_msg,sizeof(receive_msg),0);
    if (recv_len == -1) {
   
        printf("recv error\n");
        exit(EXIT_FAILURE);
    }

    receive_msg[recv_len] = '\0';
    printf("收到客户端数据:[%s]\n",receive_msg);
}


//客户端发送数据
int  Client_Send_data(int socket_fd){
   
    char msg[N];

    while (1){
   
    bzero(&msg, sizeof (msg));
    printf("请输入:\n");
    fgets(msg, sizeof(msg),stdin);
    msg[strlen(msg)-1]='\0';

    printf("发送数据%s\n",msg);

    int  Send_data_len= send(socket_fd,&msg, strlen(msg),0);
        if (Send_data_len==-1){
   
            printf("发送失败 send err\n");
            exit(EXIT_FAILURE);
        }
    printf("发送了%d个字节\n",Send_data_len);

    if (strncmp(msg, "exit", 4) == 0) {
   
        printf("退出通信\n");
        close(socket_fd);
        break;
    }
    break;
    //接收
    Client_Receive_data(socket_fd);

    }

    return 0;
}

int main(){
   
    //初始化连接
    int socket_fd = init_socket("172.17.128.1","8888");
    //发送数据
    Client_Send_data(socket_fd);


    return 0;
}
相关文章
|
4天前
|
Web App开发 缓存 网络协议
不为人知的网络编程(十八):UDP比TCP高效?还真不一定!
熟悉网络编程的(尤其搞实时音视频聊天技术的)同学们都有个约定俗成的主观论调,一提起UDP和TCP,马上想到的是UDP没有TCP可靠,但UDP肯定比TCP高效。说到UDP比TCP高效,理由是什么呢?事实真是这样吗?跟着本文咱们一探究竟!
28 10
|
22天前
|
网络协议 Java API
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
51 2
|
22天前
|
存储 网络协议 Java
【网络】UDP和TCP之间的差别和回显服务器
【网络】UDP和TCP之间的差别和回显服务器
51 1
|
23天前
|
XML 网络协议 算法
【TCP】网络原理
【TCP】网络原理
26 0
|
17天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
30 3
|
7天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
27 10
|
1天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
6天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
32 7
|
6天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
21 4
|
11天前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。

热门文章

最新文章