当前位置: 首页 > news >正文

Tcp——客户端服务器

Tcp——客户端服务器

目录

一、基本概念

二、代码

2.1 ser服务器

2.2 cil客户端


一、基本概念

TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在TCP/IP模型中,TCP位于IP层之上,应用层之下,负责在网络中的两个主机之间建立连接,进行数据传输,并确保数据的完整性和顺序性。

TCP客户端和服务器通过建立连接、进行数据交换和关闭连接的过程进行通信。TCP协议确保了数据的可靠传输,包括数据完整性、顺序性、流量控制和拥塞控制。通过使用套接字编程,可以实现TCP客户端和服务器之间的通信。

这张图展示了一个典型的网络通信过程中,服务端和客户端的常用套接字(socket)操作步骤。套接字是网络编程中用于不同主机间通信的端点。

### 服务端

1. **socket()**:创建一个新的套接字。
2. **bind(ip+port)**:将套接字绑定到指定的IP地址和端口号上,以便监听来自该地址和端口的连接请求。
3. **listen()**:使套接字进入监听状态,等待客户端的连接请求。
4. **accept()**:接受客户端的连接请求,建立连接
5. **recv()**:接收来自客户端的数据。
6. **send()**:向客户端发送数据。
7. **close()**:关闭套接字,结束通信。

### 客户端

1. **socket()**:创建一个新的套接字。
2. **connect(ip+port)**:向服务端的指定IP地址和端口号发起连接请求
3. **send()**:向服务端发送数据。
4. **recv()**:接收来自服务端的数据。
5. **close()**:关闭套接字,结束通信。

在实际的网络通信中,服务端和客户端通过这些步骤进行数据的发送和接收,实现网络通信。服务端通常先启动,等待客户端的连接请求,而客户端则主动发起连接请求,连接成功后进行数据交换。

二、代码

2.1 ser服务器

TCP服务器程序,用于接受客户端的连接请求,并与客户端进行数据交换 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);//创建套接子,失败 -1
    if( -1 == sockfd )
    {
        exit(1);
    }

    struct sockaddr_in saddr,caddr;//套接字的地址(也就是ip+port)
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;//ipv4地址族
    saddr.sin_port = htons(6000);//网络字节序列(大端)
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//点分十进制字符串表示的ip 转换为无符号整形表示的ip地址
    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if( -1 == res)
    {
        printf("bind err\n");
        exit(1);
    }

    res = listen(sockfd,5);//创建监听队列,客户端连接服务器时,要存放客户端的连接信息。 5是监听队列的大小
    if( -1 == res )
    {
        exit(1);
    }

    while( 1 )
    {
        int len = sizeof(caddr);
        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);//接受客户端连接,caddr,用来存放客户端的ip+port
        if( c < 0 )
        {
            continue;
        }

        printf("accept c=%d\n",c);

        char buff[128] = {0};
        int n = recv(c,buff,127,0);//接受客户端发送的数据
        printf("n=%d,recv:%s\n",n,buff);

        send(c,"ok",2,0);//向客户端发送数据

        close(c);
    }

}

1. 包含头文件
- `sys/socket.h` 和 `netinet/in.h` 是网络编程中常用的头文件,包含了套接字相关的函数和结构体定义。
- `arpa/inet.h` 包含了处理网络地址的函数,如 `inet_addr()` 和 `htons()`。

 2. 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
    exit(1);
}

- `socket()` 函数用于创建一个新的套接字。
- 参数 `AF_INET` 指定了地址族为IPv4,`SOCK_STREAM` 指定了套接字类型为面向连接的流套接字(TCP)。
- 如果创建失败,返回 `-1`,程序将退出。

3. 设置服务器地址

定义地址结构体

struct sockaddr_in saddr, caddr;

  • struct sockaddr_in 是一个结构体,用于存储IPv4地址和端口号。

  • saddr 用于存储服务器的地址信息 包含了ip地址和端口号。

  • caddr 用于存储客户端的地址信息。

初始化saddr结构体

memset(&saddr, 0, sizeof(saddr));

  • memset() 函数用于将 saddr 结构体的所有字节初始化为0。

  • 这样做可以确保结构体中的所有字段都被清零,避免使用未初始化的值。

设置地址族

saddr.sin_family = AF_INET;

  • sin_family 字段用于指定地址族,AF_INET 表示IPv4地址族。

设置端口号

saddr.sin_port = htons(6000);

  • sin_port 字段用于指定端口号。

  • htons(6000) 函数用于将主机字节序的端口号转换为网络字节序(大端序)。这是必要的,因为网络协议要求端口号使用网络字节序。

  • 6000 是服务器监听的端口号。

设置ip地址
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

  • sin_addr 字段用于指定IP地址。

  • inet_addr("127.0.0.1") 函数用于将点分十进制的IP地址转换为网络字节序的二进制格式。

  • "127.0.0.1" 是本地回环地址,表示服务器将监听本地回环接口。

 4. 绑定套接字
int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
if (res == -1) {
    printf("bind err\n");
    exit(1);
}

这段代码的目的是确保服务器的套接字能够被正确地绑定到指定的IP地址和端口号上,这是服务器能够监听并接受客户端连接请求的前提。

  • bind() 函数是用于将一个套接字绑定到一个特定的网络地址和端口上的函数。

  • 第一个参数 sockfd 是之前通过 socket() 函数创建的套接字的文件描述符。这个文件描述符是一个整数,用于标识这个套接字。

  • 第二个参数 (struct sockaddr*)&saddr 是一个指向 sockaddr 结构体的指针,该结构体包含了要绑定的地址信息。这里 saddr 是一个 sockaddr_in 类型的结构体,专门用于IPv4地址。通过将 saddr 的地址转换为 struct sockaddr* 类型,可以将其传递给 bind() 函数。

  • 第三个参数 sizeof(saddr)saddr 结构体的大小。这个大小告诉 bind() 函数需要多少字节来存储地址信息。sizeof(saddr) 确保了无论 saddr 结构体中包含多少字段,bind() 函数都能正确地处理它。

 5. 监听连接请求
res = listen(sockfd, 5);
if (-1 == res) {
    exit(1);
}

  • listen() 函数是用于启动套接字的监听过程的。这个函数告诉操作系统,服务器准备好接受连接请求了。

  • sockfd 是之前创建的套接字的文件描述符,它是服务器用来监听连接请求的“通道”。

  • 5 是一个参数,指定了服务器应该维护的最大挂起连接数。挂起连接是指那些已经到达但还没有被服务器接受的连接请求。如果设置为5,就意味着服务器可以同时处理5个等待接受的连接请求。如果超过5个,新的连接请求可能会被拒绝。

6. 接受客户端连接
while (1) {
    int len = sizeof(caddr);
    int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
    if (c < 0) {
        continue;
    }

    printf("accept c=%d\n", c);
}

  • sizeof(caddr) 计算 caddr 结构体占用的字节数,这样程序就知道在 accept() 函数中需要多少空间来存储客户端的地址信息。

  • accept() 函数用于接受客户端的连接请求。

  • 第一个参数 sockfd 是服务器监听的套接字文件描述符。

  • 第二个参数是指向 caddr 结构体的指针,caddr 用来存储连接请求中包含的客户端地址信息。

  • 第三个参数 &len 是一个指向 len 变量的指针,len 表示 caddr 结构体的大小,accept() 函数会更新这个值,以反映实际接收到的地址信息的大小。

7. 数据交换
char buff[128] = {0};
int n = recv(c, buff, 127, 0);
printf("n=%d,recv:%s\n", n, buff);

send(c, "ok", 2, 0);
close(c);

- 定义一个缓冲区 `buff`,用于存储从客户端接收的数据。
- `recv()` 函数用于从客户端接收数据,并将接收到的数据存储在 `buff` 中。
- 打印接收到的数据的长度和内容。
- `send()` 函数用于向客户端发送数据,这里发送字符串 "ok"。参数 c 是与客户端通信的套接字文件描述符。
- `close()` 函数用于关闭与客户端的连接。

总结

这段代码实现了一个简单的TCP服务器,它创建一个套接字,绑定到本地地址和端口,监听客户端的连接请求,并与客户端进行数据交换。如果出现错误,程序将打印错误信息并退出。

2.2 cil客户端

客户端程序,用于连接到服务器并进行数据交换

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);//创建套接子 ipv4,tcp流式
    if( -1 == sockfd )
    {
        exit(1);
    }

    struct sockaddr_in saddr;//定义套接子地址,(服务器 ip + port)
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//连接服务器,
    if( -1 == res )
    {
        printf("connect err\n");
        exit(1);
    }

    printf("input\n");
    char buff[128] = {0};
    fgets(buff,128,stdin);
    send(sockfd,buff,strlen(buff)-1,0);//给服务器发送数据
    memset(buff,0,sizeof(buff));
    recv(sockfd,buff,127,0);//接收服务器返回的数据
    printf("buff=%s\n",buff);

    close(sockfd);

    exit(0);

}
int res = connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
if (-1 == res) {
    printf("connect err\n");
    exit(1);
}
  • connect() 函数用于连接到服务器。

  • 参数 sockfd 是之前创建的套接字的文件描述符。

  • 参数 (struct sockaddr*)&saddr 是一个指向 saddr 结构体的指针,包含了服务器的地址信息。

  • 参数 sizeof(saddr)saddr 结构体的大小。

  • 如果连接失败,返回 -1,程序将打印错误信息并退出。

  • printf("input\n");
    char buff[128] = {0};
    fgets(buff, 128, stdin);
    send(sockfd, buff, strlen(buff) - 1, 0);
    memset(buff, 0, sizeof(buff));
    recv(sockfd, buff, 127, 0);
    printf("buff=%s\n", buff);
  • 提示用户输入数据。

  • 使用 fgets() 函数从标准输入读取一行数据,存储在 buff 中。

  • 使用 send() 函数将数据发送到服务器。注意,发送的数据长度是 strlen(buff) - 1,因为 fgets() 会将换行符也读取到缓冲区中。

  • 使用 memset() 函数清空 buff,为接收数据做准备。

  • 使用 recv() 函数从服务器接收数据,最多接收127字节。

  • 打印接收到的数据。

  • 这段代码实现了一个简单的客户端程序,它连接到本地服务器(127.0.0.1:6000),发送用户输入的数据,并接收服务器返回的数据。如果连接或数据传输失败,程序将打印错误信息并退出。

相关文章:

  • 【Guava】集合工具类-ImmutableListsMapsSets
  • TypeScript类型体操
  • 异步读取HTTP响应体的Rust实现
  • Linux内核内存管理 ARM32内核内存布局的详细解析和案例分析
  • 面试问题总结:qt工程师/c++工程师
  • 基于 Ollama DeepSeek、Dify RAG 和 Fay 框架的高考咨询 AI 交互系统项目方案
  • 4.1刷题(链表)
  • 初学STM32系统时钟设置
  • Vue 组件 - Slot 内容分发
  • Windows搭建AI大模型应用开发环境以及踩过的坑
  • 软件测试(2):selenium 4.0 特点以及新特性
  • 数据库权限获取
  • MySQL基本查询
  • LeetCode[15]三数之和
  • OpenAI重磅回归开源!首发推理模型不限商用,直面DeepSeek挑战
  • 操作系统高频(六)linux内核
  • 交叉熵损失
  • leetcode25.k个一组翻转链表
  • (二十六)Dart 中泛型的使用与优势
  • WEB安全--SQL注入--无列名注入
  • 横跨万里穿越百年,《受到召唤·敦煌》中张艺兴一人分饰两角
  • 俄官员说将适时宣布与乌克兰谈判代表
  • 字母哥动了离开的心思,他和雄鹿队的缘分早就到了头
  • 巴基斯坦称未违反停火协议
  • 解放军仪仗分队参加白俄罗斯纪念苏联伟大卫国战争胜利80周年阅兵活动
  • 深圳市政协原副主席王幼鹏被“双开”