用c语言编写简单的一对一服务器和多对一服务器

涉及到的一些函数:

socket

int socket(int domain, int type, int protocol);

//用于创建套接字,它是网络编程中的基础函数,用于建立网络通信。套接字是网络通信的端点,允许应用程序通过网络发送和接收数据。

//domain:表示套接字的地址族(Address Family)。常用的有 AF_INET(IPv4 地址族)和 AF_INET6(IPv6 地址族)。

//type:表示套接字的类型,常用的有 SOCK_STREAM(流套接字,用于TCP通信)和 SOCK_DGRAM(数据报套接字,用于UDP通信)等。

//protocol:表示使用的协议。通常设置为0,表示根据 domain 和 type 自动选择合适的协议。

socket函数返回一个整数值,如果创建套接字成功,它会返回一个非负的套接字描述符,用于后续的套接字操作。如果创建失败,返回 -1,表示出现了错误。

sockaddr_in

用于指定服务器地址、客户端地址

struct sockaddr_in {
    short sin_family;           // 地址族,通常设置为 AF_INET
    unsigned short sin_port;    // 端口号,需要使用 htons 函数转换为网络字节序
    struct in_addr sin_addr;    // IP地址,使用结构体 in_addr 表示
    char sin_zero[8];           // 保留字段,一般设置为 0
};

htons

在网络编程中,需要将数据在不同计算机之间进行传输,因此必须保证数据的字节序一致性。这就是 htons 函数的作用:将一个16位整数从主机字节序转换为网络字节序。具体来说,它将主机字节序的16位整数的字节顺序进行调整,以符合网络字节序的要求。

inet_addr

in_addr_t inet_addr(const char *cp);//用于将点分十进制字符串表示的IPv4地址转换为网络字节序的32位二进制整数表示的函数。

//cp: 是一个指向包含点分十进制字符串的指针,表示要转换的IPv4地址

bind

用于将一个套接字(socket)绑定到一个特定的地址和端口上,以便在这个地址和端口上监听传入的连接请求。在网络编程中,通常用于服务器端创建一个监听套接字,以便客户端能够连接到服务器。

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

//sockfd:要绑定的套接字的文件描述符。

//addr:一个指向 struct sockaddr 结构体的指针,表示要绑定的地址信息。 //addrlen:addr 结构体的长度。

listen

用于将一个已绑定的套接字设置为监听模式,以便可以接受客户端的连接请求。它告诉操作系统,该套接字将用于接受传入的连接,而不是主动发起连接。

int listen(int sockfd, int backlog);

//sockfd:要监听的套接字的文件描述符,通常是一个已经通过 bind 绑定了地址和端口的套接字。

//backlog:定义了内核允许在套接字队列中排队等待的最大连接数。这个参数通常设置为一个合适的值,通常在 5 到 128 之间。

accept

用于接受传入的连接请求,并创建一个新的套接字来处理与客户端的通信。它通常在服务器中的主循环中使用,以等待客户端连接。

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

//sockfd:表示已经通过 listen 函数设置为监听模式的套接字。

//addr:一个指向 struct sockaddr 结构体的指针,用于存储客户端的地址信息。 //addrlen:一个指向 socklen_t 类型的指针,用于指定 addr 结构体的大小。

accept 函数将会阻塞,直到有一个客户端连接请求到达为止。一旦连接请求到达,accept 函数将返回一个新的套接字文件描述符,该套接字表示与客户端的连接。同时,它会填充 addr 结构体以存储客户端的地址信息。

send

ssize_t send(int sockfd, const void *buf, size_t len, int flags);//用于将数据从一个已连接的套接字发送到另一个套接字。通常用于在客户端和服务器之间传输数据。

//sockfd:指定发送数据的套接字文件描述符。

//buf:一个指向要发送数据的缓冲区的指针。

//len:要发送的数据的长度,以字节为单位。

//flags:可选参数,通常可以设置为 0。

send 函数返回已发送的字节数,如果发生错误则返回 -1。在网络编程中,通常需要多次调用 send 函数来确保发送完整的数据。此函数通常是阻塞的,直到数据成功发送或发生错误。

connect

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//函数用于建立与远程主机的连接,通常在客户端程序中使用。

//sockfd:套接字文件描述符,指定要连接的本地套接字。

//addr:一个指向 sockaddr 结构体的指针,描述了远程服务器的地址和端口信息。

//addrlen:addr 结构体的大小。

连接成功,返回 0;如果出现错误,返回 -1。

recv

ssize_t recv(int sockfd, void *buf, size_t len, int flags);//用于从套接字接收数据

//sockfd:套接字文件描述符,指定从哪个套接字接收数据。 //buf:一个指向数据缓冲区的指针,接收到的数据将存储在这里。 //len:接收缓冲区的最大长度。 //flags:控制接收操作的标志位,通常设置为 0。

recv 函数的返回值是接收到的数据的字节数,如果出现错误,返回 -1。此函数可以阻塞等待数据,直到有数据到达,如果返回0,说明客户端关闭了。

服务器端代码(一对一)

监听指定端口(6000),并接受来自客户端的连接请求。一旦有客户端连接成功,它会接收客户端发送的数据,打印接收到的数据,并向客户端发送 "ok",然后关闭连接。

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

int main() 
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) 
    {
        perror("socket creation failed");
        exit(1);
    }

    struct sockaddr_in saddr, caddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
    if (res == -1)
    {
        perror("bind failed");
        exit(1);
    }

    res = listen(sockfd, 5);
    if (res == -1) 
    {
        perror("listen failed");
        exit(1);
    }

    while (1) 
    {
        int len = sizeof(caddr);
        int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
        if (c < 0) 
        {
            perror("accept failed");
            continue;
        }

        printf("accept c=%d\n", c);

        char buff[128] = {0};
        int n;

        while ((n = recv(c, buff, sizeof(buff) - 1, 0)) > 0)
        {
            buff[n] = '\0';
            printf("Received: %s\n", buff);

            int sent_bytes = send(c, "ok", 2, 0);
            if (sent_bytes < 0)
            {
                perror("send failed");
                break;
            }

            if (strcmp(buff, "end\n") == 0)
            {
                close(c);
                close(sockfd);
                exit(0);
            }
        }

        close(c);
    }

    close(sockfd);
    return 0;
}

服务器端代码(多对一)

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

void * fun(void* arg)
{
    int *p = (int*)arg;
    int c = *p;
    free(p);
     while( 1 )
        {
            char buff[128] = {0};
            int n = recv(c,buff,127,0);
            if ( n <= 0)// recv返回值n == 0 说明客户但关闭连接了
            {
                break;
            }

            printf("recv=%s\n",buff);
            send(c,"ok",2,0);
        }

        close(c);//关闭连接  挥手
        printf("client close\n");
}
int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);//套接字 文件描述符
    if ( sockfd == -1 )
    {
        exit(1);
    }

    struct sockaddr_in saddr,caddr;//套接字地址 ip port 
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if ( res == -1 )
    {
        printf("bind err\n");
        exit(1);
    }

    res = listen(sockfd,5);
    if ( res == -1 )
    {
        exit(1);
    }

    while( 1 )
    {
        int c = accept(sockfd,NULL,NULL);
        if ( c < 0 )
        {
            continue;
        }

        printf("accept c=%d\n",c);

        pthread_t id;
        int * p = (int*)malloc(sizeof(c));
        *p = c;
        pthread_create(&id,NULL,fun,(void*)p);
    }
}

客户端代码

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

int main() 
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) 
    {
        perror("Socket creation failed");
        exit(1);
    }

    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));//connect表示三次握手开始
    if (res == -1) 
    {
        perror("Connect error");
        exit(1);
    }

    char buff[128] = {0};
    printf("Input (type 'end' to exit):\n");

    while (1) 
    {
        fgets(buff, 128, stdin);
        if (strcmp(buff, "end\n") == 0)
        {
            break;
        }
        send(sockfd, buff, strlen(buff) - 1, 0);
        memset(buff, 0, 128);
        recv(sockfd, buff, 127, 0);
        printf("Received: %s\n", buff);
    }

    close(sockfd);
    exit(0);
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言中,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言中常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言中常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言中用于封装代码的单元,可以实现代码的复用和模块化。C语言中定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言中用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言中定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言中用于存储同类型数据的结构,可以通过索引访问和修改数组中的元素。字符串是C语言中用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言中用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言中通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值