(转)伪终端(代码!!!)

本文深入解析伪终端(PTY)的工作原理及其在进程间通信中的应用。详细介绍了如何通过调用特定函数创建和配置PTY,包括打开主设备、设置权限、清除锁标记、获取从设备路径等步骤。此外,还讲解了通过fork、setsid、dup2等系统调用在子进程中设置控制终端,并在父进程中实现数据的双向传递。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文:http://kymcuc.blog.163.com/blog/static/20194211420123293532856/

 

1、伪终端看上去像一个终端,但事实上伪终端并不是一个真正的终端。
(1)通常一个进程打开伪终端主设备,然后调用fork。子进程建立了一个新的会话,打开一个相应的伪终端从设备,将其文件描述符复制到标准输入,标准输出和标准出错,然后调用exec。伪终端从设备成为子进程的控制终端。
(2)对于伪终端从设备之上的用户进程来说,其标准输入,标准输出和标准出错都是终端设备,对于这些文件描述符,用户进程能够调用所有输入输出函数。但是因为在伪终端从设备之下并没有真正的终端设备,无意义的函数调用(如改变终端属性)将被忽略。
(3)任何写到伪终端主设备的东西都会作为从设备的输入,反之亦然。事实上所有从设备端的输入都来自于伪终端主设备上的用户进程。这看起来像一个双向管道。但从设备上的终端行规程使我们拥有普通管道没有的其他处理能力。
2、打开伪终端主设备
(1)打开一个可用的伪终端主设备
#include<stdlib.h>
#include<fcntl.h>
int posix_openpt(int oflag)
返回值:若成功则返回下一个可用的PTY主设备的文件描述符,若出错则返回-1
参数oflag是一个位屏蔽字,指定如何打开主设备。我们可以指定O_RDWR,要求打开主设备进行读、写,可以指定O_NOCTTY以防止主设备成为调用者的控制终端。其他打开标志都会导致未定义的行为。
(2)设置权限,对单个所有者是读/写,对组所有者是写
#include<stdlib.h>
int grantpt(int filedes)
若成功则返回0,若出错则返回-1
(3)清除所有的锁标记
#include<stdlib.h>
int  unlockpt(int filedes)
若成功则返回0,若出错则返回-1
(4)找到从伪终端设备的路径名
#include<stdlib.h>
char *ptsname(int filedes)
若成功则返回指向PTY从设备名的指针,若出错则返回NULL

int ptym_open(char*pts_name,int pts_namesz)
{
char*ptr;
int fdm;
strncpy(pts_name,"/dev/ptmx",pts_namesz);
pts_name[pts_namesz-1]='\0';
fdm=posix_openpt(O_RDWR);
if(fdm<0)
    return(-1);
if(grantpt(fdm)<0){
    close(fdm);
    return(-2);
}
if(unlockpt(fdm)<0){
    close(fdm);
    return(-3);
}
if((ptr=ptsname(fdm))==NULL){
    close(fdm);
    return(-4);
}
strncpy(pts_name,ptr,pts_namesz);
pts_name[pts_namesz-1]='\0';
return(fdm);
}

3、打开伪终端从设备

int ptys_open(char*pts_name)
{
int fds;
if((fds=open(pts_name,O_RDWR))<0)
    return(-5);
return(fds);
}

4、通常一个进程调用ptym_open来打开一个主设备并且得到从设备的名字,该进程然后以fork分出一个子进程,子进程在调用setsid建立新的会话后调用ptys_open打开从设备,这就是从设备如何成为子进程的控制终端的过程。然后termios和winsize这两个结构在子进程中被初始化。最后从设备的文件描述符被复制到子进程的标准输入,标准输出和标准出错中。这意味着不管子进程以后执行何种程序,它都具有同PTY从设备联系起来的上述三个描述符。在调用完fork后,父进程返回PTY主设备的描述符以及子进程ID。

pid_t pty_fork(int*ptrfdm,char*slave_name,int slave_namesz,
           conststruct termios *slave_termios,
           conststruct winsize *slave_winsize)
{
int fdm,fds;
pid_t pid;
char pts_name[20];
if((fdm=ptym_open(pts_name,sizeof(pts_name)))<0)
    perror("ptym_open");
if(slave_name!=NULL){
    strncpy(slave_name,pts_name,slave_namesz);
    slave_name[slave_namesz-1]='\0';
}
if((pid=fork())<0)
    return(-1);
elseif(pid==0){
    if(setsid()<0)
        perror("setsid");
    if((fds=ptys_open(pts_name))<0)
        perror("ptys_open");
    close(fdm);
    if(slave_termios!=NULL)
        if(tcsetattr(fds,TCSANOW,slave_termios)<0)
            perror("tcsetattr");
    if(slave_winsize!=NULL)
            if(ioctl(fds,TIOCSWINSZ,slave_winsize)<0)
                    perror("tcsetattr");
    if(dup2(fds,STDIN_FILENO)!=STDIN_FILENO)
        perror("stdin");
    if(dup2(fds,STDOUT_FILENO)!=STDOUT_FILENO)
            perror("stdout");
    if(dup2(fds,STDERR_FILENO)!=STDERR_FILENO)
            perror("stderr");
    if(fds!=STDIN_FILENO&&fds!=STDOUT_FILENO&&fds!=STDERR_FILENO)
        close(fds);
    return(0);
}else{
    *ptrfdm=fdm;
    return(pid);
}
}

5、接下来,父进程调用函数loop,该函数仅仅是将从标准输入接受到的所有内容复制到PTY主设备,并将PTY主设备接受到的所有内容复制到标准输出。此实现没有进行中断控制及对错误的忽略。

#define BUFSIZE 1024
void loop(int ptym)
{
pid_t child;
int nread;
char buf[BUFSIZE];
if((child=fork())<0)
    perror("fork");
elseif(child==0){
    for(;;)
    {
    if((nread=read(STDIN_FILENO,buf,BUFSIZE))<0){
        perror("read");
        break;
    }
    elseif(nread==0)
        break;
    if(write(ptym,buf,nread)!=nread){
        perror("write");
        break;
    }
    }
}else{
    for(;;)
    {
    if((nread=read(ptym,buf,BUFSIZE))<0)
        break;
    if(write(STDOUT_FILENO,buf,nread)!=nread){
        perror("write");
            break;
    }
    }
}
}

6、在子进程中执行需在伪终端中执行的程序

int main()
{
pid_t pid;
int fdm;
char slave_name[20];
struct termios orig_termios;
struct winsize size;
pid=pty_fork(&fdm,slave_name,sizeof(slave_name),&orig_termios,&size);
if(pid<0)
    perror("pty_fork");
elseif(pid>0){
    loop(fdm);   
}else{
      char buf[]="himan\n";
    system("ls");
}
exit(0);
}

转载于:https://www.cnblogs.com/GrowUP-EveryDay/archive/2013/01/24/2875724.html

### 集成xterm.js实现伪终端功能 为了在Java项目中集成`xterm.js`来创建伪终端,可以采用前后端分离的方式构建应用。前端部分主要负责展示和交互逻辑,而后端则处理业务逻辑以及与实际服务器通信的任务。 #### 前端配置 (基于Vue) 前端通过引入`xterm.js`及其附加组件如fit插件用于调整大小适应容器尺寸,配合WebSocket实现实时数据传输。下面是一个简单的HTML结构加上必要的JavaScript初始化代码: ```html <!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>Terminal</title> <link rel="stylesheet" href="/path/to/xterm.css"/> <script src="/path/to/xterm.js"></script> <script src="/path/to/fit.addon.min.js"></script> </head> <body> <div id="terminal-container"></div> <script type="text/javascript"> const term = new Terminal(); term.open(document.getElementById('terminal-container')); term.fit(); // WebSocket连接设置 let socket; function initSocket() { const wsUrl = 'ws://localhost:8082/websocket'; // 后端Websocket服务地址 socket = new WebSocket(wsUrl); socket.onmessage = function(event){ let data = JSON.parse(event.data); if(data.type === "output"){ term.write(data.content); } }; } initSocket(); </script> </body> </html> ``` 此段代码展示了如何加载并启动一个基本的终端实例,并建立到后端的服务链接[^2]。 #### 后端开发(Spring Boot) 对于后端而言,在Spring Boot框架内利用`@ControllerAdvice`或单独控制器接收来自客户端的消息请求;同时开启WebSocket支持以便于维持持久化通讯链路。以下是关于怎样定义处理器类的一个例子: ```java import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; public class TerminalWebSocketHandler extends TextWebSocketHandler { @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { super.afterConnectionEstablished(session); System.out.println("New connection established"); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String payload = message.getPayload(); // 处理接收到的信息... ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", payload.trim()); try{ final Process process = pb.start(); java.util.Scanner s = new java.util.Scanner(process.getInputStream()).useDelimiter("\\A"); String output = s.hasNext() ? s.next() : ""; session.sendMessage(new TextMessage("{\"type\":\"output\",\"content\":\""+output+"\"}")); }catch(Exception e){ e.printStackTrace(); } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { super.afterConnectionClosed(session, status); System.out.println("Connection closed"); } } ``` 上述示例中的`handleTextMessage()`方法会解析从前端传来的指令字符串,并执行相应的Linux shell命令,最后把结果返回给调用者显示出来[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值