C/C++实现tcp客户端和服务端的实现(从零开始写自己的高性能服务器)

目录

tcp客户端通信流程

tcp客户端设计

1、创建通信对象

2、链接服务器

[3、发送数据 / 读取数据](#3、发送数据 / 读取数据)

4、关闭通信

tcp服务端设计

1、创建通信对象

2、绑定服务器地址信息

3、设置服务器为监听模式

4、接收客户的链接请求

编写tcp客户端和服务端,实现双向通信

server.c

client.c


tcp客户端通信流程

1、创建通信对象

2、链接服务器

3、发送数据 / 读取数据

4、关闭通信

tcp客户端设计

1、创建通信对象

#include <sys/socket.h>

int socket(int domain, int type, int protocol); //创建一个终端链接

domain:网络层协议

type: 传输层协议 SOCK_STREAM TCP协议
SOCK_DGRAM UDP协议

protocol:属性,默认为 0

返回值: 成功 通信对象
失败 -1

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */

tcp_socket = socket(AF_INET, SOCK_STREAM, 0); //创建TCP对象
udp_socket = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP对象
raw_socket = socket(AF_INET, SOCK_RAW, protocol); //创建自定义协议对象

demo:

cpp 复制代码
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);

if (tcp_socket < 0)
{
    perror("创建TCP客户端通信对象失败");
    return 1;
}
else
{
    printf("创建TCP客户端通信对象成功\n");
}

2、链接服务器

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

sockfd:客户端通信对象

addr:服务器地址

addrlen:服务器地址的大小

返回值: 成功 0
失败 -1

sockaddr_in结构体

cpp 复制代码
struct sockaddr_in 
{
sa_family_t    sin_family;    /* address family: AF_INET 网络层协议*/
in_port_t      sin_port;        /* port in network byte order 端口*/  
struct in_addr sin_addr;   /* internet address 网络地址*/  
};

struct in_addr 
{
    uint32_t    s_addr;     /* address in network byte order 网络地址*/
};   

sockaddr和sockaddr_in区别

struct sockaddr

{

sa_family_t sin_family;//地址族

char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息

};

sockaddr的缺陷是sa_data把目标地址和端口信息混在一起了

二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。

sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。

sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了

demo:

cpp 复制代码
//网络服务器地址设置
struct sockaddr_in addr;                         // 创建一个服务器地址结构体
addr.sin_family = AF_INET;                       // 设置地址族为IPv4
addr.sin_port = htons(8888);                     // 设置端口号为8888
addr.sin_addr.s_addr = inet_addr("172.17.112.1"); // 设置IP地址为(我本机的网卡地址)


//链接服务器
if (connect(tcp_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
    perror("连接服务器失败:\n");
    return 1;
}
else
{
    printf("连接服务器成功\n");
}

补充:

#include <arpa/inet.h>

uint16_t htons(uint16_t hostshort); //把本地端口转换为网络端口
uint16_t ntohs(uint16_t netshort); //把网络端口转换为本地端口


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

in_addr_t inet_addr(const char *cp); //把字符串地址转换为网络地址
char *inet_ntoa(struct in_addr in); //把网络地址转换为字符串地址

3、发送数据 / 读取数据

4、关闭通信

#include <unistd.h> int close(int fd);


tcp服务端设计

1.创建通信对象

2.绑定服务器地址信息

3.设置服务器为监听模式

4.接收客户的链接请求

5.读取数据

6.发送数据

7.关闭通信

1、创建通信对象

int sockfd = socket(AF_INET, SOCK_STREAM, 0);

2、绑定服务器地址信息

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

sockfd:服务器对象

addr:服务器的地址信息

addrlen:服务器地址信息的大小

返回值: 成功 0
失败 -1

cpp 复制代码
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡的IP地址

if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
    perror("bind 失败");
    return -1;
}

一般绑定的都是本机的网卡地址,可以通过ip addr查看本机的ip地址

3、设置服务器为监听模式

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

sockfd:服务器socket

backlog:最大监听数 (同时可以有多少个客户端链接进来)

返回值:成功 0
失败 -1

cpp 复制代码
if (listen(sockfd, 10) < 0)
{
    perror("listen 失败");
    return -1;
}

4、接收客户的链接请求

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd:服务器对象

addr:对方的IP地址信息 (保存链接者的地址)

addrlen:对方的IP地址大小

返回值: 成功 返回一个新的文件描述符(用于与当客户端通信)
失败 -1

1.accept 接口会一直阻塞等待,直到有客户端链接为止!
2.accept 会返回一个新的文件描述符,该描述符用于读写通信!

cpp 复制代码
int new_socket = accept(sockfd, NULL, NULL);
if (new_socket < 0)
{
    perror("accept 失败");
    return -1;
}

char buf[1024] = {0};
scanf("%s", buf);
write(new_socket, buf, strlen(buf));

编写tcp客户端和服务端,实现双向通信

server.c

cpp 复制代码
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

void *send_task(void *arg)
{
    int *new_socket = arg;
    while (1)
    {
        char buf[1024] = {0};
        scanf("%s", buf);
        ssize_t n = write(*new_socket, buf, strlen(buf));
#if 0
        if (n < 0)
        {
            if (errno == EPIPE)
            {
                // 对端关闭了连接
                printf("客户端已关闭,线程退出\n");
                close(*new_socket);
                break;
            }
            perror("write 错误");
        }
#endif
    }
    return NULL;
}

int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind 失败");
        return -1;
    }

    if (listen(sockfd, 10) < 0)
    {
        perror("listen 失败");
        return -1;
    }

    while (1)
    {
        printf("等待客户端链接\n");
        int new_socket = accept(sockfd, NULL, NULL);
        if (new_socket < 0)
        {
            perror("accept 失败");
            return -1;
        }

        printf("新的客户端链接\n");
        pthread_t tid;
        pthread_create(&tid, NULL, send_task, &new_socket);

        // 读取客户端发送过来的数据
        char buf[1024] = {0};
        ssize_t n;
        while ((n = read(new_socket, buf, sizeof(buf))) > 0)
        {
            buf[n] = '\0';
            printf("客户端发送过来的数据:%s\n", buf);
        }
#if 0
        if (n == 0)
        {
            // 客户端关闭连接
            printf("客户端关闭连接,关闭套接字\n");
            close(new_socket);
        }
        else if (n < 0)
        {
            perror("read 错误");
        }
    }
#endif
    return 0;
}

client.c

cpp 复制代码
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int tcp_socket;

// 发送任务
void *send_task(void *arg)
{
    while (1)
    {
        // 3.发送数据
        char buf[1024] = {0};
        scanf("%s", buf);
        write(tcp_socket, buf, strlen(buf));
    }
}

int main(int argc, char *argv[])
{
    if (argc!= 3)
    {
        printf("请输入服务器的IP和端口\n");
        return 0;
    }
    // 1.创建客户端对象
    tcp_socket = socket(AF_INET, SOCK_STREAM, 0);

    // 2.设置服务器地址信息
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(atoi(argv[2]));      // 端口
    server_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP

    // 3.连接服务器
    int ret = connect(tcp_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (ret < 0)
    {
        printf("连接服务器失败\n");
        return 0;
    }
    else
    {
        printf("连接服务器成功\n");
    }

    // 创建一个线程
    pthread_t tid;
    pthread_create(&tid, NULL, send_task, NULL);

    char buf[1024] = {0};
    ssize_t n;
    while ((n = read(tcp_socket, buf, sizeof(buf))) > 0)
    {
        printf("服务端发来的数据:%s\n", buf);
    }
#if 0
    if (n == 0)
    {
        // 服务器关闭连接
        printf("服务器已关闭连接,客户端退出\n");
        close(tcp_socket);
    }
    else if (n < 0)
    {
        perror("读取数据出错");
        // 可以根据错误情况决定是否尝试重新连接或者直接退出
        close(tcp_socket);
    }
#endif
    return 0;
}

在demo中注释掉的代码是因为当服务器(客户端)进程退出时,客户端(服务器)的read函数会持续返回 0(表示对端关闭),导致无限循环接收空内容。需要在read函数返回 0 或者发生错误时进行适当处理,以避免这种情况。

相关推荐
Huazzi.4 分钟前
免费好用的静态网页托管平台全面对比介绍
前端·网络·github·web
张小小大智慧5 分钟前
HTTP 协议应用场景
网络·网络协议·http
安晴晚风7 分钟前
HTTP有哪些风险?是怎么解决的?
网络·网络协议·http
小黄编程快乐屋9 分钟前
深入理解 HTTP 请求头与请求体
网络·网络协议·http
不撸先疯。9 分钟前
docker镜像源配置、换源、dockerhub国内镜像最新可用加速源(仓库)
运维·docker·容器·dockerhub
荼靡60322 分钟前
shell(三)
linux·服务器·数据库
Dola_Pan31 分钟前
C语言:函数指针精讲
c语言·开发语言
zym大哥大31 分钟前
Linux的权限
linux·服务器
小宇python32 分钟前
动态调试对安全研究有什么帮助?
网络·安全·web安全
Stark-C34 分钟前
功能齐全,支持协作 | Docker部署一款支持多人共享的私密浏览器『n.eko』
运维·docker·容器