网络编程(1)——accept实现多并发及相关细节问题

将之前在github上的部分心得搬运至csdn,首先是网络编程板块
1。下面展示一些 多线程实现

// An highlighted block
父进程:调用accept,回收子进程,利用信号的机制
		子进程:处理连接,收发数据
		流程:
			1.创建socket,得到监听文件描述符lfd————socket
			2.将lfd和ip和端口绑定,————bind
			3.设置监听————listen
			4.进入while(1)
				{
					阻塞等待新的连接到,得到cfd————accept
					//考虑异常打断,过滤errno中的EINTR||ECONNABORTED,继续生成不打断
					fork一个子进程,让子进程处理数据
					pid=fork;
					if(pid<0){//失败退出}
					else if(pid>0)
					{
						//父进程
						//因为新的连接在父进程里面并不使用,父进程只负责接受新的连接,因此关闭cfd
						close(cdf);

					}
					else
					{
						//子进程
						//关闭监听文件描述符,子进程只负责收发数据,因此关闭lfd
						close(lfd);
						while(1)
						{
						//收发数据
							n=read(cfd,buf,sizeof(buf));
							//对方关闭连接或者异常发生
							if(n<=0{break;}
							write(cdf,buf,n);
						}
						//发送完后子进程over掉,父进程保留,
						//关闭cfd链接
						close(cdf);
						exit(0);
					}
				}

2.下面展示一些 多线程

// An highlighted block
#pragma once
		void main(){
		1.创建socket
		lfd =Socket(AF_INET,SOCK_STREAM,0);
		2.绑定结构体
		struct sockaddr_in serve;
			归零
		bzero(&serve.sizeof(serve));
			定义ip类型
		serve.sin_family=AF_INET;
			定义端口
		serve.sin_port=htos(8888);
			定义IP地址,0.0.0.0,代表所有本地IP,因为有些电脑有多个网卡,但是这个IP可以代表所有网卡的IP
		serve.sin_addr.s_addr=htonl(INADDR_ANY);
			绑定
		Bind(lfd,(struct sockaddr *)&serve,sizeof(serve));
		3.设置监听,最多听128个客户端的内容
		Listen(lfd,128);
		int cfd;
		while(1){
			4.开始阻塞接收通信socket,如果出错break掉;
			cfd=accept();
			5.创建子线程
			//如果多个客户端同时连上,那么传入的参数只能是最后一个。
			int pthread_create(pthread_t *thread, const thread_work *attr,
					  void *(*start_routine) (void *), void *arg);

			//参数:
		//thread:线程id,将线程id存入,线程标识符,线程栈的起始地址,输出型参数
		   //attr:线程属性,NULL,8种选项
		   //函数指针:新线程执行该函数指针指向的代码,线程回调函数
		   //arg:传递给函数指针指向的函数的参数,线程回调函数的参数

		    //返回值:成功返回0,失败返回错误码,如:
		    //EAGAIN   描述: 超出了系统限制,如创建的线程太多。 
		    //EINVAL   描述: tattr 的值无效。
			6.分离线程,使资源独立回收,不会形成僵尸线程,因为linux其实就是利用进程来代替线程,本质上还是进程
			pthread_detach(threadID);
			}
			close(lfd);//如果退出监听状态,则关闭监听描述符;
			//关闭线程
			pthread_exit();
		}
		7.线程回调函数
		void* thread_work(void * arg){
			//获得文件描述符,之前的参数是void*,需要转成int,不要星
			int cfd=*(int*)arg;
			while(1){
				n=read(cfd,buf,sizeof(buf));
				if(n<=0){
				break;
				}
				//读完写,但是只能读完之后再写
				write(cfd,buf,n);
			}
			close(cfd);
		}

问题:

// A code block

			子线程能否关闭lfd?
				不能,因为文件描述符是共享而不是复制,关闭后主线程直接崩了,但是子线程还是可以正常使用
			主线程能否关闭cfd?
				不能,如果close之后,read直接报错,无法继续运行
			多个子线程能否共享cfd?
				不能,因为如果短时间发两个,那么同时两个连接上后会报端口已经被使用的错误,需要使用端口复用技术避免上述问题,使用以下代码;
				int opt=1;
				setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
				但是就算是用了之后也有以下问题:
					前面连接的cfd会被覆盖,因此前面的连接会无效,只有最后一个连上了,前面的压根没连
				解决方法:
					使用struct定义,将id及cfd,客户端的信息定义在一个结构体里面, 然后定义结构体数组
				struct INFO{
					int cfd;//同时也是标志位,如果初始化为-1,那么在其中一个断开连接后,后续连接可以继续使用这个结构体,使用遍历,如果满了直接close(cfd)
					pthread_t threadID;//也可以用threadID当标志位
					struct sockaddr_in client;//客户端结构体
				}
				INFO info[100];//定义了100个

3.IP地址转换之二进制和点分十进制


//新版:
	int inet_pton(int family, const char *strptr, void *addrptr);     //将点分十进制的ip地址转化为用于网络传输的数值格式
        返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
	const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len);     //将数值格式转化为点分十进制的ip地址格式
        返回值:若成功则为指向结构的指针,若出错则为NULL
//旧版:
	把ip地址转化为用于网络传输的二进制数值
		int inet_aton(const char *cp, struct in_addr *inp);
		inet_aton() 转换网络主机地址ip(如192.168.1.10)为二进制数值,并存储在struct in_addr结构中,即第二个参数*inp,函数返回非0表示cp主机有地有效,返回0表示主机地址无效。(这个转换完后不能用于网络传输,还需要调用htons或htonl函数才能将主机字节顺序转化为网络字节顺序)
		in_addr_t inet_addr(const char *cp);
		inet_addr函数转换网络主机地址(如192.168.1.10)为网络字节序二进制值,如果参数char *cp无效,函数返回-1(INADDR_NONE),这个函数在处理地址为255.255.255.255时也返回-1,255.255.255.255是一个有效的地址,不过inet_addr无法处理;
	将网络传输的二进制数值转化为成点分十进制的ip地址
		char *inet_ntoa(struct in_addr in);
		inet_ntoa 函数转换网络字节排序的地址为标准的ASCII以点分开的地址,该函数返回指向点分开的字符串地址(如192.168.1.10)的指针,该字符串的空间为静态分配的,这意味着在第二次调用该函数时,上一次调用将会被重写(复盖),所以如果需要保存该串最后复制出来自己管理!

4.粘包问题如何解决:

        方案1:
	     包头+数据
	     如4为数据长度+数据--->00101234567890,
	     其中0010标识数据长度,一共10个字节,后米娜1234567890标识具体的10个数据
	     值得注意的是,记得字节序的转换,只要是数字就要转换,字符串不用转换。
             另外,发送端和接收端可以商议为更复杂的报文结构,相当于对方约定的一个协议(接口)
        方案2:
	    结尾追加特殊字符比如\$
        方案3:
	    发送方和接收方约定发送和接收 的字节长度,接收方确定接收长度128个字节就行,如何实现?
	    ```

5.多进程父子进程异步等待回收进程信息

// A code block
 5.1 在父进程中bind之后,进入while循环之前,写入以下代码:
        linux下:
        //创建信号集
        sigset_t mask;
        //信号集设置为空、
        segemptyset(&mask);
        //加入子进程结束信号
        sigaddset(&mask,SIGCHILD);
        //阻塞SIGCHILD信号,因为后面在accept之后的主进程中需要对SIG_CHILD处理函数进行注册,但是我们不希望注册期间子进程结束,然后自动默认处理sigchild信号因此在这个阻塞
        //这样一来就算是注册期间子进程结束,也会阻塞等待主进程处理函数注册完成,然后再按照处理函数对子进程结束信号进行处理,从而达到资源回收的目的
        sigprocmask(SIG_BLOCK,&mask,NULL)
  5.2 再父进程中注册回调函数,处理SIGCHILD信号
        //定义信号注册函数对象
        struct sigaction act;
        //初始化赋值,这一步定义信号处理函数为waitchild;
        act.sa_handler=waitchild;
        act.sa_flags=0;
        //这一步感觉没必要……;
        sigemptyset(&act.sa_mask);
        //注册信号处理函数,signal和sigaction区别:sigcation可以移植,signal不可以,尽量用sigaction
        sigaction(SIGCHILD,&act.NULL);
        //解阻塞sigchild信号
        sigprocmask(SIG_UNBLOCK,&mask,NULL);
  问:
        一定要在accept之后的主进程中写这个吗?
  答:
        个人感觉不需要,因为其实你在fork之后写反而需要考虑到信号和处理函数是否注册的问题,但是如果在之前写,就不需要考虑这个问题
        相当于建立了软连接但是子进程还没有启动,因此其实不需要先阻塞然后再解阻塞,而且如果写在accept之后(while里面)会造成以下问题:
                多次反复绑定和解阻塞信号,造成资源的浪费和结构的冗余;
              
 5.3定义信号处理函数,信号处理函数在子线程结束发送SIG——CHILD信号后被调用,
	void waitchild(int signo){
	    pid_t wpid;
	    while(1){
	    	//NULL标识默认处理回收资源操作
		//WNOHANG标识非阻塞,与wait本质区别,可以一边等一边执行下面的代码
	        wpid=waitpid(-1,NULL,WNOHANG);
		if(wpid>0){
		    //处理成功
		}
		else if(wpid=0||wpid==-1){
		    //=0,标识子进程还活着,处理失败
		    //=-1,子进程已经全部没有了
		    break;
		}
	    }
	}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无情の学习机器

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值