阿虚
咸鱼本鱼。
咸鱼本鱼

ipv4 的 tcp接口编程

什么是TCP接口编程

这玩意不管是Windows还是Linux操作系统其实都已经封装好了并不需要我来实现TCP协议,我们只需要调用几个函数,填几个参数就完事的事情。都是规定好且死板没套路,接口就那么几个玩不出太多的花来。

使用TCP接口的好处是什么?

  1. 有完整的持续连接请求
  2. 保证发送的每个数据都不会丢失。
  3. 如果发送的数据包丢失了,TCP协议层会重新发送丢失的数据包。
  4. 保证发送的数据完整性不会损坏发送的数据包。
  5. 保证每次发送的数据顺序对方接受到的顺序一致。

使用TCP接口的坏处是什么?

  1. 比UDP慢
  2. 有“粘包”的问题

TCP接口服务端和客户端的前期准备

Windows下需要依赖链接库猥琐ws2_32.lib。包含头文件winsocket2.h。

#include <winsock2.h>

#pragma comment(lib, “Ws2_32.lib”)

TCP服务端接口调用顺序

  1. 初始化套接字版本                                      WSAStartup
  2. 创建一个TCPSocket                                     socket
  3. 开始监听绑定的端口号                               listen
  4. 等待有网络上的SOCKET连接请求              accept
  5. 接受到连接请求后用recv接受对方发送来的消息或者使用send主动给对方发消息 recv / send
  6. 处理完请求后关闭和对方的socket链接。  closesocket

IPV4服务端例子

#define WIN32_LEAN_AND_MEAN 
#include <winsock2.h> 
#include <stdio.h>
#include <stdlib.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

void tcp();
  
int main(int argc, char **argv) 
{   
    tcp(); 
    return 0;
}
 
void tcp()
{
    WSADATA wsaData;
    //0x0初始化网络库
    if (WSAStartup(
        MAKEWORD(2, 2)       //参数1表示使用的网络库版本,目前最高就是2.2
                            //虽然官方说最低只支持win vist,xp系统上还是使用2.2
        , &wsaData) != 0)   //这个参数其实并没有什么卵用。
    {
        WSACleanup();
        return -1;
    }

    //0x1 创建一个TCPSocket
    SOCKET listen_socket = socket(
        AF_INET         //参数一是使用的地址族格式?绕口,AF_INET表示使用IPV4地址
        , SOCK_STREAM   //SOCK_STREAM指的就是TCP协议。
        , 0);           //0表示不适用任何协议,就是纯粹的TCP流,自己做解析。
    if (listen_socket == INVALID_SOCKET)
    {
        WSACleanup();
        return -1;
    }

    //0x2 将创建的socket绑定一个IP和端口号
    struct sockaddr_in local = { 0x0 };
    local.sin_family = AF_INET;                     //指定地址族格式,需要和socket参数一一样

    local.sin_addr.s_addr = inet_addr("127.0.0.1"); //绑定的IP地址,通常指当前机器获取到的IP地址
                                                    //127.0.0.1表示本机

    local.sin_port = htons(8080);                   //端口号,目前写死8080就行了。
                                                    //后面再补充端口号这个玩意。

    //调用绑定函数真正的把socket句柄和对应的IP端口号绑定在一起。
    if (bind(
        listen_socket                       //把IP和端口信息和创建出来的socket句柄绑定一起
        , (struct sockaddr*)&local          //这个没啥好说的
        , sizeof(local)) == SOCKET_ERROR)   //这个是结构体大小
    {
        WSACleanup();
        return -1;
    }

    //0x3 准备就绪,可以开始让当前socket句柄监听网络上面对应端口号的流量了。
    if (listen(
        listen_socket       //指定socket句柄能够开始监听端口上的流量
        , SOMAXCONN)        //这个参数意思是可以同时并发接受200~65535,具体多少取决于操作系统的版本        
                            //并不是跟队列那样来了一个就必须要先把当前连接请求处理完才能接受下一个。
        == SOCKET_ERROR)    
    {
        WSACleanup();
        return -1;
    }

    while (1)
    {
        //一个端口号毕竟不是只用一次,可以多次重复监听。
        //并且一个端口号可以同时并发多个连接请求只是学习例子没必要搞那么复杂

        struct sockaddr_in from;
        int fromlen = sizeof(from);
        //0x4 等待一个连接请求进来。
        //这个函数会一直等待其他IP连接到当前端口,如果等到了那么返回对方的socket句柄
        SOCKET msgsock = accept(
            listen_socket
            , (struct sockaddr*)&from   //如果返回成功,那么这个里面包含了对方的IP地址和端口号
            , &fromlen);
        if (msgsock == INVALID_SOCKET)
        {
            //返回的句柄如果是INVALID_SOCKET那么应该是网络情况出现了错误直接返回就行了
            WSACleanup();
            return -1;
        }

        printf("接受到连接IP %s, port %d\n"
            , inet_ntoa(from.sin_addr)  //得到请求方使用的IP地址
            , htons(from.sin_port));    //得到请求方使用的端口号

        //0x5
        char Buffer[128];
        //接受请求的消息报文
        int ret = recv(
            msgsock             //连接方的句柄
            , Buffer            //接受对方发过来数据buf
            , sizeof(Buffer)    //最大一次接受多少个字节
            , 0);
        if (ret == SOCKET_ERROR)
        {
            closesocket(msgsock);
            continue;
        }
        else if (ret == 0)
        {
            closesocket(msgsock);
            continue;
        }

        printf("recv: %s\n", Buffer);
         
        char SendBuffer[128] = "www.xuwu.org";
        //给对方发送一条消息报文
        ret = send(
            msgsock                 //连接方的句柄
            , SendBuffer            //发送报文的bufer
            , sizeof(SendBuffer)    //发送了多少个字节数据
            , 0);
        if (ret == SOCKET_ERROR)
        {
            fprintf(stderr, "send() failed: error %d\n", WSAGetLastError());
        }

        //0x6 关掉当前连接请求
        closesocket(msgsock);
    }
}

TCP客户端接口调用顺序

启动一个tcp客户端只需要5个步骤。步骤是固定的死记硬背没关系没有那么多套路。

  1. 初始化套接字版本                                            WSAStartup
  2. 创建一个TCPSocket                                          socket
  3. 指定连接的IP和端口号                                     connect
  4. 开始发送或接收对方发送来的消息或者使用      send/recv
  5. 处理完请求后关闭和对方的socket链接。  closesocket

IPV4客户端例子

#define WIN32_LEAN_AND_MEAN 
#include <winsock2.h> 
#include <stdio.h>
#include <stdlib.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

void tcp();

int main(int argc, char **argv) 
{  
    tcp();
    return 0;
}

void tcp()
{
    WSADATA wsaData;
    //0x0初始化网络库
    if (WSAStartup(
        MAKEWORD(2, 2)       //参数1表示使用的网络库版本,目前最高就是2.2
                            //虽然官方说最低只支持win vist,xp系统上还是使用2.2
        , &wsaData) != 0)   //这个参数其实并没有什么卵用。
    {
        WSACleanup();
        return -1;
    }

    //0x1 创建一个TCPSocket
    SOCKET conn_socket = socket(
        AF_INET         //参数一是使用的地址族格式?绕口,AF_INET表示使用IPV4地址
        , SOCK_STREAM   //SOCK_STREAM指的就是TCP协议。
        , 0);           //0表示不适用任何协议,就是纯粹的TCP流,自己做解析。
    if (conn_socket == INVALID_SOCKET)
    {
        WSACleanup();
        return -1;
    }

    struct sockaddr_in server = { 0x0 };
    server.sin_family = AF_INET;                     //指定地址族格式,需要和socket参数一一样
    server.sin_addr.s_addr = inet_addr("127.0.0.1"); //连接到目标IP地址
    server.sin_port = htons(8080);                   //连接到目标对应的端口号
    //不用填写发送的端口号?是的,发送端口号由操作系统来帮我们设置,我们并不需要填写。

    //0x3 开始和远程IP和端口号建立持续连接
    if (connect(conn_socket, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)
    {
        WSACleanup();
        return -1;
    }

    //0x4 给对方发送消息或者接受对方发过来的消息

    char Buffer[128] = "hello world!!!";
    //发送数据给对方
    int ret = send(
        conn_socket
        , Buffer            //发送数据的buf
        , sizeof(Buffer)    //发送了多少个字节
        , 0);
    if (ret == SOCKET_ERROR)
    {
        //发送数据失败
        closesocket(conn_socket);
        return -1;
    }

    //接受对方发送过来的数据
    ret = recv(
        conn_socket
        , Buffer            //接收buf
        , sizeof(Buffer)    //一共接收多少个字节
        , 0);
    if (ret == SOCKET_ERROR)
    {
        closesocket(conn_socket);
        return -1;
    }

    if (ret == 0)
    {
        closesocket(conn_socket);
        WSACleanup();
        return -1;
    }

    printf("recv:%s \n", Buffer);

    //0x5 通讯结束断开和对方的持续连接。
    closesocket(conn_socket);

    return;
}

TCP服务端和客户端跑起来的结果

http://www.xuwu.org/wp-content/uploads/2021/05/image.png

阿虚

文章作者

发表评论

textsms
account_circle
email

咸鱼本鱼

ipv4 的 tcp接口编程
什么是TCP接口编程 这玩意不管是Windows还是Linux操作系统其实都已经封装好了并不需要我来实现TCP协议,我们只需要调用几个函数,填几个参数就完事的事情。都是规定好且死板没套路,接…
扫描二维码继续阅读
2021-05-30