【linux网络编程】1、实现server端

139 阅读4分钟

「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。

基础概念及函数原型

网络套接字 soket

一个文件描述符指向一个套接字,该套接字内部由内核借助两个缓冲区实现。
在网络通信中,套接字是成对出现的。

网络字节序:

小端法存储(数据存储方式):高位高地址,低位存低地址
    int 0x12345678:
        12
        34
        56
        78
大端法存储:高位存低地址,低位存高地址

TCP/IP 协议规定:网络数据流英采用大端字节序,即:低地址高字节

htonl: 本地  网络(IP)
htons: 本地  网络(port)
ntohl: 网络  本地(IP)
ntohs: 网络  本地(port)

IP地址转换函数:

// 本地字节序转换为网络字节序
int inet_pton(int af, const char *src, void *dst);
    参数:
        1、af:AF_INET(TPv4) , AF_INET6(IPv6)
        2、src: 传入参数,ip地址(点分十进制)
        3、dst: 传出参数,转换后的网络字节序的IP地址
    返回值:
        成功:1
        若 src 是无效的地址,返回 0 
        若 af 不包含有效的协议,则返回-1,并且设置 errno 为 EAFNOSUPPORT

// 网络字节序转换为本地字节序
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
    参数:
        af: 同上
        src: 传入参数,网络字节序的Ip地址
        dst: 传出参数,转换后的本地字节序的ip地址
        size: dst缓冲区大小
    返回值:
        成功: dst
        失败: NULL

sockaddr 地址结构

/*
[int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);]
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 */
};

/* Internet address. */
struct in_addr {
    uint32_t   s_addr;     /* address in network byte order */
};
*/
struct sockaddr_in addr;
addr.sin_family = AF_INET/AF_INET6;
addr.sin_port = htons(9090);

addr.sin_addr.s_addr = htonl(INADDR_ANY); // 取出系统中有效的任意IP地址(二进制类型)
bind(fd,(struct sockaddr*)&addr,size);

socket函数

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数:
    1、domin: AF_INET, AF_INET6, AF_UNIX
    2、type: SOCK_STREAM , SOCK_DGRAM
    3、protocal: 0(一般默认传0)
返回值:
    成功:返回新套接字所对应的文件描述符
    失败:-1,设置errno

bind函数

#include <sys/socket.h>
给socket 绑定一个地址结构(IP+端口号)
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
    1、sockfd: 文件描述符
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(8888);
        addr.sin_addr.s_arrd = htonl(INADDR_ANY);
    2、addr: (struct sockaddr *)&addr
    3、addrlen: sizeof(addr) 地址结构的大小
返回值:    
    成功:0
    失败:-1,设置errno

listern 函数

设置同时与服务器建立链接的上限数,(同时进行3次握手的客户端数量)
int listen(int sockfd, int backlog);
参数:
    1、sockfd: socket()函数的返回值
    2、backlog: 上限数值,最大为128
返回值:
    成功:0
    失败:-1,设置errno

accept 函数

阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:
    1、sockfd: socket()函数的返回值
    2、addr: 传出参数,成功与服务器建立连接的那个客户端的地址结构(IP+端口号)
        socket_t client_addr_len = sizeof(addr);
    3、addrlen: 传入传出(client_addr_len),入:addr的大小,出:客户端addr的实际大小。
返回值:
    成功:能与服务器进行数据通信的 socket 文件描述符
    失败:-1,设置errno

connect函数

使用现有的 socket 与服务器进行连接

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen)
参数:
    1、sockfd: socket函数返回值
    2、addr: 传入参数,服务器的地址结构
    3、addrlen: 服务器地址结构的大小
返回值:
    成功: 0
    失败:-1,设置errno

如果不使用 bind 绑定客户端地址结构,系统会采用 “隐式绑定”

TCP流程分析

server端:
    1socket():创建socket
    2bind(): 绑定服务器的地址结构
    3listen(): 设置监听上限
    4accept(): 阻塞监听客户端连接
    5read(): 读从accept返回的socket中的数据
    6、小写-》大写 toupper()
    7write()
    8close()

client端:
    1socket(): 创建socket
    2connect(): 与服务器建立连接
    3write(): 写数据到socket
    4read(): 读转换后的数据
    5close()

测试案例

实现字符串小写转大写(客户端出入小写,服务端返回大写)

// server 端 : server.c

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

#define PORT 9527

// 检查错误
void sys_error(int ret, const char *str)
{
    if (ret == -1)
    {
        perror(str);
        exit(1);
    }
}

int main()
{
    // server fd , connection fd
    int lfd = 0, cfd = 0;
    int ret, i;        // 检测返回值
    int clit_addr_len; // 客户端地址长度
    char buf[BUFSIZ];

    struct sockaddr_in serv_addr, clit_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr.s_addr = ntohl(INADDR_ANY);
    lfd = socket(AF_INET, SOCK_STREAM, 0);
    sys_error(lfd, "socket error");
    // 绑定
    ret = bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    sys_error(ret, "bind error");
    // 监听
    ret = listen(lfd, 128);
    sys_error(ret, "listen error");

    // 客户端地址长度即时传入值又是传出值,这里先赋予初值
    clit_addr_len = sizeof(clit_addr);
    // 与客户端建立连接,返回客户端的 socket 描述符
    cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);
    sys_error(cfd, "accept error");

    while (1)
    {
        ret = read(cfd, buf, sizeof(buf));
        // 写出到标准输出
        write(STDOUT_FILENO, buf, ret);
        for (i = 0; i < ret; i++)
        {
            buf[i] = toupper(buf[i]);
        }

        write(cfd, buf, ret);
    }

    close(lfd);
    close(cfd);

    return 0;
}

运行结果

服务端:./server

图片.png

客户端:借助 系统工具 nc(net connection) 来进行测试

图片.png