Socket与多线程实践

上节回顾:
我们从应用层发数据发送方和接收方在不同的建筑里面,传输层有两个协议:TCP / UDP
TCP / UDP 可以看作平行宇宙,互不干扰,接收方和发送方可以用TCP / UDP
服务器的每个端口是相互隔离的。
将TCP 理解为快递公司A, UDP 理解为快递公司B
socket 理解为快递员。
分层:
第一层:有 2 16 2^{16} 216 端口,快递公司给每个用户一个端口,想要使用的时候,需要申请一个,但是与此同时还需要雇佣一个快递员socket。不同的快递公司,端口不会产生冲突,如果既想使用A,又想使用B,那么需要在两个不同的公司,分别雇佣两个快递员。
两端的用户想要沟通,需要每个用户有一个快递。在沟通的过程中,两端的快递公司需要保持统一。程序拿到数据之后,给socket, socket会把信息收入快递公司,然后a处的快递公司会以某种不可知的方式将快递运输到b处的快递公司。
看似端口是1 v 1 的,但是在accept 的时候,会产生一个新的sockfd.
最开始 使用 socket 函数,
然后 使用 bind 让套接字 和端口(x)绑定
原来的sockfd 是主动的,使用listen 变成了被动连接
原来的sockfd 可以接收任何 accept 然后三次握手,返回一个 newfd,这个newfd 负责刚刚建立的连接,这个newfd 也是关联到端口(x),来了信息,我们怎么区分?
所以,我们需要复用,新的文件知道所对应的端口是谁,以及连他的人是谁,所以sockfd 是一个四元组 (from ip, from port, to ip, to port),信息到了端口,然后内核知道这个端口有几个文件,然后将这个信息给哪个文件。(先透明化,不去细说)。
不用的文件标识符 需要关闭,总共可以分配1024个文件,如果多余的文件描述符不关闭,后面的连接会连接失败,accept 返回的套接字用完就关。

在bind的时候,端口有一定的要求,设置端口的时候,小于1024 的周知端口的时候,会有权限不足,当实在想使用这个端口,可以用sudo 提权。

accept 负责三次握手,接收syn 包,当收到syn 的时候,会返回 syn + ack …
但是在握手的时候也可能会出错,如果这个时候系统出现错误,或者文件描述符不够,可能完不成三次握手,就会导致失败。
成功的时候,会创建一个新的文件描述符,标识对方的ip, 端口,以及信息。
想要交互,但是计算机一个进程没有任何分支的时候,只有会一个时候和一个人沟通,如果未来很长的时间,有很多人想要和你交互,但是,由于一直和那一个人沟通,其他人在与你三次握手的时候,会一直阻塞。
于是这里会创建子进程。
在这里子进程不需要server_listen ,所以会关闭sever_listen 文件,父进程可以关闭新建的文件描述符。(父进程关闭父进程不需要的文件描述符),在系统中,当两个进程打开一个文件,那么这个文件在系统中的连接数会增加,在关闭之后,系统连接数只会减一,而不是彻底关闭这个文件。(不但不会影响业务,反而会增加业务的健壮性)

什么时候收到结束信息? 当 recv 的返回值为0,返回值为0是关闭连接,小于0是发生错误。

recv 类似一个快递员,去搬运东西,给他一定的缓冲区,要他把搬运缓冲区这么大的东西,当快递员去了,发现没有这么大的数据,会有多少,拿多少。
这样会导致一个问题:拆包问题
当发了两个缓冲区的东西,recv 的时候会收到一部分,后续我们需要拆包。。
当发了极小于缓冲区的东西,recv 可能会收到的东西中夹杂着火柴盒,这样我们需要进行粘包问题。
所以我们需要意识。发的包不一定就是收到包。
如果recv 一次没有收全,下一次调用时手动的。

我们做不到一次只收一个包。底层的IP 网络,发送包的时候,会以不同的路线去往目的地,所以在去的路上可能出现有一个包会很晚才回到,这样,后续的包会等待这个包的到来。

bind 中 的backlog 中的数值,是正在建立连接的数量,而不是已经连接的数量。

这里有一个问题,当在多进程的时候,父进程创建了一个套接字,在fork 的时候,会创建子进程,子进程会继承这个套接字。那么当消息来的时候,这个信息给谁?
谁先收到给谁,但是这不是我们想要的,所以在多进程中,子进程的第一句,通常是关闭父进程中创造的sockfd 文件(对父进程不会产生影响)

信号:
信号随系统的变化有差异性

#include <sys/types.h>
#include <signal.h>

int kill(pid_t, int sig);
/*
	* send signal to a process
	* 给一个进程发信号
	* 信号类似于ctrl + c 或者是时钟。。。
	* 如果sig = 0, 不会发出信号,但是还会对进程进行检查,没有权限,或者其他也会报错
	* kill -l
	* 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 	* 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
	* 11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
	* 16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
	* 21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
	* 26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
	* 31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
	* 38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
	* 43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
	* 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
	* 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
	* 58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
	* 63) SIGRTMAX-1  64) SIGRTMAX
	* 捕鼠器原理: 一个信号来了,后面的信号可能会错失
	*
	* 
	* signal
	* typedef void (*sighanler_t)(int);
	* sighandler_t signal(int signum, sighandler_t handler);
	* 信号根据UNIX 的不同的版本,有差异性,为了避免这些东西,使用sigaction 代替
	* signal 设置 signum 给处理,一般可能是
	* SIG_IGN 忽略信号
	* SIG_DFL :signal_default 默认信号, 如果处理设置这个,那么信号的默认操作会关联到这个
	* 或者是程序员定义的一个信号函数
	* 这个信号是指定给进程的,
	* 如果设置了这个函数,那么首先要么将这个函数设置为SIG_DFL, 要么这个信号会被则色,然后信号处理函数被调用。如果信号处理之前,信号被阻塞,那么下一个信号可能不被阻塞,直接过去。也就是说会无法处理下一个信号,所以也叫捕鼠器原理的处理。
	* 后面会学到解决方法
*/
/*************************************************************************
        > File Name: 1.server.c
        > Author: Monster
        > Mail: 2788490159@qq.com
        > Created Time: Thu 03 Jun 2021 02:01:53 AM CST
 ************************************************************************/

#include "head.h"

int main(int argc, char **argv) {
    int server_listen, sockfd, port;//server_listen : 服务器监听,舍弃了主动联系的功能
    if (argc != 2) {
        fprintf(stderr, "Usage : %s port!\n", argv[0]);
        exit(1);
    }
    port = atoi(argv[1]);
    if ((server_listen = socket_create(port)) < 0) {// 如果内部申请内部出错会输出信息
        perror("socket_create");
        exit(1);
    }
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    while (1) {//死循环,无期限等待

        if ((sockfd = accept(server_listen, (struct sockaddr *)&client, &len)) < 0) {//只等syn包,有syn包就回ack,三次握手...
            perror("accpept");
            exit(1);//三次握手的过程中有出错的可能
        }
        printf("<%s> is online\n", inet_ntoa(client.sin_addr));
        pid_t pid;
        if ((pid = fork()) < 0) {//如果不用子进程,父进程会阻塞在数据传输中,无法进行对其他的进程的交互
            exit(1);
        }
        if (pid == 0) {
            close(server_listen);//子进程将server_listen关闭
            while (1) {
                char buff[512] = {0};
                char tobuff[512] = {0};
                size_t ret = recv(sockfd, buff, sizeof(buff), 0);// recv 每一次将能收到的东西都收下,但是会有一个拆包问 题,如果信息太多,一次收不下,或者信息太少,容易和其他的信息粘连在一起,所以会有粘包,拆包问题
                // 如果recv 一次没有收完,需要用户主动再次recv, 系统不会自动recv
                if (ret <= 0) {
                    printf("<%s> is offline\n", inet_ntoa(client.sin_addr));
                    close(sockfd);
                    exit(1);
                }
                printf("<%s> says : %s\n", inet_ntoa(client.sin_addr), buff);
                if (strcmp(buff, "bye~886") == 0) {
                    sprintf(tobuff, "We will say bye and over, bye~");
                    send(sockfd, tobuff, strlen(tobuff), 0);
                    close(sockfd);
                    exit(1);
                } else {
                    sprintf(tobuff, "I`ve recved the mes : %s", buff);
                    send(sockfd, tobuff, strlen(tobuff), 0);
                }
            }
            return 0;
        } else {
            close(sockfd); // 关联父进程和子进程,这里关闭减少了sockfd在进程中的关联数,当关联数为0时,会删除该文件
        }
    }
}
/*************************************************************************
        > File Name: 1.client.c
        > Author: Monster
        > Mail: 2788490159@qq.com
        > Created Time: Thu 03 Jun 2021 02:08:20 AM CST
 ************************************************************************/

#include "head.h"

int sockfd;

void logout(int signum) {
    close(sockfd);
    printf("\nByeBye!\n");
    exit(0);
}

int main(int argc,char **argv) {
    int port;
    char buff[512] = {0}, ip[20] = {0};
    char tobuff[512] = {0};
    if (argc != 3) {
        fprintf(stderr, "Usage : %s ip port \n",argv[0]);
        exit(1);
    }

    strcpy(ip, argv[1]);
    port = atoi(argv[2]);
    //signal(SIGINT, SIG_IGN); 设置这个会忽略掉退出的信号
    signal(SIGINT, logout);// 设置信号
    if ((sockfd = socket_connect(ip, port)) < 0) {
        perror("socket_connect");
        exit(1);
    }

    while (1) {
        scanf("%[^\n]s", buff);
        getchar();
        if (!strlen(buff)) continue;
        send(sockfd, buff, strlen(buff), 0);
        recv(sockfd, tobuff, sizeof(tobuff), 0);
        if (strlen(tobuff)) printf("recv : %s\n", tobuff);
        bzero(tobuff, sizeof(tobuff));
        bzero(buff, sizeof(buff));
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值