基于TCP协议实现Linux下客户端与服务器之间的通信,实现多线程、多进程服务器

TCP是TCP/IP协议族中一个比较重要的协议,这是一种可靠、建立链接、面向字节流的传输,工作在传输层。和TCP相对的不可靠、无链接、面向数据报的协议UDP,了解UDP客户端与服务器之间通信请戳UDP协议实现的服务器与客户端通信

TCP协议建立连接

首先我们通过一个大概的图来了解。
建立
建立连接首先必须是服务器启动,这没什么好说的,服务器为被动方,客户端为主动方,当客户端发起请求建立连接,服务器被动接受,经过上图三次握手建立连接,注意这三次连接都是在操作系统内部实现的。
那么我们就来介绍建立连接的相关API

socket获取通信的文件描述符。

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

端口号的绑定

#include <sys/socket.h>
int bind(int sock, const struct sockaddr* address, socklen_t address_len);

这俩个API在UDP协议实现的服务器与客户端通信中有详细的参数返回值介绍。

作为服务器首先要进行监听

#include <sys/socket.h>
int listen(int socket, int backog);

参数介绍:
socket: 为socket函数返回的文件描述符
backlog: 建立连接过程中等待建立的请求个数
返回值:
成功返回0,失败返回-1;

这里我们再介绍这个backlog参数的含义:
这就相当与我们去银行取钱,到了发现人比较多,这个时候就需要坐在凳子上等,那么这里的凳子就是backlog的含义,就是现在最大的等待处理的个数。

接受请求:

#include <sys.socket.h>
int accept(int socket,  struct sockaddr* address, socklen_t* address_len);

参数:
socket:文件描述符
address:输出型参数,用来接受对方的IP 端口号,是一个结构体。
address_len:是一个输入输出型参数,输入进去为当前address的大小,输出为实际的大小。
返回值:
返回值成功为一个文件描述符,失败为-1;

这里要解释一下,socket不是已经创建出一个文件描述符,怎么还有?
accept的文件描述符,使用来直接进行数据的发送与收取,处理请求。
前面socket创建的文件描述符,在listen函数中用,是为了来接受请求.

举个例子:
socket创建的文件描述符,就像一个饭店,拉客人(listen函数)就是在外面进行监听,是否有客人要来饭店,当listen接受到客人,就会带到饭店,然后交给服务员(accept)来进行对客人吃饭请求的处理。这时候accpet就会是这个客人的请求的处理。

客户端连接函数:

#include <sys/socket.h>
int connect(int socketfd, const struct sockaddr* addr, socklen_t addrlen);

参数:
socketfd: 文件描述符;
addr :为要建立连接服务器的IP、端口号、使用网络协议IPV4;
addrlen:addr结构体的大小;
参数:
建立成功返回0,失败返回-1;

相关API介绍完我们开始实现一个简单的客户端与服务器

我们实现最简单的没有业务处理逻辑的通信,就把接受到的消息,发送给客户端

服务器

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

void Response(int new_sock, struct sockaddr_in * client)
{
    while (1)
    {
        char buf[512] = {0};
        // 接受请求
        ssize_t rd = read(new_sock, buf, sizeof(buf)-1);
        if (rd < 0)
        {
            perror("read error\n");
            return;
        }
        if (rd == 0)
        {
            printf("read done!\n");
            return;
        }
        buf[rd] = '\0';
        // 处理请求
        printf("client say [%s:%d] # %s\n",inet_ntoa(client->sin_addr), ntohs(client->sin_port), buf);

        // response 响应
        write(new_sock, buf, strlen(buf));
    }
}

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        printf("usage:./server_tcp [ip] [port]");
        return 1;
    }

    int sock = socket(AF_INET, SOCK_STREAM, 0); // 文件描述符
    if (sock < 0)
    {
        perror("sock error\n");
        return 2;
    }

    // 绑定端口号
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));
    int b = bind(sock, (struct sockaddr*)&server, sizeof(server));
    if (b < 0)
    {
        perror("bind error\n");
        return 3;
    }

    // 监听
    int l = listen(sock, 3); // 最大的连接数量
    if (l < 0)
    {
        perror("listen error\n");
        return 4;
    }

    printf("bind and listen .... \n");

    // 接受请求处理
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    int new_sock = accept(sock, (struct sockaddr*)&client, &len);
    Response(new_sock, &client); // 处理函数
    return 0;
}

客户端:

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

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        perror("usage:./client_tcp [ip] [port]");
        return 1;
    }

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        perror("scoket error\n");
        return 2;
    }

    // 建立连接
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));
    socklen_t len = sizeof(server);
    int co = connect(sock, (struct sockaddr*)&server, len);
    if (co < 0)
    {
        perror("connect error\n");
        return 3;
    }

    while (1)
    {
        char buf[512] = {0};
        ssize_t rd = read(0, buf, sizeof(buf)-1);
        if (rd < 0)
        {
            perror("read error\n");
            return 4;
        }
        if (rd == 0)
        {
            printf("read done!\n");
            return 5;
        }
        buf[rd-1] = '\0';

        write(sock, buf, strlen(buf)); // 会阻塞的写与读

        ssize_t sockrd = read(sock, buf, sizeof(buf)-1);
        if (sockrd < 0)
        {
            perror("read error\n");
            return 4;
        }

        if (sockrd == 0)
        {
            printf("read done!\n");
            return 5;
        }

        buf[rd] = '\0';
        printf("server say # %s\n", buf);
    }
    return 0;
}

结果:
客户端
客户
服务器
server

TCP协议的的断开连接

建立连接需要三次交互,断开连接需要四次交互。
我们用图分析一下
断开
当数据发送完客户端调用close 这时候就会发送 FIN 服务器内核立刻恢复ACK 服务器再调用close 发送FIN 客户端回复ACK 进入等待。一段时间后退出。

基于TCP实现多线程、多进程服务器

代码戳
多进程TCP服务器
多线程TCP服务器

关于创建进程和创建线程
线程的创建
进程的创建

猜你喜欢

转载自blog.csdn.net/gangstudyit/article/details/80870466