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

本文介绍了如何基于TCP协议在Linux环境下实现客户端与服务器的通信,详细讲解了TCP的连接建立和断开过程,并提供了简单的通信示例。此外,还探讨了如何构建多线程和多进程服务器,包括代码示例。

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服务器

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值