您好,欢迎来到汇意旅游网。
搜索
您的当前位置:首页alin的学习之路(Linux网络编程:二)(三次握手四次挥手、read函数返回值、错误函数封装、多进程高并发服务器)

alin的学习之路(Linux网络编程:二)(三次握手四次挥手、read函数返回值、错误函数封装、多进程高并发服务器)

来源:汇意旅游网

alin的学习之路(Linux网络编程:二)(三次握手四次挥手、read函数返回值、错误函数封装、多进程高并发服务器)

1. 服务器获取客户端地址和端口号

char clt_IP[1024];
clt_addr_len = sizeof(clt_addr);
cfd = Accept(lfd,(struct sockaddr*)&clt_addr,&clt_addr_len);
printf("客户端ip:%s,port:%d接入服务器\n",
	inet_ntop(AF_INET,&clt_addr.sin_addr.s_addr,clt_IP,sizeof(clt_IP)),
    ntohs(clt_addr.sin_port));

2. TCP 通信时序

1. 三次握手

  • 注意:三次握手由客户端发出请求,而非服务器

SYN和序号 + ACK和确认序号表示该序号之前的数据包全部接收到,该序号和确认序号也就是TCP通信的安全之处

2. 四次挥手

  1. 主动关闭连接请求端(客户端),发送 FIN 标志位 , 携带序号。
  2. 被动关闭连接请求端(服务端),接收 FIN, 发送 ACK 标志位,携带 确认序号。
    —— 半关闭完成。
  3. 被动关闭连接请求端(服务端),发送 FIN 标志位 , 携带序号。
  4. 主动关闭连接请求端(客户端),接收 FIN, 发送 ACK 标志位,携带 确认序号。
    —— 最后一个 ACK 被 收到, 标志着 4次挥手完成。 TCP 连接关闭。

3. 半关闭

半关闭的实现, 依赖底层内核实现 socket 的原理。

半关闭关闭的是socket缓冲区,也就是关闭了数据的发送,但标志位等在TCP数据报格式的前面

4. 滑动窗口

通知通信的对端, 本端缓冲区的剩余空间大小(实时), 保证数据不会丢失。
滑动窗口 在 TCP 协议格式中存储, 上限 为 65536

3. read函数的返回值

  1. > 0 实际读到的字节数。
  2. = 0 已经读到结尾。(对端关闭)【重点】
  3. -1 应该进一步判断 errno 的值。
    errno == EAGAIN or EWOULDBLOCK : 设置了非阻塞方式读,但没有数据。
    errno == EINTR : 慢速系统调用,被信号中断。
    errno == “其他情况”。 异常。

4. 错误处理函数封装

因为在socket编程中,每一个函数的调用都需要检查返回值,并且所有的函数都加上返回值后使得代码变得冗余。

解决办法:可以自行封装函数,将函数名写为首字母大写,这样在vim中还可以使用K进行跳转

  • wrap.c
    • 存放 网络通信常用的 自定义函数实现。
    • 命名方式: 系统调用函数首字母大写, 方便跳转 man 手册。
    • 函数功能,添加系统调用的 出错场景。
    • 在 server.c / client.c 中, 调用自定义函数。
  • 编译:
    server.c 和 wrap.c 编译生成 server
    client.c 和 wrap.c 编译生成 client
  • wrap.h
    存放 网络通信常用的 自定义函数原型。

wrap.c

#include "wrap.h"

void sys_err(const char* str)
{
    perror(str);
    exit(1);
}

int Socket(int domain, int type, int protocol)
{
    int n = 0;

    n = socket(domain,type,protocol);
    if(n < 0)
        sys_err("socket error");

    return n;
}

int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
    int n = 0;

again:
    n = accept(sockfd,addr,addrlen);
    if(n < 0)
    {
        if(errno == EINTR || errno == ECONNABORTED)
            goto again;
        else
            sys_err("accept error");
    }

    return n;
}

int Bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen)
{
    int n = 0;

    n = bind(sockfd,addr,addrlen);
    if(n < 0)
        sys_err("bind error");

    return n;
}

int Listen(int sockfd, int backlog)
{
    int n = 0;

    n = listen(sockfd,backlog);
    if(n < 0)
        sys_err("listen error");

    return n;
}

int Connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen)
{
    int n = 0;

    n = connect(sockfd,addr,addrlen);
    if(n < 0)
        sys_err("connect error");

    return n;
}

ssize_t Read(int fd, void *buf, size_t count)
{
    ssize_t n = 0;
again:
    n = read(fd,buf,count);
    if(n < 0)
    {
        if(errno == EINTR)
            goto again;
        else
            sys_err("read error");
    }

    return n;
}

ssize_t Write(int fd, const void *buf, size_t count)
{
    ssize_t n = 0;
again:
    n = write(fd,buf,count);
    if(n < 0)
    {
        if(errno == EINTR)
            goto again;
        else
            sys_err("write error");
    }

    return n;
}

int Close(int fd)
{
    int n = 0;

    n = close(fd);
    if(n < 0)
        sys_err("close error");

    return n;
}

ssize_t Readn(int fd, void *vptr, size_t n)
{
    ssize_t nread;
    ssize_t nleft = n;
    char* ptr = (char*)vptr;
    while(nleft > 0)
    {
        nread = read(fd,ptr,nleft);
        if(nread < 0)
        {
            if(errno == EINTR)
                nread = 0;
            else
                return -1;
        }
        else if(nread == 0)
            break;

        nleft -= nread;
        ptr += nread;
    }
    return n-nleft;
}

ssize_t Writen(int fd, const void *vptr, size_t n)
{
    ssize_t nwrite;
    ssize_t nleft = n;
    char* ptr = (char*)vptr;
    while(nleft > 0)
    {
        nwrite = write(fd,ptr,nleft);
        if(nwrite < 0)
        {
            if(errno == EINTR)
                nwrite = 0;
            else
                return -1;
        }
        else if(nwrite == 0)
            break;

        nleft -= nwrite;
        ptr += nwrite;
    }
    return n;
}

static ssize_t my_read(int fd, char *ptr)
{
    static int read_cnt;
    static char* read_ptr;
    static char read_buf[100];

    if(read_cnt <= 0)
    {
again:
        read_cnt = read(fd,read_buf,sizeof(read_buf));
        if(read_cnt == -1)
        {
            if(errno == EINTR)
                goto again;
            else
                return -1;
        }
        else if(read_cnt == 0)
            return 0;

        read_ptr = read_buf;
    }
    --read_cnt;
    *ptr = *read_ptr++;

    return 1;
}

ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
    ssize_t n,rc;
    char c,*ptr;
    ptr = (char*)vptr;

    for(n=1 ;n<maxlen ;++n)
    {
        rc = my_read(fd,&c);
        if(rc == 1)
        {
            *ptr++ = c;
            if(c == '\n')
                break;
        }
        else if(rc == 0)
        {
            *ptr = 0;
            return n-1;
        }
        else if(rc == -1)
            return -1;
    }
    *ptr = 0;
    return n;
}

5. 高并发服务器(多进程)

1. 程序流程

  1. Socket函数创建监听套接字

  2. Listen函数设置最大监听数

  3. while(1){

  4. Accept函数阻塞等待客户端接入,创建通信套接字

  5. fork创建子进程

    子进程:

    1. Close关闭监听套接字文件描述符
    2. Read函数接收客户端发送的数据(当Read的返回值为0的时候,Close通信套接字文件描述符,子进程退出)
    3. 小写转大写
    4. Write函数将处理后的数据发送给客户端
    5. Close关闭通信套接字文件描述符

    父进程:

    1. 关闭通信套接字文件描述
    2. 注册 SIGCHLD 的信号捕捉函数,用来回收子进程
    3. continue 继续回到循环,等待新的客户端接入
  6. }

2. 代码实现

#include <ctype.h>
#include "wrap.h"
#include <sys/wait.h>
#include <signal.h>

#define SRV_PORT 9999

void func(int signo)
{
    while(1)
    {
        int ret = waitpid(-1,NULL,0);
        if(ret == -1)
            break;
    }
    return ;
}

int main()
{
    int lfd,cfd;
    socklen_t clt_addr_len;
    pid_t pid;
    char clt_IP[1024];
    char buf[BUFSIZ] = {0};

    struct sockaddr_in srv_addr,clt_addr;
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(SRV_PORT);
    srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);


    lfd = Socket(AF_INET,SOCK_STREAM,0);

    Bind(lfd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));

    Listen(lfd,128);

    char SRV_IP[1024];
    printf("服务器已开启ip:%s,port:%d\n",
           inet_ntop(AF_INET,&srv_addr.sin_addr.s_addr,SRV_IP,sizeof(SRV_IP)),
           ntohs(srv_addr.sin_port));

    while(1)
    {
        clt_addr_len = sizeof(clt_addr);
        cfd = Accept(lfd,(struct sockaddr*)&clt_addr,&clt_addr_len);

        printf("客户端ip:%s,port:%d接入服务器\n",
               inet_ntop(AF_INET,&clt_addr.sin_addr.s_addr,clt_IP,sizeof(clt_IP)),
               ntohs(clt_addr.sin_port));

        pid = fork();
        if(-1 == pid)
        {
            sys_err("fork error");
        }
        else if(0 == pid)
        {
            Close(lfd);
            break;
        }
        else
        {
            Close(cfd);

            struct sigaction act = {
                .sa_handler = func
            };
            int ret = sigaction(SIGCHLD,&act,NULL);
            if(-1 == ret)
            {
                sys_err("sigaction error");
            }
            
            continue;
        }
    }

    if(0 == pid)
    {
        while(1)
        {
            int ret = Read(cfd,buf,sizeof(buf));
            if(0 == ret)
            {
                printf("客户端ip:%s,port:%d断开连接\n",
                       clt_IP,ntohs(clt_addr.sin_port));
                break;
            }
            for(int i=0 ;i<ret ;++i)
            {
                buf[i] = toupper(buf[i]);
            }
            Write(cfd,buf,ret);
            printf("发给ip:%s,port:%d数据:%s",clt_IP,ntohs(clt_addr.sin_port),buf);
        }
        Close(cfd);
    }
    return 0;
}

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- hids.cn 版权所有 赣ICP备2024042780号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务