网络编程——实现服务端与客户端TCP的消息发送与接收

网络编程——实现服务端与客户端TCP的消息发送与接收

本文主要涉及网络编程的具体实现过程,实现客户端与服务端的TCP的信息传输,注意还只是单向的客户端发送,服务端接收。

一、 服务端

1.1 服务端通信详细流程

让我更详细地描述服务端通信流程

  1. 初始化

    • 使用socket函数创建一个服务器套接字。

      sockfd = socket(AF_INET, SOCK_STREAM, 0);
      
    • 使用bind函数将服务器套接字绑定到指定的IP地址和端口。

      bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
      
    • 使用listen函数开始监听连接请求。

      listen(sockfd, 5);  // 5 是监听队列的最大长度
      
  2. 等待客户端连接

    • 使用accept函数等待客户端连接,并返回一个新的套接字描述符。

      int accceptfd = accept(sockfd, (struct sockaddr *)&sa, &addrlen);
      
  3. 为客户端创建处理线程

    • 为每个新连接创建一个线程,使用pthread_create

      pthread_create(&thread, NULL, ClinetFunction, (void *)&accceptfd);
      
  4. 与客户端通信

    • ClinetFunction中使用recv函数接收客户端发送的数据。

      recv(accceptfd, buf, sizeof(buf), 0);
      
    • 处理或响应接收到的数据。

    • 使用send函数将处理后的数据发送回客户端。

      send(accceptfd, sendbuf, strlen(sendbuf) + 1, 0);
      
  5. 关闭连接

    • 使用close函数关闭与客户端的连接。

      close(accceptfd);
      

1.2 关键函数及其详细描述:

  • socket: 创建一个新的套接字。返回一个文件描述符。

    • 参数:协议族(AF_INET表示IPv4)、套接字类型(SOCK_STREAM表示TCP)、协议编号(通常为0)。
  • bind: 将套接字绑定到特定的IP地址和端口。

    • 参数:套接字描述符、指向地址结构的指针、地址结构的大小。
  • listen: 将套接字设为监听模式,等待客户端连接。

    • 参数:套接字描述符、监听队列的最大长度。
  • accept: 等待并接受客户端的连接请求,返回一个新的套接字描述符。

    • 参数:套接字描述符、指向客户端地址结构的指针、地址结构的大小。
  • pthread_create: 创建新的线程。

    • 参数:指向线程ID的指针、线程属性(通常为NULL)、线程函数、传递给线程函数的参数。
  • recv: 从已连接的套接字接收数据。

    • 参数:套接字描述符、缓冲区、缓冲区的大小、标志。
  • send: 发送数据到已连接的套接字。

    • 参数:套接字描述符、要发送的数据、数据的大小、标志。
  • close: 关闭套接字或文件描述符。

    • 参数:套接字或文件描述符。

通过这些详细的步骤和函数,服务端能够建立与客户端的连接,为每个客户端创建独立的处理线程,并进行有效的数据交换。

1.3 服务端完整代码

//服务器
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

//全局的套接字
int sockfd = -1;
//4 循环等待客户端的连接,如果没有连接则等待,如果有连接则返回一个连接套接字
//5 和客户端进行正常收发
//6 弄完了关闭套接字

//将套接字创建好  并且绑定 监听
//将ip地址和端口号传进来 端口号释放需要时间(轮询机制)
void TcpInit(const char * ipaddr,unsigned short port)
{
   
    //1 创建套接字 ---- 神马都是文件,因此你的网络通信也是一个文件
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sockfd)
    {
   
        perror("server socket error");
        exit(1);//没有必要运行了
    }
    //2 我们需要将服务器的IP地址绑定到套接字
    struct sockaddr_in sa;
    sa.sin_family = AF_INET;//协议族
    sa.sin_port = htons(port);//端口号  内存是小端的 我们要转大端
    sa.sin_addr.s_addr = inet_addr(ipaddr);//将我们点分式的ip地址转换为一个大端整数
    //printf("ipaddr = %x port = %x\n",inet_addr(ipaddr),htons(port));
    int r = bind(sockfd,(struct sockaddr *)&sa,sizeof(sa));
    if(-1 == r)
    {
   
        perror("server bind error");
        exit(2);//没有必要运行了
    }

    //3 监听连接 ---- 创建一个监听队列   建立5个10个可以了
    listen(sockfd,5);

}

//专门用于去服务一个客户的线程
void * ClinetFunction(void * arg)
{
   
    
    pthread_detach(pthread_self());//将其分离

    int * accceptfd = (int *)arg;
    printf(" * accceptfd = %d\n", * accceptfd);
    char buf[1024] = {
   0};
    
    //你发什么信息过来  我就在这个信息之前加上一节 然后回发给你
    while(1)
    {
   
        char sendbuf[1024] = {
   "SB250->"};
        int r = recv(*accceptfd,buf,1024,0);//阻塞等待数据过来
        if(-1 == r)
        {
   
            perror("recv error");
            break;
        }
        else if(0 == r)//客户端已经断了
        {
   
            printf("对方断开连接了\n");
            break;
        }
        else//接收到信息了
        {
   
            printf("recv buf = %s\n",buf);
            strcat(sendbuf,buf);
            send(*accceptfd,sendbuf,strlen(sendbuf) + 1,0);
        }
    }


    close(*accceptfd);
    free(accceptfd);

}
//等待客户端的连接 
void waitconnect(void)
{
   
    //我们要基于这个连接套接字去通信
    struct sockaddr_in sa;
    socklen_t addrlen = sizeof(sa);
    while(1)
    {
   
        printf("一直等待对方的连接.......\n");
        int * accceptfd = malloc(4);//避免释放 因此我们要动态内存分配才可以
        *accceptfd = accept(sockfd,(struct sockaddr *)&sa,&addrlen);
        printf("连接者为:%s %d\n",inet_ntoa(sa.sin_addr),ntohs(sa.sin_port));
        //开一个线程出去  让它去服务与我的连接
        pthread_t thread;
        if(pthread_create(&thread,NULL,ClinetFunction,(void *)accceptfd) != 0)
        {
   
            perror("create thread error");
            continue;
        }
    }
    
    close(sockfd);
}
//通过main函数的参数 我们将这个ip地址和端口给进去
//./a.out 192.168.31.251 8888
int main(int argc,char * argv[])
{
   
    if(argc < 3)
    {
   
        printf("参数都不齐\n");
        return -1;
    }
    TcpInit(argv[1],atoi(argv[2]));

    waitconnect();

    return 0;
}

二、 客户端

2.1 客户端通信详细流程

以下是客户端进行通信的详细流程及其关键函数:

  1. 初始化:

    • 创建套接字:

      sockfd = socket(AF_INET, SOCK_STREAM, 0);
      

      使用socket函数创建一个IPv4的TCP套接字。

    • 填充服务器地址信息:

      struct sockaddr_in sa;
      sa.sin_family = AF_INET;
      sa.sin_port = htons(port);
      sa.sin_addr.s_addr = inet_addr(ipaddr);
      

      这里设置了服务器的IP地址、端口号,并将其转换为网络字节序。

    • 连接到服务器:

      int r = connect(sockfd, (struct sockaddr *)&sa, sizeof(sa));
      

      使用connect函数与服务器建立连接。

  2. 通信循环:

    • 从用户那里获取要发送的消息:

      printf("输入你要发送的信息(输入quit退出):");
      fflush(stdout);
      scanf("%s", buf);
      

      这里等待用户输入消息。

    • 检查用户是否想退出:

      if(!strcmp(buf,"quit")) {
             
          break;
      }
      

      如果用户输入了“quit”,则退出循环。

    • 发送消息到服务器:

      send(sockfd, buf, strlen(buf) + 1, 0);
      

      使用send函数将用户输入的消息发送到服务器。

    • 接收来自服务器的响应:

      recv(sockfd, buf, 1024, 0);
      

      使用recv函数从服务器接收响应。

    • 打印服务器的响应:

      printf("服务器回我的信息是:%s\n", buf);
      

      将接收到的消息打印到控制台。

  3. 关闭连接:

    • 关闭套接字:

      close(sockfd);
      

      使用close函数关闭套接字,释放资源并结束程序。

这就是客户端进行通信的完整流程。它首先连接到服务器,然后进入一个循环,允许用户发送和接收消息,最后关闭连接并退出。

2.2 完整代码

/*
 * @Description: 客户端代码
 * @Version: 1.0
 * @Autor: Huining Li777
 * @Date: 2023-12-26 09:07:23
 * @LastEditors: 李慧宁-Huining Li777
 * @LastEditTime: 2023-12-26 12:46:58
 */

//客户端
#include <stdio.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
//全局的套接字
int sockfd = -1;

//将套接字创建好  并且绑定 监听
//将ip地址和端口号传进来 端口号释放需要时间(轮询机制)
void TcpInit(const char * ipaddr,unsigned short port)
{
   
    //1 创建套接字 ---- 神马都是文件,因此你的网络通信也是一个文件
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sockfd)
    {
   
        perror("server socket error");
        exit(1);//没有必要运行了
    }
    //2 我们需要将服务器的IP地址绑定到套接字
    struct sockaddr_in sa;
    sa.sin_family = AF_INET;//协议族
    sa.sin_port = htons(port);//端口号  内存是小端的 我们要转大端
    sa.sin_addr.s_addr = inet_addr(ipaddr);//将我们点分式的ip地址转换为一个大端整数
    

    //连接服务器
    int r = connect(sockfd,(struct sockaddr *)&sa,sizeof(sa));
    if(-1 == r)
    {
   
        perror("connect error");
        exit(2);
    }

}

//开始正常的收发
void function(void)
{
   
    char buf[1024] = {
   0};
    while(1)
    {
   
        printf("输入你要发送的信息(输入quit退出):");
        fflush(stdout);
        scanf("%s",buf);
        if(!strcmp(buf,"quit"))
        {
   
            break;
        }
        send(sockfd,buf,strlen(buf) + 1,0);

        recv(sockfd,buf,1024,0);
        printf("服务器回我的信息是:%s\n",buf);

    }
}

int main(int argc,char * argv[])
{
   
    if(argc < 3)
    {
   
        printf("参数都不齐\n");
        return -1;
    }
    TcpInit(argv[1],atoi(argv[2]));
    function();



    close(sockfd);
    return 0;
}

最近更新

  1. TCP协议是安全的吗?

    2023-12-27 12:22:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-27 12:22:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-27 12:22:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-27 12:22:03       20 阅读

热门阅读

  1. 大语言模型激活函数绘图

    2023-12-27 12:22:03       44 阅读
  2. 51单片机结构组成相关知识点

    2023-12-27 12:22:03       33 阅读
  3. 极智开发 | 解读英伟达软件生态 一切的基础CUDA

    2023-12-27 12:22:03       45 阅读
  4. 【2024考研】心情记录

    2023-12-27 12:22:03       41 阅读
  5. 实验五 哈希表的算法实现

    2023-12-27 12:22:03       38 阅读
  6. aws-sdk-cpp通过bazel构建的S3_client轮子

    2023-12-27 12:22:03       44 阅读