Linux网络编程 | 你回眸多娇我泪中带笑
Cobb 639 1

TCP客户端

柔性数组/main参数/粘包问题

知识储备 TCP流式协议(字节流协议): ----发送方发送的次数和接收方接收的次数并没有直接关系,数据是没有边界的---- 详情如下: -------发送方:应用程序发送数据,内核TCP发送缓冲区里面,只有等到缓冲区满了以后才发出去。 -------接收方:一次性接收 -------发送方发送 send "111";send "222";send "333", 接受方就接收recv "111222333",于是就产生了粘包问题。

解决粘包问题的方案: 0.01. send一次后,接着就recv操作 0.02. 发送数据的时候,给数据都带上数据头 struct Data { int datalen; char *buffer; } 数据头中包含了数据的长度纤细,在接收方解析的时候,根据数据的长度,解析具体的数据。



#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <strings.h>
#include <string.h>
#include <unistd.h>
#include <memory>
using namespace std;


struct DataPacket
{
    int dataLen; // 数据头
    char buf[0]; // 柔性数组  存储字符数据
};

// tcp实现的客户端程序
int main(int argc, char **argv)
{
    // argc:参数的个数
    // argv:参数的内容
    // ./client 127.0.0.1 8080
    // cout << "argc:" << argc << endl;
    // for (int i = 0; i < argc; i++)
    // {
    //     cout << argv[i] << endl;
    // }  
    if (argc < 3)
    {
        cout << "usage: ./client ip port" << endl;
        exit(-1);
    }

    char *ip = argv[1];
    unsigned short port = stoi(argv[2]);

    // 创建socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd < 0)
    {
        perror("socket err");
        exit(-1);
    }

    struct sockaddr_in server;
    server.sin_family = AF_INET; // ipv4地址家族
    server.sin_port = htons(port);
    server.sin_addr.s_addr = inet_addr(ip);

    // 连接服务器
    if (connect(clientfd, (struct sockaddr*)&server, sizeof(server)) < 0)
    {
        perror("connect err");
        exit(-1);
    }

    char buf[1024];
    for (;;)
    {
        bzero(buf, 1024);
        cin.getline(buf, 1024);

        if (strncmp(buf, "exit", 4) == 0)
        {
            break;
        }

        // 发送
        // 模拟tcp客户端连续发送数据的场景,模拟tcp粘包问题的产生
        // hello-0 hello-1 hello-2 hello-3 ... hello-9
        for (int i = 0; i < 10; i++)
        {
            char tmpbuf[1024] = {0};
            sprintf(tmpbuf, "%s-%d", buf, i);

            // 组装数据   datalen+datacontent
            shared_ptr<DataPacket> dp((DataPacket*)new char[4+strlen(tmpbuf)]);
            dp->dataLen = strlen(tmpbuf);
            strcpy(dp->buf, tmpbuf);

            //int ret = send(clientfd, tmpbuf, strlen(tmpbuf)+1, 0);
            int ret = send(clientfd, dp.get(), 4+strlen(tmpbuf), 0);

            cout << "send:" << tmpbuf << endl;
            if (ret < 0)
            {
                perror("send err");
                exit(-1);
            }
        }

        // 接收一次 
        //recv(clientfd, buf, 1024, 0);
        //cout << "server: " << buf << endl;
    }

    close(clientfd);
}

TCP服务端

时间戳应用

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

using namespace std;

// 时间戳
string getSysTime()
{
    char buffer[1024] = {0};
    time_t now = time(NULL);
    struct tm *t = localtime(&now);

    sprintf(buffer, "%d/%02d/%02d %02d:%02d:%02d\n", 
        t->tm_year+1900, t->tm_mon+1, t->tm_mday,
        t->tm_hour, t->tm_min, t->tm_sec);

    return buffer;
}

/*
Tcp服务端程序
*/
int main()
{
    // 创建socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0)
    {
        perror("socket err");
        exit(-1);
    }

    // 设置socket选择,网络地址重用  ip+port timewait 也是可以重新绑定的
    // setsocketopt
    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
    {
        perror("setsockopt err");
        exit(-1);
    }

    struct sockaddr_in s;
    s.sin_family = AF_INET;
    // unsigned short   0 - 65535
    // 0-1023 系统保留    1024 - 65535之间
    s.sin_port = htons(8080);
    // "192.168.131.129"
    // s.sin_addr.s_addr = htonl(INADDR_ANY);
    s.sin_addr.s_addr = inet_addr("192.168.131.129");

    // 绑定socket到指定的网络地址上
    if (bind(listenfd, (struct sockaddr*)&s, sizeof(s)) < 0)
    {
        perror("bind err");
        exit(-1);
    }

    // 进入监听状态
    if (listen(listenfd, 1024) < 0)
    {
        perror("listen err.");
        exit(-1);
    }

    struct sockaddr_in c;
    socklen_t clen = sizeof(c);
    // 循环等待客户端连接
    for (;;)
    {
        // accept阻塞线程,等待客户端的连接;如果有客户端connect连接服务器
        // accept会从监听队列中取出通信用的fd
        int clientfd = accept(listenfd, (struct sockaddr*)&c, &clen);
        if (clientfd < 0)
        {
            perror("accept err");
            break;
        }

        // 打印下客户端的信息
        // inet_ntoa()将 网络地址转换成“.”点隔的字符串格式。
        // ntohs = net to host short int 16位, 网络端到本地
        cout << "new connection - " << inet_ntoa(c.sin_addr) << " " 
            << ntohs(c.sin_port) << endl;

        // clientfd就可以和连接的客户端进行通信了
        string now = getSysTime();
        // string => char*
        send(clientfd, now.c_str(), now.size(), 0);
        close(clientfd);

        cout << "disconnect - " << inet_ntoa(c.sin_addr) << " "
            << ntohs(c.sin_port) << endl;
    }

    // 关闭监听套接字
    close(listenfd);
}

IO密集型回显服务器


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

using namespace std;

/*
Tcp服务端程序
*/
int main()
{
    // 创建socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0)
    {
        perror("socket err");
        exit(-1);
    }

    // 设置socket选择,网络地址重用  ip+port timewait 也是可以重新绑定的
    // setsocketopt
    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
    {
        perror("setsockopt err");
        exit(-1);
    }

    struct sockaddr_in s;
    s.sin_family = AF_INET;
    // unsigned short   0 - 65535
    // 0-1023 系统保留    1024 - 65535之间
    s.sin_port = htons(8080);
    // "192.168.131.129"
    // s.sin_addr.s_addr = htonl(INADDR_ANY);
    s.sin_addr.s_addr = inet_addr("192.168.131.129");

    // 绑定socket到指定的网络地址上
    if (bind(listenfd, (struct sockaddr*)&s, sizeof(s)) < 0)
    {
        perror("bind err");
        exit(-1);
    }

    // 进入监听状态
    if (listen(listenfd, 1024) < 0)
    {
        perror("listen err.");
        exit(-1);
    }

    struct sockaddr_in c;
    socklen_t clen = sizeof(c);
    // 循环等待客户端连接
    // telnet  ip  port
    for (;;)
    {
        // accept阻塞线程,等待客户端的连接;如果有客户端connect连接服务器
        // accept会从监听队列中取出通信用的fd
        int clientfd = accept(listenfd, (struct sockaddr*)&c, &clen);
        if (clientfd < 0)
        {
            perror("accept err");
            break;
        }

        // 打印下客户端的信息
        cout << "new connection - " << inet_ntoa(c.sin_addr) << " " 
            << ntohs(c.sin_port) << endl;

        // IO密集型
        for (;;)
        {
            // 回显服务器  10ms
            char buf[1024] = {0};
            recv(clientfd, buf, 1024, 0);
            if (strncmp(buf, "exit", 4) == 0)
            {
                close(clientfd);
                cout << "disconnect - " << inet_ntoa(c.sin_addr) << " "
                << ntohs(c.sin_port) << endl;
                break;
            }
            send(clientfd, buf, strlen(buf), 0);
        }
    }

    // 关闭监听套接字
    close(listenfd);
}

基于多进程实现的回显服务器


#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>

using namespace std;

void handler(int)
{
    while (waitpid(-1, NULL, WNOHANG) > 0)
    {
        cout << "child process exit!" << endl;
    }
}

/*
基于多进程实现的回显服务器
*/
int main()
{
    // 注册信号,防止出现僵尸进程
    signal(SIGCHLD, handler);

    // 创建socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0)
    {
        perror("socket err");
        exit(-1);
    }

    // 设置socket选择,网络地址重用  ip+port timewait 也是可以重新绑定的
    // setsocketopt
    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
    {
        perror("setsockopt err");
        exit(-1);
    }

    struct sockaddr_in s;
    s.sin_family = AF_INET;
    // unsigned short   0 - 65535
    // 0-1023 系统保留    1024 - 65535之间
    s.sin_port = htons(8080);
    // "192.168.131.129"
    // s.sin_addr.s_addr = htonl(INADDR_ANY);
    s.sin_addr.s_addr = inet_addr("192.168.131.129");

    // 绑定socket到指定的网络地址上
    if (bind(listenfd, (struct sockaddr*)&s, sizeof(s)) < 0)
    {
        perror("bind err");
        exit(-1);
    }

    // 进入监听状态
    if (listen(listenfd, 1024) < 0)
    {
        perror("listen err.");
        exit(-1);
    }

    struct sockaddr_in c;
    socklen_t clen = sizeof(c);
    // 循环等待客户端连接
    // telnet  ip  port
    for (;;)
    {
        // accept阻塞线程,等待客户端的连接;如果有客户端connect连接服务器
        // accept会从监听队列中取出通信用的fd
        int clientfd = accept(listenfd, (struct sockaddr*)&c, &clen);
        if (clientfd < 0)
        {
            perror("accept err");
            break;
        }

        // 打印下客户端的信息
        cout << "new connection - " << inet_ntoa(c.sin_addr) << " " 
            << ntohs(c.sin_port) << endl;

        // 分配一个新进程,专门和clientfd的客户端进行通信
        pid_t pid = fork();
        if (pid < 0)
        {
            perror("fork err");
            close(clientfd);
            continue;
        }

        if (pid > 0)
        {
            // 父进程
            close(clientfd);
        }
        else
        {
            // 子进程
            close(listenfd);

            // IO密集型
            for (;;)
            {
                // 回显服务器  10ms
                char buf[1024] = {0};
                /*
                1.成功。返回接收的字节数
                2.出错。-1
                3.客户端断开。0
                */
                int ret = recv(clientfd, buf, 1024, 0);
                if (ret == -1 || ret == 0 || strncmp(buf, "exit", 4) == 0)
                {
                    close(clientfd);
                    cout << "disconnect - " << inet_ntoa(c.sin_addr) << " "
                    << ntohs(c.sin_port) << endl;
                    exit(0);
                }
                //send(clientfd, buf, strlen(buf), 0);
            }
        }
    }

    // 关闭监听套接字
    close(listenfd);
}


基于多线程的回显服务器


#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <pthread.h>
#include <memory>

using namespace std;

struct ThreadData
{
    ThreadData(int fd, struct sockaddr_in a)
        : clientfd(fd)
        , addr(a)
    {}
    int clientfd;
    struct sockaddr_in addr;
};

struct DataPacket
{
    int dataLen; // 数据头
    char buf[0]; // 柔性数组  存储字符数据
};

// 我怎么通过4个线程,提供成百上千的客户端的服务呢???   IO复用接口select  poll  epoll
// 线程函数  线程数量 == cpu核心数量   不宜创建数量过多的线程,不仅要占大量的内存、频繁的任务调度上下文切换也很耗时
void* process(void *arg)
{
    shared_ptr<ThreadData> ptr((ThreadData*)arg);
    int clientfd = ptr->clientfd;

    // IO密集型   修改,实现成能和客户端聊天的服务器呢?
    for (;;)
    {
        // 回显服务器  10ms
        char buf[1024] = {0};
        // clientfd默认是阻塞的,没有网络IO数据,会把当前线程阻塞起来
        int ret = recv(clientfd, buf, 1024, 0);
        if (ret == -1 || ret == 0 || strncmp(buf, "exit", 4) == 0)
        {
            close(clientfd);
            cout << "disconnect - " << inet_ntoa(ptr->addr.sin_addr) << " "
            << ntohs(ptr->addr.sin_port) << endl;
            break;
        }

        cout << "buf:";
        for (int i = 0; i < ret; i++)
        {
            // 00 00 00 04
            cout << "i:" << i << " data:" << (int)buf[i] << endl;
        }
        cout << endl;

        char *q = buf;
        while(q != buf + ret)
        {
            DataPacket *p = (DataPacket*)q;
            int datalen = p->dataLen;
            cout << "datalen:" << datalen << endl; 
            char tmp[1024] = {0};
            strncpy(tmp, p->buf, datalen);
            cout << tmp << endl;
            q = q+4+datalen;
        }
        //send(clientfd, buf, strlen(buf), 0);
    }
    return NULL;
}

/*
Tcp服务端程序
*/
int main()
{
    // 创建socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0)
    {
        perror("socket err");
        exit(-1);
    }

    // 设置socket选择,网络地址重用  ip+port timewait 也是可以重新绑定的
    // setsocketopt
    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
    {
        perror("setsockopt err");
        exit(-1);
    }

    struct sockaddr_in s;
    s.sin_family = AF_INET;
    // unsigned short   0 - 65535
    // 0-1023 系统保留    1024 - 65535之间
    s.sin_port = htons(8080);
    // "192.168.131.129"
    // s.sin_addr.s_addr = htonl(INADDR_ANY);
    s.sin_addr.s_addr = inet_addr("192.168.131.129");

    // 绑定socket到指定的网络地址上
    if (bind(listenfd, (struct sockaddr*)&s, sizeof(s)) < 0)
    {
        perror("bind err");
        exit(-1);
    }

    // 进入监听状态
    if (listen(listenfd, 1024) < 0)
    {
        perror("listen err.");
        exit(-1);
    }

    struct sockaddr_in c;
    socklen_t clen = sizeof(c);
    // 循环等待客户端连接
    // telnet  ip  port
    for (;;)
    {
        // accept阻塞线程,等待客户端的连接;如果有客户端connect连接服务器
        // accept会从监听队列中取出通信用的fd
        int clientfd = accept(listenfd, (struct sockaddr*)&c, &clen);
        if (clientfd < 0)
        {
            perror("accept err");
            break;
        }

        // 打印下客户端的信息
        cout << "new connection - " << inet_ntoa(c.sin_addr) << " " 
            << ntohs(c.sin_port) << endl;

        ThreadData *ptData = new ThreadData(clientfd, c);
        // 分配一个线程处理客户端
        pthread_t tid;
        pthread_create(&tid, NULL, process, ptData);
        // 设置分离线程
        pthread_detach(tid);
    }

    // 关闭监听套接字
    close(listenfd);
}


UDP客户端


#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <strings.h>
#include <string.h>
#include <unistd.h>
#include <memory>
using namespace std;

int main()
{
    int clientfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (clientfd < 0)
    {
        perror("socket err");
        exit(-1);
    }

    struct sockaddr_in s;
    s.sin_family = AF_INET;
    s.sin_port = htons(8080);
    s.sin_addr.s_addr = inet_addr("192.168.131.129");

    char buf[1024];
    for (;;)
    {
        bzero(buf, 1024);
        cin.getline(buf, 1024);

        if (strncmp(buf, "exit", 4) == 0)
        {
            break;
        }

        sendto(clientfd, buf, strlen(buf), 0, (sockaddr*)&s, sizeof(s));

        struct sockaddr_in c;
        socklen_t clen = sizeof(c);
        recvfrom(clientfd, buf, 1024, 0, (sockaddr*)&c, &clen);
        cout << buf << endl;
    }
    close(clientfd);
}

UDP服务端

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <strings.h>
#include <string.h>
#include <unistd.h>
#include <memory>
using namespace std;


int main()
{
    int clientfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (clientfd < 0)
    {
        perror("socket err");
        exit(-1);
    }

    // server
    struct sockaddr_in s;
    s.sin_family = AF_INET;
    s.sin_port = htons(8080);
    s.sin_addr.s_addr = inet_addr("192.168.131.129");

    // 绑定socket到指定的网络地址上
    if (bind(clientfd, (struct sockaddr*)&s, sizeof(s)) < 0)
    {
        perror("bind err");
        exit(-1);
    }

    // echo server   tcp=> send recv    udp=> 
    for (;;)
    {
        struct sockaddr_in c;
        socklen_t clen = sizeof(c);
        char buf[1024] = {0};
        recvfrom(clientfd, buf, 1024, 0, (struct sockaddr*)&c, &clen);

        sendto(clientfd, buf, strlen(buf), 0, (struct sockaddr*)&c, sizeof(c));
    }
}
评论区

索引目录