• TCP C语言实现详解


    原文网址:https://www.jianshu.com/p/7c7fe00d01b1

    TCP - C语言实现详解

    chiwin
    2020.06.09 17:17:46字数 616阅读 589
     
    tcp-三次握手-四次挥手.jpg

    【一】服务端

    1、创建一个socket,拿到tcp的一个文件描述符

    int serverSocket = socket(AF_INET, SOCK_STREAM, 0)
    

    其中,第一个参数表示地址类型,AF_INET为IPV4,AF_INET6可支持IPV6;第二个参数表明是TCP【面向连接的稳定数据传输SOCK_STREAM】连接;第三个参数默认0

    2、申请一个服务端结构体并初始化

    struct sockaddr_in server_addr; // 结构体
    bzero(&server_addr, sizeof(server_addr)); // 判空
    
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    //server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 本机通信,INADDR_ANY代表0.0.0.0:所有地址
    server_addr.sin_addr.s_addr = inet_addr("186.66.66.66"); //非本机通信
    

    申请了结构体sockaddr_in,将协议族设置为ipv4,传入了port,设置了ip

    3、绑定socket和结构体ipport

    bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(server_addr)
    

    返回小于0为失败,否则绑定成功
    注:
    sockaddr在头文件#include <sys/socket.h>中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了
    sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中

    4、监听socket句柄

    listen(serverSocket, 5)
    

    返回小于0为失败,否则监听成功

    5、为客户端连接做准备,构造结构体

    struct sockaddr_in client_addr;
    int addr_len = sizeof(client_addr);
    

    6、等待客户端连接

    int clientSocket = accept(serverSocket, (struct sockaddr*)&client_addr, (socklen_t*)&addr_len);
    

    返回小于0为失败,否则接受客户端连接
    注:
    accept接收服务端的链接请求,并返回一个客户端的socket句柄,参数一为服务端socket句柄,参数二为客户端结构体,参数三位客户端结构体长度,其中参数二和参数三为出参,可以解析到客户端的信息,如下:

    printf("Client IP %s\n", inet_ntoa(client_addr.sin_addr));
    printf("Client Port %d\n", htons(client_addr.sin_port));
    

    7、客户端连接后,接受msg消息

    char buffer[200];
    int dataLen = recv(clientSocket , buffer, 1024, 0);
    buffer[dataLen] = '\0';
    

    dateLen为接受到的data的大小,buffer为收到的消息

    8、服务端收完消息后,回给客户端相同的消息【或者自己写消息到buffer里并发送】

    send(clientSocket , buffer, dataLen , 0);
    

    9、继续监听客户端连接请求

    使用while循环返回到accept函数

    【二】客户端

    1、创建一个socket,拿到tcp的一个文件描述符

    int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    

    2、申请一个服务端结构体并填充服务端信息

    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    //指定服务器端的ip,本地测试:127.0.0.1
    //inet_addr()函数,将点分十进制IP转换成网络字节序IP
    serverAddr.sin_addr.s_addr = inet_addr("186.66.66.66");
    

    3、尝试连接服务端

    connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr))
    

    返回小于0为失败,否则连接成功

    4、开始发送数据并在发送成功后接收服务端返回的数据

    char sendbuf[200] = {"sendMsg to server"};
    char recvbuf[200] = {0};
    send(clientSocket, sendbuf, strlen(sendbuf), 0);
    int recvData = recv(clientSocket, recvbuf, 200, 0);
    recvbuf[recvData] = '\0';
    

    5、完成数据通信后关闭连接

    close(clientSocket);
    

    【三】代码

    参考:https://blog.csdn.net/lovekun1989/article/details/41042273

    服务端代码如下:

    /*socket tcp服务器端*/
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
     
    #define SERVER_PORT 12345
     
    /*
     监听后,一直处于accept阻塞状态,
     直到有客户端连接,
     当客户端输入quit后,客户端主动close与服务端的链接
     */
     
    int main()
    {
        int serverSocket;
        struct sockaddr_in server_addr;
        struct sockaddr_in client_addr;
        int addr_len = sizeof(client_addr);
        int clientSocket ;
        char buffer[200];
        int dataLen;
        
        if((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            perror("socket");
            return 1;
        }
     
        bzero(&server_addr, sizeof(server_addr));
    
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(SERVER_PORT);
    
        //本机通信
        //server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        //非本机通信
        server_addr.sin_addr.s_addr = inet_addr("186.66.66.66");
      
      
        //对于bind,accept之类的函数,里面套接字参数都是需要强制转换成(struct sockaddr *)
        if(bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
        {
            perror("connect");
            return 1;
        }
        //设置服务器上的socket为监听状态
        if(listen(serverSocket, 5) < 0) 
        {
            perror("listen");
            return 1;
        }
     
        while(1)
        {
            printf("Listening on port: %d\n", SERVER_PORT);
            //调用accept函数后,会进入阻塞状态
            //accept返回一个套接字的文件描述符,这样服务器端便有两个套接字的文件描述符,
            //serverSocket和clientSocket 。
            //serverSocket仍然继续在监听状态,clientSocket 则负责接收和发送数据
            //client_addr是一个传出参数,accept返回时,传出客户端的地址和端口号
            //addr_len是一个传入-传出参数,传入的是调用者提供的缓冲区的client_addr的长度,以避免缓冲区溢出。
            //传出的是客户端地址结构体的实际长度。
            //出错返回-1
            clientSocket  = accept(serverSocket, (struct sockaddr*)&client_addr, (socklen_t*)&addr_len);
            if(clientSocket  < 0)
            {
                perror("accept");
                continue;
            }
            printf("\nrecv clientSocket  data...n");
            //inet_ntoa   ip地址转换函数,将网络字节序IP转换为点分十进制IP
            //表达式:char *inet_ntoa (struct in_addr);
            printf("IP is %s\n", inet_ntoa(client_addr.sin_addr));
            printf("Port is %d\n", htons(client_addr.sin_port));
            while(1)
            {
                dataLen = recv(clientSocket , buffer, 1024, 0);
                if(dataLen < 0)
                {
                    perror("recv");
                    continue;
                }
                buffer[dataLen] = '\0';
                if(strcmp(buffer, "quit") == 0)
                    break;
                printf("%drecv data is %s\n", dataLen, buffer);
                send(clientSocket , buffer, dataLen, 0);
            }
        }
        return 0;
    }
    

    客户端代码如下:

    /*socket tcp客户端*/
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
     
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
     
    #define SERVER_PORT 12345
     
    /*
     连接到服务器后,会不停循环,等待输入,
     输入quit后,断开与服务器的连接
     */
     
    int main()
    {
        //客户端只需要一个套接字文件描述符,用于和服务器通信
        int clientSocket;
        //描述服务器的socket
        struct sockaddr_in serverAddr;
        char sendbuf[200];
        char recvbuf[200];
        int recvData;
        if((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            perror("socket");
            return 1;
        }
     
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_port = htons(SERVER_PORT);
        //指定服务器端的ip,本地测试:127.0.0.1
        //inet_addr()函数,将点分十进制IP转换成网络字节序IP
        serverAddr.sin_addr.s_addr = inet_addr("186.66.66.66");
        if(connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
        {
            perror("connect");
            return 1;
        }
     
        printf("connect with destination host...\n");
     
        while(1)
        {
            printf("Input your world:>");
            scanf("%s", sendbuf);
            printf("\n");
     
            send(clientSocket, sendbuf, strlen(sendbuf), 0);
            if(strcmp(sendbuf, "quit") == 0)
                break;
            recvData = recv(clientSocket, recvbuf, 200, 0);
            recvbuf[recvData] = '\0';
            printf("recv data of my world is: %s\n", recvbuf);
        }
        close(clientSocket);
        return 0;
    }
  • 相关阅读:
    Cron表达式,springboot定时任务
    go 语言中windows Linux 交叉编译
    SSM框架处理跨域问题
    golang gin解决跨域访问
    关于Integer类的值使用==比较
    IoC注解
    spring基础知识
    SQL SERVER大话存储结构(3)_数据行的行结构
    SQL SERVER
    MySQL-记一次备份失败的排查过程
  • 原文地址:https://www.cnblogs.com/bruce1992/p/16560262.html
Copyright © 2020-2023  润新知