栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

《UNP》随笔——实现一个简单的回射服务器

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

《UNP》随笔——实现一个简单的回射服务器

文章目录
    • 1.什么是回射服务器
    • 2.服务器程序
    • 3.客户端程序
    • 4.并行服务器的轮廓
    • 5. 拓展(观察连接状态等)
    • 6.总结
    • 7.后端监听ip地址的三种主要的方式
        • 监听127.0.0.1
        • 监听0.0.0.0
        • 监听主机ip 192.168.0.113
        • 总结

1.什么是回射服务器

回射服务器的执行步骤:
(1)客户从标准输入读入一行文本,并写给服务器。
(2)服务器从网络输入读入这行文本,并回射给客户;
(3)客户从网络输入读入这行文本,并显示在标准输出上。

2.服务器程序
// 5.2 TCP回射服务器程序 
#include	"unp.h"
void str_echo(int sockfd)
{
	ssize_t		n;
	char		buf[MAXLINE];

again:
	while ( (n = read(sockfd, buf, MAXLINE)) > 0)
		Writen(sockfd, buf, n);

	if (n < 0 && errno == EINTR)
		goto again;
	else if (n < 0)
		err_sys("str_echo: read error");
}

int main(int argc, char **argv){
    int listenfd, connfd;
    pid_t childpid;
    socklen_t clilen;
    struct sockaddr_in  cliaddr, servaddr;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 大端字节序,INADDR_ANY表示通配地址, 
    servaddr.sin_port        = htons(SERV_PORT); // 小端字节序,服务器众所周知的端口,在unp中为9877

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    Listen(listenfd, LISTENQ); // LISTENQ 表示监听队列中最大容量,unp中定义为1024

    for( ; ; ){
        clilen = sizeof(cliaddr);
        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen );
        
        if((childpid = Fork()) == 0){
            Close(listenfd); // 子进程需要关闭监听套接字
            str_echo(connfd);
            exit(0);
        }
        Close(connfd); //父进程关闭已连接套接字
    }
}

流程:①定义好socket的地址address。②Bind函数将地址和socket绑定起来。③使用Listen函数,监听socket信号。④利用死循环和子进程,处理每一个客户端的连接请求。

需要注意的点:

  • INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。也叫做通配地址, 其作用就是告诉主机,该socket可以接受目的地址为任何本地接口的连接。(经测试,无法使用局域网其他主机进行连接)
  • Accept函数,用于从已完成连接队列队头返回下一个已完成连接,如果已完成连接队列为空,那么进程将置于休眠状态(一般嵌套字socket会默认为阻塞状态)。
  • 并发服务器的程序轮廓。当服务器可能要接受多个主机的连接请求时,最简单的方法就是fork一个子进程来服务每个客户。子进程关闭监听套接字,父进程关闭已连接套接字,并等待其他连接。(原因在第4节解释)
3.客户端程序
#include	"unp.h"
void
str_cli(FILE *fp, int sockfd)
{
	char	sendline[MAXLINE], recvline[MAXLINE];
    // Fgets 将会调用fgets函数,读取从键盘输入的数据
	while (Fgets(sendline, MAXLINE, fp) != NULL) {

        // 会调用write函数,将该行数据发送给客户端
		Writen(sockfd, sendline, strlen(sendline));
        // 调用read函数,接受由服务器返回的数据,并用fputs,将其显示
		if (Readline(sockfd, recvline, MAXLINE) == 0)
			err_quit("str_cli: server terminated prematurely");

		Fputs(recvline, stdout);
	}
}

int main(int argc, char **argv)
{
	int					sockfd;
	struct sockaddr_in	servaddr;

	if (argc != 2)
		err_quit("usage: tcpcli ");

	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);  // 端口使用unp的默认端口 9877
	Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); // 将指定的ip argv[1] 放入sin_addr中, 

	Connect(sockfd, (SA *) &servaddr, sizeof(servaddr)); // 发起连接申请

	str_cli(stdin, sockfd);		

	exit(0);
}

4.并行服务器的轮廓

疑问点:为什么在子进程中就要关闭监听套接字listenfd? 为什么已连接套接字connfd关闭了两次?

用图解来解释:

此时有一连接请求,服务器接受该请求,并创建新的套接字connfd,这是一个已连接套接字,可以使用该套接字来跨连接读写数据。如下图:

下一步就是调用fork创建子进程,如下图

可以看到,子进程把父进程的监听套接字listenfd和连接套接字connfd都复制了一份。但是,我们创建子进程的目的就是为了处理已连接套接字,如果一直保持两个监听套接字,当有新的连接进行过来时,监听队列会出现冗余的连接套接字,同样,我们也希望父进程只处理监听套接字。 因此,父进程关闭已连接套接字,子进程关闭监听套接字。

这就是两个套接字最终的状态。父进程与子进程各司其职,子进程处理当前的已连接套接字,进行跨连接读写操作,父进程继续处理下一客户连接,创建新的子进程来处理新的连接。

  • 为什么已连接套接字关闭了两次?
    每个文件或者套接字,都有一个引用计数,它是当前打开着的引用该文件或套接字的描述符的个数。当listenfd和connfd从函数中返回时,其引用计数都是1,但是调用了fork,创建子进程时,这两个描述符也在父进程子进程中共享,因此与这两个套接字相关联的文件表项各自的引用计数都为2。这样的话,当子进程执行close()的时候,引用计数由2减为1,并不会被真正的清理和资源释放。(子进程结束的时候,也会自动对未执行引用计数减一的描述符来执行引用减一,因此close(connfd)常常略写)
5. 拓展(观察连接状态等)
  1. 观察IP报文

    客户端输入的是asd,可以在报文中看到asd的ASCII字符,也就是包含的数据信息。同时也是TCP传输信息的过程,发送方发送一个信息报文,接受方返回一个确认报文
  2. 查看端口信息
losf -i:端口号

  1. 查看进程间的关系
ps -t pts/5 -t pts/7 -o pid,ppid,tty,stat,args,wchan

  • pts表示打开的终端
  • wait_woken就是在休眠(一般是阻塞状态),等待唤醒。
  • PID=5045的进程,状态是Z,也就是僵死状态,是因为服务器子进程终止时,会给父进程发送一个SIGCHLD信号,但是本程序没有在代码中捕获这一信号,该信号默认被忽略,所以导致子进程僵死。(僵死进程同样会消耗系统资源,所以需要对其进行处理)
6.总结
  1. 如何设计一个可以监听局域网的服务端?
    可以,只需要将监听地址进行修改即可,修改为本地的IP地址A,这样其他主机的服务器即可通过A:端口,来进行服务器请求。

不需要修改任何代码,只需要修改防火墙即可让其他主机连接。这一点涉及到后端监听ip的三种主要方式,相关内容往下查看

    servaddr.sin_family = AF_INET;

	const char* ip = argv[1];
	inet_pton(AF_INET, ip, &servaddr.sin_addr); // 修改这里进行绑定本地地址即可
	
    //servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 大端字节序,INADDR_ANY表示通配地址
    servaddr.sin_port        = htons(SERV_PORT); // 小端字节序,服务器众所周知的端口,在unp中为9877

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    Listen(listenfd, LISTENQ); // LISTENQ 表示监听队列中最大容量,unp中定义为1024

设置过程中遇到的问题:

  • 主机B上监听9877端口,主机A不能通过主机B的IP地址和这个端口进行连接,发送了TCP请求报文,但是主机B没有回复报文。(利用tcodump,可以看到主机B收到了该请求报文)
  • 为了验证是否是程序的问题,将服务端程序换到主机A上,主机A上监听9877端口,然后主机B可以通过这个端口建立TCP连接。说明程序没有问题
  • 最后发现是防火墙的原因,主机A关闭了防火墙,而主机B开启了,所以主机B不会回复主机A的连接请求。
sudo ufw status
# 查看防火墙状态
sudo ufw allow 9877  
#允许外部访问9877端口

sudo ufw delete allow 9877 
#禁止外部访问9877 端口

7.后端监听ip地址的三种主要的方式
  • 监听127.0.0.1
  • 监听0.0.0.0
  • 监听主机IP地址
监听127.0.0.1

127.0.0.1也称为回环地址。该地址指电脑本身,主要预留测试本机的TCP/IP协议是否正常。只要使用这个地址发送数据,则数据包不会出现在网络传输过程中。

  1. 本机通过127.0.0.1访问成功,网络接口为loopback
  2. 本机通过局域网IP 192.168.0.113访问失败,网络接口为loopback
  3. 同一局域网下的外部主句通过局域网IP 192.168.0.112访问失败,网络接口-et

因此,在实际应用中,我们在服务端监听ip地址的时候不要绑定到127.0.0.1,如果绑定到了127.0.0.1,会导致我们的应用只能在本地127.0.0.1访问,其他人无法通过其他任何方式进行访问

监听0.0.0.0

0.0.0.0也称作是通配地址,用INADDR_ANY指代。在IPv4中,0.0.0.0地址被用于表示一个无效的,未知的或者不可用的目标。在服务器中,0.0.0.0指的是本机上的所有IPV4地址,如果一个主机有两个IP地址,192.168.1.1 和 10.1.2.1,并且该主机上的一个服务监听的地址是0.0.0.0,那么通过两个ip地址都能够访问该服务。

  1. 本机通过127.0.0.1访问成功,网络接口为loopback
  2. 本机通过局域网IP 192.168.0.113 访问成功,网络接口为loopback
  3. 同一局域网下的外部主句通过局域网IP 192.168.0.112访问成功,网络接口-et1

比如我有一台服务器,一个外网A,一个内网B,如果我绑定的端口指定了0.0.0.0,那么通过内网地址或外网地址都可以访问我的应用。

监听主机ip 192.168.0.113
  1. 本机通过127.0.0.1访问失败,网络接口为loopback
  2. 本机通过局域网IP 192.168.0.113 访问成功,网络接口为loopback
  3. 同一局域网下的外部主句通过局域网IP 192.168.0.112访问成功,网络接口-et1
总结

在实际应用中,最好的监听ip地址方式为:监听到0.0.0.0,这样本机、局域网内其他主机,均可以访问该主机。

参考https://segmentfault.com/a/1190000018629247

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/307600.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号