数码之家

 找回密码
 立即注册

QQ登录

只需一步,快速开始

微信登录

微信扫一扫,快速登录

搜索
查看: 369|回复: 15

[软件] 分享一个 windows 打印服务器 ,类似p910nd

[复制链接]
发表于 前天 16:19 | 显示全部楼层 |阅读模式

分享一个自己写的 windows  打印服务器 ,类似p910nd
客户端添加打印机的方式和 使用路由器 p910nd 一模一样, 都是 通过添加9100网络打印端口方式
彻底绕过windows 自带的samba “文件与打印共享” 模式,彻底 0x0000709  0x000011b  0x0000bcb 等各种报错再见。
打印服务器配置: 解压缩的2个文件放在文件夹里,运行 ”打印服务器.exe“ , 在下拉框选择需要共享的打印机,点击”安装打印服务“ 按钮即可。
客户端添加共享打印机方式,参考使用路由器p910nd 的方式添加

此程序使用从C# 编写,用到 .net framework 4.5.1,  win10 系统可直接运行。 win7 可能要安装 .net framework
自己编译的文件没有数字签名,可能会报病毒,介意的,可以等我发源代码,自己编译




打印服务器.zip
链接:  https://pan.baidu.com/s/1-QFZBFLnnPSq3Py2tbkDIQ?pwd=1234  

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册 微信登录

x

打赏

参与人数 1家元 +60 收起 理由
不长叶子的树 + 60

查看全部打赏

发表于 前天 16:31 | 显示全部楼层
建议制作成service的服务形式可以自启动
回复 支持 反对

使用道具 举报

 楼主| 发表于 前天 16:34 | 显示全部楼层
enlike 发表于 2025-8-14 16:31
建议制作成service的服务形式可以自启动


已经是 service 的服务形式了
运行服务的电脑不用用户登录 也能在后台运行服务

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册 微信登录

x
回复 支持 反对

使用道具 举报

发表于 前天 16:45 | 显示全部楼层
bearxu 发表于 2025-8-14 16:34
已经是 service 的服务形式了
运行服务的电脑不用用户登录 也能在后台运行服务
...

哦哦, 感谢大神分享。
回复 支持 反对

使用道具 举报

发表于 前天 16:56 | 显示全部楼层
有这么厉害?
回复 支持 反对

使用道具 举报

发表于 前天 17:29 | 显示全部楼层
自己也算是搞这么多年电脑了,有这么好的东西得看看。
首先打印机产商出产品都要根据系统出相应的驱动,这就是所谓的生态。
太深入的也讲不来。你这个东东呢也不是不能用,一句话还是不能解决现有问题(兼容所有打印机),windows虽然有BUG出0x0000709  0x000011b  0x0000bcb,但是解决这类问题后基本上所有的打印机都可以用。

开源的RawPrintServer.exe在经大老们的修改下也算是越来越好,但还是不能兼容大部打印机。
http://bbs.c3.wuyou.net/forum.php?mod=viewthread&tid=431664&extra=&page=1

这个就讲得更详细,但是“完美”二字就太吹了。
https://www.52pojie.cn/thread-2050339-1-1.html


总之我个人还是喜欢Windows自带的,解决现有的共享打印报错BUG,禁用Windows更新,用得很顺溜。
工具嘛,本人也有。
网络访问修复5.2,可修复文件共享、打印共享、无权限访问网上邻居
https://www.mydigit.cn/thread-473196-1-1.html


彻底禁止Windows更新补丁3.5
https://www.mydigit.cn/thread-449256-1-1.html
回复 支持 反对

使用道具 举报

发表于 前天 21:14 | 显示全部楼层
厉害了,会编程,想让电脑干啥就干啥。

我只会把打印机挂在路由上面,也能实现网络打印。
回复 支持 反对

使用道具 举报

发表于 昨天 06:54 | 显示全部楼层
cj8510 发表于 2025-8-14 21:14
厉害了,会编程,想让电脑干啥就干啥。

我只会把打印机挂在路由上面,也能实现网络 ...

只会把打印机连电脑的菜鸟羡慕嫉妒
回复 支持 反对

使用道具 举报

发表于 昨天 09:04 | 显示全部楼层
谢谢楼主分享
回复 支持 反对

使用道具 举报

发表于 昨天 09:04 | 显示全部楼层
谢谢楼主分享
回复 支持 反对

使用道具 举报

 楼主| 发表于 昨天 09:46 | 显示全部楼层
ba21 发表于 2025-8-14 17:29
自己也算是搞这么多年电脑了,有这么好的东西得看看。
首先打印机产商出产品都要根据系统出相应的驱动,这 ...

不是不能兼容大部打印机
而是不能兼容 所谓 “惠普GDI打印机”
惠普的部分打印机自身不带固件,开机时需要电脑发送固件到打印机完成初始化后才能打印
只要挂路由器p910nd 能支持的打印机,这个软件打印服务器一样支持。 因为采用了相同原理

windows 的报错其实不算bug,而是修复漏洞。 如果你硬是恢复这些漏洞,那也就存在勒索病毒传播风险了
其实不用工具也是可以解决问题的: 你只要建个域控制器,让所有电脑加入域,也就能让电脑彼此信任,打印时就不出错了。 但这种正确方法只适合企业不适合个人

个人用户不适用windows 的打印共享
回复 支持 反对

使用道具 举报

发表于 昨天 09:56 | 显示全部楼层
west001 发表于 2025-8-15 06:54
只会把打印机连电脑的菜鸟羡慕嫉妒

我也是看网上各路大神的教程操作的。
回复 支持 反对

使用道具 举报

发表于 昨天 10:18 | 显示全部楼层
本帖最后由 firn 于 2025-8-15 15:19 编辑

写一个 windows  打印服务器 ,类似p910nd 客户端添加打印机的方式和 使用路由器 p910nd 一模一样, 都是 通过添加9100网络打印端口方式彻底绕过windows 自带的samba “文件与打印共享” 模式,彻底 0x0000709  0x000011b  0x0000bcb 等各种报错再见。打印服务器配置: 解压缩的2个文件放在文件夹里,运行 ”打印服务器.exe“ , 在下拉框选择需要共享的打印机,点击”安装打印服务“ 按钮即可。客户端添加共享打印机方式,参考使用路由器p910nd 的方式添加








ChatGPT said:
你要的其实是一个 Windows 上的“原始端口(RAW)打印服务”,它的行为和路由器上的 p910nd 一样——直接监听 TCP 9100,把客户端发送的原始打印数据流原封不动送给指定的本地打印机,不走 SMB / RPC 协议,这样就完全绕开了 Windows 自带的 “文件与打印共享” 机制,也就不会再碰到 0x00000709、0x000011b、0x00000bcb 等打印共享相关的错误。
我可以帮你写一个可执行的 Windows 打印服务器,核心功能是:
  • 列出系统已安装的本地打印机,让用户在下拉框选择;
  • 监听 TCP 9100 端口;
  • 收到客户端数据后,直接发送到所选打印机的 RAW 打印通道;
  • 提供“安装打印服务”按钮,启动后台监听;提供“停止服务”按钮可停止监听。


核心原理
  • 枚举本地打印机
    Windows API EnumPrinters 可以获取打印机列表。
  • 打开打印机句柄
    OpenPrinter + StartDocPrinter(RAW 模式) + WritePrinter + EndDocPrinter。
  • TCP 监听
    用 WinSock 监听 9100 端口,客户端连接后不断读取数据流。
  • 数据直通
    将读取到的数据直接送入 WritePrinter,不进行格式处理。


示例核心代码(C++ WinAPI 版)
下面是一个精简版示例(去掉 UI,只演示核心逻辑):
cpp
CopyEdit


#include <windows.h>#include <winsock2.h>#include <stdio.h>#pragma comment(lib, "ws2_32.lib")#define PORT 9100#define BUFFER_SIZE 4096bool print_raw_data(const char* printerName, const BYTE* data, DWORD length) {    HANDLE hPrinter;    if (!OpenPrinter((LPSTR)printerName, &hPrinter, NULL)) {        printf("OpenPrinter failed: %d\n", GetLastError());        return false;    }    DOC_INFO_1 docInfo;    docInfo.pDocName = (LPSTR)"RAW TCP Job";    docInfo.pOutputFile = NULL;    docInfo.pDatatype = (LPSTR)"RAW";    if (StartDocPrinter(hPrinter, 1, (LPBYTE)&docInfo)) {        if (StartPagePrinter(hPrinter)) {            DWORD written;            WritePrinter(hPrinter, (LPVOID)data, length, &written);            EndPagePrinter(hPrinter);        }        EndDocPrinter(hPrinter);    }    ClosePrinter(hPrinter);    return true;}int main() {    WSADATA wsaData;    SOCKET serverSocket, clientSocket;    struct sockaddr_in serverAddr, clientAddr;    int clientAddrSize = sizeof(clientAddr);    char buffer[BUFFER_SIZE];    WSAStartup(MAKEWORD(2,2), &wsaData);    serverSocket = socket(AF_INET, SOCK_STREAM, 0);    serverAddr.sin_family = AF_INET;    serverAddr.sin_port = htons(PORT);    serverAddr.sin_addr.s_addr = INADDR_ANY;    bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));    listen(serverSocket, 1);    printf("Listening on TCP port %d...\n", PORT);    while (1) {        clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrSize);        printf("Client connected.\n");        int bytesRead;        while ((bytesRead = recv(clientSocket, buffer, BUFFER_SIZE, 0)) > 0) {            print_raw_data("Your Printer Name", (BYTE*)buffer, bytesRead);        }        closesocket(clientSocket);    }    closesocket(serverSocket);    WSACleanup();    return 0;}


客户端添加打印机方式
客户端添加方法和 p910nd 完全一致:
  • 在“添加打印机”向导中选择 添加本地打印机
  • 新建端口 → Standard TCP/IP Port
  • 输入打印服务器的 IP 和端口(9100);
  • 选择对应打印机驱动(本地安装的驱动)。


优点
  • 完全绕过 SMB、RPC 等共享机制,不依赖 Windows “文件与打印共享”;
  • 端口打印协议(RAW 9100)简单、跨平台;
  • 客户端只需要知道 IP 和驱动,不需要账号密码。


如果你愿意,我可以帮你做一个带 UI 界面的 Windows 可执行程序,像你描述的那样:
解压两个文件 → 运行 打印服务器.exe → 下拉框选择打印机 → 点击“安装打印服务” → 自动在后台运行监听 9100 端口。
你要我帮你直接做成这种可执行程序吗?这样你可以直接发给别人用,完全不需要编译环境。









You said:








ChatGPT said:

Thought for 10s



好!我给你一个Windows 原生“9100 端口直通(p910nd 同款)打印服务器”,双击运行即可选打印机并启动/停止监听。客户端按“添加标准 TCP/IP 打印端口(9100)”的方式加即可,完全绕开 SMB/RPC,和路由器上的 p910nd 用法一模一样。
下面给出可直接编译发布为单文件 EXE的完整工程(WinForms,.NET 6+)。你只需把两段文件存到同一文件夹,执行两条 dotnet 命令即可得到 打印服务器.exe。

1) 工程文件:RawPrintServer.csprojxml
CopyEdit


<Project Sdk="Microsoft.NET.Sdk">  <PropertyGroup>    <OutputType>WinExe</OutputType>    <TargetFramework>net6.0-windows</TargetFramework>    <UseWindowsForms>true</UseWindowsForms>    <Nullable>enable</Nullable>    <ImplicitUsings>enable</ImplicitUsings>    <ApplicationIcon />    <AssemblyName>打印服务器</AssemblyName>  </PropertyGroup></Project>

2) 程序代码:Program.cs
单文件,无设计器。包含:打印机枚举、9100 监听、RAW 直通打印、开始/停止按钮、开机自记忆上次选择的打印机与端口。
csharp
CopyEdit


using System;using System.IO;using System.Net;using System.Net.Sockets;using System.Runtime.InteropServices;using System.Text.Json;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;using System.Drawing.Printing;internal static class Program{    [STAThread]    static void Main()    {        ApplicationConfiguration.Initialize();        Application.Run(new MainForm());    }}public sealed class MainForm : Form{    ComboBox cbPrinters = new();    TextBox tbPort = new() { Text = "9100" };    Button btnStart = new() { Text = "安装/启动打印服务" };    Button btnStop = new() { Text = "停止" };    Label lbl = new() { Text = "选择共享的打印机  +  监听端口(默认9100)" };    TextBox tbLog = new() { Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Vertical };    CancellationTokenSource? cts;    TcpListener? listener;    AppConfig config = AppConfig.Load();    public MainForm()    {        Text = "打印服务器(RAW 9100)— 像 p910nd 一样用";        Width = 720; Height = 420;        FormBorderStyle = FormBorderStyle.FixedSingle; MaximizeBox = false;        var table = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 4, RowCount = 6, Padding = new Padding(8) };        table.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 40));        table.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 30));        table.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 15));        table.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 15));        lbl.Dock = DockStyle.Fill;        table.Controls.Add(lbl, 0, 0); table.SetColumnSpan(lbl, 4);        cbPrinters.DropDownStyle = ComboBoxStyle.DropDownList;        foreach (string p in PrinterSettings.InstalledPrinters) cbPrinters.Items.Add(p);        if (cbPrinters.Items.Count > 0)        {            int idx = config.PrinterName is not null ? cbPrinters.Items.IndexOf(config.PrinterName) : -1;            cbPrinters.SelectedIndex = idx >= 0 ? idx : 0;        }        table.Controls.Add(cbPrinters, 0, 1); table.SetColumnSpan(cbPrinters, 2);        table.Controls.Add(new Label { Text = "端口", TextAlign = System.Drawing.ContentAlignment.MiddleRight, Dock = DockStyle.Fill }, 2, 1);        table.Controls.Add(tbPort, 3, 1);        btnStart.Click += (_, __) => StartServer();        btnStop.Click += (_, __) => StopServer();        table.Controls.Add(btnStart, 2, 2);        table.Controls.Add(btnStop, 3, 2);        tbLog.Dock = DockStyle.Fill;        table.Controls.Add(tbLog, 0, 3); table.SetColumnSpan(tbLog, 4); table.SetRowSpan(tbLog, 3);        Controls.Add(table);        if (config.Port > 0) tbPort.Text = config.Port.ToString();        FormClosing += (_, __) => StopServer();    }    void StartServer()    {        if (listener != null)        {            Log("服务已在运行。");            return;        }        if (cbPrinters.SelectedItem == null)        {            MessageBox.Show("未找到任何本机打印机。请先在本机安装打印机驱动。");            return;        }        if (!int.TryParse(tbPort.Text, out int port) || port < 1 || port > 65535)        {            MessageBox.Show("端口无效。");            return;        }        string printer = cbPrinters.SelectedItem!.ToString()!;        try        {            listener = new TcpListener(IPAddress.Any, port);            listener.Start();            cts = new CancellationTokenSource();            // 保存配置            config.PrinterName = printer;            config.Port = port;            config.Save();            Log($"已启动:监听 TCP {port},目标打印机:{printer}");            _ = AcceptLoopAsync(listener, printer, cts.Token);            TryAddFirewallRule(port);        }        catch (Exception ex)        {            listener = null; cts?.Cancel(); cts = null;            Log("启动失败:" + ex.Message);            MessageBox.Show("启动失败:" + ex.Message);        }    }    void StopServer()    {        try        {            cts?.Cancel();            listener?.Stop();            listener = null;            Log("服务已停止。");        }        catch { /* ignore */ }    }    async Task AcceptLoopAsync(TcpListener lsn, string printer, CancellationToken token)    {        while (!token.IsCancellationRequested)        {            TcpClient? client = null;            try            {                client = await lsn.AcceptTcpClientAsync(token);                _ = HandleClientAsync(client, printer, token);            }            catch (OperationCanceledException) { break; }            catch (Exception ex) { Log("Accept 失败:" + ex.Message); client?.Close(); }        }    }    async Task HandleClientAsync(TcpClient client, string printer, CancellationToken token)    {        string ep = client.Client.RemoteEndPoint?.ToString() ?? "client";        Log($"连接:{ep}");        using (client)        using (var ns = client.GetStream())        {            // 每个连接对应一个打印作业(与 p910nd 行为一致)            try            {                using var rawJob = new RawPrintJob(printer, "RAW TCP Job");                byte[] buf = new byte[64 * 1024];                int read;                while ((read = await ns.ReadAsync(buf, 0, buf.Length, token)) > 0)                {                    if (!rawJob.Write(buf, read))                    {                        Log($"[{ep}] 写入打印机失败,中止作业。");                        break;                    }                }                Log($"断开:{ep} (作业结束)");            }            catch (Exception ex)            {                Log($"[{ep}] 处理异常:{ex.Message}");            }        }    }    void Log(string s)    {        if (tbLog.InvokeRequired) { tbLog.BeginInvoke(new Action<string>(Log), s); return; }        tbLog.AppendText($"[{DateTime.Now:HH:mm:ss}] {s}\r\n");    }    void TryAddFirewallRule(int port)    {        try        {            // 尝试添加允许入站规则(需要管理员,但失败也不影响使用)            var psi = new System.Diagnostics.ProcessStartInfo("netsh",                $"advfirewall firewall add rule name=\"RAW9100_Print_Server\" dir=in action=allow protocol=TCP localport={port}")            {                CreateNoWindow = true, UseShellExecute = false            };            System.Diagnostics.Process.Start(psi);        }        catch { /* ignore */ }    }}file class AppConfig{    public string? PrinterName { get; set; }    public int Port { get; set; } = 9100;    static string PathFile => System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "打印服务器.json");    public static AppConfig Load()    {        try        {            if (File.Exists(PathFile))                return JsonSerializer.Deserialize<AppConfig>(File.ReadAllText(PathFile)) ?? new AppConfig();        }        catch { }        return new AppConfig();    }    public void Save()    {        try        {            File.WriteAllText(PathFile, JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true }));        }        catch { }    }}/// <summary>/// 通过 WinSpool RAW 通道发送数据到本地打印机(单作业生命周期)/// </summary>sealed class RawPrintJob : IDisposable{    [DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true)]    static extern bool OpenPrinter(string pPrinterName, out IntPtr phPrinter, IntPtr pDefault);    [DllImport("winspool.Drv", SetLastError = true)]    static extern bool ClosePrinter(IntPtr hPrinter);    [DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Ansi)]    static extern bool StartDocPrinter(IntPtr hPrinter, int Level, ref DOC_INFO_1 pDocInfo);    [DllImport("winspool.Drv", SetLastError = true)]    static extern bool EndDocPrinter(IntPtr hPrinter);    [DllImport("winspool.Drv", SetLastError = true)]    static extern bool StartPagePrinter(IntPtr hPrinter);    [DllImport("winspool.Drv", SetLastError = true)]    static extern bool EndPagePrinter(IntPtr hPrinter);    [DllImport("winspool.Drv", SetLastError = true)]    static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, int dwCount, out int dwWritten);    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]    struct DOC_INFO_1    {        public string pDocName;        public string pOutputFile;        public string pDatatype;    }    IntPtr _hPrinter = IntPtr.Zero;    bool _pageStarted = false;    public RawPrintJob(string printerName, string jobName)    {        if (!OpenPrinter(printerName, out _hPrinter, IntPtr.Zero))            throw new InvalidOperationException($"OpenPrinter 失败:{Marshal.GetLastWin32Error()}");        var di = new DOC_INFO_1 { pDocName = jobName, pOutputFile = null!, pDatatype = "RAW" };        if (!StartDocPrinter(_hPrinter, 1, ref di))            throw new InvalidOperationException($"StartDocPrinter 失败:{Marshal.GetLastWin32Error()}");        if (!StartPagePrinter(_hPrinter))            throw new InvalidOperationException($"StartPagePrinter 失败:{Marshal.GetLastWin32Error()}");        _pageStarted = true;    }    public bool Write(byte[] data, int count)    {        IntPtr p = Marshal.AllocHGlobal(count);        try        {            Marshal.Copy(data, 0, p, count);            return WritePrinter(_hPrinter, p, count, out _);        }        finally { Marshal.FreeHGlobal(p); }    }    public void Dispose()    {        if (_hPrinter != IntPtr.Zero)        {            if (_pageStarted) EndPagePrinter(_hPrinter);            EndDocPrinter(_hPrinter);            ClosePrinter(_hPrinter);            _hPrinter = IntPtr.Zero;        }    }}


3) 编译 & 打包(生成单文件 EXE)
在装有 .NET 6+ SDK 的 Windows 机器上,打开此文件夹执行:
powershell
CopyEdit


dotnet restoredotnet publish -c Release -r win-x64 -p:PublishSingleFile=true -p:SelfContained=true

生成目录:
.\bin\Release\net6.0-windows\win-x64\publish\
把里面的 打印服务器.exe 拿出来,和自动生成的 打印服务器.json 放同一目录即可“解压即用”。
如需 x86:把 -r win-x64 改成 -r win-x86。

4) 使用说明
  • 服务器端(本机装着物理打印机驱动)

    • 双击 打印服务器.exe
    • 下拉框里选目标打印机
    • 端口保持 9100(也可改)
    • 点“安装/启动打印服务”
    • 首次运行可能弹出防火墙提示,请允许;程序也会尝试自动添加入站规则

  • 客户端(任意 Windows)

    • “添加打印机” → “我需要的打印机未列出” → “添加本地打印机或网络打印机(手动)”
    • 端口选择:Standard TCP/IP Port
    • 目标 IP:填服务器的 IP,端口:9100
    • 驱动:选择该打印机的驱动(与服务器同型号)
    • 之后像 p910nd 一样直接用


注意:这是原始数据直通。客户端发送的内容必须是目标驱动能识别的打印语言(PCL、ESC/POS、GDI 等),这和 p910nd 的行为完全一致。

5) 常见问题
  • 0x00000709 / 0x000011b / 0x00000bcb:不会再遇到,因为它不使用 SMB/RPC。
  • 客户端能连但不出纸:多半是客户端驱动与打印机不匹配;或该打印机是 GDI 专用,尝试装官方驱动。
  • 一连就出一张空白页:多数是应用/驱动在作业头尾自动加 PJL/分页符;属客户端配置问题。
  • 改端口:服务器端把端口改为你想要的并重启服务,客户端添加端口时改成相同端口即可。


如果你希望做成系统服务(开机自启、无界面托管)、最小化到托盘、或多打印机多端口同时监听,我也可以把上面的代码再扩展一下。


回复 支持 反对

使用道具 举报

发表于 昨天 10:49 | 显示全部楼层
谢大神分享。
回复 支持 反对

使用道具 举报

发表于 昨天 12:36 | 显示全部楼层
感谢楼主的分享,进来一看,大神真不少。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册 微信登录

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-8-16 14:30 , Processed in 0.156000 second(s), 10 queries , Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

快速回复 返回顶部 返回列表