高并发服务器-多进程

文章讲述了在B站就业班中,如何改进服务端代码以支持多个客户端同时访问,避免了因accept和read阻塞导致的问题,并介绍了TCP连接的三次握手、粘包问题及其解决方案,包括使用报头、数据包定长等方法。
摘要由CSDN通过智能技术生成

B站就业班视频-对应52课

28_哔哩哔哩_bilibili

上一篇文章,如果再多开一个终端(客户端),发送小写字母,想转换成大写,就没有反应了,关闭这个客户端,服务器也没有丝毫反应,说明上篇代码只能1对1 ,这是不现实的,所以这节课来讲如何实现多个客户端同时访问服务端,服务端能挨个回信息。

原因:

由于accept 和read函数都会阻塞,如当read的时候,不能调用accept接受新的连接,当accept阻塞等待的时候,不能read读数据。

注意,这篇代码只是服务端的,客户端代码跟我昨天文里一样

1.基础思路

socket()创建套接字

bind 将套接字文件描述符,和本地一个IP 端口联系在一起

listen

accept()创建一个连接描述符,一般阻塞在这里;;等待;;

fork () 父进程关闭上述描述符   子进程关闭套接字文件描述符

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

//将网络大端模式的地址,转化为字符串
//const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

int main() {
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd <0){
        printf("socket fun error/n");
        return -1;
    }

    //定义一个地址结gou体
    struct sockaddr_in myad;
    //清空  memory
    bzero(&myad, sizeof(myad));
    myad.sin_family =AF_INET;
    myad.sin_port =htons(8888);
    myad.sin_addr.s_addr = htonl (INADDR_ANY);

    int ret = bind(sfd, (struct sockaddr *)&myad, sizeof(myad));
    if (ret<0) {
        printf("bind error/n");
        return -1;
    }

    //将socket从主动变为被动(服务器必备),这样可以监听来自客户的请求
    listen( sfd,128);   
    // int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    //addr是传出参数,保留客户的地址,所以这是客户地址

    //定义一个地址结gou体接受客户端地址结构体
    struct sockaddr_in dst;
    bzero(&myad, sizeof(myad));
    dst.sin_family =AF_INET;
    dst.sin_port =htons(8888);
    socklen_t dstleng = sizeof(dst);
    while (1) {
	//jieshou client connect 
	int newfd = accept (sfd, (struct sockaddr* )&dst, &dstleng);
        if (newfd <0) {
	printf("服务端,accept error、\n");
        return -1;
        }
        char addstring[128];
        memset(addstring, 0x00,128);
        printf("服务器端,还没有fork,客户端地址是:\n");
        printf("IP:%s,PORT: %d\n", inet_ntop(AF_INET, &dst.sin_addr.s_addr,addstring,sizeof(addstring)),ntohs(dst.sin_port));
       //fork  child tackle message, father listen
       pid_t clientpid;
	clientpid = fork();
	if( clientpid<0) {
	    printf("fork error\n");
	    return -1;
	}
	else if (clientpid>0){
	    //father process ,close message 文件描述符
	    close (newfd);
	}
	else if (clientpid ==0){
	    //子进程,打印端口,发现一个子进程1个端口,当然ip共享 
            printf("child process,IP:%s,PORT: %d\n", inet_ntop(AF_INET, &dst.sin_addr.s_addr,addstring,sizeof(addstring)),ntohs(dst.sin_port));
            //把客户端发送过来的数据读出来
            int i =0;
            int n =0;
            char buf[1024];
            while (1){
                memset(buf,0x00,sizeof(buf));
        	n = read (newfd,buf,sizeof(buf));
        	if (n<=0){
            	    printf("有一个客户端已关闭,或者读到的字符为0/n");
	            break;
        	}
        	for (i=0;i<n;i++){
            	    buf[i] =toupper(buf[i]);
                 }
    		//把数据传回客户端,变成大写了已经
        	write (newfd, buf, n);
    	    }
    	close(newfd);
	exit(0); //子进程退出,避免再次fork子进程
	}
    }
close (sfd);//这行是自己写的,老师有没有这行没看清.
return 0;
}

注意,其实这里面很多函数老师用的是封装过的,,我这里还是写的原始函数 

2.本节课其他知识点 

(在CSDN上搜索相应知识点自学)

2.1 TCP连接时的 3次握手和4次挥手过程,并且写出其中的数字

2.2 TCP数据格式,IP数据格式

2.3 流量控制

当接受和发送的速度不匹配的时候,(例如:fast sender, slow receiver)解决方案(有很多),着重讲解(滑动窗口)

当socket编程中,我们调用read write函数的时候,是从内核的缓冲区中调集数据,一个是发送缓冲区,另一个是接受缓冲区,被同一个fd(文件描述符)所控制。

2.4 mss 和MTU

MTU:最大传输单元,通信术语,Maximum Transmission Unit,  是指一种通信协议的某一层上面能通过的最大数据包大小(以字节为单位)。最大传输单元这个参数通常与通信接口有关(网络接口卡,串口等),这个值如果设置为太大会导致丢包重传的时候重传的数据量较大,图中的最大值是1500,其实是一个经验值。

mss :最大报文长度,只是在建立连接的时候,告诉对方我最大能够接收多少数据,在数据通信的过程中就没有mss了

2.5封装函数

封装函数的优点 --节省重复代码

在封装函数的时候,返回值是-1的时候表示失败了。

errno宏:可以用 man errno(命令)来查询宏名称和对应编号,例如

ECHILD          No child processes (POSIX.1-2001).

2.6 errno

老师的讲义,和许多其他文章说,在/usr/include/asm-generic/errno.h文件中包含了所有的编号。但是我cat errno.h 只有一堆注释和以下内容——

#ifndef _ERRNO_H

#define _ERRNO_H 1

#include <features.h>

/* The system-specific definitions of the E* constants, as macros.  */

#include <bits/errno.h>

/* When included from assembly language, this header only provides the

   E* constants.  */

#ifndef __ASSEMBLER__

__BEGIN_DECLS

/* The error code set by various library functions.  */

extern int *__errno_location (void) __THROW __attribute_const__;

# define errno (*__errno_location ())

# ifdef __USE_GNU

/* The full and simple forms of the name with which the program was

   invoked.  These variables are set up automatically at startup based on

   the value of argv[0].  */

extern char *program_invocation_name;

extern char *program_invocation_short_name;

#include <bits/types/error_t.h>

# endif /* __USE_GNU */

__END_DECLS

#endif /* !__ASSEMBLER__ */

#endif /* errno.h */)

关于socket编程中,忽略以下两种情况的errno,不认为是失败,而是返回循环,继续尝试

    1. errno =EINTR

像accept ,read这样能够引起阻塞的函数,若被信号打断,由于信号的优先级较高,会优先处理信号,等信号处理完成后,会使accept 或者read解除阻塞,然后返回,此时返回值是-1,设置errno =EINTR

    1. errno = ECONNABORTED

表示连接被打断

***********   ************************   *************

以下块引用内容来自CSDN博客,博主iteye_5425的《几个常见的 Socket 连接错误及原因》

ECONNABORTED

     该错误被描述为“software caused connection abort”,即“软件引起的连接中止”。原因在于当服务和客户进程在完成用于 TCP 连接的“三次握手”后,客户 TCP 却发送了一个 RST (复位)分节,在服务进程看来,就在该连接已由 TCP 排队,等着服务进程调用 accept 的时候 RST 却到达了。POSIX 规定此时的 errno 值必须 ECONNABORTED。源自 Berkeley 的实现完全在内核中处理中止的连接,服务进程将永远不知道该中止的发生。服务器进程一般可以忽略该错误,直接再次调用accept。/* Linux system */

include/asm-alpha/errno.h:#define ECONNABORTED 53 /* Software caused connection

abort */

include/asm-generic/errno.h:#define ECONNABORTED 103 /* Software caused

connection abort */

include/asm-mips/errno.h:#define ECONNABORTED 130 /* Software caused connection

abort */

***********   ************************   *************

2.7 粘包问题

多次发送,接收端不能分辨第一次发送多少,第二次发送多少。例如:第一次发送30个,第二次发送10个,接受的时候每次最多接20个,那么第一次剩下10个就在第二次接受了。

解决方案

(1)报头+数据

报头设置为4字节,报头里写明了数据长度,这样read函数跳过4字节之后,就知道后面要读多少了,避免了粘包问题。

(2)添加结尾标记。但是需要双方协商,还需要挨个去判断。

(3)数据包定长。例如,约定每个包就是128字节。

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值