当前位置: 首页 > article >正文

150 Linux 网络编程6 ,从socket 到 epoll整理。listen函数参数再研究

一 . 只能被一个client 链接 socket例子

此例子用于socket 例子,
该例子只能用于一个客户端连接server。
不能用于多个client 连接  server

socket_server_support_one_clientconnect.c


/*
此例子用于socket 例子,
该例子只能用于一个客户端连接server。
不能用于多个client 连接  server


*/
#include "wrap.h"
#define SERVER_PORT 8888

int main(int argc, const char* argv[]) {

	int lfd;
	// 第一步:创建 lfd
	lfd = Socket(AF_INET, SOCK_STREAM, 0);


	// 第二步: 给lfd 绑定 IP 和 PORT,这里需要将 port 和IP 从小端转成大端
	struct sockaddr_in sockaddrin_server;
	sockaddrin_server.sin_family = AF_INET;
	sockaddrin_server.sin_port = htons(SERVER_PORT);
	sockaddrin_server.sin_addr.s_addr = htonl(INADDR_ANY);

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

	//第三步:设定监听上限,这个监听上限的意思是:同时只能有8个客户端和此 server连接,我们这个server只能要一个客户端和我们连接,因此这个值在这里没有具体意义
	Listen(lfd, 8);

	// 第四步 :阻塞,并等待客户端连接. accept的第二个参数是传入传出参数,代表的是:成功于服务器连接的客户端的 地址结构
	struct sockaddr_in sockaddrin_client;
	socklen_t sockaddrin_clientsocklen_t = sizeof(sockaddrin_client);
	int  cfd = Accept(lfd, (struct sockaddr *)&sockaddrin_client, &sockaddrin_clientsocklen_t);
	char clientip[33] = { 0 };
	const char* clientipresult = inet_ntop(AF_INET, &sockaddrin_client.sin_addr.s_addr, clientip, 32);

	if (clientipresult == NULL) {
		printf("inet_ntop error\n");
	}
	else {
		printf("client addr port = %d,addr ip = %s \n", ntohs(sockaddrin_client.sin_port), clientipresult);
	}
	printf("client addr port = %d,addr ip = %s \n",ntohs(sockaddrin_client.sin_port), clientip);

	//只要客户端连接上了, 没有错误,就会走到这一步。

	//第五步:连接上后,server端就可以循环的 使用read函数 读取客户端发送过来的数据了。
	//注意的是,read函数默认是阻塞读取的
	char readbuffer[256] = { 0 };
	int readbufferlen = 0;
	
	while (1) {
		readbufferlen = Read(cfd, readbuffer, 256);
		printf("read buffer from client %s \n", readbuffer);
		for (int i = 0; i < readbufferlen; ++i) {
			readbuffer[i] = toupper(readbuffer[i]);
		}
		Write(cfd, readbuffer, readbufferlen);
		Write(STDOUT_FILENO, readbuffer, readbufferlen);
	}
}

wrap.h

#ifndef __WRAP_H_
#define __WRAP_H_

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

void perr_exit(const char* s);
int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr);
int Bind(int fd, const struct sockaddr* sa, socklen_t salen);
int Connect(int fd, const struct sockaddr* sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void* ptr, size_t nbytes);
ssize_t Write(int fd, const void* ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void* vptr, size_t n);
ssize_t Writen(int fd, const void* vptr, size_t n);
ssize_t my_read(int fd, char* ptr);
ssize_t Readline(int fd, void* vptr, size_t maxlen);

#endif

wrap.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>

void perr_exit(const char* s)
{
	perror(s);
	exit(-1);
}

int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr)
{
	int n;

again:
	if ((n = accept(fd, sa, salenptr)) < 0) {
		if ((errno == ECONNABORTED) || (errno == EINTR))
			goto again;
		else
			perr_exit("accept error");
	}
	return n;
}

int Bind(int fd, const struct sockaddr* sa, socklen_t salen)
{
	int n;

	if ((n = bind(fd, sa, salen)) < 0)
		perr_exit("bind error");

	return n;
}

int Connect(int fd, const struct sockaddr* sa, socklen_t salen)
{
	int n;

	if ((n = connect(fd, sa, salen)) < 0)
		perr_exit("connect error");

	return n;
}

int Listen(int fd, int backlog)
{
	int n;

	if ((n = listen(fd, backlog)) < 0)
		perr_exit("listen error");

	return n;
}

int Socket(int family, int type, int protocol)
{
	int n;

	if ((n = socket(family, type, protocol)) < 0)
		perr_exit("socket error");

	return n;
}

ssize_t Read(int fd, void* ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ((n = read(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

ssize_t Write(int fd, const void* ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ((n = write(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

int Close(int fd)
{
	int n;
	if ((n = close(fd)) == -1)
		perr_exit("close error");

	return n;
}

/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void* vptr, size_t n)
{
	size_t  nleft;              //usigned int 剩余未读取的字节数
	ssize_t nread;              //int 实际读到的字节数
	char* ptr;

	ptr = vptr;
	nleft = n;

	while (nleft > 0) {
		if ((nread = read(fd, ptr, nleft)) < 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)
{
	size_t nleft;
	ssize_t nwritten;
	const char* ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ((nwritten = write(fd, ptr, nleft)) <= 0) {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0;
			else
				return -1;
		}

		nleft -= nwritten;
		ptr += nwritten;
	}
	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:
		if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
			if (errno == EINTR)
				goto again;
			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 = vptr;
	for (n = 1; n < maxlen; n++) {
		if ((rc = my_read(fd, &c)) == 1) {
			*ptr++ = c;
			if (c == '\n')
				break;
		}
		else if (rc == 0) {
			*ptr = 0;
			return n - 1;
		}
		else
			return -1;
	}
	*ptr = 0;

	return n;
}

编译命令

gcc socket_server_support_one_clientconnect.c wrap.c -o socket_server_support_one_clientconnect.out
这里使用的是 gcc build, 如果使用g++,则会有build error,因为g++的语法检查,要比 gcc 严格一些。
error 信息如下



 g++ socket_server_support_one_clientconnect.c wrap.c -o socket_server_support_one_clientconnect.out
wrap.c: In function ‘ssize_t Readn(int, void*, size_t)’:
wrap.c:111:8: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
  ptr = vptr;
        ^~~~
wrap.c: In function ‘ssize_t Writen(int, const void*, size_t)’:
wrap.c:136:8: error: invalid conversion from ‘const void*’ to ‘const char*’ [-fpermissive]
  ptr = vptr;
        ^~~~
wrap.c: In function ‘ssize_t Readline(int, void*, size_t)’:
wrap.c:180:8: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
  ptr = vptr;

二 . 可以被多个 client 链接 socket 例子,使用 父子进程完成。

2.1 添加 将 SIGCHLD 信号屏蔽

    //信号屏蔽是指进程主动阻止某些信号的递达,直到屏蔽解除,被屏蔽的信号在屏蔽期间仍然会产生,但不会被处理,直到屏蔽解除后才会被处理,信号屏蔽通常用于保护临界区代码,防止信号处理程序与普通代码同时执行导致的数据不一致问题。
 

            sigset_t set;
            sigemptyset(&set);
            sigaddset(&set, SIGCHLD);
            sigprocmask(SIG_BLOCK, &set, NULL);

2.2 端口复用

        server IP 和 port可以复用

        setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
 

2.3 父进程完成的任务有:

        2.3.1.负责监听lfd功能.因此先要关闭 cfd

        2.3.2 父进程还需要回收子进程,以避免子进程完成后变成僵尸进程

                sigaction 在windows 上使用vs2019 编写linux代码,即使添加了#include <signal.h>,也无法识别,原因未知,但是在 linux环境下 build 是可以pass 的。

                            struct sigaction act;
                            act.sa_handler = catchchild;
                            sigemptyset(&act.sa_mask);
                            act.sa_flags = 0;
                            sigaction(SIGCHLD, &act, NULL);

        2.3.3 屏蔽解除 sigprocmask(SIG_UNBLOCK, &set, NULL);

2.4 子进程完成的任务有:

        2.4.1 子进程,负责 cfd 和 client的交换数据发送信息功能。因此先要关闭 lfd

                close(lfd);

        2.4.2 连接上后,循环的 使用read函数 读取客户端发送过来的数据了。注意的是,read函数默认是阻塞读取的
 

            char readbuffer[256] = { 0 };
            int readbufferlen = 0;

            while (1) {
                readbufferlen = Read(cfd, readbuffer, 256);
                printf("read buffer from client %s \n", readbuffer);
                for (int i = 0; i < readbufferlen; ++i) {
                    readbuffer[i] = toupper(readbuffer[i]);
                }
                Write(cfd, readbuffer, readbufferlen);
                Write(STDOUT_FILENO, readbuffer, readbufferlen);
            }

2.5 完整代码:

socket_server_support_more_clientconnect_use_fork.c


/*
此例子用于socket 例子,
该例子只能用于多个客户端连接server。

基于 父子进程 实现
父进程 用于 lfd ,
子进程 用于 cfd.

测试的问题1: listen(lfd,num) 监听的上限数量问题。




*/
#include "wrap.h"
#define SERVER_PORT 8888

void catchchild(int single) {
	pid_t pid;
	int status;
	while (1) {
		pid = waitpid(-1, &status, WNOHANG);
		if (pid <=0 ) {

			break;
		}
		else if (pid > 0) {
			printf("child pid = %\d exit \n",pid);
			if (WIFEXITED(status)) {//WIFEXITED(status)为真,说明是子线程是正常结束的。。使用WEXITSTATUS(status)  ---  获取进程退出状态 (exit的参数)
				printf("child normal die ,die information : %d \n", WEXITSTATUS(status));
			}
			if (WIFSIGNALED(status)) {//WIFSIGNALED(status)为真,说明子线程异常终止。。使用WTERMSIG(status) --- 取得使进程终止的那个信号的编号
				printf("child not normal die ,die information : %d \n", WTERMSIG(status));
			}
		}
	}
}

int main(int argc, const char* argv[]) {

	//第零步:将 SIGCHLD 信号屏蔽,这样做的原因是,有可能当父进程还没有 注册 完成 SIGCHID 的时候,子进程已经执行完毕了,因此在main 函数的最前面,将信号屏蔽了
	//信号屏蔽是指进程主动阻止某些信号的递达,直到屏蔽解除,被屏蔽的信号在屏蔽期间仍然会产生,但不会被处理,直到屏蔽解除后才会被处理,信号屏蔽通常用于保护临界区代码,防止信号处理程序与普通代码同时执行导致的数据不一致问题。
	sigset_t set;
	sigemptyset(&set);
	sigaddset(&set, SIGCHLD);
	sigprocmask(SIG_BLOCK, &set, NULL);


	int lfd;
	// 第一步:创建 lfd
	lfd = Socket(AF_INET, SOCK_STREAM, 0);

	// 第一步 让 server IP 和 port可以复用。
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));


	// 第二步: 给lfd 绑定 IP 和 PORT,这里需要将 port 和IP 从小端转成大端
	struct sockaddr_in sockaddrin_server;
	sockaddrin_server.sin_family = AF_INET;
	sockaddrin_server.sin_port = htons(SERVER_PORT);
	sockaddrin_server.sin_addr.s_addr = htonl(INADDR_ANY);

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

	//第三步:设定监听上限,这个监听上限的意思是:同时只能有2个客户端和此 server连接,我们这个server只能要2个客户端和我们连接,测试如果有3个的客户端连接的情况下,server端发生的现象 和 client端发生的现象,因此还需要写一个客户端
	Listen(lfd, 2);

	
	// 第四步 :accept 阻塞监听客户端连接,并 fork出子进程。
	// 4.1 阻塞,并等待客户端连接. accept的第二个参数是传入传出参数,代表的是:成功于服务器连接的客户端的 地址结构

	struct sockaddr_in sockaddrin_client;
	socklen_t sockaddrin_clientsocklen_t = sizeof(sockaddrin_client);
	pid_t pid;
	while (1) {

		int  cfd = Accept(lfd, (struct sockaddr*)&sockaddrin_client, &sockaddrin_clientsocklen_t);
		char clientip[33] = { 0 };
		const char* clientipresult = inet_ntop(AF_INET, &sockaddrin_client.sin_addr.s_addr, clientip, 32);

		if (clientipresult == NULL) {
			printf("inet_ntop error\n");
		}
		else {
			printf("client addr port = %d,addr ip = %s \n", ntohs(sockaddrin_client.sin_port), clientipresult);
		}
		pid = fork();

		if (pid == -1) {
			//fork 函数的 error 处理
			perr_exit("fork error");
		}
		else if (pid > 0) {
			//4.1 父进程,负责监听lfd功能.因此先要关闭 cfd
			close(cfd);
			//4.2 父进程还需要回收子进程,以避免子进程完成后变成僵尸进程。
			// 那么如何回收呢? 如果是 阻塞回收,那么while(1)的循环在父进程这里就卡在这里了
			// 如果是 不阻塞忙轮询 回收呢?也不行,怎么忙轮询呢?
			// 因此这里只能用信号捕捉函数 ,捕捉 SIGCHID 信号



			//struct sigaction {
			//	void     (*sa_handler)(int);
			//	void     (*sa_sigaction)(int, siginfo_t*, void*);
			//	sigset_t   sa_mask;
			//	int        sa_flags;
			//	void     (*sa_restorer)(void);
			//};

			//int sigaction(int signum, const struct sigaction* act,struct sigaction* oldact);
			
			struct sigaction act;
			act.sa_handler = catchchild;
			sigemptyset(&act.sa_mask);
			act.sa_flags = 0;
			sigaction(SIGCHLD, &act, NULL);

			//4.3 屏蔽解除
			sigprocmask(SIG_UNBLOCK, &set, NULL);

		}
		else if (pid == 0 ) {
			//4.4 子进程,负责 cfd 和 client的交换数据发送信息功能。因此先要关闭 lfd
			close(lfd);

			//4.5 连接上后,循环的 使用read函数 读取客户端发送过来的数据了。注意的是,read函数默认是阻塞读取的
			char readbuffer[256] = { 0 };
			int readbufferlen = 0;

			while (1) {
				readbufferlen = Read(cfd, readbuffer, 256);
				printf("read buffer from client %s \n", readbuffer);
				for (int i = 0; i < readbufferlen; ++i) {
					readbuffer[i] = toupper(readbuffer[i]);
				}
				Write(cfd, readbuffer, readbufferlen);
				Write(STDOUT_FILENO, readbuffer, readbufferlen);
			}
		}
	}
}

2.6 编译命令

gcc wrap.c socket_server_support_more_clientconnect_use_fork.c -o socket_server_support_more_clientconnect_use_fork.out

*****关于 listen 第二个参数的 问题研究

在代码中,设定的listen(lfd,2); 那么这个参数2刚开始的自己的想法是:最多有两个客户端连接上server,但是实际测试中,用了5客户端连接,都没有问题,这说明啥呢?说明自己对 listen的第二个参数理解是不对的。
 

listen(lfd,2); 这个listen的第二个参数是啥意思呢?赋值多少比较合理呢?

我们先来看 listen参数的说明:

       The backlog argument defines the maximum length to which the  queue  of
       pending  connections  for  sockfd  may  grow.   If a connection request
       arrives when the queue is full, the client may receive an error with an
       indication  of  ECONNREFUSED  or,  if  the underlying protocol supports
       retransmission, the request may be ignored so that a later reattempt at
       connection succeeds.

  backlog :就是你设置的参数值

中文翻译就是:

backlog 参数定义了待处理连接队列的最大长度,sockfd 可能会增长。如果连接请求在队列已满时到达,客户端可能会收到带有 ECONNREFUSED 指示的错误,或者如果底层协议支持重传,该请求可能会被忽略,以便稍后重新尝试连接成功。

这两个博客说的很仔细:

https://zhuanlan.zhihu.com/p/634606981

listen()函数的第二个参数详解_listen函数的第二个参数-CSDN博客

整理核心:所以,backlog 目前的真正含义就是:

(1)在linux 2.2 内核之前,backlog是指半连接队列(syns_queue)的长度。

(2)在linux2.2及之后,backlog是指已经完全建立连接,但是还没有被应用层accept之前,socket所处全连接队列(accetp_queue)的长度。

全连接队列是什么?

全连接队列存储3次握手成功并已建立的连接,将其称为全连接队列,也可称为接收队列(Accept队列),本文中的描述将称为Accept队列或全连接队列。如下红框中所示,全连已成功建立三次握手,当前的TCP状态为ESTABLISHED,但是服务端还未Accept的队列。

要核心学习这,实际上要学习linux 内核的整体课程才能有深入的了解,这里只是对这个知识点比较迷惑,记录一下。

三. 可以被多个 client 链接 socket 例子,使用 子线程完成


http://www.kler.cn/a/513922.html

相关文章:

  • Flutter调用HarmonyOS NEXT原生相机拍摄相册选择照片视频
  • 查看电脑或笔记本CPU的核心数方法及CPU详细信息
  • 系统思考—转型
  • (10)深入浅出智能合约OpenZeppelin开源框架
  • Linux-C/C++--深入探究文件 I/O (下)(文件共享、原子操作与竞争冒险、系统调用、截断文件)
  • 【计算机视觉】人脸识别
  • 解决github无法clone的问题
  • 树莓派4基于Debian GNU/Linux 12 (Bookworm)设置程序开机自启动
  • TiDB 的高可用实践:一文了解代理组件 TiProxy 的原理与应用
  • pyautogui自动化鼠标键盘操作
  • Redis 7.0 新特性助力:小红书利用 I/O 多线程模型应对高并发挑战
  • 汽车和工业用激光雷达行业分析
  • C语言练习(16)
  • Ubuntu16.04 安装OpenCV4.5.4 避坑
  • 深度学习之监督学习和无监督学习的探讨
  • ECharts 海量数据渲染性能优化方案
  • “物联网+高职”:VR虚拟仿真实训室的发展前景
  • 【分布式架构设计理论1】架构设计的演进过程
  • 云知声:语音交互领域的技术先锋与创新引擎
  • 一文讲解Redis常见使用方式
  • 高校宿舍信息|基于Spring Boot的高校宿舍信息管理系统的设计与实现(源码+数据库+文档)
  • Httprunner接口测试框架入门
  • 微信小程序使用上拉加载onReachBottom。页面拖不动。一直无法触发上拉的事件。
  • SQL-leetcode—1148. 文章浏览 I
  • tensorflow源码编译在C++环境使用
  • pycharm+pyside6+desinger实现查询汉字笔顺GIF动图