Linux-TCP编程流程-Socket编程-单线程实现TCP客户端和服务端交互-多进程实现TCP客户端和服务端交互

Posted 天津 唐秙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux-TCP编程流程-Socket编程-单线程实现TCP客户端和服务端交互-多进程实现TCP客户端和服务端交互相关的知识,希望对你有一定的参考价值。

1. TCP的编程流程

在这里插入图片描述

2. socket编程接口

2.1 监听

  int listen
在这里插入图片描述
  sockfd:套接字描述符
  backlog:已完成连接队列的大小
在这里插入图片描述
  没有完成三次握手的连接存放在未完成连接队列,已经完成三次握手的连接存放在已完成连接队列,已经完成三次握手的连接等待被accept的连接,或者可以理解为连接状态为”连接建立“的连接。每成功连接一个,backlog++,backlog影响了服务端并发接收连接的能力。

2.2 获取连接

  int accept
在这里插入图片描述
  sockfd:套接字描述符
  addr:客户端的地址信息结构(客户端的ip,客户端的端口)
  addrlen:客户端地址信息结构的长度
  返回值:
    成功:返回值是新连接的套接字描述符,文件描述符
    失败:-1

2.3 发起连接

  int connect
在这里插入图片描述
  sockfd:套接字描述符
  addr:服务端的地址信息结构
    服务端的ip
    服务端的端口
  addrlen:
  返回值:
    小于0:连接失败
    成功:0

2.4 发送

  ssize_t send
在这里插入图片描述
  sockfd:套接字描述符
  buf:待要发送的数据
  flags:
    0:阻塞发送
  MSG_OOB:发送带外数据
  返回值:
    大于0:返回发送的字节数量
    -1:发送失败

2.5 接收

  ssize_t recv
在这里插入图片描述
  sockfd:套接字描述符
  accept的返回值,新连接的套接字
  buf:将接收的数据放到哪里去
  len:buf的最大接收能力
  flags:0阻塞接收
  返回值:
    大于0:正常接收了多少字节数据
    等于0:对端将连接关闭了
    小于0:接收失败

2.6 关闭

  int close
在这里插入图片描述

3. 单线程实现TCP代码

客户端部分
tcp_client.cpp

#include <stdio.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, IPPROTO_TCP);
    if(sockfd < 0)
    {
        perror("listen_sock");
        return 0;
    }

    struct sockaddr_in dest_addr;
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(18989);
    dest_addr.sin_addr.s_addr = inet_addr("49.232.211.4");
    int ret = connect(sockfd, (struct sockaddr*)&dest_addr, sizeof(dest_addr));
    if(ret < 0)
    {
        perror("connect");
        return 0;
    }

    while(1)
    {
        sleep(1);
        char buf[1024] = "i am tcp clients";
        ssize_t send_size = send(sockfd, buf, strlen(buf), 0);
        if(send_size < 0)
        {
            perror("send");
            continue;
        }

        memset(buf, '\\0', sizeof(buf));
        ssize_t recv_size = recv(sockfd, buf, sizeof(buf) - 1, 0);
        if(recv_size < 0)
        {
            perror("recv");
            continue;
        }
        else if(recv_size == 0)
		{
            printf("server close\\n");
            close(sockfd);
            return 0;
        }
        printf("server say: %s\\n", buf);

    }
}

服务器部分
tcp_server.cpp

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

int main()
{
    int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(listen_sock < 0)
    {
    perror("listen_sock");
    return 0;
}

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(18989);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind");
        return 0;
    }

    ret = listen(listen_sock, 1);
    if(ret < 0)
    {
        perror("listen");
        return 0;
    }

    while(1)
    {
        struct sockaddr_in peer_addr;
        socklen_t peer_addrlen = sizeof(peer_addr);
        int new_sock = accept(listen_sock, (struct sockaddr*)&peer_addr, &peer_addrlen);
        if(new_sock < 0)
        {
            perror("accept");
            return 0;
        }

        printf("accept %s:%d, create new_sock %d\\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port), new_sock);

        //recv
        char buf[1024] = {0};
        ssize_t recv_size = recv(new_sock, buf, sizeof(buf) - 1, 0);
        if(recv_size < 0)
        {
            perror("recv");
            continue;
        }
        else if(recv_size == 0)
        {
            perror("peer shutdown\\n");
            close(new_sock);
            close(listen_sock);
            return 0;
        }
        printf("client %d say: %s\\n", new_sock, buf);

        memset(buf, '\\0', sizeof(buf));
        sprintf(buf, "i am server, hi client %d\\n", new_sock);

        ssize_t send_size = send(new_sock, buf, strlen(buf), 0);
        if(send_size < 0)
        {
            perror("send");
            continue;
        }
    }
    close(listen_sock);
    return 0;
}

总结:
  在单线程中实现客户端和服务端之间的交互,如果按照如上代码执行,将accept放在while循环的里面,那么每次客户端发送数据之后,只能和服务端交互一次,然后就会阻塞在accept函数,如果将accept放在while循环外面,那么进程就可以和客户端实现多次的数据交互,但是服务端只能和同一个客户端交互,因为永远无法再执行到accept函数。
  对于上面这种问题,提出了三种解决方法,方法一,使用多进程实现,方法二,使用多线程实现,方法三,使用高级io中的知识。

4. 多进程实现TCP代码

tcp_server.cpp

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

void sigcallback(int signo)
{
    wait(NULL);
}

int main()
{
    signal(SIGCHLD, sigcallback);

    int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(listen_sock < 0)
    {
        perror("socket");
        return 0;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(18989);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind");
        return 0;
    }

    ret = listen(listen_sock, 1);
    if(ret < 0)
    {
        perror("listen");
        return 0;
    }

    while(1)
    {
        struct sockaddr_in peer_addr;
        socklen_t peer_addrlen = sizeof(peer_addr);
        int new_sock = accept(listen_sock, (struct sockaddr*)&peer_addr, &peer_addrlen);
        if(new_sock < 0)
        {
            perror("accept");
            return 0;
        }
    
        printf("accept %s:%d, create new_sock %d\\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port), new_sock);

        pid_t pid = fork();
        if(pid < 0)
        {
            perror("fork");
            close(new_sock);
            continue;
        }
        else if(pid == 0)
        {
            //child
        
            close(listen_sock);
            while(1)
            {
                char buf[1024] = {0};
                ssize_t recv_size = recv(new_sock, buf, strlen(buf) - 1, 0);
                if(recv_size < 0)
                {
                    perror("recv");
                    continue;
                }
                else if(recv_size == 0)
                {
                    printf("peer shutdown");
                    close(new_sock);
                    return 0;
                }
            
                printf("client %d say: %s\\n", new_sock, buf);

                memset(buf, '\\0', sizeof(buf));
                sprintf(buf, "i am server, hi client %d\\n", new_sock);
                ssize_t send_size = send(new_sock, buf, strlen(buf), 0);
                if(send_size < 0)
                {
                    perror("send");
                    continue;
                }
            }
        }
        else
        {
            //father
            close(new_sock);
            continue;
        }
    }
    close(listen_sock);

    return 0;
}

  使用多进程实现,对于每一个客户端都对应创建一个子进程,子进程用来和客户端的交互,父进程就负责接收经过三次握手的客户端。

以上是关于Linux-TCP编程流程-Socket编程-单线程实现TCP客户端和服务端交互-多进程实现TCP客户端和服务端交互的主要内容,如果未能解决你的问题,请参考以下文章

网络编程-SOCKET开发之----3. socket通信工作流程

linux--网络编程之socket

tcp编程socket编程

Linux:TCP Socket编程(代码实战)

Linux:TCP Socket编程(代码实战)

socket编程为啥要用wsastartup